diff --git a/.eslintrc.json b/.eslintrc.json index 6d0addb49..4c862c827 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -97,8 +97,9 @@ "Avatar": true, "Avatars": true, "BlazeComponent": false, - + "BlazeLayout": false, "CollectionHooks": false, + "DocHead": false, "ESSearchResults": false, "FastRender": false, "FlowRouter": false, diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..07765d05f --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,56 @@ +## 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 + diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 0d633d30f..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,69 +0,0 @@ -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: dropdown - id: platform - attributes: - label: Platform / Installation Method - options: - - Snap Stable - - Snap Candidate - - Docker - - Sandstorm - - Source (Meteor) - - Windows - - Mac - - Other - validations: - required: true - - type: dropdown - id: CPU - attributes: - label: CPU - options: - - amd64 - - arm64 - - s390x - - Other - 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. - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index 99c26554c..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,31 +0,0 @@ -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. - - diff --git a/.github/ISSUE_TEMPLATE/security-report.yml b/.github/ISSUE_TEMPLATE/security-report.yml deleted file mode 100644 index fb4e5fbe6..000000000 --- a/.github/ISSUE_TEMPLATE/security-report.yml +++ /dev/null @@ -1,14 +0,0 @@ -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. - diff --git a/.github/ISSUE_TEMPLATE/ucs-issue.yml b/.github/ISSUE_TEMPLATE/ucs-issue.yml deleted file mode 100644 index a8358360f..000000000 --- a/.github/ISSUE_TEMPLATE/ucs-issue.yml +++ /dev/null @@ -1,23 +0,0 @@ -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. diff --git a/.github/workflows/depsreview.yaml b/.github/workflows/depsreview.yaml index ae5ae5989..8461b453c 100644 --- a/.github/workflows/depsreview.yaml +++ b/.github/workflows/depsreview.yaml @@ -9,6 +9,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v6 + uses: actions/checkout@v5 - name: 'Dependency Review' uses: actions/dependency-review-action@v4 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 86bed1db5..54af974ce 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -32,13 +32,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v5 # 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@c94ce9fb468520275223c153574b00df6fe4bcc9 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -48,7 +48,7 @@ jobs: # https://github.com/docker/metadata-action - name: Extract Docker metadata id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} diff --git a/.github/workflows/dockerimage.yml b/.github/workflows/dockerimage.yml index 0f85c0d96..14f8dfe01 100644 --- a/.github/workflows/dockerimage.yml +++ b/.github/workflows/dockerimage.yml @@ -15,6 +15,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v5 - name: Build the Docker image run: docker build . --file Dockerfile --tag wekan:$(date +%s) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c05c85e6..9d93b7588 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/test_suite.yml b/.github/workflows/test_suite.yml index 1b3effdac..7e2e4944a 100644 --- a/.github/workflows/test_suite.yml +++ b/.github/workflows/test_suite.yml @@ -18,7 +18,7 @@ jobs: # runs-on: ubuntu-latest # steps: # - name: checkout -# uses: actions/checkout@v6 +# uses: actions/checkout@v5 # # - name: setup node # uses: actions/setup-node@v1 @@ -42,7 +42,7 @@ jobs: # needs: [lintcode] # steps: # - name: checkout -# uses: actions/checkout@v6 +# uses: actions/checkout@v5 # # - name: setup node # uses: actions/setup-node@v1 @@ -65,7 +65,7 @@ jobs: # needs: [lintcode,lintstyle] # steps: # - name: checkout -# uses: actions/checkout@v6 +# uses: actions/checkout@v5 # # - name: setup node # uses: actions/setup-node@v1 @@ -90,12 +90,12 @@ jobs: # CHECKOUTS - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 # CACHING - name: Install Meteor id: cache-meteor-install - uses: actions/cache@v5 + uses: actions/cache@v4 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@v5 + uses: actions/cache@v4 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@v5 + uses: actions/cache@v4 with: path: | .meteor/local/resolver-result-cache.json @@ -136,7 +136,7 @@ jobs: run: sh ./test-wekan.sh -cv - name: Upload coverage - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v5 with: name: coverage-folder path: .coverage/ @@ -147,10 +147,10 @@ jobs: needs: [tests] steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 - name: Download coverage - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v6 with: name: coverage-folder path: .coverage/ diff --git a/.meteor/packages b/.meteor/packages index e9a6af949..e0baa96d9 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -16,13 +16,17 @@ es5-shim@4.8.0 # Collections aldeed:collection2 -reywood:publish-composite +cottz:publish-relations dburles:collection-helpers +idmontie:migrations +easy:search mongo@1.16.8 +mquandalle:collection-mutations # Account system accounts-password@2.4.0 useraccounts:core +useraccounts:flow-routing useraccounts:unstyled simple:rest-accounts-password wekan-ldap @@ -40,7 +44,11 @@ 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 @@ -50,6 +58,9 @@ http@2.0.0! # force new http package # 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 @@ -61,25 +72,25 @@ meteorhacks:subs-manager meteorhacks:aggregate@1.3.0 wekan-markdown konecty:mongo-counter -quave:synced-cron +percolate:synced-cron ostrio:cookies ostrio:files@2.3.0 +pascoual:pdfkit lmieulet:meteor-coverage meteortesting:mocha@2.0.3 aldeed:simple-schema 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 wekan-fullcalendar momentjs:moment@2.29.3 -wekan-fontawesome - -useraccounts:flow-routing-extra -ostrio:flow-router-extra +# wekan-fontawesome diff --git a/.meteor/release b/.meteor/release index 5152abe9d..c500c39d6 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@2.16 +METEOR@2.14 diff --git a/.meteor/versions b/.meteor/versions index 3c512c95f..886c8cb4a 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,5 +1,5 @@ -accounts-base@2.2.11 -accounts-oauth@1.4.4 +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 @@ -7,6 +7,7 @@ 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 @@ -19,25 +20,29 @@ boilerplate-generator@1.7.2 caching-compiler@1.2.2 caching-html-compiler@1.2.1 callback-hook@1.5.1 -check@1.4.1 +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.2 -ddp-common@1.4.1 +ddp-client@2.6.1 +ddp-common@1.4.0 ddp-rate-limiter@1.2.1 -ddp-server@2.7.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.6 +email@2.2.5 es5-shim@4.8.0 fetch@0.1.4 geojson-utils@1.0.11 @@ -46,12 +51,16 @@ 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.4 +logging@1.3.3 matb33:collection-hooks@1.3.0 mdg:validation-error@0.5.1 meteor@1.11.5 @@ -65,39 +74,45 @@ meteortesting:browser-tests@1.4.2 meteortesting:mocha@2.1.0 meteortesting:mocha-core@8.0.1 minifier-css@1.6.4 -minifier-js@2.8.0 +minifier-js@2.7.5 minifiers@1.1.8-faster-rebuild.0 -minimongo@1.9.4 +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.10 +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 ostrio:cstorage@4.0.1 ostrio:files@2.3.3 -ostrio:flow-router-extra@3.10.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 -quave:synced-cron@2.2.1 raix:eventemitter@0.1.3 raix:handlebar-helpers@0.2.5 random@1.2.1 @@ -107,9 +122,8 @@ reactive-dict@1.3.1 reactive-var@1.0.12 reload@1.3.1 retry@1.1.0 -reywood:publish-composite@1.9.0 routepolicy@1.1.1 -service-configuration@1.3.4 +service-configuration@1.3.3 session@1.2.1 sha@1.0.9 shell-server@0.5.0 @@ -130,21 +144,21 @@ templating-tools@1.2.2 tracker@1.3.3 typescript@4.9.5 ui@1.0.13 -underscore@1.6.1 +underscore@1.0.13 url@1.3.2 useraccounts:core@1.16.2 -useraccounts:flow-routing-extra@1.1.0 +useraccounts:flow-routing@1.15.0 useraccounts:unstyled@1.14.2 -webapp@1.13.8 +webapp@1.13.6 webapp-hashing@1.1.1 wekan-accounts-cas@0.1.0 -wekan-accounts-lockout@1.1.0 +wekan-accounts-lockout@1.0.0 wekan-accounts-oidc@1.0.10 wekan-accounts-sandstorm@0.8.0 -wekan-fontawesome@6.4.2 wekan-fullcalendar@3.10.5 wekan-ldap@0.0.2 wekan-markdown@1.0.9 wekan-oidc@1.0.12 yasaricli:slugify@0.0.7 -zodern:types@1.0.13 +zimme:active-route@2.3.2 +zodern:types@1.0.10 diff --git a/.tx/config b/.tx/config index f6bdb4cda..7745b5608 100644 --- a/.tx/config +++ b/.tx/config @@ -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, 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, 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 [o:wekan:p:wekan:r:application] file_filter = imports/i18n/data/.i18n.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 860ffbbba..d5e338938 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,8 +9,5 @@ "TERM": "xterm-256color" }, "terminal.integrated.shell.linux": "/bin/bash", - "terminal.integrated.shellArgs.linux": [ - "-l" - ], - "files.simpleDialog.enable": true + "terminal.integrated.shellArgs.linux": ["-l"] } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 873eb176e..64f8ee592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,472 +8,20 @@ Newest WeKan at these platforms: - [Mac amd64, works also with Rosetta2 at Apple Silicon](https://github.com/wekan/wekan/blob/main/docs/Platforms/Propietary/Mac.md) - https://wekan.fi/install/ - Snap Candidate amd64 - - Docker amd64/arm64/s390x + - Docker amd64 - Kubernetes Docker amd64 - Bitnami MongoDB Docker images do not exist anymore. [MongoDump/MongoRestore to groundhog2k MongoDB images](https://github.com/wekan/charts/issues/45) Fixing other platforms In Progress. -- [Node.js 14.21.4](https://github.com/wekan/node-v14-esm/releases/tag/v14.21.4) or [Node.js 14.21.3](https://nodejs.org/dist/latest-v14.x/) -- MongoDB 6.x and 7.x, or [FerretDB2/PostgreSQL](https://github.com/wekan/wekan/blob/main/docs/Databases/FerretDB2-PostgreSQL.md) +- Node.js 14.x at https://github.com/wekan/node-v14-esm/releases/tag/v14.21.4 and https://nodejs.org/dist/latest-v14.x/ +- MongoDB 6.x and 7.x, or FerretDB/PostgreSQL https://blog.ferretdb.io/building-project-management-stack-wekan-ferretdb/ [Upgrade WeKan](https://wekan.fi/upgrade/) WeKan 8.00-8.06 had wrong raw database directory setting /var/snap/wekan/common/wekan and some cards were not visible. Those are fixed at WeKan 8.07 where database directory is back to /var/snap/wekan/common and all cards are visible. -WeKan 8.00-8.24 used Colorful Unicode Emoji Icons, versions before and after use mostly Font Awesome 4.7 icons. - -# v8.31 2026-02-08 WeKan ® release - -This release fixes the following bugs: - -- [Fix Copy Card and Move Card](https://github.com/wekan/wekan/commit/f8aa487e9118264f4d96c4d0cde384bcaf05e0a0). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.30 2026-02-08 WeKan ® release - -This release reverts the following new features and adds the following fixes: - -- [Reverted New UI Design of WeKan v8.29 and added more fixes and performance improvements](https://github.com/wekan/wekan/commit/1b8b8d2eef5b56654026597ae445f3f20ad886b2). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.29 2026-02-07 WeKan ® release - -This release adds the following new features: - -- New UI Design. - [Part 1](https://github.com/wekan/wekan/pull/6131), - [Part 2](https://github.com/wekan/wekan/pull/6133). - Thanks to Chostakovitch. - -and fixes the following bugs: - -- [Fix List widths](https://github.com/wekan/wekan/pull/6129). - Thanks to KhaoulaMaleh. -- [Fix extra space at RTL need margin](https://github.com/wekan/wekan/commit/4456bc13609b2d0e944ee71a82df200060a601b2). - Thanks to mimZD and xet7. -- [Fix No Add Card + etc](https://github.com/wekan/wekan/commit/55710835fe8879775b73c8bc921bac5febf552a2). - Thanks to mimZD and xet7. -- [Removed extra file](https://github.com/wekan/wekan/commit/0987154a7fea89b0416f48d9bffd5fa7fba9908a). - Thanks to xet7. -- [Added missing linefeeds](https://github.com/wekan/wekan/commit/0ae9865fcbad42966988225393fa66bca49cf14e). - Thanks to xet7. -- [Fix Notifications from not allowed Boards](https://github.com/wekan/wekan/commit/0a92e896f8d2cf0677891857d163ada336a45c61). - Thanks to FK-PATZ3 and xet7. -- [Fix move and copy popup duplicate view](https://github.com/wekan/wekan/commit/631c250f403172937b76ddd37bab54bc9b6dbb78). - Thanks to mimZD and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.28 2026-02-05 WeKan ® release - -This release adds the following updates: - -- [Bump docker/login-action from 3.6.0 to 3.7.0](https://github.com/wekan/wekan/pull/6122). - Thanks to dependabot. -- [Updated meteor-node-stubs](https://github.com/wekan/wekan/commit/6c2e2f271d6343b347224430a4eedfe54db2d838). - Thanks to Meteor developers. - -and fixes the following bugs: - -- [Fixed text truncation at quick-access board link bar](https://github.com/wekan/wekan/pull/6121). - Thanks to KhaoulaMaleh. -- [Improved cardDetails.css for better UI](https://github.com/wekan/wekan/pull/6124). - Thanks to AymenHassini19. -- [Fixed Jade syntax at header](https://github.com/wekan/wekan/commit/c31758960f5372e88f47e8d081404294751284c8). - Thanks to xet7. -- [Await async setDone before closing popup in copy/move dialogs](https://github.com/wekan/wekan/pull/6126). - Thanks to harryadel. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.27 2026-01-31 WeKan ® release - -This release adds the following updates: - -- [Updated MongoDB to 7.0.29 at Windows install docs](https://github.com/wekan/wekan/commit/b55e1bbd409f76bd0388d19d4d0a8420cee8df96). - Thanks to MongoDB developers. - -and fixes the following bugs: - -- [Fix async/await in copy/move card operations](https://github.com/wekan/wekan/pull/6120). - Thanks to harryadel. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.26 2026-01-31 WeKan ® release - -This release adds the following updates: - -- [Migrate wekan-accounts-lockout to async API for Meteor 3.0](https://github.com/wekan/wekan/pull/6113). - Thanks to harryadel. -- Added Docs: Spreadsheet vs Kanban. - [Part 1](https://github.com/wekan/wekan/commit/a0a8d0186cbc7fefe38f72244723bcff292ae2f4), - [Part 2](https://github.com/wekan/wekan/commit/37d0daee590ab48cbfa1672e4bc5efd95d341211). - Thanks to xet7. -- [Updated dependencies](https://github.com/wekan/wekan/commit/03439d1bccf82511870eed7301b621b1d495941b). - Thanks to developers of dependencies. - -and fixes the following bugs: - -- [Reduce visual overflow in Member Settings menu by extending container height](https://github.com/wekan/wekan/pull/6104). - Thanks to AymenHassini19. -- [Fix Card copy menu is not displayed](https://github.com/wekan/wekan/commit/0b891464b907b272e075d8aafd3ce29e704739cf). - Thanks to xet7. -- [Fix Bug: Rules view translation not is not shown correctly](https://github.com/wekan/wekan/commit/f73eab23f997efe5347aa1f06515bf355cfe7ed5). - Thanks to cactus7as and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.25 2026-01-28 WeKan ® release - -This release fixes the following CRITICAL SECURITY ISSUES of [Floppybleed](https://wekan.fi/hall-of-fame/floppybleed/): - -- [Fix Filebleed of Floppybleed](https://github.com/wekan/wekan/commit/a419d831a408f251c798f5410375b20afd98c04b). - Thanks to Luke Hebenstreit Twitter lheben_ and xet7. - -and adds the following updates: - -- [Updated code counts](https://github.com/wekan/wekan/commit/2f25f47d0ba4c7f543264cd7fe2ed117ab0ec9ee). - Thanks to xet7. -- Updated FerretDB 2 / PostgreSQL docs location. - [Part 1](https://github.com/wekan/wekan/commit/710d522e069b7521b6c2ec4f93f1491a897cf2b4), - [Part 2](https://github.com/wekan/wekan/commit/0ede9d6d93a688f24fc36c0c456e184a0aa6af8c), - [Part 3](https://github.com/wekan/wekan/commit/bf5d50e8a9fce327a16b069932fa3e13c6d81978). - Thanks to xet7. -- [Updated Dockerfile](https://github.com/wekan/wekan/commit/d298ab7486d489d353fc410232a9dcdd68501c72). - Thanks to xet7. -- Docker for Linux amd64/arm64/s390x. - [Part 1](https://github.com/wekan/wekan/commit/38711f0a29bf37d1e0a3fd9c8a9bcfb2442934b3), - [Part 2](https://github.com/wekan/wekan/commit/e72019fa55ef6142767fd83e928bf2a0a966f9e6), - [Part 3](https://github.com/wekan/wekan/commit/b2c7c7f55b5136bc91251cd57125316ec622d4a3), - [Part 4](https://github.com/wekan/wekan/commit/98e5adfba80ee935b2a1293851d88812ad707b78), - [Part 5](https://github.com/wekan/wekan/commit/60846a44959d46262672c6a3048bd76d829c03bf), - [Part 6](https://github.com/wekan/wekan/commit/7ff174cf660f43dfbb471b29d75820f527771bbd). - Thanks to xet7. -- [Most Unicode Icons back to Font Awesome 4.7 for better accessibility. Less always visible buttons, More at ☰ Menu](https://github.com/wekan/wekan/commit/7ad04f45353e1628881fec310caedf7625a34d4d). - Thanks to xet7. -- [Updated to MongoDB 7.0.29 at Snap Candidate](https://github.com/wekan/wekan/commit/ac70fe28488c09364133a65fbc80f5a819a1e4bf). - Thanks to developers of MongoDB. -- [Updated to MongoDB 7.0.29 at Helm Charts](https://github.com/wekan/charts/commit/8169739260b6f104c4d011dac5a4bf5485db8b45). - Thanks to developers of MongoDB. - -and fixes the following bugs: - -- [Fix autofocus](https://github.com/wekan/wekan/commit/440f553de0baf460acc891ee5864f84bb982104a). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.24 2026-01-24 WeKan ® release - -This release adds the following updates: - -- Secure Sandbox for VSCode at Debian 13 amd64. - [Part 1](https://github.com/wekan/wekan/commit/639ac9549f88069d8569de777c533ab4c9438088), - [Part 2](https://github.com/wekan/wekan/commit/cc8b771eb448199fa23a87955cf9fa1a504ba8d2). - Thanks to xet7. -- [Updated build scripts and docs to Meteor 2.16](https://github.com/wekan/wekan/commit/1d374db0f3ed35a0463b5f89ca2d01078e245d11). - Thanks to xet7. -- [Replace mquandalle:collection-mutations with collection helpers](https://github.com/wekan/wekan/pull/6086). - Thanks to harryadel. -- [Replace ongoworks:speakingurl with limax](https://github.com/wekan/wekan/pull/6087). - Thanks to harryadel. -- [Migrate createIndex to createIndexAsync](https://github.com/wekan/wekan/pull/6093). - Thanks to harryadel. -- [Remove idmontie:migrations](https://github.com/wekan/wekan/pull/6095). - Thanks to harryadel. -- Remove mquandalle:autofocus. - [Part 1](https://github.com/wekan/wekan/pull/6088), - [Part 2](https://github.com/wekan/wekan/pull/6096). - Thanks to harryadel. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.23 2026-01-21 WeKan ® release - -This release adds the following updates: - -- [Migrate from percolate:synced-cron to quave:synced-cron](https://github.com/wekan/wekan/pull/6080). - Thanks to harryadel. -- [Replace mousetrap](https://github.com/wekan/wekan/pull/6082). - Thanks to harryadel. -- [Remove kadira:dochead](https://github.com/wekan/wekan/pull/6083). - Thanks to harryadel. -- [Replace cottz:publish-relations with reywood:publish-composite](https://github.com/wekan/wekan/pull/6084). - Thanks to harryadel. -- [Bump tar from 7.5.3 to 7.5.6](https://github.com/wekan/wekan/pull/6085). - Thanks to dependabot. -- [Updated dependencies](https://github.com/wekan/wekan/commit/04bfa0e6ba278a9d6544a678d1fba3ea71841062). - Thanks to developers of dependencies. - -and fixes the following bugs: - -- [Fixed newly created "Default" swimlane are displayed as "key 'default (LOCALE)' returned an object instead of string"](https://github.com/wekan/wekan/commit/ce55f0d8f432922ca4c0e3d28b1fb0e826d8008f). - Thanks to brlin-tw and xet7. -- [Fix DB migration from 8.19 to 8.21 stuck forever](https://github.com/wekan/wekan/commit/a31a615da6911a2db22d4db86875b31fc951ae96). - Thanks to MaccabeeY and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.22 2026-01-20 WeKan ® release - -This release fixes the following bugs: - -- [Fixed Add member and @mentions](https://github.com/wekan/wekan/commit/ad511bd1378afdca7264597900a11ab6b5e09b77). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.21 2026-01-18 WeKan ® release - -This release fixes the following CRITICAL SECURITY ISSUES of [Snowbleed](https://wekan.fi/hall-of-fame/snowbleed/): - -- [Security Fix 2: OrgsTeamsBleed](https://github.com/wekan/wekan/commit/cabfeed9a68e21c469bf206d8655941444b9912c). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 3: ChecklistRESTBleed](https://github.com/wekan/wekan/commit/251d49eea94834cf351bb395808f4a56fb4dbb44). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 4: MigrationsBleed2](https://github.com/wekan/wekan/commit/cc35dafef57ef6e44a514a523f9a8d891e74ad8f). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 5: PositionHistoryBleed](https://github.com/wekan/wekan/commit/55576ec17722db094835470b386162c9a662fb60). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 6: SyncLDAPBleed](https://github.com/wekan/wekan/commit/146905a459106b5d00b4f09453a6554255e6965a). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 7: AttachmentMigrationBleed](https://github.com/wekan/wekan/commit/053bf1dfb76ef230db162c64a6ed50ebedf67eee). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 8: MoveStorageBleed](https://github.com/wekan/wekan/commit/c413a7e860bc4d93fe2adcf82516228570bf382d). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 9: ListWIPBleed](https://github.com/wekan/wekan/commit/8c0b4f79d8582932528ec2fdf2a4487c86770fb9). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 10: BoardTitleRESTBleed](https://github.com/wekan/wekan/commit/545566f5663545d16174e0f2399f231aa693ab6e). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 11: CardPubSubBleed](https://github.com/wekan/wekan/commit/0f5a9c38778ca550cbab6c5093470e1e90cb837f). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 12: FixDuplicateBleed](https://github.com/wekan/wekan/commit/4ce181d17249778094f73d21515f7f863f554743). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 13: LinkedBoardActivitiesBleed](https://github.com/wekan/wekan/commit/91a936e07d2976d4246dfe834281c3aaa87f9503). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 14: RulesBleed](https://github.com/wekan/wekan/commit/a787bcddf33ca28afb13ff5ea9a4cb92dceac005). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. - -and adds the following new features: - -- [Show password at Login and Register pages](https://github.com/wekan/wekan/commit/d30192f7f925a055e6f31723c47ad32b628ff2c0). - Thanks to xet7. - -and adds the following updates: - -- [Updated Docker build command](https://github.com/wekan/wekan/commit/b88b27689af8c5abf23dd7891780581a2d92001d). - Thanks to xet7. -- [Updated Windows Bundle build .bat script](https://github.com/wekan/wekan/commit/f0118d52e984628b0e06e36d7b7f90166d18fbf7). - Thanks to xet7. -- [Updated Linux arm64 bundle build script](https://github.com/wekan/wekan/commit/e2ec50730ff7fd4eb805071bb17fe0c105514f83). - Thanks to xet7. -- [Updated Linux s390x bundle build script](https://github.com/wekan/wekan/commit/980510d71ad428325645dd53297f4ce20bd12983). - Thanks to xet7. -- [Bump tar and @mapbox/node-pre-gyp](https://github.com/wekan/wekan/pull/6071). - Thanks to dependabot. -- [Upgrade to Meteor 2.16](https://github.com/wekan/wekan/pull/6072). - Thanks to harryadel. -- [Updated dependencies](https://github.com/wekan/wekan/commit/95da8966fe3bebc7c5ef2c1fc555de5fa239f8ca). - Thanks to developers of dependencies. - -and fixes the following bugs: - -- [Fixed "Copy card link to clipboard" icon often not working](https://github.com/wekan/wekan/commit/d337afd5d3e8ca719adcde13d2b24d983e0f9926). - Thanks to brlin-tw and xet7. -- [Fix DB migration from 8.19 to 8.20 is in a loop](https://github.com/wekan/wekan/commit/2fa490d83da858b193ca6a363e1599c5bbe55640). - Thanks to MaccabeeY and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.20 2026-01-16 WeKan ® release - -This release fixes the following CRITICAL SECURITY ISSUES of [Snowbleed](https://wekan.fi/hall-of-fame/snowbleed/): - -- [Security Fix 1: MigrationsBleed](https://github.com/wekan/wekan/commit/cbb1cd78de3e40264a5e047ace0ce27f8635b4e6). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. - -and adds the following features: - -- [Added back feature: Toggle Drag Handles. Improved positions of Add List etc buttons](https://github.com/wekan/wekan/commit/5cb712bee4cf46c6fe13d7dacf4b62298152b894). - Thanks to xet7. - -and adds the following updates: - -- [Updated dependencies](https://github.com/wekan/wekan/pull/6059). - Thanks to dependabot. -- [Updated dependencies and published as @wekanteam npm packages to npmjs.com](https://github.com/wekan/wekan/commit/a9a89b501a91ffcdbdd611a05029d9483c59e4db). - Thanks to xet7. -- Added FerretDB2/PostgreSQL Docs. - [Part 1](https://github.com/wekan/wekan/commit/9fb1aeb8272b011c3d0b6b2c26ff7cb498c7b37f), - [Part 2](https://github.com/wekan/wekan/commit/f198421f10dd3be9d58f64a242d12ea1ef45fee3), - [Part 3](https://github.com/wekan/wekan/commit/9431b2d53014289bebb06567f5662fdcb6dd409c), - [Part 4](https://github.com/wekan/wekan/commit/ffd37b9fd9171ca22973d6d0a62baef4a18494f5). - Thanks to juri_ at WeKan Libera.Chat IRC and xet7. -- [Added s390x firewall Docs](https://github.com/wekan/wekan/commit/ec7c0e6dc3641f43b1a110d285f6ef15c146584a). - Thanks to xet7. -- Updated GitHub issue templates. - [Part 1](https://github.com/wekan/wekan/commit/bd37b88e4d508c1f2712184a27dbbfd9df0e4c4e), - [Part 2](https://github.com/wekan/wekan/commit/cf6e6914989a7bf1d79f8b753a0a576c54ad7580), - [Part 3](https://github.com/wekan/wekan/commit/4a658dc02a770f8219669dc10bfe1077c760744f). - Thanks to xet7. -- [Migrate kadira:flow-router to ostrio:flow-router-extra](https://github.com/wekan/wekan/pull/6067), related to Meteor 3 upgrades. - Thanks to harryadel. -- [Some fixes to make WeKan working after Meteor 3 related router upgrades](https://github.com/wekan/wekan/commit/984a2dcec18fd20ebd1a5add8380d4c13d8303ba). - Thanks to xet7. - -and fixes the following bugs: - -- [Fix attachment download error with non-ASCII filenames](https://github.com/wekan/wekan/pull/6056). - Thanks to brlin-tw. -- [Swimlane drag button position improvements](https://github.com/wekan/wekan/commit/376a30f8a9c5cc6b5341fda7336244ee1b9983fd). - Thanks to TDSCDMA and xet7. -- [Removed extra list borders](https://github.com/wekan/wekan/commit/a4f8faa48e3fb6c617cf9c5a398bc7f85b8bae92). - Thanks to TDSCDMA and xet7. -- [Add back button texts to Filter, Search, Board View and MultiSelection](https://github.com/wekan/wekan/commit/dac7e17500de97febc7ad8f84cd1bf5edab27c52). - Thanks to audiocrush and xet7. -- [Removed extra pipe character from UI](https://github.com/wekan/wekan/commit/66e79d2df7ecf5526dbae360cf93352657db7fcf). - Thanks to xet7. -- [Changed find.sh to not search from translations, because I'm trying to find code, not translations](https://github.com/wekan/wekan/commit/58ae2b6c6848235132308611fe3083533e120f72). - Thanks to xet7. -- [Fixed Change Avatar. Improved Admin Panel: People columns order, selected tab background color. Fixed can not edit existing user at Admin Panel/People/People](https://github.com/wekan/wekan/commit/07186e12a93c56555feb3b7332d43a918abe7f20). - Thanks to xet7. -- [Fix mentions and notifications drawer](https://github.com/wekan/wekan/commit/20b5e2ab8fd37303cda8305d87d757c1cb9bdd12). - Thanks to xet7. -- Fix New Board Permissions: NormalAssignedOnly, CommentAssignedOnly, ReadOnly, ReadAssignedOnly. - [Part 1](https://github.com/wekan/wekan/commit/eabb6a239d20530f538d22f94d9cfbebeb847493). - Thanks to nazim-oss and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.19 2025-12-29 WeKan ® release - -This release fixes the following CRITICAL SECURITY ISSUES of [Megableed](https://wekan.fi/hall-of-fame/megableed/): - -- [Security Fix 1: IDOR in setCreateTranslation. Non-admin could change Custom Translation](https://github.com/wekan/wekan/commit/f244a43771f6ebf40218b83b9f46dba6b940d7de). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 2: Private-only board setting can be bypassed](https://github.com/wekan/wekan/commit/7ed76c180ede46ab1dac6b8ad27e9128a272c2c8). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 3: Card comment author spoofing (IDOR) via API](https://github.com/wekan/wekan/commit/67cb47173c1a152d9eaf5469740992b2dacdf62d). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 4: Cross-board card move without destination authorization](https://github.com/wekan/wekan/commit/198509e7600981400353aec6259247b3c04e043e). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 5: Read-only roles can still update cards](https://github.com/wekan/wekan/commit/181f837d8cbae96bdf9dcbd31beaa3653c2c0285). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 6: Checklist delete IDOR: checklist not verified against board/card](https://github.com/wekan/wekan/commit/08a6f084eba09487743a7c807fb4a9000fcfa9ac). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 7: Checklist create IDOR: cardId not verified against boardId](https://github.com/wekan/wekan/commit/5cd875813fdec5a3c40a0358b30a347967c85c14). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 8: Attachments publication leaks metadata without auth](https://github.com/wekan/wekan/commit/6dfa3beb2b6ab23438d0f4395b84bf0749eb4820). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 9: Attachment upload not scoped to card/board relationship](https://github.com/wekan/wekan/commit/1d16955b6d4f0a0282e89c2c1b0415c7597019b8). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 10: LDAP filter injection in LDAP auth](https://github.com/wekan/wekan/commit/0b0e16c3eae28bbf453d33a81a9c58ce7db6d5bb). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. - -and adds the following new features: - -- [Opened card Checklist menu: Hide finished tasks. Show Checklist at Minicard](https://github.com/wekan/wekan/commit/fbfde81bc8208b718c070a6eeba4b2e2d2ce83ba). - Thanks to C0rn3j and xet7. - -and adds the following updates: - -- [Helm Chart: Updated MongoDB to 7.0.28 at artifacthub.io](https://github.com/wekan/charts/commit/5e6d344e0b976ce683116b66a1fb8417590115aa). - Thanks to xet7 and titver968. - -and fixes the following bugs: - -- [Re-add JS closing class to unicode close announcement symbol](https://github.com/wekan/wekan/pull/6050). - Thanks to Chostakovitch. -- [Cannot re-arrange lists within swimlanes](https://github.com/wekan/wekan/pull/6052). - Thanks to Chostakovitch. -- Converted Gantt from js to Jade, and made card title to render markdown at Gantt view. - [Part 1](https://github.com/wekan/wekan/commit/2d3bef9033134c3b62cf22179bbee4b6fea81444), - [Part 2](https://github.com/wekan/wekan/commit/3af3c9a89d8a4020b6f1ccada7da2ccbec1a8562). - Thanks to xet7. -- [Fix find.sh work with spaces, for example: ./find.sh "Some text"](https://github.com/wekan/wekan/commit/db4b04d8377523440fd2c36c1633ee74d7b05146). - Thanks to xet7. -- [Fix copy move card at board and MultiSelect to have numbered target of board, card above or below. Added MultiSelect change color](https://github.com/wekan/wekan/commit/74f1dfde72b9448645552ae28ba8d989d3e823d8). - Thanks to mimZD and xet7. -- [Fix move card last selection is gone](https://github.com/wekan/wekan/commit/2d87ba18b31ab5d8dc91dce01199cf7b313bd560). - Thanks to mimZD and xet7. -- [Fix Unable to delete Checklist. Added confirm delete to Checklist and Chekclist Item](https://github.com/wekan/wekan/commit/cf62807ad5d056ce9b8045c55f7cf6c29044967b). - Thanks to C0rn3j and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.18 2025-12-28 WeKan ® release - -This release adds the following CRITICAL SECURITY FIXES: - -- [Upgraded MongoDB to 7.0.28 to fix mongobleed at Snap Candidate](https://github.com/wekan/wekan/commit/e210c9973be55a4fa4e7dd15aefc24e06dbc3e7f). - Thanks to developers of MongoDB. - -and adds the following new features: - -- [Gantt chart view to one board view menu Swimlanes/Lists/Calendar/Gantt](https://github.com/wekan/wekan/commit/f34e4c0e363e386dbcce8e6ee8933b2d50491c58). - Thanks to xet7. -- [Number of cards per list and sum of custom number field in list head](https://github.com/wekan/wekan/commit/e569c2957ecc2b5fbf65ddcf0793b97c3ed5da81). - Thanks to xet7. -- [New Board Permissions: NormalAssignedOnly, CommentAssignedOnly, ReadOnly, ReadAssignedOnly](https://github.com/wekan/wekan/commit/c1168d181b3ff34f5ee7794a5740281c4ab5e253). - Thanks to xet7. -- [More translations. Added support page to Admin Panel / Settings / Layout](https://github.com/wekan/wekan/commit/a7400dca4503961267cc5fd6a1c8efaa78668f77). - Thanks to xet7. -- [Right top User Settings / Grey Icons. Also fixed Change Language popup](https://github.com/wekan/wekan/commit/300b653ea3416892faf2d08f5e0be3752e2041d6). - Thanks to xet7. -- [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](https://github.com/wekan/wekan/commit/58f4884ad603e4f8c68a8819dfb1440234da70b6). - Thanks to xet7. -- Per-User and Board-level data save fixes. Per-User is collapse, width, height. Per-Board is Swimlanes, Lists, Cards etc. - [Part 1](https://github.com/wekan/wekan/commit/414b8dbf41ecf368d54aeceb6a78ccd0aa58f6a6), - [Part 2](https://github.com/wekan/wekan/commit/58e970d68508a76a1b9333941eb1696fb8fb7727). - Thanks to xet7. - -and adds the following updates: - -- [Update GitHub docker/metadata-action from 5.8.0 to 5.9.0](https://github.com/wekan/wekan/pull/6012). - Thanks to dependabot. -- [Updated security.md](https://github.com/wekan/wekan/commit/7ff1649d8909917cae590c68def6eecac0442f91). - Thanks to xet7. -- [Updated build script for Linux arm64 bundle](https://github.com/wekan/wekan/commit/3db1305e58168f7417023ccd8d54995026844b18). - Thanks to xet7. -- Update Backup docs about migrating to newest WeKan. - [Part 1](https://github.com/wekan/wekan/commit/e669b1b9c72278c8debbc9de74d3fa02224a66d8), - [Part 2](https://github.com/wekan/wekan/commit/19fa12bb26a0444acffd49f24123ed993c425f6a), - [Part 3](https://github.com/wekan/wekan/commit/4e346c0ab7fbfb39544063cbd0e095307b26648f), - [Part 4](https://github.com/wekan/wekan/commit/59fc756a0bda8e11b9d86961daa35bb755110a68), - [Part 5](https://github.com/wekan/wekan/commit/30541260f0f979662889bc40b4db461af1583a07), - [Part 6](https://github.com/wekan/wekan/commit/784c5c6b0c83397ab4344d1a0fa231f33ff26564), - [Part 7](https://github.com/wekan/wekan/commit/5686c92e05452a5d91c10ed436fae71103ecfb1f), - [Part 8](https://github.com/wekan/wekan/commit/b7ff370561153bbfbb07426f9bd8b4d2977b1d0c), - [Part 9](https://github.com/wekan/wekan/commit/fe4b36b85d4ac8efddb2c7148bc5d2413cd643e1), - [Part 10](https://github.com/wekan/wekan/commit/9ebdc82d46d86029df12adaafba95c0ecfc9d2c2), - [Part 11](https://github.com/wekan/wekan/commit/3ef0a3e685657eba1cc07314ac8d195f89dbef74), - [Part 12](https://github.com/wekan/wekan/commit/2cbf64da33aff2d0b77ee91e7e9ac360cd1edb99), - [Part 13](https://github.com/wekan/wekan/commit/3c578403404084ae10e4349b5570b0d50ecd8eb4), - [Part 14](https://github.com/wekan/wekan/commit/451e9f78705dbbac2ed6ce123fd5440a871b6dcc), - [Part 15](https://github.com/wekan/wekan/commit/e07e461e482f54c8ddaebc63373c93dc4aa0d956). - -and fixes the following bugs: - -- [Fix Broken Strikethroughs in Markdown to HTML conversion](https://github.com/wekan/wekan/pull/6009). - Thanks to brlin-tw. -- [Updated Mac docs for Applite](https://github.com/wekan/wekan/commit/400eb81206f346a973d871a8aaa55d4ac5d48753). - Thanks to xet7. -- [Fix checklist delete action (issue #6020), link-card popup defaults, and stabilize due-cards ordering](https://github.com/wekan/wekan/pull/5967). - Thanks to seve12. -- [Improve rules UI board dropdowns/loading, rule header titles, and ensure card move updates attachment metadata](https://github.com/wekan/wekan/pull/5967). - Thanks to seve12. -- [Improve imports: normalize id → _id, add default swimlane fallback, and add regression test](https://github.com/wekan/wekan/pull/5967). - Thanks to seve12. - -Thanks to above GitHub users for their contributions and translators for their translations. - # v8.17 2025-11-06 WeKan ® release This release adds the following new feature: @@ -512,7 +60,7 @@ Thanks to above GitHub users for their contributions and translators for their t # v8.16 2025-11-02 WeKan ® release -This release fixes the following CRITICAL SECURITY ISSUES of [Spacebleed](https://wekan.fi/hall-of-fame/spacebleed/): +This release fixes SpaceBleed that is the following CRITICAL SECURITY ISSUES: - [Fix SECURITY ISSUE 1: File Attachments enables stored XSS (High)](https://github.com/wekan/wekan/commit/e9a727301d7b4f1689a703503df668c0f4f4cab8). Thanks to Siam Thanat Hack (STH) and xet7. diff --git a/Dockerfile b/Dockerfile index d81c0ffce..aedb88c5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,23 +4,28 @@ LABEL org.opencontainers.image.ref.name="ubuntu" LABEL org.opencontainers.image.version="24.04" LABEL org.opencontainers.image.source="https://github.com/wekan/wekan" -# TARGETARCH is automatically provided by Docker Buildx -ARG TARGETARCH +# 2022-04-25: +# - gyp does not yet work with Ubuntu 22.04 ubuntu:rolling, +# so changing to 21.10. https://github.com/wekan/wekan/issues/4488 + +# 2021-09-18: +# - Above Ubuntu base image copied from Docker Hub ubuntu:hirsute-20210825 +# to Quay to avoid Docker Hub rate limits. ARG DEBIAN_FRONTEND=noninteractive -ENV BUILD_DEPS="apt-utils gnupg wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip" +ENV BUILD_DEPS="apt-utils gnupg gosu wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip" ENV \ DEBUG=false \ NODE_VERSION=v14.21.4 \ - METEOR_RELEASE=METEOR@2.16 \ + METEOR_RELEASE=METEOR@2.14 \ USE_EDGE=false \ METEOR_EDGE=1.5-beta.17 \ NPM_VERSION=6.14.17 \ FIBERS_VERSION=4.0.1 \ + ARCHITECTURE=linux-x64 \ SRC_PATH=./ \ WITH_API=true \ - MONGO_OPLOG_URL="" \ RESULTS_PER_PAGE="" \ DEFAULT_BOARD_ID="" \ ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \ @@ -158,69 +163,134 @@ 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 < { } }); }); - -// 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(); - } - }); -}); diff --git a/client/components/activities/activities.jade b/client/components/activities/activities.jade index 140335178..f44673ae4 100644 --- a/client/components/activities/activities.jade +++ b/client/components/activities/activities.jade @@ -199,5 +199,4 @@ template(name="activity") else if(currentData.timeValue) | {{_ activity.activityType currentData.timeValue}} - if($neq mode 'none') - div(title=activity.createdAt).activity-meta {{ moment activity.createdAt }} + div(title=activity.createdAt).activity-meta {{ moment activity.createdAt }} diff --git a/client/components/activities/comments.css b/client/components/activities/comments.css index de0189de7..f495ca361 100644 --- a/client/components/activities/comments.css +++ b/client/components/activities/comments.css @@ -108,12 +108,15 @@ text-decoration: none; height: 24px; } -.comments .comment .comment-desc .reactions .open-comment-reaction-popup span { - display: inline-block; - font-size: clamp(14px, 2vw, 18px); +.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o { + font-size: 17px; font-weight: 500; - line-height: 1; - margin-left: 4px; + 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; } .comments .comment .comment-desc .reactions .reaction { cursor: pointer; diff --git a/client/components/activities/comments.jade b/client/components/activities/comments.jade index 1860eb4f4..07b52d47d 100644 --- a/client/components/activities/comments.jade +++ b/client/components/activities/comments.jade @@ -25,8 +25,7 @@ template(name="comment") = text .edit-controls button.primary(type="submit") {{_ 'edit'}} - a.js-close-inlined-form(title="{{_ 'close' }}") - i.fa.fa-times-thin + .fa.fa-times-thin.js-close-inlined-form else .comment-text +viewer @@ -55,11 +54,9 @@ template(name="commentReactions") span.reaction-codepoint !{reaction.reactionCodepoint} span.reaction-count #{reaction.userIds.length} if (currentUser.isBoardMember) - unless currentUser.isReadOnly - unless currentUser.isReadAssignedOnly - a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}") - span(title="{{_ 'reaction' }}") 😀 - span(title="{{_ 'add' }}") ➕ + a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}") + i.fa.fa-smile-o + i.fa.fa-plus template(name="addReactionPopup") .reactions-popup diff --git a/client/components/activities/comments.js b/client/components/activities/comments.js index 74441021e..62629252d 100644 --- a/client/components/activities/comments.js +++ b/client/components/activities/comments.js @@ -57,9 +57,8 @@ BlazeComponent.extendComponent({ BlazeComponent.extendComponent({ getComments() { - const data = this.data(); - if (!data || typeof data.comments !== 'function') return []; - return data.comments(); + const ret = this.data().comments(); + return ret; }, }).register("comments"); diff --git a/client/components/boardConversionProgress.css b/client/components/boardConversionProgress.css index 7c17e561e..fd186908f 100644 --- a/client/components/boardConversionProgress.css +++ b/client/components/boardConversionProgress.css @@ -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; } diff --git a/client/components/boardConversionProgress.jade b/client/components/boardConversionProgress.jade index 77b0321c0..37a404d90 100644 --- a/client/components/boardConversionProgress.jade +++ b/client/components/boardConversionProgress.jade @@ -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'}} diff --git a/client/components/boardConversionProgress.js b/client/components/boardConversionProgress.js index 4dd7bfb41..454df5006 100644 --- a/client/components/boardConversionProgress.js +++ b/client/components/boardConversionProgress.js @@ -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(); } diff --git a/client/components/boards/boardArchive.jade b/client/components/boards/boardArchive.jade index 839f183e1..d9a251ebd 100644 --- a/client/components/boards/boardArchive.jade +++ b/client/components/boards/boardArchive.jade @@ -1,7 +1,6 @@ template(name="archivedBoards") h2 - span(title="{{_ 'archived-boards'}}") - i.fa.fa-archive + i.fa.fa-archive | {{_ 'archived-boards'}} ul.archived-lists @@ -9,7 +8,7 @@ template(name="archivedBoards") li.archived-lists-item div.board-header-btns button.board-header-btn.js-delete-board - i.fa.fa-trash + i.fa.fa-trash-o | {{_ 'delete-board'}} button.board-header-btn.js-restore-board i.fa.fa-undo diff --git a/client/components/boards/boardArchive.js b/client/components/boards/boardArchive.js index 18ccf0b93..87525c1f7 100644 --- a/client/components/boards/boardArchive.js +++ b/client/components/boards/boardArchive.js @@ -1,5 +1,4 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; BlazeComponent.extendComponent({ onCreated() { @@ -23,7 +22,7 @@ BlazeComponent.extendComponent({ events() { return [ { - async 'click .js-restore-board'() { + 'click .js-restore-board'() { // TODO : Make isSandstorm variable global const isSandstorm = Meteor.settings && @@ -31,13 +30,13 @@ BlazeComponent.extendComponent({ Meteor.settings.public.sandstorm; if (isSandstorm && Utils.getCurrentBoardId()) { const currentBoard = Utils.getCurrentBoard(); - await currentBoard.archive(); + currentBoard.archive(); } const board = this.currentData(); - await board.restore(); + board.restore(); Utils.goBoardId(board._id); }, - 'click .js-delete-board': Popup.afterConfirm('boardDelete', async function() { + 'click .js-delete-board': Popup.afterConfirm('boardDelete', function() { Popup.back(); const isSandstorm = Meteor.settings && @@ -45,9 +44,9 @@ BlazeComponent.extendComponent({ Meteor.settings.public.sandstorm; if (isSandstorm && Utils.getCurrentBoardId()) { const currentBoard = Utils.getCurrentBoard(); - await Boards.removeAsync(currentBoard._id); + Boards.remove(currentBoard._id); } - await Boards.removeAsync(this._id); + Boards.remove(this._id); FlowRouter.go('home'); }), }, diff --git a/client/components/boards/boardBody.css b/client/components/boards/boardBody.css index d8b13ba8c..f65cbaffc 100644 --- a/client/components/boards/boardBody.css +++ b/client/components/boards/boardBody.css @@ -231,30 +231,6 @@ 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; @@ -287,35 +263,6 @@ body.mobile-mode.iphone-device .card-details .card-details-item-title { 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; diff --git a/client/components/boards/boardBody.jade b/client/components/boards/boardBody.jade index 271af09b4..d2118112b 100644 --- a/client/components/boards/boardBody.jade +++ b/client/components/boards/boardBody.jade @@ -1,6 +1,8 @@ template(name="board") - - if isConverting.get + + if isMigrating.get + +migrationProgress + else if isConverting.get +boardConversionProgress else if isBoardReady.get if currentBoard @@ -22,7 +24,7 @@ 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}} | {{_ 'has-swimlanes'}}: {{hasSwimlanes}} | {{_ 'swimlanes'}}: {{currentBoard.swimlanes.length}} + | Board: {{currentBoard.title}} | View: {{boardView}} | HasSwimlanes: {{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}}" @@ -47,8 +49,6 @@ 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,10 +56,6 @@ 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") @@ -67,4 +63,4 @@ template(name="calendarView") .calendar-view.swimlane if currentCard +cardDetails(currentCard) - +fullcalendar(calendarOptions) \ No newline at end of file + +fullcalendar(calendarOptions) diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index 735e83620..b0af16e43 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -1,10 +1,10 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import '../gantt/gantt.js'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { TAPi18n } from '/imports/i18n'; import dragscroll from '@wekanteam/dragscroll'; import { boardConverter } from '/client/lib/boardConverter'; -import { formatDateByUserPreference } from '/imports/lib/dateUtils'; +import { migrationManager } from '/client/lib/migrationManager'; +import { attachmentMigrationManager } from '/client/lib/attachmentMigrationManager'; +import { migrationProgressManager } from '/client/components/migrationProgress'; import Swimlanes from '/models/swimlanes'; import Lists from '/models/lists'; @@ -16,6 +16,7 @@ BlazeComponent.extendComponent({ onCreated() { this.isBoardReady = new ReactiveVar(false); this.isConverting = new ReactiveVar(false); + this.isMigrating = new ReactiveVar(false); this._swimlaneCreated = new Set(); // Track boards where we've created swimlanes this._boardProcessed = false; // Track if board has been processed this._lastProcessedBoardId = null; // Track last processed board ID @@ -27,16 +28,17 @@ BlazeComponent.extendComponent({ this.autorun(() => { const currentBoardId = Session.get('currentBoard'); if (!currentBoardId) return; - + const handle = subManager.subscribe('board', currentBoardId, false); - + // Use a separate autorun for subscription ready state to avoid reactive loops this.subscriptionReadyAutorun = Tracker.autorun(() => { if (handle.ready()) { + // Only run conversion/migration logic once per board if (!this._boardProcessed || this._lastProcessedBoardId !== currentBoardId) { this._boardProcessed = true; this._lastProcessedBoardId = currentBoardId; - + // Ensure default swimlane exists (only once per board) this.ensureDefaultSwimlane(currentBoardId); // Check if board needs conversion @@ -67,7 +69,7 @@ BlazeComponent.extendComponent({ if (!board) return; const swimlanes = board.swimlanes(); - + if (swimlanes.length === 0) { // Check if any swimlane exists in the database to avoid race conditions const existingSwimlanes = ReactiveCache.getSwimlanes({ boardId }); @@ -97,31 +99,422 @@ BlazeComponent.extendComponent({ return; } + // Automatic migration disabled - migrations must be run manually from sidebar + // Board admins can run migrations from the sidebar Migrations menu this.isBoardReady.set(true); } catch (error) { console.error('Error during board conversion check:', error); this.isConverting.set(false); + this.isMigrating.set(false); this.isBoardReady.set(true); // Show board even if conversion check failed } }, + /** + * Check if board needs comprehensive migration + */ + async checkComprehensiveMigration(boardId) { + try { + return new Promise((resolve, reject) => { + Meteor.call('comprehensiveBoardMigration.needsMigration', boardId, (error, result) => { + if (error) { + console.error('Error checking comprehensive migration:', error); + reject(error); + } else { + resolve(result); + } + }); + }); + } catch (error) { + console.error('Error checking comprehensive migration:', error); + return false; + } + }, + + /** + * Execute comprehensive migration for a board + */ + async executeComprehensiveMigration(boardId) { + try { + // Start progress tracking + migrationProgressManager.startMigration(); + + // Simulate progress updates since we can't easily pass callbacks through Meteor methods + const progressSteps = [ + { step: 'analyze_board_structure', name: 'Analyze Board Structure', duration: 1000 }, + { step: 'fix_orphaned_cards', name: 'Fix Orphaned Cards', duration: 2000 }, + { step: 'convert_shared_lists', name: 'Convert Shared Lists', duration: 3000 }, + { step: 'ensure_per_swimlane_lists', name: 'Ensure Per-Swimlane Lists', duration: 1500 }, + { step: 'validate_migration', name: 'Validate Migration', duration: 1000 }, + { step: 'fix_avatar_urls', name: 'Fix Avatar URLs', duration: 1000 }, + { step: 'fix_attachment_urls', name: 'Fix Attachment URLs', duration: 1000 } + ]; + + // Start the actual migration + const migrationPromise = new Promise((resolve, reject) => { + Meteor.call('comprehensiveBoardMigration.execute', boardId, (error, result) => { + if (error) { + console.error('Error executing comprehensive migration:', error); + migrationProgressManager.failMigration(error); + reject(error); + } else { + if (process.env.DEBUG === 'true') { + console.log('Comprehensive migration completed for board:', boardId, result); + } + resolve(result.success); + } + }); + }); + + // Simulate progress updates + const progressPromise = this.simulateMigrationProgress(progressSteps); + + // Wait for both to complete + const [migrationResult] = await Promise.all([migrationPromise, progressPromise]); + + migrationProgressManager.completeMigration(); + return migrationResult; + + } catch (error) { + console.error('Error executing comprehensive migration:', error); + migrationProgressManager.failMigration(error); + return false; + } + }, + + /** + * Simulate migration progress updates + */ + async simulateMigrationProgress(progressSteps) { + const totalSteps = progressSteps.length; + + for (let i = 0; i < progressSteps.length; i++) { + const step = progressSteps[i]; + const stepProgress = Math.round(((i + 1) / totalSteps) * 100); + + // Update progress for this step + migrationProgressManager.updateProgress({ + overallProgress: stepProgress, + currentStep: i + 1, + totalSteps, + stepName: step.step, + stepProgress: 0, + stepStatus: `Starting ${step.name}...`, + stepDetails: null, + boardId: Session.get('currentBoard') + }); + + // Simulate step progress + const stepDuration = step.duration; + const updateInterval = 100; // Update every 100ms + const totalUpdates = stepDuration / updateInterval; + + for (let j = 0; j < totalUpdates; j++) { + const stepStepProgress = Math.round(((j + 1) / totalUpdates) * 100); + + migrationProgressManager.updateProgress({ + overallProgress: stepProgress, + currentStep: i + 1, + totalSteps, + stepName: step.step, + stepProgress: stepStepProgress, + stepStatus: `Processing ${step.name}...`, + stepDetails: { progress: `${stepStepProgress}%` }, + boardId: Session.get('currentBoard') + }); + + await new Promise(resolve => setTimeout(resolve, updateInterval)); + } + + // Complete the step + migrationProgressManager.updateProgress({ + overallProgress: stepProgress, + currentStep: i + 1, + totalSteps, + stepName: step.step, + stepProgress: 100, + stepStatus: `${step.name} completed`, + stepDetails: { status: 'completed' }, + boardId: Session.get('currentBoard') + }); + } + }, + + async startBackgroundMigration(boardId) { + try { + // Start background migration using the cron system + Meteor.call('boardMigration.startBoardMigration', boardId, (error, result) => { + if (error) { + console.error('Failed to start background migration:', error); + } else { + if (process.env.DEBUG === 'true') { + console.log('Background migration started for board:', boardId); + } + } + }); + } catch (error) { + console.error('Error starting background migration:', error); + } + }, + + async convertSharedListsToPerSwimlane(boardId) { + try { + const board = ReactiveCache.getBoard(boardId); + if (!board) return; + + // Check if board has already been processed for shared lists conversion + if (board.hasSharedListsConverted) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has already been processed for shared lists conversion`); + } + return; + } + + // Get all lists for this board + const allLists = board.lists(); + const swimlanes = board.swimlanes(); + + if (swimlanes.length === 0) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has no swimlanes, skipping shared lists conversion`); + } + return; + } + + // Find shared lists (lists with empty swimlaneId or null swimlaneId) + const sharedLists = allLists.filter(list => !list.swimlaneId || list.swimlaneId === ''); + + if (sharedLists.length === 0) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has no shared lists to convert`); + } + // Mark as processed even if no shared lists + Boards.update(boardId, { $set: { hasSharedListsConverted: true } }); + return; + } + + if (process.env.DEBUG === 'true') { + console.log(`Converting ${sharedLists.length} shared lists to per-swimlane lists for board ${boardId}`); + } + + // Convert each shared list to per-swimlane lists + for (const sharedList of sharedLists) { + // Create a copy of the list for each swimlane + for (const swimlane of swimlanes) { + // Check if this list already exists in this swimlane + const existingList = Lists.findOne({ + boardId: boardId, + swimlaneId: swimlane._id, + title: sharedList.title + }); + + if (!existingList) { + // Double-check to avoid race conditions + const doubleCheckList = ReactiveCache.getList({ + boardId: boardId, + swimlaneId: swimlane._id, + title: sharedList.title + }); + + if (!doubleCheckList) { + // Create a new list in this swimlane + const newListData = { + title: sharedList.title, + boardId: boardId, + swimlaneId: swimlane._id, + sort: sharedList.sort || 0, + archived: sharedList.archived || false, // Preserve archived state from original list + createdAt: new Date(), + modifiedAt: new Date() + }; + + // Copy other properties if they exist + if (sharedList.color) newListData.color = sharedList.color; + if (sharedList.wipLimit) newListData.wipLimit = sharedList.wipLimit; + if (sharedList.wipLimitEnabled) newListData.wipLimitEnabled = sharedList.wipLimitEnabled; + if (sharedList.wipLimitSoft) newListData.wipLimitSoft = sharedList.wipLimitSoft; + + Lists.insert(newListData); + + if (process.env.DEBUG === 'true') { + const archivedStatus = sharedList.archived ? ' (archived)' : ' (active)'; + console.log(`Created list "${sharedList.title}"${archivedStatus} for swimlane ${swimlane.title || swimlane._id}`); + } + } else { + if (process.env.DEBUG === 'true') { + console.log(`List "${sharedList.title}" already exists in swimlane ${swimlane.title || swimlane._id} (double-check), skipping`); + } + } + } else { + if (process.env.DEBUG === 'true') { + console.log(`List "${sharedList.title}" already exists in swimlane ${swimlane.title || swimlane._id}, skipping`); + } + } + } + + // Remove the original shared list completely + Lists.remove(sharedList._id); + + if (process.env.DEBUG === 'true') { + console.log(`Removed shared list "${sharedList.title}"`); + } + } + + // Mark board as processed + Boards.update(boardId, { $set: { hasSharedListsConverted: true } }); + + if (process.env.DEBUG === 'true') { + console.log(`Successfully converted ${sharedLists.length} shared lists to per-swimlane lists for board ${boardId}`); + } + + } catch (error) { + console.error('Error converting shared lists to per-swimlane:', error); + } + }, + + async fixMissingLists(boardId) { + try { + const board = ReactiveCache.getBoard(boardId); + if (!board) return; + + // Check if board has already been processed for missing lists fix + if (board.fixMissingListsCompleted) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has already been processed for missing lists fix`); + } + return; + } + + // Check if migration is needed + const needsMigration = await new Promise((resolve, reject) => { + Meteor.call('fixMissingListsMigration.needsMigration', boardId, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + + if (!needsMigration) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} does not need missing lists fix`); + } + return; + } + + if (process.env.DEBUG === 'true') { + console.log(`Starting fix missing lists migration for board ${boardId}`); + } + + // Execute the migration + const result = await new Promise((resolve, reject) => { + Meteor.call('fixMissingListsMigration.execute', boardId, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + + if (result && result.success) { + if (process.env.DEBUG === 'true') { + console.log(`Successfully fixed missing lists for board ${boardId}: created ${result.createdLists} lists, updated ${result.updatedCards} cards`); + } + } + + } catch (error) { + console.error('Error fixing missing lists:', error); + } + }, + + async fixDuplicateLists(boardId) { + try { + const board = ReactiveCache.getBoard(boardId); + if (!board) return; + + // Check if board has already been processed for duplicate lists fix + if (board.fixDuplicateListsCompleted) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has already been processed for duplicate lists fix`); + } + return; + } + + if (process.env.DEBUG === 'true') { + console.log(`Starting duplicate lists fix for board ${boardId}`); + } + + // Execute the duplicate lists fix + const result = await new Promise((resolve, reject) => { + Meteor.call('fixDuplicateLists.fixBoard', boardId, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + + if (result && result.fixed > 0) { + if (process.env.DEBUG === 'true') { + console.log(`Successfully fixed ${result.fixed} duplicate lists for board ${boardId}: ${result.fixedSwimlanes} swimlanes, ${result.fixedLists} lists`); + } + + // Mark board as processed + Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } }); + } else if (process.env.DEBUG === 'true') { + console.log(`No duplicate lists found for board ${boardId}`); + // Still mark as processed to avoid repeated checks + Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } }); + } else { + // Still mark as processed to avoid repeated checks + Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } }); + } + + } catch (error) { + console.error('Error fixing duplicate lists:', error); + } + }, + + async startAttachmentMigrationIfNeeded(boardId) { + try { + // Check if board has already been migrated + if (attachmentMigrationManager.isBoardMigrated(boardId)) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has already been migrated, skipping`); + } + return; + } + + // Check if there are unconverted attachments + const unconvertedAttachments = attachmentMigrationManager.getUnconvertedAttachments(boardId); + + if (unconvertedAttachments.length > 0) { + if (process.env.DEBUG === 'true') { + console.log(`Starting attachment migration for ${unconvertedAttachments.length} attachments in board ${boardId}`); + } + await attachmentMigrationManager.startAttachmentMigration(boardId); + } else { + // No attachments to migrate, mark board as migrated + // This will be handled by the migration manager itself + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has no attachments to migrate`); + } + } + } catch (error) { + console.error('Error starting attachment migration:', error); + } + }, + onlyShowCurrentCard() { const isMiniScreen = Utils.isMiniScreen(); const currentCardId = Utils.getCurrentCardId(true); return isMiniScreen && currentCardId; }, - openCards() { - // In desktop mode, return array of all open cards - const isMobile = Utils.getMobileMode(); - if (!isMobile) { - const openCardIds = Session.get('openCards') || []; - return openCardIds.map(id => ReactiveCache.getCard(id)).filter(card => card); - } - return []; - }, - goHome() { FlowRouter.go('home'); }, @@ -130,6 +523,10 @@ BlazeComponent.extendComponent({ return this.isConverting.get(); }, + isMigrating() { + return this.isMigrating.get(); + }, + isBoardReady() { return this.isBoardReady.get(); }, @@ -221,9 +618,9 @@ BlazeComponent.extendComponent({ const popupObserver = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { mutation.addedNodes.forEach(function(node) { - if (node.nodeType === 1 && + if (node.nodeType === 1 && (node.classList.contains('popup') || node.classList.contains('modal') || node.classList.contains('menu')) && - !node.closest('.js-swimlanes') && + !node.closest('.js-swimlanes') && !node.closest('.swimlane') && !node.closest('.list') && !node.closest('.minicard')) { @@ -339,9 +736,9 @@ BlazeComponent.extendComponent({ .js-add-card[tabindex] { outline: none; } - /* Sidebar hamburger menu button in header */ - .js-toggle-sidebar .fa-bars { - color: #fff !important; + /* Hamburger menu */ + .fa-bars, .icon-hamburger { + color: #222 !important; } /* Grey icons in card detail header */ .card-detail-header .fa, .card-detail-header .icon { @@ -540,60 +937,47 @@ BlazeComponent.extendComponent({ isViewSwimlanes() { const currentUser = ReactiveCache.getCurrentUser(); let boardView; - + if (currentUser) { boardView = (currentUser.profile || {}).boardView; } else { boardView = window.localStorage.getItem('boardView'); } - + // If no board view is set, default to swimlanes if (!boardView) { boardView = 'board-view-swimlanes'; } - + return boardView === 'board-view-swimlanes'; }, isViewLists() { const currentUser = ReactiveCache.getCurrentUser(); let boardView; - + if (currentUser) { boardView = (currentUser.profile || {}).boardView; } else { boardView = window.localStorage.getItem('boardView'); } - + return boardView === 'board-view-lists'; }, isViewCalendar() { const currentUser = ReactiveCache.getCurrentUser(); let boardView; - + if (currentUser) { boardView = (currentUser.profile || {}).boardView; } else { boardView = window.localStorage.getItem('boardView'); } - + return boardView === 'board-view-cal'; }, - isViewGantt() { - const currentUser = ReactiveCache.getCurrentUser(); - let boardView; - - if (currentUser) { - boardView = (currentUser.profile || {}).boardView; - } else { - boardView = window.localStorage.getItem('boardView'); - } - - return boardView === 'board-view-gantt'; - }, - hasSwimlanes() { const currentBoard = Utils.getCurrentBoard(); if (!currentBoard) { @@ -602,7 +986,7 @@ BlazeComponent.extendComponent({ } return false; } - + try { const swimlanes = currentBoard.swimlanes(); const hasSwimlanes = swimlanes && swimlanes.length > 0; @@ -637,24 +1021,27 @@ BlazeComponent.extendComponent({ const currentBoardId = Session.get('currentBoard'); const isBoardReady = this.isBoardReady.get(); const isConverting = this.isConverting.get(); + const isMigrating = this.isMigrating.get(); const boardView = Utils.boardView(); - + if (process.env.DEBUG === 'true') { console.log('=== BOARD DEBUG STATE ==='); console.log('currentBoardId:', currentBoardId); console.log('currentBoard:', !!currentBoard, currentBoard ? currentBoard.title : 'none'); console.log('isBoardReady:', isBoardReady); console.log('isConverting:', isConverting); + console.log('isMigrating:', isMigrating); console.log('boardView:', boardView); console.log('========================'); } - + return { currentBoardId, hasCurrentBoard: !!currentBoard, currentBoardTitle: currentBoard ? currentBoard.title : 'none', isBoardReady, isConverting, + isMigrating, boardView }; }, @@ -1021,8 +1408,3 @@ BlazeComponent.extendComponent({ } }, }).register('calendarView'); -/** - * Gantt View Component - * Displays cards as a Gantt chart with start/due dates - */ - diff --git a/client/components/boards/boardColors.css b/client/components/boards/boardColors.css index 641f85ad7..146991d27 100644 --- a/client/components/boards/boardColors.css +++ b/client/components/boards/boardColors.css @@ -49,12 +49,6 @@ 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; } @@ -156,12 +150,6 @@ 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; } @@ -263,12 +251,6 @@ 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; } @@ -370,12 +352,6 @@ 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; } @@ -477,12 +453,6 @@ 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; } @@ -584,12 +554,6 @@ 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; } @@ -691,12 +655,6 @@ 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; } @@ -798,12 +756,6 @@ 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; } @@ -905,12 +857,6 @@ 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; } @@ -1013,12 +959,6 @@ 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; } @@ -1222,12 +1162,6 @@ 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; } @@ -1358,12 +1292,6 @@ 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; } @@ -1469,12 +1397,6 @@ 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; } @@ -1503,7 +1425,7 @@ THEME - Clear Blue } .board-color-clearblue .list { background: rgba(255,255,255,0.35); - margin: 10px 0; + margin: 10px; border: 0; border-radius: 14px; } @@ -1738,12 +1660,6 @@ 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; } @@ -1854,12 +1770,6 @@ 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; } @@ -2152,12 +2062,6 @@ 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; } @@ -2643,12 +2547,6 @@ 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; } @@ -2690,7 +2588,7 @@ THEME - Exodark background: #222; } .board-color-exodark .list { - margin: 10px 0; + margin: 10px; color: #fff; border-radius: 15px; background-color: #1c1c1c; @@ -3242,12 +3140,6 @@ 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; @@ -4000,13 +3892,6 @@ 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; diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index 42cc8d592..bac4216ed 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -14,39 +14,41 @@ template(name="boardHeaderBar") with currentBoard if currentUser.isBoardAdmin a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title) - i.fa.fa-pencil-square-o + | ✏️ a.board-header-btn( class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}" title="{{_ currentBoard.permission}}") - i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}") + | {{#if currentBoard.isPublic}}🌐{{else}}🔒{{/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}}") + a.board-header-btn.js-star-board(title="{{_ 'star-board'}}") + if isStarred + | ⭐ + else + | ☆ if showStarCounter span.board-star-counter {{currentBoard.stars}} a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}") - i.fa.fa-sort + | {{sortCardsIcon}} span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}} if isSortActive a.board-header-btn-close.js-sort-reset(title="{{_ 'remove-sort'}}") - i.fa.fa-times-thin + | ❌ else a.board-header-btn.js-log-in( title="{{_ 'log-in'}}") - i.fa.fa-sign-in + | 🚪 span {{_ 'log-in'}} .board-header-btns.center @@ -57,114 +59,100 @@ 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) - i.fa.fa-pencil-square-o + | ✏️ a.board-header-btn( class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}" title="{{_ currentBoard.permission}}") - i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}") + | {{#if currentBoard.isPublic}}🌐{{else}}🔒{{/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.js-star-board(title="{{_ 'star-board'}}") + if isStarred + | ⭐ + else + | ☆ a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}") - i.fa.fa-sort - span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}} + | {{sortCardsIcon}} if isSortActive a.board-header-btn-close.js-sort-reset(title="{{_ 'remove-sort'}}") - i.fa.fa-times-thin + | ❌ else a.board-header-btn.js-log-in( title="{{_ 'log-in'}}") - i.fa.fa-sign-in + | 🚪 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}}js-filter-active{{/if}}") - i.fa.fa-filter - span {{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}} + class="{{#if Filter.isActive}}emphasis{{/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 - i.fa.fa-caret-down + a.board-header-btn.js-toggle-board-view( + title="{{_ 'board-view'}}") + | ▼ 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}}") - i.fa.fa-check-square-o - span {{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}} - if MultiSelection.isActive - a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}") - i.fa.fa-times-thin + | ☑️ + if MultiSelection.isActive + a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}") + | ❌ .separator a.board-header-btn.js-toggle-sidebar(title="{{_ 'sidebar-open'}} {{_ 'or'}} {{_ 'sidebar-close'}}") - i.fa.fa-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") @@ -175,26 +163,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") @@ -202,31 +190,24 @@ template(name="boardChangeViewPopup") li with "board-view-swimlanes" a.js-open-swimlanes-view - i.fa.fa-th-large - | {{_ 'swimlanes'}} + | 🏊 + | {{_ 'board-view-swimlanes'}} if $eq Utils.boardView "board-view-swimlanes" - i.fa.fa-check + | ✅ li with "board-view-lists" a.js-open-lists-view - i.fa.fa-trello + | 📋 | {{_ '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 @@ -238,11 +219,11 @@ template(name="createBoard") else p.quiet if $eq visibility.get 'public' - span.fa.fa-globe.colorful + span 🌐 = " " | {{{_ 'board-public-info'}}} else - span.fa.fa-lock.colorful + span 🔒 = " " | {{{_ 'board-private-info'}}} a.js-change-visibility {{_ 'change'}}. @@ -267,11 +248,11 @@ template(name="createBoardPopup") else p.quiet if $eq visibility.get 'public' - span.fa.fa-globe.colorful + span 🌐 = " " | {{{_ 'board-public-info'}}} else - span.fa.fa-lock.colorful + span 🔒 = " " | {{{_ 'board-private-info'}}} a.js-change-visibility {{_ 'change'}}. @@ -297,11 +278,11 @@ template(name="createTemplateContainerPopup") else p.quiet if $eq visibility.get 'public' - span.fa.fa-globe.colorful + span 🌐 = " " | {{{_ 'board-public-info'}}} else - span.fa.fa-lock.colorful + span 🔒 = " " | {{{_ 'board-private-info'}}} a.js-change-visibility {{_ 'change'}}. @@ -317,30 +298,19 @@ template(name="createTemplateContainerPopup") 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 -// - i.fa.fa-check +// 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 +// | ✅ + template(name="boardChangeTitlePopup") form label @@ -360,21 +330,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'}} diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index 5c37e19df..c84b593c6 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -1,6 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import dragscroll from '@wekanteam/dragscroll'; /* @@ -10,7 +9,7 @@ const UPCLS = 'fa-sort-up'; const sortCardsBy = new ReactiveVar(''); Template.boardChangeTitlePopup.events({ - async submit(event, templateInstance) { + submit(event, templateInstance) { const newTitle = templateInstance .$('.js-board-name') .val() @@ -20,8 +19,8 @@ Template.boardChangeTitlePopup.events({ .val() .trim(); if (newTitle) { - await this.rename(newTitle); - await this.setDescription(newDesc); + this.rename(newTitle); + this.setDescription(newDesc); Popup.back(); } event.preventDefault(); @@ -182,7 +181,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 @@ -191,7 +190,7 @@ Template.boardHeaderBar.helpers({ } else if (sortBy.createdAt) { return sortBy.createdAt === 1 ? '⬆️' : '⬇️'; // Up/down arrow based on direction } - + return '🃏'; // Default card icon }, }); @@ -209,10 +208,6 @@ Template.boardChangeViewPopup.events({ Utils.setBoardView('board-view-cal'); Popup.back(); }, - 'click .js-open-gantt-view'() { - Utils.setBoardView('board-view-gantt'); - Popup.back(); - }, }); const CreateBoard = BlazeComponent.extendComponent({ @@ -364,10 +359,10 @@ const CreateBoard = BlazeComponent.extendComponent({ }).register('createTemplateContainerPopup'); (class HeaderBarCreateBoard extends CreateBoard { - async onSubmit(event) { + onSubmit(event) { super.onSubmit(event); // Immediately star boards crated with the headerbar popup. - await ReactiveCache.getCurrentUser().toggleBoardStar(this.boardId.get()); + ReactiveCache.getCurrentUser().toggleBoardStar(this.boardId.get()); } }.register('headerBarCreateBoardPopup')); diff --git a/client/components/boards/boardsList.css b/client/components/boards/boardsList.css index 9ba4ee1c7..dc7efdd66 100644 --- a/client/components/boards/boardsList.css +++ b/client/components/boards/boardsList.css @@ -436,17 +436,10 @@ 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 .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 { top: 0; @@ -590,9 +583,9 @@ } .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); + background: #2196F3; + border-color: #2196F3; + box-shadow: 0 2px 8px rgba(33, 150, 243, 0.6); width: 24px !important; height: 24px !important; top: auto !important; @@ -608,22 +601,10 @@ 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: 4px solid #2196F3; outline-offset: -4px; - box-shadow: 0 4px 12px rgba(60, 181, 0, 0.4); + box-shadow: 0 4px 12px rgba(33, 150, 243, 0.4); } /* Visual hint when multiselection is active */ @@ -664,19 +645,8 @@ body.grey-icons-enabled .board-list.is-multiselection-active .js-board.is-checke } .board-backgrounds-list .board-background-select .background-box i.fa-check { font-size: 25px; - color: #3cb500; + color: #fff; } -/* 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); diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade index fc3ab582a..5cf488c55 100644 --- a/client/components/boards/boardsList.jade +++ b/client/components/boards/boardsList.jade @@ -8,30 +8,18 @@ template(name="boardList") 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-label ⭐ {{_ '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-label 📋 {{_ '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-label 📂 {{_ 'allboards.remaining'}} span.menu-count {{menuItemCount 'remaining'}} .workspaces-header - span - span.emoji-icon - i.fa.fa-folder-open - | {{_ 'allboards.workspaces'}} + span 🗂️ {{_ 'allboards.workspaces'}} a.js-add-workspace(title="{{_ 'allboards.add-workspace'}}") + // Workspaces tree +workspaceTree(nodes=workspacesTree selectedWorkspaceId=selectedWorkspaceId) @@ -55,58 +43,44 @@ template(name="boardList") li.AllBoardBtns div.AllBoardButtonsContainer if userHasOrgsOrTeams - span.emoji-icon - i.fa.fa-search + span 🔍 input#filterBtn(type="button" value="{{_ 'filter'}}") button#resetBtn.filter-reset-btn - span.reset-icon - span.emoji-icon - i.fa.fa-times-thin + span.reset-icon ❌ span {{_ 'filter-clear'}} // Right boards grid .boards-right-grid .boards-path-header .path-left - span.path-icon.emoji-icon {{currentMenuPath.icon}} + span.path-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'}} + span.multiselection-hint 📌 {{_ '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 📦 span {{_ 'archive-board'}} button.js-duplicate-selected-boards.board-header-btn - span.emoji-icon - i.fa.fa-clipboard + span 📋 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 - |  {{_ 'add-template-container'}} + | ➕ {{_ 'add-template-container'}} else a.board-list-item.label(title="{{_ 'add-board'}}") - span.emoji-icon - i.fa.fa-plus - |  {{_ 'add-board'}} + | ➕ {{_ '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 @@ -119,8 +93,7 @@ template(name="boardList") 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}} + | {{#if isStarred}}⭐{{else}}☆{{/if}} p.board-list-item-desc {{_ 'just-invited'}} button.js-accept-invite.primary {{_ 'accept'}} button.js-decline-invite {{_ 'decline'}} @@ -130,10 +103,7 @@ template(name="boardList") 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 - + span.board-handle(title="{{_ 'drag-board'}}") ↕️ a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}") span.details span.board-list-item-name(title="{{_ 'template-container'}}") @@ -146,22 +116,17 @@ template(name="boardList") 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}}") + | {{#if isStarred}}⭐{{else}}☆{{/if}} else .board-list-item if BoardMultiSelection.isActive .materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection( class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}") - span.board-handle(title="{{_ 'drag-board'}}") - span.emoji-icon - i.fa.fa-arrows - + span.board-handle(title="{{_ 'drag-board'}}") ↕️ 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'}}") @@ -186,29 +151,21 @@ template(name="boardList") 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}}") + | {{#if isStarred}}⭐{{else}}☆{{/if}} 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") @@ -217,22 +174,16 @@ template(name="workspaceTree") 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 - + span.workspace-drag-handle ↕️ a.js-select-workspace(data-id="{{id}}") span.workspace-icon if icon +viewer = icon else - span.emoji-icon - i.fa.fa-folder + | 📁 span.workspace-name= name - a.js-edit-workspace(data-id="{{id}}" title="{{_ 'allboards.edit-workspace'}}") - span.emoji-icon - i.fa.fa-pencil-square-o + a.js-edit-workspace(data-id="{{id}}" title="{{_ 'allboards.edit-workspace'}}") ✏️ span.workspace-count {{workspaceCount id}} a.js-add-subworkspace(data-id="{{id}}" title="{{_ 'allboards.add-subworkspace'}}") + if children diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index fcb2461e6..bb1d258d0 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -1,7 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; -import getSlug from 'limax'; const subManager = new SubsManager(); @@ -19,7 +17,7 @@ Template.boardList.helpers({ BoardMultiSelection() { return BoardMultiSelection; }, -}); +}) Template.boardListHeaderBar.events({ 'click .js-open-archived-board'() { @@ -27,7 +25,8 @@ Template.boardListHeaderBar.events({ }, }); -Template.boardList.events({}); +Template.boardList.events({ +}); Template.boardListHeaderBar.helpers({ title() { @@ -55,7 +54,7 @@ BlazeComponent.extendComponent({ let currUser = ReactiveCache.getCurrentUser(); let userLanguage; if (currUser && currUser.profile) { - userLanguage = currUser.profile.language; + userLanguage = currUser.profile.language } if (userLanguage) { TAPi18n.setLanguage(userLanguage); @@ -70,7 +69,7 @@ BlazeComponent.extendComponent({ reorderWorkspaces(draggedSpaceId, targetSpaceId) { const tree = this.workspacesTreeVar.get(); - + // Helper to remove a space from tree const removeSpace = (nodes, id) => { for (let i = 0; i < nodes.length; i++) { @@ -87,7 +86,7 @@ BlazeComponent.extendComponent({ } return { tree: nodes, removed: null }; }; - + // Helper to insert a space after target const insertAfter = (nodes, targetId, spaceToInsert) => { for (let i = 0; i < nodes.length; i++) { @@ -103,20 +102,17 @@ BlazeComponent.extendComponent({ } return false; }; - + // Clone the tree const newTree = EJSON.clone(tree); - + // Remove the dragged space - const { tree: treeAfterRemoval, removed } = removeSpace( - newTree, - draggedSpaceId, - ); - + const { tree: treeAfterRemoval, removed } = removeSpace(newTree, draggedSpaceId); + if (removed) { // Insert after target insertAfter(treeAfterRemoval, targetSpaceId, removed); - + // Save the new tree Meteor.call('setWorkspacesTree', treeAfterRemoval, (err) => { if (err) console.error(err); @@ -127,6 +123,7 @@ BlazeComponent.extendComponent({ onRendered() { // jQuery sortable is disabled in favor of HTML5 drag-and-drop for space management // The old sortable code has been removed to prevent conflicts + /* OLD SORTABLE CODE - DISABLED const itemsSelector = '.js-board:not(.placeholder)'; @@ -144,7 +141,7 @@ BlazeComponent.extendComponent({ ui.placeholder.height(ui.helper.height()); EscapeActions.executeUpTo('popup-close'); }, - async stop(evt, ui) { + stop(evt, ui) { const prevBoardDom = ui.item.prev('.js-board').get(0); const nextBoardDom = ui.item.next('.js-board').get(0); const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardDom, 1); @@ -154,7 +151,7 @@ BlazeComponent.extendComponent({ $boards.sortable('cancel'); const currentUser = ReactiveCache.getCurrentUser(); if (currentUser && typeof currentUser.setBoardSortIndex === 'function') { - await currentUser.setBoardSortIndex(board._id, sortIndex.base); + currentUser.setBoardSortIndex(board._id, sortIndex.base); } }, }); @@ -169,118 +166,89 @@ BlazeComponent.extendComponent({ */ }, userHasTeams() { - if (ReactiveCache.getCurrentUser()?.teams?.length > 0) return true; - else return false; + if (ReactiveCache.getCurrentUser()?.teams?.length > 0) + return true; + else + return false; }, teamsDatas() { - const teams = ReactiveCache.getCurrentUser()?.teams; + const teams = ReactiveCache.getCurrentUser()?.teams if (teams) - return teams.sort((a, b) => - a.teamDisplayName.localeCompare(b.teamDisplayName), - ); - else return []; + return teams.sort((a, b) => a.teamDisplayName.localeCompare(b.teamDisplayName)); + else + return []; }, userHasOrgs() { - if (ReactiveCache.getCurrentUser()?.orgs?.length > 0) return true; - else return false; + if (ReactiveCache.getCurrentUser()?.orgs?.length > 0) + return true; + else + return false; }, orgsDatas() { const orgs = ReactiveCache.getCurrentUser()?.orgs; if (orgs) - return orgs.sort((a, b) => - a.orgDisplayName.localeCompare(b.orgDisplayName), - ); - else return []; + return orgs.sort((a, b) => a.orgDisplayName.localeCompare(b.orgDisplayName)); + else + return []; }, userHasOrgsOrTeams() { const ret = this.userHasOrgs() || this.userHasTeams(); return ret; }, currentMenuPath() { - try { - const selectedMenuVar = this.selectedMenu; - if (!selectedMenuVar || typeof selectedMenuVar.get !== 'function') { - return { icon: '🗂️', text: 'Workspaces' }; + const sel = this.selectedMenu.get(); + const currentUser = ReactiveCache.getCurrentUser(); + + // Helper to find space by id in tree + const findSpaceById = (nodes, targetId, path = []) => { + for (const node of nodes) { + if (node.id === targetId) { + return [...path, node]; + } + if (node.children && node.children.length > 0) { + const result = findSpaceById(node.children, targetId, [...path, node]); + if (result) return result; + } } - const sel = selectedMenuVar.get(); - const currentUser = ReactiveCache.getCurrentUser(); - - // Helper function to safely get translation or fallback - const safeTranslate = (key, fallback) => { - try { - return TAPi18n.__(key) || fallback; - } catch (e) { - return fallback; - } - }; - - // Helper to find space by id in tree - const findSpaceById = (nodes, targetId, path = []) => { - if (!nodes || !Array.isArray(nodes)) return null; - for (const node of nodes) { - if (node.id === targetId) { - return [...path, node]; - } - if (node.children && node.children.length > 0) { - const result = findSpaceById(node.children, targetId, [ - ...path, - node, - ]); - if (result) return result; - } - } - return null; - }; - - if (sel === 'starred') { - return { icon: '⭐', text: safeTranslate('allboards.starred', 'Starred') }; - } else if (sel === 'templates') { - return { icon: '📋', text: safeTranslate('allboards.templates', 'Templates') }; - } else if (sel === 'remaining') { - return { icon: '📂', text: safeTranslate('allboards.remaining', 'Remaining') }; - } else { - // sel is a workspaceId, build path - if (!this.workspacesTreeVar || typeof this.workspacesTreeVar.get !== 'function') { - return { icon: '🗂️', text: safeTranslate('allboards.workspaces', 'Workspaces') }; - } - const tree = this.workspacesTreeVar.get(); - const spacePath = findSpaceById(tree, sel); - if (spacePath && spacePath.length > 0) { - const pathText = spacePath.map((s) => s.name).join(' / '); - return { - icon: '🗂️', - text: `${safeTranslate('allboards.workspaces', 'Workspaces')} / ${pathText}`, - }; - } - return { icon: '🗂️', text: safeTranslate('allboards.workspaces', 'Workspaces') }; + return null; + }; + + if (sel === 'starred') { + return { icon: '⭐', text: TAPi18n.__('allboards.starred') }; + } else if (sel === 'templates') { + return { icon: '📋', text: TAPi18n.__('allboards.templates') }; + } else if (sel === 'remaining') { + return { icon: '📂', text: TAPi18n.__('allboards.remaining') }; + } else { + // sel is a workspaceId, build path + const tree = this.workspacesTreeVar.get(); + const spacePath = findSpaceById(tree, sel); + if (spacePath && spacePath.length > 0) { + const pathText = spacePath.map(s => s.name).join(' / '); + return { icon: '🗂️', text: `${TAPi18n.__('allboards.workspaces')} / ${pathText}` }; } - } catch (error) { - console.error('Error in currentMenuPath:', error); - return { icon: '🗂️', text: 'Workspaces' }; + return { icon: '🗂️', text: TAPi18n.__('allboards.workspaces') }; } }, boards() { let query = { + // { type: 'board' }, + // { type: { $in: ['board','template-container'] } }, $and: [ { archived: false }, { type: { $in: ['board', 'template-container'] } }, - { title: { $not: { $regex: /^\^.*\^$/ } } }, - ], + { $or: [] }, + { title: { $not: { $regex: /^\^.*\^$/ } } } + ] }; - const membershipOrs = []; - let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne( - 'tableVisibilityMode-allowPrivateOnly', - ); + let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly'); if (FlowRouter.getRouteName() === 'home') { - membershipOrs.push({ 'members.userId': Meteor.userId() }); + query.$and[2].$or.push({ 'members.userId': Meteor.userId() }); - if ( - allowPrivateVisibilityOnly !== undefined && - allowPrivateVisibilityOnly.booleanValue - ) { - query.$and.push({ permission: 'private' }); + if (allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue) { + query.$and.push({ 'permission': 'private' }); } const currUser = ReactiveCache.getCurrentUser(); @@ -292,7 +260,7 @@ BlazeComponent.extendComponent({ // } //query.$and[2].$or.push({'orgs': {$elemMatch : {orgId: orgsIds[0]}}}); - membershipOrs.push({ 'orgs.orgId': { $in: orgsIds } }); + query.$and[2].$or.push({ 'orgs.orgId': { $in: orgsIds } }); } let teamIdsUserBelongs = currUser?.teamIdsUserBelongs() || ''; @@ -302,15 +270,10 @@ BlazeComponent.extendComponent({ // query.$or[2].$or.push({'teams.teamId': teamsIds[i]}); // } //query.$and[2].$or.push({'teams': { $elemMatch : {teamId: teamsIds[0]}}}); - membershipOrs.push({ 'teams.teamId': { $in: teamsIds } }); + query.$and[2].$or.push({ 'teams.teamId': { $in: teamsIds } }); } - if (membershipOrs.length) { - query.$and.splice(2, 0, { $or: membershipOrs }); - } - } else if ( - allowPrivateVisibilityOnly !== undefined && - !allowPrivateVisibilityOnly.booleanValue - ) { + } + else if (allowPrivateVisibilityOnly !== undefined && !allowPrivateVisibilityOnly.booleanValue) { query = { archived: false, //type: { $in: ['board','template-container'] }, @@ -324,33 +287,28 @@ BlazeComponent.extendComponent({ let list = boards; // Apply left menu filtering const sel = this.selectedMenu.get(); - const assignments = - (currentUser && - currentUser.profile && - currentUser.profile.boardWorkspaceAssignments) || - {}; + const assignments = (currentUser && currentUser.profile && currentUser.profile.boardWorkspaceAssignments) || {}; if (sel === 'starred') { - list = list.filter((b) => currentUser && currentUser.hasStarred(b._id)); + list = list.filter(b => currentUser && currentUser.hasStarred(b._id)); } else if (sel === 'templates') { - list = list.filter((b) => b.type === 'template-container'); + list = list.filter(b => b.type === 'template-container'); } else if (sel === 'remaining') { // Show boards not in any workspace AND not templates // Keep starred boards visible in Remaining too - list = list.filter( - (b) => !assignments[b._id] && b.type !== 'template-container', + list = list.filter(b => + !assignments[b._id] && + b.type !== 'template-container' ); } else { // assume sel is a workspaceId // Keep starred boards visible in their workspace too - list = list.filter((b) => assignments[b._id] === sel); + list = list.filter(b => assignments[b._id] === sel); } if (currentUser && typeof currentUser.sortBoardsForUser === 'function') { return currentUser.sortBoardsForUser(list); } - return list - .slice() - .sort((a, b) => (a.title || '').localeCompare(b.title || '')); + return list.slice().sort((a, b) => (a.title || '').localeCompare(b.title || '')); }, boardLists(boardId) { /* Bug Board icons random dance https://github.com/wekan/wekan/issues/4214 @@ -410,35 +368,29 @@ BlazeComponent.extendComponent({ }, 'click .js-add-workspace'(evt) { evt.preventDefault(); - const name = prompt( - TAPi18n.__('allboards.add-workspace-prompt') || 'New Space name', - ); + const name = prompt(TAPi18n.__('allboards.add-workspace-prompt') || 'New Space name'); if (name && name.trim()) { - Meteor.call( - 'createWorkspace', - { parentId: null, name: name.trim() }, - (err, res) => { - if (err) console.error(err); - }, - ); + Meteor.call('createWorkspace', { parentId: null, name: name.trim() }, (err, res) => { + if (err) console.error(err); + }); } }, 'click .js-add-board'(evt) { // Store the currently selected workspace/menu for board creation const selectedWorkspaceId = this.selectedWorkspaceIdVar.get(); const selectedMenu = this.selectedMenu.get(); - + if (selectedWorkspaceId) { Session.set('createBoardInWorkspace', selectedWorkspaceId); } else { Session.set('createBoardInWorkspace', null); } - - // Open different popup based on context - if (selectedMenu === 'templates') { - Popup.open('createTemplateContainer')(evt); - } else { - Popup.open('createBoard')(evt); + + // Open different popup based on context + if (selectedMenu === 'templates') { + Popup.open('createTemplateContainer')(evt); + } else { + Popup.open('createBoard')(evt); } }, 'click .js-star-board'(evt) { @@ -452,27 +404,16 @@ BlazeComponent.extendComponent({ // HTML5 DnD from boards to spaces 'dragstart .js-board'(evt) { const boardId = this.currentData()._id; - + // Support multi-drag - if ( - BoardMultiSelection.isActive() && - BoardMultiSelection.isSelected(boardId) - ) { + if (BoardMultiSelection.isActive() && BoardMultiSelection.isSelected(boardId)) { const selectedIds = BoardMultiSelection.getSelectedBoardIds(); - try { - evt.originalEvent.dataTransfer.setData( - 'text/plain', - JSON.stringify(selectedIds), - ); - evt.originalEvent.dataTransfer.setData( - 'application/x-board-multi', - 'true', - ); + try { + evt.originalEvent.dataTransfer.setData('text/plain', JSON.stringify(selectedIds)); + evt.originalEvent.dataTransfer.setData('application/x-board-multi', 'true'); } catch (e) {} } else { - try { - evt.originalEvent.dataTransfer.setData('text/plain', boardId); - } catch (e) {} + try { evt.originalEvent.dataTransfer.setData('text/plain', boardId); } catch (e) {} } }, 'click .js-clone-board'(evt) { @@ -545,11 +486,8 @@ BlazeComponent.extendComponent({ 'click .js-archive-selected-boards'(evt) { evt.preventDefault(); const selectedBoards = BoardMultiSelection.getSelectedBoardIds(); - if ( - selectedBoards.length > 0 && - confirm(TAPi18n.__('archive-board-confirm')) - ) { - selectedBoards.forEach((boardId) => { + if (selectedBoards.length > 0 && confirm(TAPi18n.__('archive-board-confirm'))) { + selectedBoards.forEach(boardId => { Meteor.call('archiveBoard', boardId); }); BoardMultiSelection.reset(); @@ -558,11 +496,8 @@ BlazeComponent.extendComponent({ 'click .js-duplicate-selected-boards'(evt) { evt.preventDefault(); const selectedBoards = BoardMultiSelection.getSelectedBoardIds(); - if ( - selectedBoards.length > 0 && - confirm(TAPi18n.__('duplicate-board-confirm')) - ) { - selectedBoards.forEach((boardId) => { + if (selectedBoards.length > 0 && confirm(TAPi18n.__('duplicate-board-confirm'))) { + selectedBoards.forEach(boardId => { const board = ReactiveCache.getBoard(boardId); if (board) { Meteor.call( @@ -575,7 +510,7 @@ BlazeComponent.extendComponent({ }, (err, res) => { if (err) console.error(err); - }, + } ); } }); @@ -583,56 +518,46 @@ BlazeComponent.extendComponent({ } }, 'click #resetBtn'(event) { - let allBoards = document.getElementsByClassName('js-board'); + let allBoards = document.getElementsByClassName("js-board"); let currBoard; for (let i = 0; i < allBoards.length; i++) { currBoard = allBoards[i]; - currBoard.style.display = 'block'; + currBoard.style.display = "block"; } }, 'click #filterBtn'(event) { event.preventDefault(); - let selectedTeams = document.querySelectorAll( - '#jsAllBoardTeams option:checked', - ); - let selectedTeamsValues = Array.from(selectedTeams).map( - function (elt) { - return elt.value; - }, - ); - let index = selectedTeamsValues.indexOf('-1'); + let selectedTeams = document.querySelectorAll('#jsAllBoardTeams option:checked'); + let selectedTeamsValues = Array.from(selectedTeams).map(function (elt) { return elt.value }); + let index = selectedTeamsValues.indexOf("-1"); if (index > -1) { selectedTeamsValues.splice(index, 1); } - let selectedOrgs = document.querySelectorAll( - '#jsAllBoardOrgs option:checked', - ); - let selectedOrgsValues = Array.from(selectedOrgs).map(function (elt) { - return elt.value; - }); - index = selectedOrgsValues.indexOf('-1'); + let selectedOrgs = document.querySelectorAll('#jsAllBoardOrgs option:checked'); + let selectedOrgsValues = Array.from(selectedOrgs).map(function (elt) { return elt.value }); + index = selectedOrgsValues.indexOf("-1"); if (index > -1) { selectedOrgsValues.splice(index, 1); } if (selectedTeamsValues.length > 0 || selectedOrgsValues.length > 0) { const query = { - $and: [{ archived: false }, { type: 'board' }], + $and: [ + { archived: false }, + { type: 'board' }, + { $or: [] } + ] }; - const ors = []; if (selectedTeamsValues.length > 0) { - ors.push({ 'teams.teamId': { $in: selectedTeamsValues } }); + query.$and[2].$or.push({ 'teams.teamId': { $in: selectedTeamsValues } }); } if (selectedOrgsValues.length > 0) { - ors.push({ 'orgs.orgId': { $in: selectedOrgsValues } }); - } - if (ors.length) { - query.$and.push({ $or: ors }); + query.$and[2].$or.push({ 'orgs.orgId': { $in: selectedOrgsValues } }); } let filteredBoards = ReactiveCache.getBoards(query, {}); - let allBoards = document.getElementsByClassName('js-board'); + let allBoards = document.getElementsByClassName("js-board"); let currBoard; if (filteredBoards.length > 0) { let currBoardId; @@ -644,13 +569,16 @@ BlazeComponent.extendComponent({ return board._id == currBoardId; }); - if (found !== undefined) currBoard.style.display = 'block'; - else currBoard.style.display = 'none'; + if (found !== undefined) + currBoard.style.display = "block"; + else + currBoard.style.display = "none"; } - } else { + } + else { for (let i = 0; i < allBoards.length; i++) { currBoard = allBoards[i]; - currBoard.style.display = 'none'; + currBoard.style.display = "none"; } } } @@ -659,7 +587,7 @@ BlazeComponent.extendComponent({ evt.preventDefault(); evt.stopPropagation(); const workspaceId = evt.currentTarget.getAttribute('data-id'); - + // Find the space in the tree const findSpace = (nodes, id) => { for (const node of nodes) { @@ -671,43 +599,33 @@ BlazeComponent.extendComponent({ } return null; }; - + const tree = this.workspacesTreeVar.get(); const space = findSpace(tree, workspaceId); - + if (space) { - const newName = prompt( - TAPi18n.__('allboards.edit-workspace-name') || 'Space name:', - space.name, - ); - const newIcon = prompt( - TAPi18n.__('allboards.edit-workspace-icon') || - 'Space icon (markdown):', - space.icon || '📁', - ); - + const newName = prompt(TAPi18n.__('allboards.edit-workspace-name') || 'Space name:', space.name); + const newIcon = prompt(TAPi18n.__('allboards.edit-workspace-icon') || 'Space icon (markdown):', space.icon || '📁'); + if (newName !== null && newName.trim()) { // Update space in tree const updateSpaceInTree = (nodes, id, updates) => { - return nodes.map((node) => { + return nodes.map(node => { if (node.id === id) { return { ...node, ...updates }; } if (node.children) { - return { - ...node, - children: updateSpaceInTree(node.children, id, updates), - }; + return { ...node, children: updateSpaceInTree(node.children, id, updates) }; } return node; }); }; - - const updatedTree = updateSpaceInTree(tree, workspaceId, { - name: newName.trim(), - icon: newIcon || '📁', + + const updatedTree = updateSpaceInTree(tree, workspaceId, { + name: newName.trim(), + icon: newIcon || '📁' }); - + Meteor.call('setWorkspacesTree', updatedTree, (err) => { if (err) console.error(err); }); @@ -718,29 +636,19 @@ BlazeComponent.extendComponent({ evt.preventDefault(); evt.stopPropagation(); const parentId = evt.currentTarget.getAttribute('data-id'); - const name = prompt( - TAPi18n.__('allboards.add-subworkspace-prompt') || 'Subspace name:', - ); - + const name = prompt(TAPi18n.__('allboards.add-subworkspace-prompt') || 'Subspace name:'); + if (name && name.trim()) { - Meteor.call( - 'createWorkspace', - { parentId, name: name.trim() }, - (err) => { - if (err) console.error(err); - }, - ); + Meteor.call('createWorkspace', { parentId, name: name.trim() }, (err) => { + if (err) console.error(err); + }); } }, 'dragstart .workspace-node'(evt) { - const workspaceId = - evt.currentTarget.getAttribute('data-workspace-id'); + const workspaceId = evt.currentTarget.getAttribute('data-workspace-id'); evt.originalEvent.dataTransfer.effectAllowed = 'move'; - evt.originalEvent.dataTransfer.setData( - 'application/x-workspace-id', - workspaceId, - ); - + evt.originalEvent.dataTransfer.setData('application/x-workspace-id', workspaceId); + // Create a better drag image const dragImage = evt.currentTarget.cloneNode(true); dragImage.style.position = 'absolute'; @@ -749,28 +657,25 @@ BlazeComponent.extendComponent({ document.body.appendChild(dragImage); evt.originalEvent.dataTransfer.setDragImage(dragImage, 0, 0); setTimeout(() => document.body.removeChild(dragImage), 0); - + evt.currentTarget.classList.add('dragging'); }, 'dragend .workspace-node'(evt) { evt.currentTarget.classList.remove('dragging'); - document.querySelectorAll('.workspace-node').forEach((el) => { + document.querySelectorAll('.workspace-node').forEach(el => { el.classList.remove('drag-over'); }); }, 'dragover .workspace-node'(evt) { evt.preventDefault(); evt.stopPropagation(); - + const draggingEl = document.querySelector('.workspace-node.dragging'); const targetEl = evt.currentTarget; - + // Allow dropping boards on any space // Or allow dropping spaces on other spaces (but not on itself or descendants) - if ( - !draggingEl || - (targetEl !== draggingEl && !draggingEl.contains(targetEl)) - ) { + if (!draggingEl || (targetEl !== draggingEl && !draggingEl.contains(targetEl))) { evt.originalEvent.dataTransfer.dropEffect = 'move'; targetEl.classList.add('drag-over'); } @@ -781,25 +686,19 @@ BlazeComponent.extendComponent({ 'drop .workspace-node'(evt) { evt.preventDefault(); evt.stopPropagation(); - + const targetEl = evt.currentTarget; targetEl.classList.remove('drag-over'); - + // Check what's being dropped - board or workspace - const draggedWorkspaceId = evt.originalEvent.dataTransfer.getData( - 'application/x-workspace-id', - ); - const isMultiBoard = evt.originalEvent.dataTransfer.getData( - 'application/x-board-multi', - ); - const boardData = - evt.originalEvent.dataTransfer.getData('text/plain'); - + const draggedWorkspaceId = evt.originalEvent.dataTransfer.getData('application/x-workspace-id'); + const isMultiBoard = evt.originalEvent.dataTransfer.getData('application/x-board-multi'); + const boardData = evt.originalEvent.dataTransfer.getData('text/plain'); + if (draggedWorkspaceId && !boardData) { // This is a workspace reorder operation - const targetWorkspaceId = - targetEl.getAttribute('data-workspace-id'); - + const targetWorkspaceId = targetEl.getAttribute('data-workspace-id'); + if (draggedWorkspaceId !== targetWorkspaceId) { this.reorderWorkspaces(draggedWorkspaceId, targetWorkspaceId); } @@ -807,13 +706,13 @@ BlazeComponent.extendComponent({ // This is a board assignment operation // Get the workspace ID directly from the dropped workspace-node's data-workspace-id attribute const workspaceId = targetEl.getAttribute('data-workspace-id'); - + if (workspaceId) { if (isMultiBoard) { // Multi-board drag try { const boardIds = JSON.parse(boardData); - boardIds.forEach((boardId) => { + boardIds.forEach(boardId => { Meteor.call('assignBoardToWorkspace', boardId, workspaceId); }); } catch (e) { @@ -829,7 +728,7 @@ BlazeComponent.extendComponent({ 'dragover .js-select-menu'(evt) { evt.preventDefault(); evt.stopPropagation(); - + const menuType = evt.currentTarget.getAttribute('data-type'); // Only allow drop on "remaining" menu to unassign boards from spaces if (menuType === 'remaining') { @@ -843,25 +742,22 @@ BlazeComponent.extendComponent({ 'drop .js-select-menu'(evt) { evt.preventDefault(); evt.stopPropagation(); - + const menuType = evt.currentTarget.getAttribute('data-type'); evt.currentTarget.classList.remove('drag-over'); - + // Only handle drops on "remaining" menu if (menuType !== 'remaining') return; - - const isMultiBoard = evt.originalEvent.dataTransfer.getData( - 'application/x-board-multi', - ); - const boardData = - evt.originalEvent.dataTransfer.getData('text/plain'); - + + const isMultiBoard = evt.originalEvent.dataTransfer.getData('application/x-board-multi'); + const boardData = evt.originalEvent.dataTransfer.getData('text/plain'); + if (boardData) { if (isMultiBoard) { // Multi-board drag - unassign all from workspaces try { const boardIds = JSON.parse(boardData); - boardIds.forEach((boardId) => { + boardIds.forEach(boardId => { Meteor.call('unassignBoardFromWorkspace', boardId); }); } catch (e) { @@ -891,59 +787,50 @@ BlazeComponent.extendComponent({ }, menuItemCount(type) { const currentUser = ReactiveCache.getCurrentUser(); - const assignments = - (currentUser && - currentUser.profile && - currentUser.profile.boardWorkspaceAssignments) || - {}; - + const assignments = (currentUser && currentUser.profile && currentUser.profile.boardWorkspaceAssignments) || {}; + // Get all boards for counting let query = { $and: [ { archived: false }, { type: { $in: ['board', 'template-container'] } }, { $or: [{ 'members.userId': Meteor.userId() }] }, - { title: { $not: { $regex: /^\^.*\^$/ } } }, - ], + { title: { $not: { $regex: /^\^.*\^$/ } } } + ] }; const allBoards = ReactiveCache.getBoards(query, {}); - + if (type === 'starred') { - return allBoards.filter( - (b) => currentUser && currentUser.hasStarred(b._id), - ).length; + return allBoards.filter(b => currentUser && currentUser.hasStarred(b._id)).length; } else if (type === 'templates') { - return allBoards.filter((b) => b.type === 'template-container').length; + return allBoards.filter(b => b.type === 'template-container').length; } else if (type === 'remaining') { // Count boards not in any workspace AND not templates // Include starred boards (they appear in both Starred and Remaining) - return allBoards.filter( - (b) => !assignments[b._id] && b.type !== 'template-container', + return allBoards.filter(b => + !assignments[b._id] && + b.type !== 'template-container' ).length; } return 0; }, workspaceCount(workspaceId) { const currentUser = ReactiveCache.getCurrentUser(); - const assignments = - (currentUser && - currentUser.profile && - currentUser.profile.boardWorkspaceAssignments) || - {}; - + const assignments = (currentUser && currentUser.profile && currentUser.profile.boardWorkspaceAssignments) || {}; + // Get all boards for counting let query = { $and: [ { archived: false }, { type: { $in: ['board', 'template-container'] } }, { $or: [{ 'members.userId': Meteor.userId() }] }, - { title: { $not: { $regex: /^\^.*\^$/ } } }, - ], + { title: { $not: { $regex: /^\^.*\^$/ } } } + ] }; const allBoards = ReactiveCache.getBoards(query, {}); - + // Count boards directly assigned to this space (not including children) - return allBoards.filter((b) => assignments[b._id] === workspaceId).length; + return allBoards.filter(b => assignments[b._id] === workspaceId).length; }, canModifyBoards() { const currentUser = ReactiveCache.getCurrentUser(); diff --git a/client/components/boards/miniboard.jade b/client/components/boards/miniboard.jade index f0a1ea367..d1fb0b075 100644 --- a/client/components/boards/miniboard.jade +++ b/client/components/boards/miniboard.jade @@ -3,7 +3,6 @@ template(name="miniboard") class="minicard-{{colorClass}}") .minicard-title .handle - span.drag-handle(title="{{_ 'dragBoard'}}") - i.fa.fa-arrows + .fa.fa-arrows +viewer = title diff --git a/client/components/boards/originalPositionsView.css b/client/components/boards/originalPositionsView.css index c2e1a3405..ec3abd4c5 100644 --- a/client/components/boards/originalPositionsView.css +++ b/client/components/boards/originalPositionsView.css @@ -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; } diff --git a/client/components/boards/originalPositionsView.html b/client/components/boards/originalPositionsView.html index 3bcc9fb06..6a58beeb0 100644 --- a/client/components/boards/originalPositionsView.html +++ b/client/components/boards/originalPositionsView.html @@ -5,7 +5,7 @@ {{#if isShowingOriginalPositions}}Hide{{else}}Show{{/if}} Original Positions - + {{#if isShowingOriginalPositions}} - - -