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/FUNDING.yml b/.github/FUNDING.yml index 2392f33c6..5b621fac0 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,3 @@ # These are supported funding model platforms -github: wekan custom: ['https://wekan.fi/commercial-support/'] 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..b3498f613 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@v4 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@v5 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..cb1bae471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,548 +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: - -- [Feature: Workspaces, at All Boards page](https://github.com/wekan/wekan/commit/0afbdc95b49537e06b4f9cf98f51a669ef249384). - Thanks to xet7. - -and fixes the following bugs: - -- [Fix 8.16: Switching Board View fails with 403 error](https://github.com/wekan/wekan/commit/550d87ac6cb3ec946600616485afdbd242983ab4). - Thanks to xet7. -- [Moved migrations from opening board to right sidebar / Migrations](https://github.com/wekan/wekan/commit/1b25d1d5720d4f486a10d2acce37e315cf9b6057). - Thanks to xet7. -- [Fix 8.16 Lists with no items are deleted every time when board is opened. Moved migrations to right sidebar](https://github.com/wekan/wekan/commit/7713e613b431e44dc13cee72e7a1e5f031473fa6). - Thanks to xet7. -- [Remove old translations and code not in use anymore](https://github.com/wekan/wekan/commit/ba49d4d140bc0d4cfb5a96db9ab077bc85db58f1). - Thanks to xet7. -- [Fixed sidebar migrations to be per-board, not global. Clarified translations](https://github.com/wekan/wekan/commit/e4638d5fbcbe004ac393462331805cac3ba25097). - Thanks to xet7. -- [Fix star board](https://github.com/wekan/wekan/commit/8711b476be30496b96b845529b5717bb6e685c27). - Thanks to xet7. -- [Fix Card emoji issues](https://github.com/wekan/wekan/commit/e5e711c938edcca23c974c3eec97296898bcf24e). - Thanks to xet7. -- [Try to fix Edit Custom Fields button not working. Removed duplicate option from Boards Settings](https://github.com/wekan/wekan/commit/20af0a2ef55b11e7205845859ee92a929616ce91). - Thanks to xet7. -- [Fix Regression - calendar popup to set due date has gone](https://github.com/wekan/wekan/commit/581733d605b7e0494e72229c45947cff134f6dd6). - Thanks to xet7. -- [Remove not working Bookmark menu option](https://github.com/wekan/wekan/commit/c829c073cf822e48b7cd84bbfb79d42867412517). - Thanks to xet7. -- [Fix Workspaces at All Boards to have correct count of remaining etc, while starred also at Starred/Favorites](https://github.com/wekan/wekan/commit/6244657ca53a54646ec01e702851a51d89bd0d55). - Thanks to xet7. -- [Fix Worker Permissions does not allow for cards to be moved. - v8.15. Removed buttons Worker should not use](https://github.com/wekan/wekan/commit/18003900c2d497c129793d1653d4d9872a2f19da). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.16 2025-11-02 WeKan ® release - -This release fixes the following CRITICAL SECURITY ISSUES of [Spacebleed](https://wekan.fi/hall-of-fame/spacebleed/): - -- [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. -- [Fix SECURITY ISSUE 2: Access to boards of any Orgs/Teams, and avatar permissions](https://github.com/wekan/wekan/commit/f26d58201855e861bab1cd1fda4d62c664efdb81). - Thanks to Siam Thanat Hack (STH) and xet7. -- [Fix SECURITY ISSUE 3: Unauthenticated (or any) user can update board sort](https://github.com/wekan/wekan/commit/ea310d7508b344512e5de0dfbc9bdfd38145c5c5). - Thanks to Siam Thanat Hack (STH) and xet7. -- [Fix SECURITY ISSUE 4: Members can forge others’ votes (Low). Bonus: Similar fixes to planning poker too done by xet7](https://github.com/wekan/wekan/commit/0a1a075f3153e71d9a858576f1c68d2925230d9c). - Thanks to Siam Thanat Hack (STH) and xet7. -- [Fix SECURITY ISSUE 5: Attachment API uses bearer value as userId and DoS (Low)](https://github.com/wekan/wekan/commit/ccd90343394f433b287733ad0a33c08e0a71f53c). - Thanks to Siam Thanat Hack (STH) and xet7. - -and adds the following new features: - -- [List menu / More / Delete duplicate lists that do not have any cards](https://github.com/wekan/wekan/commit/91b846e2cdee9154b045d11b4b4c1a7ae1d79016). - Thanks to xet7. -- [Disabled migrations that happen when opening board. Defaulting to per-swimlane lists and drag drop list to same or different swimlane](https://github.com/wekan/wekan/commit/034dc08269520ca31c780cce64e0150969e9228e). - Thanks to xet7. - -and fixes the following bugs: - -- [Fix changing swimlane color to not reload webpage](https://github.com/wekan/wekan/commit/ecf2418347cae4329deb292b534f68eb099d3f90). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.15 2025-10-23 WeKan ® release - -This release fixes the following bugs: - -- Fix drag lists did not work - [Part 1](https://github.com/wekan/wekan/commit/8662c96d1c8d4fa76ce7b31eb06678ad59c3ebe1), - [Part 2](https://github.com/wekan/wekan/commit/0cebd8aa4dbe0bf2418b814716744ab806b671c2). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - # v8.14 2025-10-23 WeKan ® release This release fixes the following bugs: diff --git a/Dockerfile b/Dockerfile index d81c0ffce..83582c102 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 < { - https://github.com/wekan/wekan/blob/main/client/components/cards/attachments.js#L303-L312 - https://wekan.github.io/hall-of-fame/filebleed/ -### Attachments: Forced download to prevent stored XSS - -- To prevent browser-side execution of uploaded content under the app origin, all attachment downloads are served with safe headers: - - `Content-Type: application/octet-stream` - - `Content-Disposition: attachment` - - `X-Content-Type-Options: nosniff` - - A restrictive `Content-Security-Policy` with `sandbox` -- This means attachments are downloaded instead of rendered inline by default. This mitigates HTML/JS/SVG based stored XSS vectors. -- Avatars and inline images remain supported but SVG uploads are blocked and never rendered inline. - -## Users: Client update restrictions - -- Client-side updates to user documents are limited to safe fields only: - - `username` - - `profile.*` -- Sensitive fields are blocked from any client updates and can only be modified by server methods with authorization: - - `orgs`, `teams`, `roles`, `isAdmin`, `createdThroughApi`, `loginDisabled`, `authenticationMethod`, `services.*`, `emails.*`, `sessionData.*` -- Attempts to update forbidden fields from the client are denied. -- Admin operations like managing org/team membership or toggling flags must use server methods that check permissions. - -## Voting: integrity and authorization - -- Client updates to card `vote` fields are blocked to prevent forged votes and inconsistent policy enforcement. -- Voting is performed via a server method that enforces: - - Authentication and board membership, or an explicit per-card flag allowing non-members to vote. - - Only the caller's own userId is added/removed from `vote.positive`/`vote.negative`. -- This prevents members from fabricating other users' votes and ensures non-members cannot vote unless explicitly allowed. - -## Planning Poker: integrity and authorization - -- Client updates to card `poker` fields are blocked. All poker actions go through server methods that enforce: - - Authentication and board membership for configuration and results. - - For casting a poker vote, either board membership or an explicit per-card flag allowing non-members to participate. - - Only the caller's own userId is added/removed from the selected estimation bucket (e.g., one, two, five, etc.). -- Methods cover setting/unsetting poker question/end, casting votes, replaying, and setting final estimation. - -## Attachment API: authentication and DoS prevention - -- The attachment API (`/api/attachment/*`) requires proper authentication using `X-User-Id` and `X-Auth-Token` headers. -- Authentication validates tokens by hashing with `Accounts._hashLoginToken` and matching against stored login tokens, preventing identity spoofing. -- Request handlers implement: - - 30-second timeout to prevent hanging connections. - - Request body size limits (50MB for uploads, 10MB for metadata operations). - - Proper error handling and guaranteed response completion. - - Request error event handlers to clean up failed connections. -- This prevents: - - DoS attacks via concurrent unauthenticated or malformed requests. - - Identity spoofing by using arbitrary bearer tokens or user IDs. - - Resource exhaustion from hanging connections or excessive payloads. -- Access control: all attachment operations verify board membership before allowing access. - ## Brute force login protection - https://github.com/wekan/wekan/commit/23e5e1e3bd081699ce39ce5887db7e612616014d @@ -272,4 +218,9 @@ Typical already known or "no impact" bugs such as: - Email spoofing, SPF, DMARC & DKIM. Wekan does not include email server. Wekan is Open Source with MIT license, and free to use also for commercial use. -We welcome all fixes to improve security by email to security@wekan.fi +We welcome all fixes to improve security by email to security@wekan.team + +## Bonus Points + +If your Responsible Security Disclosure includes code for fixing security issue, +you get bonus points, as seen on [Hall of Fame](https://wekan.github.io/hall-of-fame). diff --git a/Stackerfile.yml b/Stackerfile.yml index c540e96f0..567bcc521 100644 --- a/Stackerfile.yml +++ b/Stackerfile.yml @@ -1,5 +1,5 @@ appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928 -appVersion: "v8.31.0" +appVersion: "v8.14.0" files: userUploads: - README.md diff --git a/client/00-startup.js b/client/00-startup.js index b5d0c4d64..a6f049322 100644 --- a/client/00-startup.js +++ b/client/00-startup.js @@ -10,73 +10,8 @@ import '/client/lib/boardConverter'; import '/client/components/boardConversionProgress'; // Import migration manager and progress UI -import '/client/lib/attachmentMigrationManager'; -import '/client/components/settings/migrationProgress'; +import '/client/lib/migrationManager'; +import '/client/components/migrationProgress'; // Import cron settings import '/client/components/settings/cronSettings'; - -// Mirror Meteor login token into a cookie for server-side file route auth -// This enables cookie-based auth for /cdn/storage/* without leaking ROOT_URL -// Token already lives in localStorage; cookie adds same-origin send-on-request semantics -Meteor.startup(() => { - const COOKIE_NAME = 'meteor_login_token'; - const cookieAttrs = () => { - const attrs = ['Path=/', 'SameSite=Lax']; - try { - if (window.location && window.location.protocol === 'https:') { - attrs.push('Secure'); - } - } catch (_) {} - return attrs.join('; '); - }; - - const setCookie = (name, value) => { - if (!value) return; - document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; ${cookieAttrs()}`; - }; - const clearCookie = (name) => { - document.cookie = `${encodeURIComponent(name)}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; ${cookieAttrs()}`; - }; - - const syncCookie = () => { - try { - const token = Accounts && typeof Accounts._storedLoginToken === 'function' ? Accounts._storedLoginToken() : null; - if (token) setCookie(COOKIE_NAME, token); else clearCookie(COOKIE_NAME); - } catch (e) { - // ignore - } - }; - - // Initial sync on startup - syncCookie(); - - // Keep cookie in sync on login/logout - if (Accounts && typeof Accounts.onLogin === 'function') Accounts.onLogin(syncCookie); - if (Accounts && typeof Accounts.onLogout === 'function') Accounts.onLogout(syncCookie); - - // Sync across tabs/windows when localStorage changes - window.addEventListener('storage', (ev) => { - if (ev && typeof ev.key === 'string' && ev.key.indexOf('Meteor.loginToken') !== -1) { - syncCookie(); - } - }); -}); - -// Subscribe to per-user small publications -Meteor.startup(() => { - Tracker.autorun(() => { - if (Meteor.userId()) { - Meteor.subscribe('userGreyIcons'); - 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..05fa8fc58 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,106 +263,63 @@ 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; } /* Mobile view styles - applied when isMiniScreen is true (iPhone, etc.) */ .board-wrapper.mobile-view { - width: 100vw !important; - max-width: 100vw !important; - min-width: 100vw !important; + width: 100% !important; + min-width: 100% !important; left: 0 !important; right: 0 !important; - overflow-x: hidden !important; - overflow-y: auto !important; } .board-wrapper.mobile-view .board-canvas { - width: 100vw !important; - max-width: 100vw !important; - min-width: 100vw !important; + width: 100% !important; + min-width: 100% !important; left: 0 !important; right: 0 !important; - overflow-x: hidden !important; - overflow-y: auto !important; } .board-wrapper.mobile-view .board-canvas.mobile-view .swimlane { border-bottom: 1px solid #ccc; - display: block !important; + display: flex; flex-direction: column; margin: 0; padding: 0; - overflow-x: hidden !important; + overflow-x: hidden; overflow-y: auto; - width: 100vw !important; - max-width: 100vw !important; - min-width: 100vw !important; + width: 100%; + min-width: 100%; } @media screen and (max-width: 800px), screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) { .board-wrapper { - width: 100vw !important; - max-width: 100vw !important; - min-width: 100vw !important; + width: 100% !important; + min-width: 100% !important; left: 0 !important; right: 0 !important; - overflow-x: hidden !important; - overflow-y: auto !important; } .board-wrapper .board-canvas { - width: 100vw !important; - max-width: 100vw !important; - min-width: 100vw !important; + width: 100% !important; + min-width: 100% !important; left: 0 !important; right: 0 !important; - overflow-x: hidden !important; - overflow-y: auto !important; } .board-wrapper .board-canvas .swimlane { border-bottom: 1px solid #ccc; - display: block !important; + display: flex; flex-direction: column; margin: 0; padding: 0; - overflow-x: hidden !important; + overflow-x: hidden; overflow-y: auto; - width: 100vw !important; - max-width: 100vw !important; - min-width: 100vw !important; + width: 100%; + min-width: 100%; } } .calendar-event-green { 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..5eb317b9b 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -1,10 +1,9 @@ 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 Swimlanes from '/models/swimlanes'; import Lists from '/models/lists'; @@ -16,6 +15,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 +27,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 +68,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 +98,334 @@ BlazeComponent.extendComponent({ return; } - this.isBoardReady.set(true); + // Check if board needs migration based on migration version + const needsMigration = !board.migrationVersion || board.migrationVersion < 1; + + if (needsMigration) { + // Start background migration for old boards + this.isMigrating.set(true); + await this.startBackgroundMigration(boardId); + this.isMigrating.set(false); + } + // Check if board needs conversion (for old structure) + if (boardConverter.isBoardConverted(boardId)) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has already been converted, skipping conversion`); + } + this.isBoardReady.set(true); + } else { + const needsConversion = boardConverter.needsConversion(boardId); + + if (needsConversion) { + this.isConverting.set(true); + const success = await boardConverter.convertBoard(boardId); + this.isConverting.set(false); + + if (success) { + this.isBoardReady.set(true); + } else { + console.error('Board conversion failed, setting ready to true anyway'); + this.isBoardReady.set(true); // Still show board even if conversion failed + } + } else { + this.isBoardReady.set(true); + } + } + + // Convert shared lists to per-swimlane lists if needed + await this.convertSharedListsToPerSwimlane(boardId); + + // Fix missing lists migration (for cards with wrong listId references) + await this.fixMissingLists(boardId); + + // Fix duplicate lists created by WeKan 8.10 + await this.fixDuplicateLists(boardId); + + // Start attachment migration in background if needed + this.startAttachmentMigrationIfNeeded(boardId); } 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 } }, + 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 +434,10 @@ BlazeComponent.extendComponent({ return this.isConverting.get(); }, + isMigrating() { + return this.isMigrating.get(); + }, + isBoardReady() { return this.isBoardReady.get(); }, @@ -221,9 +529,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 +647,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 +848,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 +897,7 @@ BlazeComponent.extendComponent({ } return false; } - + try { const swimlanes = currentBoard.swimlanes(); const hasSwimlanes = swimlanes && swimlanes.length > 0; @@ -637,24 +932,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 +1319,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.css b/client/components/boards/boardHeader.css index faf20e2f5..f3cb652e7 100644 --- a/client/components/boards/boardHeader.css +++ b/client/components/boards/boardHeader.css @@ -505,73 +505,73 @@ flex-wrap: nowrap !important; align-items: stretch !important; justify-content: flex-start !important; - width: 100vw !important; - max-width: 100vw !important; - min-width: 100vw !important; + width: 100% !important; + max-width: 100% !important; + min-width: 100% !important; overflow-x: hidden !important; overflow-y: auto !important; } - .mobile-mode .swimlane { - display: block !important; - width: 100vw !important; - max-width: 100vw !important; - min-width: 100vw !important; - margin: 0 0 2rem 0 !important; - padding: 0 !important; - float: none !important; - clear: both !important; - } +.mobile-mode .swimlane { + display: block !important; + width: 100% !important; + max-width: 100% !important; + min-width: 100% !important; + margin: 0 0 2rem 0 !important; + padding: 0 !important; + float: none !important; + clear: both !important; +} - .mobile-mode .swimlane .swimlane-header { - display: block !important; - width: 100vw !important; - max-width: 100vw !important; - min-width: 100vw !important; - margin: 0 0 1rem 0 !important; - padding: 1rem !important; - font-size: clamp(18px, 2.5vw, 32px) !important; - font-weight: bold !important; - border-bottom: 2px solid #ccc !important; - } +.mobile-mode .swimlane .swimlane-header { + display: block !important; + width: 100% !important; + max-width: 100% !important; + min-width: 100% !important; + margin: 0 0 1rem 0 !important; + padding: 1rem !important; + font-size: clamp(18px, 2.5vw, 32px) !important; + font-weight: bold !important; + border-bottom: 2px solid #ccc !important; +} - .mobile-mode .swimlane .lists { - display: block !important; - width: 100vw !important; - max-width: 100vw !important; - min-width: 100vw !important; - margin: 0 !important; - padding: 0 !important; - flex-direction: column !important; - flex-wrap: nowrap !important; - align-items: stretch !important; - justify-content: flex-start !important; - } +.mobile-mode .swimlane .lists { + display: block !important; + width: 100% !important; + max-width: 100% !important; + min-width: 100% !important; + margin: 0 !important; + padding: 0 !important; + flex-direction: column !important; + flex-wrap: nowrap !important; + align-items: stretch !important; + justify-content: flex-start !important; +} - .mobile-mode .list { - display: block !important; - width: 100vw !important; - max-width: 100vw !important; - min-width: 100vw !important; - margin: 0 0 2rem 0 !important; - padding: 0 !important; - float: none !important; - clear: both !important; - border-left: none !important; - border-right: none !important; - border-top: none !important; - border-bottom: 2px solid #ccc !important; - flex: none !important; - flex-basis: auto !important; - flex-grow: 0 !important; - flex-shrink: 0 !important; - position: static !important; - left: auto !important; - right: auto !important; - top: auto !important; - bottom: auto !important; - transform: none !important; - } +.mobile-mode .list { + display: block !important; + width: 100% !important; + max-width: 100% !important; + min-width: 100% !important; + margin: 0 0 2rem 0 !important; + padding: 0 !important; + float: none !important; + clear: both !important; + border-left: none !important; + border-right: none !important; + border-top: none !important; + border-bottom: 2px solid #ccc !important; + flex: none !important; + flex-basis: auto !important; + flex-grow: 0 !important; + flex-shrink: 0 !important; + position: static !important; + left: auto !important; + right: auto !important; + top: auto !important; + bottom: auto !important; + transform: none !important; +} .mobile-mode .list:first-child { margin-left: 0 !important; @@ -667,9 +667,9 @@ flex-wrap: nowrap !important; align-items: stretch !important; justify-content: flex-start !important; - width: 100vw !important; - max-width: 100vw !important; - min-width: 100vw !important; + width: 100% !important; + max-width: 100% !important; + min-width: 100% !important; overflow-x: hidden !important; overflow-y: auto !important; } diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index 42cc8d592..013cb3619 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.js-star-board(class="{{#if isStarred}}is-active{{/if}}" + title="{{#if isStarred}}{{_ 'star-board-short-unstar'}}{{else}}{{_ 'star-board-short-star'}}{{/if}}" aria-label="{{#if isStarred}}{{_ 'star-board-short-unstar'}}{{else}}{{_ 'star-board-short-star'}}{{/if}}") + | {{#if isStarred}}⭐{{else}}☆{{/if}} + if showStarCounter + span + = currentBoard.stars 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}}") - 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,99 @@ 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.js-star-board(class="{{#if isStarred}}is-active{{/if}}" + title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}") + | {{#if isStarred}}⭐{{else}}☆{{/if}} 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(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 +162,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 +189,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 +218,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,41 +247,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 - = " " - | {{{_ 'board-private-info'}}} - a.js-change-visibility {{_ 'change'}}. - a.flex.js-toggle-add-template-container - .materialCheckBox#add-template-container - span {{_ 'add-template-container'}} - input.primary.wide(type="submit" value="{{_ 'create'}}") - span.quiet - | {{_ 'or'}} - a.js-import-board {{_ 'import'}} - span.quiet - | / - a.js-board-template {{_ 'template'}} - -// New popup for Template Container creation; shares the same form content -template(name="createTemplateContainerPopup") - form - label - | {{_ 'title'}} - input.js-new-board-title(type="text" placeholder="{{_ 'bucket-example'}}" autofocus required) - if visibilityMenuIsOpen.get - +boardVisibilityList - else - p.quiet - if $eq visibility.get 'public' - span.fa.fa-globe.colorful - = " " - | {{{_ 'board-public-info'}}} - else - span.fa.fa-lock.colorful + span 🔒 = " " | {{{_ 'board-private-info'}}} a.js-change-visibility {{_ 'change'}}. @@ -317,30 +267,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 +299,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..b95a45395 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(); @@ -73,10 +72,7 @@ BlazeComponent.extendComponent({ { 'click .js-edit-board-title': Popup.open('boardChangeTitle'), 'click .js-star-board'() { - const boardId = Session.get('currentBoard'); - if (boardId) { - Meteor.call('toggleBoardStar', boardId); - } + ReactiveCache.getCurrentUser().toggleBoardStar(Session.get('currentBoard')); }, 'click .js-open-board-menu': Popup.open('boardMenu'), 'click .js-change-visibility': Popup.open('boardChangeVisibility'), @@ -182,7 +178,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 +187,7 @@ Template.boardHeaderBar.helpers({ } else if (sortBy.createdAt) { return sortBy.createdAt === 1 ? '⬆️' : '⬇️'; // Up/down arrow based on direction } - + return '🃏'; // Default card icon }, }); @@ -209,10 +205,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({ @@ -299,15 +291,6 @@ const CreateBoard = BlazeComponent.extendComponent({ }, ); - // Assign to space if one was selected - const spaceId = Session.get('createBoardInWorkspace'); - if (spaceId) { - Meteor.call('assignBoardToWorkspace', this.boardId.get(), spaceId, (err) => { - if (err) console.error('Error assigning board to space:', err); - }); - Session.set('createBoardInWorkspace', null); // Clear after use - } - Utils.goBoardId(this.boardId.get()); } else { @@ -326,15 +309,6 @@ const CreateBoard = BlazeComponent.extendComponent({ boardId: this.boardId.get(), }); - // Assign to space if one was selected - const spaceId = Session.get('createBoardInWorkspace'); - if (spaceId) { - Meteor.call('assignBoardToWorkspace', this.boardId.get(), spaceId, (err) => { - if (err) console.error('Error assigning board to space:', err); - }); - Session.set('createBoardInWorkspace', null); // Clear after use - } - Utils.goBoardId(this.boardId.get()); } }, @@ -356,18 +330,11 @@ const CreateBoard = BlazeComponent.extendComponent({ }, }).register('createBoardPopup'); -(class CreateTemplateContainerPopup extends CreateBoard { - onRendered() { - // Always pre-check the template container checkbox for this popup - $('#add-template-container').addClass('is-checked'); - } -}).register('createTemplateContainerPopup'); - (class HeaderBarCreateBoard extends CreateBoard { - 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..f834af830 100644 --- a/client/components/boards/boardsList.css +++ b/client/components/boards/boardsList.css @@ -8,273 +8,6 @@ padding: 1vh 0; } -/* Two-column layout for All Boards */ -.boards-layout { - display: grid; - grid-template-columns: 260px 1fr; - gap: 16px; -} - -.boards-left-menu { - border-right: 1px solid #e0e0e0; - padding-right: 12px; -} - -.boards-left-menu ul.menu { - list-style: none; - padding: 0; - margin: 0 0 12px 0; -} - -.boards-left-menu .menu-item { - margin: 4px 0; -} -.boards-left-menu .menu-item a { - display: flex; - justify-content: space-between; - align-items: center; - padding: 8px 10px; - border-radius: 4px; - cursor: pointer; -} -.boards-left-menu .menu-item .menu-label { - flex: 1; -} -.boards-left-menu .menu-item .menu-count { - background: #ddd; - padding: 2px 8px; - border-radius: 12px; - font-size: 12px; - font-weight: bold; - margin-left: 8px; -} -.boards-left-menu .menu-item.active a, -.boards-left-menu .menu-item a:hover { - background: #f0f0f0; -} -.boards-left-menu .menu-item.active .menu-count { - background: #bbb; -} - -/* Drag-over state for menu items (for dropping boards on Remaining) */ -.boards-left-menu .menu-item a.drag-over { - background: #d0e8ff; - border: 2px dashed #2196F3; -} - -.workspaces-header { - display: flex; - align-items: center; - justify-content: space-between; - font-weight: bold; - margin-top: 12px; -} -.workspaces-header .js-add-space { - text-decoration: none; - font-weight: bold; - border: 1px solid #ccc; - padding: 2px 8px; - border-radius: 4px; -} - -.workspace-tree { - list-style: none; - padding-left: 10px; -} - -.workspace-node { - margin: 2px 0; - position: relative; -} - -.workspace-node-content { - display: flex; - align-items: center; - gap: 4px; - padding: 4px; - border-radius: 4px; - transition: background-color 0.2s; -} - -.workspace-node.dragging > .workspace-node-content { - opacity: 0.5; - background: #e0e0e0; -} - -.workspace-node.drag-over > .workspace-node-content { - background: #d0e8ff; - border: 2px dashed #2196F3; -} - -.workspace-drag-handle { - cursor: grab; - color: #999; - font-size: 14px; - padding: 0 4px; - user-select: none; -} - -.workspace-drag-handle:active { - cursor: grabbing; -} - -.workspace-node .js-select-space { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - border-radius: 4px; - cursor: pointer; - flex: 1; - text-decoration: none; -} - -.workspace-node .workspace-icon { - font-size: 16px; - line-height: 1; -} - -.workspace-node .workspace-name { - flex: 1; -} - -.workspace-node .workspace-count { - background: #ddd; - padding: 2px 6px; - border-radius: 10px; - font-size: 11px; - font-weight: bold; - min-width: 20px; - text-align: center; -} - -.workspace-node .js-edit-space, -.workspace-node .js-add-subspace { - padding: 2px 6px; - border-radius: 3px; - cursor: pointer; - text-decoration: none; - font-size: 14px; - opacity: 0.6; - transition: opacity 0.2s; -} - -.workspace-node .js-edit-space:hover, -.workspace-node .js-add-subspace:hover { - opacity: 1; - background: #e0e0e0; -} - -.workspace-node.active > .workspace-node-content .js-select-space, -.workspace-node > .workspace-node-content:hover .js-select-space { - background: #f0f0f0; -} - -.workspace-node.active .workspace-count { - background: #bbb; -} - -.boards-right-grid { - min-height: 200px; -} - -.boards-path-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 12px 16px; - margin-bottom: 16px; - background: #f5f5f5; - border-radius: 6px; - font-size: 16px; - font-weight: 500; -} - -.boards-path-header .path-left { - display: flex; - align-items: center; - gap: 8px; - flex: 1; -} - -.boards-path-header .multiselection-hint { - background: #FFF3CD; - color: #856404; - padding: 4px 12px; - border-radius: 4px; - font-size: 13px; - font-weight: normal; - border: 1px solid #FFE69C; - animation: pulse 2s ease-in-out infinite; -} - -@keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.7; } -} - -.boards-path-header .path-right { - display: flex; - align-items: center; - gap: 8px; -} - -.boards-path-header .path-icon { - font-size: 18px; -} - -.boards-path-header .path-text { - color: #333; -} - -.boards-path-header .board-header-btn { - padding: 6px 12px; - background: #fff; - border: 1px solid #ddd; - border-radius: 4px; - cursor: pointer; - display: flex; - align-items: center; - gap: 6px; - font-size: 14px; - transition: all 0.2s; -} - -.boards-path-header .board-header-btn:hover { - background: #f0f0f0; - border-color: #bbb; -} - -.boards-path-header .board-header-btn.emphasis { - background: #2196F3; - color: #fff; - border-color: #2196F3; - font-weight: bold; - box-shadow: 0 2px 8px rgba(33, 150, 243, 0.5); - transform: scale(1.05); -} - -.boards-path-header .board-header-btn.emphasis:hover { - background: #1976D2; - box-shadow: 0 3px 12px rgba(33, 150, 243, 0.7); -} - -.boards-path-header .board-header-btn-close { - padding: 4px 10px; - background: #f44336; - color: #000; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 16px; - margin-left: 10px; /* Extra space between MultiSelection toggle and Remove Filter */ -} - -.boards-path-header .board-header-btn-close:hover { - background: #d32f2f; -} - .zoom-controls { display: flex; align-items: center; @@ -373,35 +106,23 @@ .board-list li.starred .is-star-active, .board-list li.starred .is-not-star-active { opacity: 1; - color: #ffd700; -} -/* Show star icon on hover even for non-starred boards */ -.board-list li:hover .is-star-active, -.board-list li:hover .is-not-star-active { - opacity: 1; } .board-list .board-list-item { overflow: hidden; - background-color: inherit; /* Inherit board color from parent li.js-board */ + background-color: #999; color: #f6f6f6; min-height: 100px; font-size: 16px; line-height: 22px; - border-radius: 0; /* No border-radius - parent .js-board has it */ + border-radius: 3px; display: block; font-weight: 700; - padding: 36px 8px 32px 8px; /* Top padding for drag handle, bottom for checkbox */ - margin: 0; /* No margin - moved to parent .js-board */ + padding: 8px; + margin: 8px; position: relative; text-decoration: none; word-wrap: break-word; } - -.board-list .board-list-item > .js-open-board { - text-decoration: none; - color: inherit; - display: block; -} .board-list .board-list-item.template-container { border: 4px solid #fff; } @@ -429,27 +150,13 @@ .board-list .js-add-board .label { font-weight: normal; line-height: 56px; - min-height: 100px; - display: flex; - align-items: center; - justify-content: center; - background-color: #999; /* Darker background for better text contrast */ - border-radius: 3px; - padding: 36px 8px 32px 8px; - color: #fff; /* White text */ } -.board-list .js-add-board .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 .js-add-board :hover { + background-color: #939393; } .board-list .is-star-active, .board-list .is-not-star-active { - top: 0; + bottom: 0; font-size: 14px; height: 18px; line-height: 18px; @@ -457,6 +164,7 @@ padding: 9px 9px; position: absolute; right: 0; + top: 0; transition-duration: 0.15s; transition-property: color, font-size, background; } @@ -530,107 +238,6 @@ .board-list li:hover a .is-not-star-active { opacity: 1; } - -/* Board drag handle - always visible and positioned at top */ -.board-list .board-handle { - position: absolute; - padding: 4px 6px; - top: 4px; - left: 50%; - transform: translateX(-50%); - font-size: 14px; - color: #fff; - background: rgba(0,0,0,0.4); - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - z-index: 10; - transition: background-color 0.2s ease; - cursor: grab; - opacity: 1; - user-select: none; -} - -.board-list .board-handle:active { - cursor: grabbing; -} - -.board-list .board-handle:hover { - background: rgba(255, 255, 0, 0.8) !important; - color: #000; -} - -/* Multiselection checkbox on board items */ -.board-list .board-list-item .multi-selection-checkbox { - position: absolute !important; - bottom: 4px !important; - left: 4px !important; - top: auto !important; - width: 24px; - height: 24px; - border: 3px solid #fff; - background: rgba(0,0,0,0.5); - border-radius: 4px; - cursor: pointer; - z-index: 11; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s; - box-shadow: 0 2px 4px rgba(0,0,0,0.3); - transform: none !important; - margin: 0 !important; -} - -.board-list .board-list-item .multi-selection-checkbox:hover { - background: rgba(0,0,0,0.7); - transform: scale(1.15) !important; - box-shadow: 0 3px 6px rgba(0,0,0,0.5); -} - -.board-list .board-list-item .multi-selection-checkbox.is-checked { - background: #3cb500; - border-color: #3cb500; - box-shadow: 0 2px 8px rgba(60, 181, 0, 0.6); - width: 24px !important; - height: 24px !important; - top: auto !important; - left: 4px !important; - transform: none !important; - border-radius: 4px !important; -} - -.board-list .board-list-item .multi-selection-checkbox.is-checked::after { - content: '✓'; - color: #fff; - font-size: 16px; - font-weight: bold; -} - -/* Grey checkboxes when grey icons setting is enabled */ -body.grey-icons-enabled .board-list .board-list-item .multi-selection-checkbox.is-checked { - background: #7a7a7a; - border-color: #7a7a7a; - box-shadow: 0 2px 8px rgba(122, 122, 122, 0.6); -} - -body.grey-icons-enabled .board-list.is-multiselection-active .js-board.is-checked { - outline: 4px solid #7a7a7a; - box-shadow: 0 4px 12px rgba(122, 122, 122, 0.4); -} - -.board-list.is-multiselection-active .js-board.is-checked { - outline: 4px solid #3cb500; - outline-offset: -4px; - box-shadow: 0 4px 12px rgba(60, 181, 0, 0.4); -} - -/* Visual hint when multiselection is active */ -.board-list.is-multiselection-active .board-list-item { - border: 2px dashed rgba(33, 150, 243, 0.3); -} - .board-backgrounds-list .board-background-select { box-sizing: border-box; display: block; @@ -664,19 +271,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); @@ -1143,62 +739,9 @@ body.grey-icons-enabled .checkmark-no-grey { #resetBtn { display: inline; } - -#resetBtn.filter-reset-btn { - background: #f44336; - color: #000; - border: none; - border-radius: 4px; - padding: 6px 12px; - cursor: pointer; - font-size: 14px; - display: inline-flex; - align-items: center; - gap: 6px; - transition: background 0.2s; -} - -#resetBtn.filter-reset-btn:hover { - background: #d32f2f; -} - -#resetBtn.filter-reset-btn .reset-icon { - font-size: 14px; -} - .js-board { display: block; - background-color: #999; /* Default gray background if no color class is applied */ - border-radius: 3px; /* Rounded corners for board items */ - overflow: hidden; /* Ensure children respect rounded corners */ - margin: 8px; /* Space between board items */ } - -/* Reset background for add-board button */ -.js-add-board { - background-color: transparent !important; - margin: 8px !important; /* Keep margin for add-board */ -} - -/* Apply board colors to li.js-board parent instead of just the link */ -.board-list .board-color-nephritis { background-color: #27ae60; } -.board-list .board-color-pomegranate { background-color: #c0392b; } -.board-list .board-color-belize { background-color: #2980b9; } -.board-list .board-color-wisteria { background-color: #8e44ad; } -.board-list .board-color-midnight { background-color: #2c3e50; } -.board-list .board-color-pumpkin { background-color: #e67e22; } -.board-list .board-color-moderatepink { background-color: #cd5a91; } -.board-list .board-color-strongcyan { background-color: #00aecc; } -.board-list .board-color-limegreen { background-color: #4bbf6b; } -.board-list .board-color-dark { background-color: #2c3e51; } -.board-list .board-color-relax { background-color: #27ae61; } -.board-list .board-color-corteza { background-color: #568ba2; } -.board-list .board-color-clearblue { background-color: #3498db; } -.board-list .board-color-natural { background-color: #596557; } -.board-list .board-color-modern { background-color: #2a80b8; } -.board-list .board-color-moderndark { background-color: #2a2a2a; } -.board-list .board-color-exodark { background-color: #222; } - .minicard-members { padding: 6px 0 6px 8px; width: 100%; diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade index fc3ab582a..3d01c19c9 100644 --- a/client/components/boards/boardsList.jade +++ b/client/components/boards/boardsList.jade @@ -2,238 +2,158 @@ template(name="boardList") .wrapper .board-list-header - .boards-layout - // Left menu - .boards-left-menu - ul.menu - li(class="menu-item {{#if isSelectedMenu 'starred'}}active{{/if}}") - a.js-select-menu(data-type="starred") - span.menu-label - span.emoji-icon - i.fa.fa-star - | {{_ 'allboards.starred'}} - span.menu-count {{menuItemCount 'starred'}} - li(class="menu-item {{#if isSelectedMenu 'templates'}}active{{/if}}") - a.js-select-menu(data-type="templates") - span.menu-label - span.emoji-icon - i.fa.fa-clipboard - | {{_ 'allboards.templates'}} - span.menu-count {{menuItemCount 'templates'}} - li(class="menu-item {{#if isSelectedMenu 'remaining'}}active{{/if}}") - a.js-select-menu(data-type="remaining") - span.menu-label - span.emoji-icon - i.fa.fa-folder - | {{_ 'allboards.remaining'}} - span.menu-count {{menuItemCount 'remaining'}} - .workspaces-header - span - span.emoji-icon - i.fa.fa-folder-open - | {{_ 'allboards.workspaces'}} - a.js-add-workspace(title="{{_ 'allboards.add-workspace'}}") + - // Workspaces tree - +workspaceTree(nodes=workspacesTree selectedWorkspaceId=selectedWorkspaceId) + ul.AllBoardTeamsOrgs + li.AllBoardTeams + if userHasTeams + select.js-AllBoardTeams#jsAllBoardTeams("multiple") + option(value="-1") {{_ 'teams'}} : + each teamsDatas + option(value="{{teamId}}") {{_ teamDisplayName}} - // Existing filter by orgs/teams (kept) - ul.AllBoardTeamsOrgs - li.AllBoardTeams - if userHasTeams - select.js-AllBoardTeams#jsAllBoardTeams("multiple") - option(value="-1") {{_ 'teams'}} : - each teamsDatas - option(value="{{teamId}}") {{_ teamDisplayName}} + li.AllBoardOrgs + if userHasOrgs + select.js-AllBoardOrgs#jsAllBoardOrgs("multiple") + option(value="-1") {{_ 'organizations'}} : + each orgsDatas + option(value="{{orgId}}") {{orgDisplayName}} - li.AllBoardOrgs - if userHasOrgs - select.js-AllBoardOrgs#jsAllBoardOrgs("multiple") - option(value="-1") {{_ 'organizations'}} : - each orgsDatas - option(value="{{orgId}}") {{orgDisplayName}} + //li.AllBoardTemplates + // if userHasTemplates + // select.js-AllBoardTemplates#jsAllBoardTemplates("multiple") + // option(value="-1") {{_ 'templates'}} : + // each templatesDatas + // option(value="{{templateId}}") {{_ templateDisplayName}} - li.AllBoardBtns - div.AllBoardButtonsContainer - if userHasOrgsOrTeams - span.emoji-icon - i.fa.fa-search - input#filterBtn(type="button" value="{{_ 'filter'}}") - button#resetBtn.filter-reset-btn - span.reset-icon - span.emoji-icon - i.fa.fa-times-thin - span {{_ 'filter-clear'}} + li.AllBoardBtns + div.AllBoardButtonsContainer + if userHasOrgsOrTeams + i.fa.fa-filter + input#filterBtn(type="button" value="{{_ 'filter'}}") + input#resetBtn(type="button" value="{{_ 'filter-clear'}}") - // Right boards grid - .boards-right-grid - .boards-path-header - .path-left - span.path-icon.emoji-icon {{currentMenuPath.icon}} - span.path-text {{currentMenuPath.text}} - if BoardMultiSelection.isActive - span.multiselection-hint - span.emoji-icon - i.fa.fa-thumb-tack - | {{_ 'multi-selection-active'}} - .path-right - if canModifyBoards - if hasBoardsSelected - button.js-archive-selected-boards.board-header-btn - span.emoji-icon - i.fa.fa-archive - span {{_ 'archive-board'}} - button.js-duplicate-selected-boards.board-header-btn - span.emoji-icon - i.fa.fa-clipboard - span {{_ 'duplicate-board'}} - a.board-header-btn.js-multiselection-activate( - title="{{#if BoardMultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}" - class="{{#if BoardMultiSelection.isActive}}emphasis{{/if}}") - span.emoji-icon - i.fa.fa-check-square-o - if BoardMultiSelection.isActive - a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}") - span.emoji-icon - i.fa.fa-times - ul.board-list.clearfix.js-boards(class="{{#if isMiniScreen}}mobile-view{{/if}} {{#if BoardMultiSelection.isActive}}is-multiselection-active{{/if}}") - li.js-add-board - if isSelectedMenu 'templates' - a.board-list-item.label(title="{{_ 'add-template-container'}}") - span.emoji-icon - i.fa.fa-plus - |  {{_ 'add-template-container'}} + ul.board-list.clearfix.js-boards(class="{{#if isMiniScreen}}mobile-view{{/if}}") + li.js-add-board + a.board-list-item.label(title="{{_ 'add-board'}}") + | {{_ 'add-board'}} + each boards + li(class="{{_id}}" class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board + if isInvited + .board-list-item + span.details + span.board-list-item-name= title + i.fa.js-star-board( + class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}" + title="{{_ 'star-board-title'}}") + p.board-list-item-desc {{_ 'just-invited'}} + button.js-accept-invite.primary {{_ 'accept'}} + button.js-decline-invite {{_ 'decline'}} + else + if $eq type "template-container" + a.js-open-board.template-container.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}") + span.details + span.board-list-item-name(title="{{_ 'template-container'}}") + +viewer + = title + i.fa.js-star-board( + class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}" + title="{{_ 'star-board-title'}}") + p.board-list-item-desc + +viewer + = description + if hasSpentTimeCards + i.fa.js-has-spenttime-cards( + class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" + title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") + i.fa.board-handle( + class="fa-arrows" + title="{{_ 'drag-board'}}") + if isSandstorm + i.fa.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + i.fa.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + else if isAdministrable + i.fa.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + i.fa.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + else if currentUser.isAdmin + i.fa.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + i.fa.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") else - a.board-list-item.label(title="{{_ 'add-board'}}") - span.emoji-icon - i.fa.fa-plus - |  {{_ 'add-board'}} - each boards - li.js-board(class="{{_id}} {{#if isStarred}}starred{{/if}} {{colorClass}} {{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}", draggable="true") - if isInvited - .board-list-item - if BoardMultiSelection.isActive - .materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection( - class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}") - span.details - span.board-list-item-name= title - span.js-star-board( - class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}" - title="{{_ 'star-board-title'}}") - span.emoji-icon - | {{#if isStarred}}⭐{{else}}☆{{/if}} - p.board-list-item-desc {{_ 'just-invited'}} - button.js-accept-invite.primary {{_ 'accept'}} - button.js-decline-invite {{_ 'decline'}} - else - if $eq type "template-container" - .template-container.board-list-item - if BoardMultiSelection.isActive - .materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection( - class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}") - span.board-handle(title="{{_ 'drag-board'}}") - span.emoji-icon - i.fa.fa-arrows - - a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}") - span.details - span.board-list-item-name(title="{{_ 'template-container'}}") - +viewer - = title - p.board-list-item-desc - +viewer - = description - if hasSpentTimeCards - span.js-has-spenttime-cards( - class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}" - title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") - span.emoji-icon - i.fa.fa-clock-o - span.js-star-board( - class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}" - title="{{_ 'star-board-title'}}") - span.emoji-icon - i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}") - else - .board-list-item - if BoardMultiSelection.isActive - .materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection( - class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}") - span.board-handle(title="{{_ 'drag-board'}}") - span.emoji-icon - i.fa.fa-arrows - - a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}") - span.details - span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}") - +viewer - = title - unless currentSetting.hideBoardMemberList - if allowsBoardMemberList - .minicard-members - each member in boardMembers _id - a.name - +userAvatar(userId=member noRemove=true) - unless currentSetting.hideCardCounterList - if allowsCardCounterList - .minicard-lists.flex.flex-wrap - each list in boardLists _id - .item - | {{ list }} - p.board-list-item-desc - +viewer - = description - if hasSpentTimeCards - span.js-has-spenttime-cards( - class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}" - title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") - span.emoji-icon - i.fa.fa-clock-o - a.js-star-board( - class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}" - title="{{_ 'star-board-title'}}") - span.emoji-icon - i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}") + a.js-open-board.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}") + span.details + span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}") + +viewer + = title + unless currentSetting.hideBoardMemberList + if allowsBoardMemberList + .minicard-members + each member in boardMembers _id + a.name + +userAvatar(userId=member noRemove=true) + unless currentSetting.hideCardCounterList + if allowsCardCounterList + .minicard-lists.flex.flex-wrap + each list in boardLists _id + .item + | {{ list }} + a.js-star-board( + class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}" + title="{{_ 'star-board-title'}}") + | {{#if isStarred}}⭐{{else}}☆{{/if}} + p.board-list-item-desc + +viewer + = description + if hasSpentTimeCards + i.fa.js-has-spenttime-cards( + class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" + title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") + i.fa.board-handle( + class="fa-arrows" + title="{{_ 'drag-board'}}") + if isSandstorm + a.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + | 📋 + a.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + | 📦 + else if isAdministrable + a.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + | 📋 + a.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + | 📦 + else if currentUser.isAdmin + a.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + | 📋 + a.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + | 📦 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'}} - -// Recursive template for workspaces tree -template(name="workspaceTree") - if nodes - ul.workspace-tree.js-workspace-tree - each nodes - li.workspace-node(class="{{#if $eq id selectedWorkspaceId}}active{{/if}}" data-workspace-id="{{id}}" draggable="true") - .workspace-node-content - span.workspace-drag-handle - span.emoji-icon - i.fa.fa-arrows - - a.js-select-workspace(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 - span.workspace-count {{workspaceCount id}} - a.js-add-subworkspace(data-id="{{id}}" title="{{_ 'allboards.add-subworkspace'}}") + - if children - +workspaceTree(nodes=children selectedWorkspaceId=selectedWorkspaceId) + // 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'}} diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index fcb2461e6..1d655fd11 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(); @@ -16,10 +14,7 @@ Template.boardList.helpers({ return Utils.isMiniScreen() && Session.get('currentBoard'); */ return true; }, - BoardMultiSelection() { - return BoardMultiSelection; - }, -}); +}) Template.boardListHeaderBar.events({ 'click .js-open-archived-board'() { @@ -27,7 +22,8 @@ Template.boardListHeaderBar.events({ }, }); -Template.boardList.events({}); +Template.boardList.events({ +}); Template.boardListHeaderBar.helpers({ title() { @@ -49,85 +45,17 @@ BlazeComponent.extendComponent({ onCreated() { Meteor.subscribe('setting'); Meteor.subscribe('tableVisibilityModeSettings'); - this.selectedMenu = new ReactiveVar('starred'); - this.selectedWorkspaceIdVar = new ReactiveVar(null); - this.workspacesTreeVar = new ReactiveVar([]); let currUser = ReactiveCache.getCurrentUser(); let userLanguage; if (currUser && currUser.profile) { - userLanguage = currUser.profile.language; + userLanguage = currUser.profile.language } if (userLanguage) { TAPi18n.setLanguage(userLanguage); } - // Load workspaces tree reactively - this.autorun(() => { - const u = ReactiveCache.getCurrentUser(); - const tree = (u && u.profile && u.profile.boardWorkspacesTree) || []; - this.workspacesTreeVar.set(tree); - }); - }, - - reorderWorkspaces(draggedSpaceId, targetSpaceId) { - const tree = this.workspacesTreeVar.get(); - - // Helper to remove a space from tree - const removeSpace = (nodes, id) => { - for (let i = 0; i < nodes.length; i++) { - if (nodes[i].id === id) { - const removed = nodes.splice(i, 1)[0]; - return { tree: nodes, removed }; - } - if (nodes[i].children) { - const result = removeSpace(nodes[i].children, id); - if (result.removed) { - return { tree: nodes, removed: result.removed }; - } - } - } - return { tree: nodes, removed: null }; - }; - - // Helper to insert a space after target - const insertAfter = (nodes, targetId, spaceToInsert) => { - for (let i = 0; i < nodes.length; i++) { - if (nodes[i].id === targetId) { - nodes.splice(i + 1, 0, spaceToInsert); - return true; - } - if (nodes[i].children) { - if (insertAfter(nodes[i].children, targetId, spaceToInsert)) { - return true; - } - } - } - return false; - }; - - // Clone the tree - const newTree = EJSON.clone(tree); - - // Remove the dragged space - const { tree: treeAfterRemoval, removed } = removeSpace( - newTree, - draggedSpaceId, - ); - - if (removed) { - // Insert after target - insertAfter(treeAfterRemoval, targetSpaceId, removed); - - // Save the new tree - Meteor.call('setWorkspacesTree', treeAfterRemoval, (err) => { - if (err) console.error(err); - }); - } }, onRendered() { - // jQuery sortable is disabled in favor of HTML5 drag-and-drop for space management - // The old sortable code has been removed to prevent conflicts - /* OLD SORTABLE CODE - DISABLED const itemsSelector = '.js-board:not(.placeholder)'; const $boards = this.$('.js-boards'); @@ -144,21 +72,28 @@ BlazeComponent.extendComponent({ ui.placeholder.height(ui.helper.height()); EscapeActions.executeUpTo('popup-close'); }, - async stop(evt, ui) { + stop(evt, ui) { + // To attribute the new index number, we need to get the DOM element + // of the previous and the following card -- if any. const prevBoardDom = ui.item.prev('.js-board').get(0); - const nextBoardDom = ui.item.next('.js-board').get(0); - const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardDom, 1); + const nextBoardBom = ui.item.next('.js-board').get(0); + const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardBom, 1); const boardDomElement = ui.item.get(0); const board = Blaze.getData(boardDomElement); + // Normally the jquery-ui sortable library moves the dragged DOM element + // to its new position, which disrupts Blaze reactive updates mechanism + // (especially when we move the last card of a list, or when multiple + // users move some cards at the same time). To prevent these UX glitches + // we ask sortable to gracefully cancel the move, and to put back the + // DOM in its initial state. The card move is then handled reactively by + // Blaze with the below query. $boards.sortable('cancel'); - const currentUser = ReactiveCache.getCurrentUser(); - if (currentUser && typeof currentUser.setBoardSortIndex === 'function') { - await currentUser.setBoardSortIndex(board._id, sortIndex.base); - } + board.move(sortIndex.base); }, }); + // Disable drag-dropping if the current user is not a board member or is comment only this.autorun(() => { if (Utils.isTouchScreenOrShowDesktopDragHandles()) { $boards.sortable({ @@ -166,121 +101,56 @@ 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 = 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') }; - } - } catch (error) { - console.error('Error in currentMenuPath:', error); - return { icon: '🗂️', text: '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 +162,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 +172,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'] }, @@ -319,38 +184,10 @@ BlazeComponent.extendComponent({ }; } - const boards = ReactiveCache.getBoards(query, {}); - const currentUser = ReactiveCache.getCurrentUser(); - let list = boards; - // Apply left menu filtering - const sel = this.selectedMenu.get(); - const assignments = - (currentUser && - currentUser.profile && - currentUser.profile.boardWorkspaceAssignments) || - {}; - if (sel === 'starred') { - list = list.filter((b) => currentUser && currentUser.hasStarred(b._id)); - } else if (sel === 'templates') { - list = list.filter((b) => b.type === 'template-container'); - } else if (sel === 'remaining') { - // Show boards not in any workspace AND not templates - // Keep starred boards visible in Remaining too - list = list.filter( - (b) => !assignments[b._id] && b.type !== 'template-container', - ); - } else { - // assume sel is a workspaceId - // Keep starred boards visible in their workspace too - list = list.filter((b) => assignments[b._id] === sel); - } - - if (currentUser && typeof currentUser.sortBoardsForUser === 'function') { - return currentUser.sortBoardsForUser(list); - } - return list - .slice() - .sort((a, b) => (a.title || '').localeCompare(b.title || '')); + const ret = ReactiveCache.getBoards(query, { + sort: { sort: 1 /* boards default sorting */ }, + }); + return ret; }, boardLists(boardId) { /* Bug Board icons random dance https://github.com/wekan/wekan/issues/4214 @@ -398,82 +235,11 @@ BlazeComponent.extendComponent({ events() { return [ { - 'click .js-select-menu'(evt) { - const type = evt.currentTarget.getAttribute('data-type'); - this.selectedWorkspaceIdVar.set(null); - this.selectedMenu.set(type); - }, - 'click .js-select-workspace'(evt) { - const id = evt.currentTarget.getAttribute('data-id'); - this.selectedWorkspaceIdVar.set(id); - this.selectedMenu.set(id); - }, - 'click .js-add-workspace'(evt) { - evt.preventDefault(); - const name = prompt( - TAPi18n.__('allboards.add-workspace-prompt') || 'New Space name', - ); - if (name && name.trim()) { - Meteor.call( - 'createWorkspace', - { parentId: null, name: name.trim() }, - (err, res) => { - if (err) console.error(err); - }, - ); - } - }, - 'click .js-add-board'(evt) { - // Store the currently selected workspace/menu for board creation - const selectedWorkspaceId = this.selectedWorkspaceIdVar.get(); - const selectedMenu = this.selectedMenu.get(); - - if (selectedWorkspaceId) { - Session.set('createBoardInWorkspace', selectedWorkspaceId); - } else { - Session.set('createBoardInWorkspace', null); - } - - // Open different popup based on context - if (selectedMenu === 'templates') { - Popup.open('createTemplateContainer')(evt); - } else { - Popup.open('createBoard')(evt); - } - }, + 'click .js-add-board': Popup.open('createBoard'), 'click .js-star-board'(evt) { + const boardId = this.currentData()._id; + ReactiveCache.getCurrentUser().toggleBoardStar(boardId); evt.preventDefault(); - evt.stopPropagation(); - const boardId = this.currentData()._id; - if (boardId) { - Meteor.call('toggleBoardStar', boardId); - } - }, - // HTML5 DnD from boards to spaces - 'dragstart .js-board'(evt) { - const boardId = this.currentData()._id; - - // Support multi-drag - if ( - BoardMultiSelection.isActive() && - BoardMultiSelection.isSelected(boardId) - ) { - const selectedIds = BoardMultiSelection.getSelectedBoardIds(); - try { - evt.originalEvent.dataTransfer.setData( - 'text/plain', - JSON.stringify(selectedIds), - ); - evt.originalEvent.dataTransfer.setData( - 'application/x-board-multi', - 'true', - ); - } catch (e) {} - } else { - try { - evt.originalEvent.dataTransfer.setData('text/plain', boardId); - } catch (e) {} - } }, 'click .js-clone-board'(evt) { if (confirm(TAPi18n.__('duplicate-board-confirm'))) { @@ -524,115 +290,47 @@ BlazeComponent.extendComponent({ } }); }, - 'click .js-multiselection-activate'(evt) { - evt.preventDefault(); - if (BoardMultiSelection.isActive()) { - BoardMultiSelection.disable(); - } else { - BoardMultiSelection.activate(); - } - }, - 'click .js-multiselection-reset'(evt) { - evt.preventDefault(); - BoardMultiSelection.disable(); - }, - 'click .js-toggle-board-multi-selection'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - const boardId = this.currentData()._id; - BoardMultiSelection.toogle(boardId); - }, - 'click .js-archive-selected-boards'(evt) { - evt.preventDefault(); - const selectedBoards = BoardMultiSelection.getSelectedBoardIds(); - if ( - selectedBoards.length > 0 && - confirm(TAPi18n.__('archive-board-confirm')) - ) { - selectedBoards.forEach((boardId) => { - Meteor.call('archiveBoard', boardId); - }); - BoardMultiSelection.reset(); - } - }, - 'click .js-duplicate-selected-boards'(evt) { - evt.preventDefault(); - const selectedBoards = BoardMultiSelection.getSelectedBoardIds(); - if ( - selectedBoards.length > 0 && - confirm(TAPi18n.__('duplicate-board-confirm')) - ) { - selectedBoards.forEach((boardId) => { - const board = ReactiveCache.getBoard(boardId); - if (board) { - Meteor.call( - 'copyBoard', - boardId, - { - sort: ReactiveCache.getBoards({ archived: false }).length, - type: 'board', - title: board.title, - }, - (err, res) => { - if (err) console.error(err); - }, - ); - } - }); - BoardMultiSelection.reset(); - } - }, 'click #resetBtn'(event) { - let allBoards = document.getElementsByClassName('js-board'); + let 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,312 +342,21 @@ 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"; } } } }, - 'click .js-edit-workspace'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - const workspaceId = evt.currentTarget.getAttribute('data-id'); - - // Find the space in the tree - const findSpace = (nodes, id) => { - for (const node of nodes) { - if (node.id === id) return node; - if (node.children) { - const found = findSpace(node.children, id); - if (found) return found; - } - } - return null; - }; - - const tree = this.workspacesTreeVar.get(); - const space = findSpace(tree, workspaceId); - - if (space) { - const newName = prompt( - TAPi18n.__('allboards.edit-workspace-name') || 'Space name:', - space.name, - ); - const newIcon = prompt( - TAPi18n.__('allboards.edit-workspace-icon') || - 'Space icon (markdown):', - space.icon || '📁', - ); - - if (newName !== null && newName.trim()) { - // Update space in tree - const updateSpaceInTree = (nodes, id, updates) => { - return nodes.map((node) => { - if (node.id === id) { - return { ...node, ...updates }; - } - if (node.children) { - return { - ...node, - children: updateSpaceInTree(node.children, id, updates), - }; - } - return node; - }); - }; - - const updatedTree = updateSpaceInTree(tree, workspaceId, { - name: newName.trim(), - icon: newIcon || '📁', - }); - - Meteor.call('setWorkspacesTree', updatedTree, (err) => { - if (err) console.error(err); - }); - } - } - }, - 'click .js-add-subworkspace'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - const parentId = evt.currentTarget.getAttribute('data-id'); - const name = prompt( - TAPi18n.__('allboards.add-subworkspace-prompt') || 'Subspace name:', - ); - - if (name && name.trim()) { - Meteor.call( - 'createWorkspace', - { parentId, name: name.trim() }, - (err) => { - if (err) console.error(err); - }, - ); - } - }, - 'dragstart .workspace-node'(evt) { - const workspaceId = - evt.currentTarget.getAttribute('data-workspace-id'); - evt.originalEvent.dataTransfer.effectAllowed = 'move'; - evt.originalEvent.dataTransfer.setData( - 'application/x-workspace-id', - workspaceId, - ); - - // Create a better drag image - const dragImage = evt.currentTarget.cloneNode(true); - dragImage.style.position = 'absolute'; - dragImage.style.top = '-9999px'; - dragImage.style.opacity = '0.8'; - document.body.appendChild(dragImage); - evt.originalEvent.dataTransfer.setDragImage(dragImage, 0, 0); - setTimeout(() => document.body.removeChild(dragImage), 0); - - evt.currentTarget.classList.add('dragging'); - }, - 'dragend .workspace-node'(evt) { - evt.currentTarget.classList.remove('dragging'); - document.querySelectorAll('.workspace-node').forEach((el) => { - el.classList.remove('drag-over'); - }); - }, - 'dragover .workspace-node'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - - const draggingEl = document.querySelector('.workspace-node.dragging'); - const targetEl = evt.currentTarget; - - // Allow dropping boards on any space - // Or allow dropping spaces on other spaces (but not on itself or descendants) - if ( - !draggingEl || - (targetEl !== draggingEl && !draggingEl.contains(targetEl)) - ) { - evt.originalEvent.dataTransfer.dropEffect = 'move'; - targetEl.classList.add('drag-over'); - } - }, - 'dragleave .workspace-node'(evt) { - evt.currentTarget.classList.remove('drag-over'); - }, - 'drop .workspace-node'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - - const targetEl = evt.currentTarget; - targetEl.classList.remove('drag-over'); - - // Check what's being dropped - board or workspace - const draggedWorkspaceId = evt.originalEvent.dataTransfer.getData( - 'application/x-workspace-id', - ); - const isMultiBoard = evt.originalEvent.dataTransfer.getData( - 'application/x-board-multi', - ); - const boardData = - evt.originalEvent.dataTransfer.getData('text/plain'); - - if (draggedWorkspaceId && !boardData) { - // This is a workspace reorder operation - const targetWorkspaceId = - targetEl.getAttribute('data-workspace-id'); - - if (draggedWorkspaceId !== targetWorkspaceId) { - this.reorderWorkspaces(draggedWorkspaceId, targetWorkspaceId); - } - } else if (boardData) { - // This is a board assignment operation - // Get the workspace ID directly from the dropped workspace-node's data-workspace-id attribute - const workspaceId = targetEl.getAttribute('data-workspace-id'); - - if (workspaceId) { - if (isMultiBoard) { - // Multi-board drag - try { - const boardIds = JSON.parse(boardData); - boardIds.forEach((boardId) => { - Meteor.call('assignBoardToWorkspace', boardId, workspaceId); - }); - } catch (e) { - // Error parsing multi-board data - } - } else { - // Single board drag - Meteor.call('assignBoardToWorkspace', boardData, workspaceId); - } - } - } - }, - 'dragover .js-select-menu'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - - const menuType = evt.currentTarget.getAttribute('data-type'); - // Only allow drop on "remaining" menu to unassign boards from spaces - if (menuType === 'remaining') { - evt.originalEvent.dataTransfer.dropEffect = 'move'; - evt.currentTarget.classList.add('drag-over'); - } - }, - 'dragleave .js-select-menu'(evt) { - evt.currentTarget.classList.remove('drag-over'); - }, - 'drop .js-select-menu'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - - const menuType = evt.currentTarget.getAttribute('data-type'); - evt.currentTarget.classList.remove('drag-over'); - - // Only handle drops on "remaining" menu - if (menuType !== 'remaining') return; - - const isMultiBoard = evt.originalEvent.dataTransfer.getData( - 'application/x-board-multi', - ); - const boardData = - evt.originalEvent.dataTransfer.getData('text/plain'); - - if (boardData) { - if (isMultiBoard) { - // Multi-board drag - unassign all from workspaces - try { - const boardIds = JSON.parse(boardData); - boardIds.forEach((boardId) => { - Meteor.call('unassignBoardFromWorkspace', boardId); - }); - } catch (e) { - // Error parsing multi-board data - } - } else { - // Single board drag - unassign from workspace - Meteor.call('unassignBoardFromWorkspace', boardData); - } - } - }, }, ]; }, - // Helpers for templates - workspacesTree() { - return this.workspacesTreeVar.get(); - }, - selectedWorkspaceId() { - return this.selectedWorkspaceIdVar.get(); - }, - isSelectedMenu(type) { - return this.selectedMenu.get() === type; - }, - isSpaceSelected(id) { - return this.selectedWorkspaceIdVar.get() === id; - }, - menuItemCount(type) { - const currentUser = ReactiveCache.getCurrentUser(); - const assignments = - (currentUser && - currentUser.profile && - currentUser.profile.boardWorkspaceAssignments) || - {}; - - // Get all boards for counting - let query = { - $and: [ - { archived: false }, - { type: { $in: ['board', 'template-container'] } }, - { $or: [{ 'members.userId': Meteor.userId() }] }, - { title: { $not: { $regex: /^\^.*\^$/ } } }, - ], - }; - const allBoards = ReactiveCache.getBoards(query, {}); - - if (type === 'starred') { - return allBoards.filter( - (b) => currentUser && currentUser.hasStarred(b._id), - ).length; - } else if (type === 'templates') { - return allBoards.filter((b) => b.type === 'template-container').length; - } else if (type === 'remaining') { - // Count boards not in any workspace AND not templates - // Include starred boards (they appear in both Starred and Remaining) - return allBoards.filter( - (b) => !assignments[b._id] && b.type !== 'template-container', - ).length; - } - return 0; - }, - workspaceCount(workspaceId) { - const currentUser = ReactiveCache.getCurrentUser(); - const assignments = - (currentUser && - currentUser.profile && - currentUser.profile.boardWorkspaceAssignments) || - {}; - - // Get all boards for counting - let query = { - $and: [ - { archived: false }, - { type: { $in: ['board', 'template-container'] } }, - { $or: [{ 'members.userId': Meteor.userId() }] }, - { title: { $not: { $regex: /^\^.*\^$/ } } }, - ], - }; - const allBoards = ReactiveCache.getBoards(query, {}); - - // Count boards directly assigned to this space (not including children) - return allBoards.filter((b) => assignments[b._id] === workspaceId).length; - }, - canModifyBoards() { - const currentUser = ReactiveCache.getCurrentUser(); - return currentUser && !currentUser.isCommentOnly(); - }, - hasBoardsSelected() { - return BoardMultiSelection.count() > 0; - }, }).register('boardList'); 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}} - - -