mirror of
https://github.com/wekan/wekan.git
synced 2026-01-13 13:08:50 +01:00
Compare commits
No commits in common. "main" and "v8.17" have entirely different histories.
316 changed files with 5909 additions and 26770 deletions
2
.github/workflows/depsreview.yaml
vendored
2
.github/workflows/depsreview.yaml
vendored
|
|
@ -9,6 +9,6 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout Repository'
|
- name: 'Checkout Repository'
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
- name: 'Dependency Review'
|
- name: 'Dependency Review'
|
||||||
uses: actions/dependency-review-action@v4
|
uses: actions/dependency-review-action@v4
|
||||||
|
|
|
||||||
4
.github/workflows/docker-publish.yml
vendored
4
.github/workflows/docker-publish.yml
vendored
|
|
@ -32,7 +32,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# Login against a Docker registry except on PR
|
# Login against a Docker registry except on PR
|
||||||
# https://github.com/docker/login-action
|
# https://github.com/docker/login-action
|
||||||
|
|
@ -48,7 +48,7 @@ jobs:
|
||||||
# https://github.com/docker/metadata-action
|
# https://github.com/docker/metadata-action
|
||||||
- name: Extract Docker metadata
|
- name: Extract Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051
|
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/dockerimage.yml
vendored
2
.github/workflows/dockerimage.yml
vendored
|
|
@ -15,6 +15,6 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v5
|
||||||
- name: Build the Docker image
|
- name: Build the Docker image
|
||||||
run: docker build . --file Dockerfile --tag wekan:$(date +%s)
|
run: docker build . --file Dockerfile --tag wekan:$(date +%s)
|
||||||
|
|
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|
|
||||||
20
.github/workflows/test_suite.yml
vendored
20
.github/workflows/test_suite.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# steps:
|
# steps:
|
||||||
# - name: checkout
|
# - name: checkout
|
||||||
# uses: actions/checkout@v6
|
# uses: actions/checkout@v5
|
||||||
#
|
#
|
||||||
# - name: setup node
|
# - name: setup node
|
||||||
# uses: actions/setup-node@v1
|
# uses: actions/setup-node@v1
|
||||||
|
|
@ -42,7 +42,7 @@ jobs:
|
||||||
# needs: [lintcode]
|
# needs: [lintcode]
|
||||||
# steps:
|
# steps:
|
||||||
# - name: checkout
|
# - name: checkout
|
||||||
# uses: actions/checkout@v6
|
# uses: actions/checkout@v5
|
||||||
#
|
#
|
||||||
# - name: setup node
|
# - name: setup node
|
||||||
# uses: actions/setup-node@v1
|
# uses: actions/setup-node@v1
|
||||||
|
|
@ -65,7 +65,7 @@ jobs:
|
||||||
# needs: [lintcode,lintstyle]
|
# needs: [lintcode,lintstyle]
|
||||||
# steps:
|
# steps:
|
||||||
# - name: checkout
|
# - name: checkout
|
||||||
# uses: actions/checkout@v6
|
# uses: actions/checkout@v5
|
||||||
#
|
#
|
||||||
# - name: setup node
|
# - name: setup node
|
||||||
# uses: actions/setup-node@v1
|
# uses: actions/setup-node@v1
|
||||||
|
|
@ -90,12 +90,12 @@ jobs:
|
||||||
|
|
||||||
# CHECKOUTS
|
# CHECKOUTS
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
# CACHING
|
# CACHING
|
||||||
- name: Install Meteor
|
- name: Install Meteor
|
||||||
id: cache-meteor-install
|
id: cache-meteor-install
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.meteor
|
path: ~/.meteor
|
||||||
key: v1-meteor-${{ hashFiles('.meteor/versions') }}
|
key: v1-meteor-${{ hashFiles('.meteor/versions') }}
|
||||||
|
|
@ -104,7 +104,7 @@ jobs:
|
||||||
|
|
||||||
- name: Cache NPM dependencies
|
- name: Cache NPM dependencies
|
||||||
id: cache-meteor-npm
|
id: cache-meteor-npm
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
key: v1-npm-${{ hashFiles('package-lock.json') }}
|
key: v1-npm-${{ hashFiles('package-lock.json') }}
|
||||||
|
|
@ -113,7 +113,7 @@ jobs:
|
||||||
|
|
||||||
- name: Cache Meteor build
|
- name: Cache Meteor build
|
||||||
id: cache-meteor-build
|
id: cache-meteor-build
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
.meteor/local/resolver-result-cache.json
|
.meteor/local/resolver-result-cache.json
|
||||||
|
|
@ -136,7 +136,7 @@ jobs:
|
||||||
run: sh ./test-wekan.sh -cv
|
run: sh ./test-wekan.sh -cv
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: coverage-folder
|
name: coverage-folder
|
||||||
path: .coverage/
|
path: .coverage/
|
||||||
|
|
@ -147,10 +147,10 @@ jobs:
|
||||||
needs: [tests]
|
needs: [tests]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Download coverage
|
- name: Download coverage
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: coverage-folder
|
name: coverage-folder
|
||||||
path: .coverage/
|
path: .coverage/
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
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]
|
[o:wekan:p:wekan:r:application]
|
||||||
file_filter = imports/i18n/data/<lang>.i18n.json
|
file_filter = imports/i18n/data/<lang>.i18n.json
|
||||||
|
|
|
||||||
178
CHANGELOG.md
178
CHANGELOG.md
|
|
@ -22,182 +22,6 @@ Fixing other platforms In Progress.
|
||||||
WeKan 8.00-8.06 had wrong raw database directory setting /var/snap/wekan/common/wekan and some cards were not visible.
|
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.
|
Those are fixed at WeKan 8.07 where database directory is back to /var/snap/wekan/common and all cards are visible.
|
||||||
|
|
||||||
# Upcoming WeKan ® release
|
|
||||||
|
|
||||||
This release fixes the following CRITICAL SECURITY ISSUES of [Snowbleed](https://wekan.fi/hall-of-fame/snowbleed/):
|
|
||||||
|
|
||||||
- [Security Fix 1: There was not enough permission checks. Moved migrations to Admin Panel/Settings/Cron](https://github.com/wekan/wekan/commit/cbb1cd78de3e40264a5e047ace0ce27f8635b4e6).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 2: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 3: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 4: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 5: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 6: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 7: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 8: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 9: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 10: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 11: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 12: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 13: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
|
|
||||||
- [Security Fix 14: ](https://github.com/wekan/wekan/commit/).
|
|
||||||
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and 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).
|
|
||||||
Thanks to juri_ at WeKan Libera.Chat IRC and xet7.
|
|
||||||
- [Added s390x firewall Docs](https://github.com/wekan/wekan/commit/ec7c0e6dc3641f43b1a110d285f6ef15c146584a).
|
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
# v8.17 2025-11-06 WeKan ® release
|
||||||
|
|
||||||
This release adds the following new feature:
|
This release adds the following new feature:
|
||||||
|
|
@ -236,7 +60,7 @@ Thanks to above GitHub users for their contributions and translators for their t
|
||||||
|
|
||||||
# v8.16 2025-11-02 WeKan ® release
|
# v8.16 2025-11-02 WeKan ® release
|
||||||
|
|
||||||
This release fixes the following CRITICAL SECURITY ISSUES of [Spacebleed](https://wekan.fi/hall-of-fame/spacebleed/):
|
This release fixes SpaceBleed that is the following CRITICAL SECURITY ISSUES:
|
||||||
|
|
||||||
- [Fix SECURITY ISSUE 1: File Attachments enables stored XSS (High)](https://github.com/wekan/wekan/commit/e9a727301d7b4f1689a703503df668c0f4f4cab8).
|
- [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.
|
Thanks to Siam Thanat Hack (STH) and xet7.
|
||||||
|
|
|
||||||
|
|
@ -249,9 +249,9 @@ cd /home/wekan/app
|
||||||
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
|
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
|
||||||
#rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy
|
#rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy
|
||||||
#mv /home/wekan/app_build/bundle /build
|
#mv /home/wekan/app_build/bundle /build
|
||||||
wget "https://github.com/wekan/wekan/releases/download/v8.19/wekan-8.19-amd64.zip"
|
wget "https://github.com/wekan/wekan/releases/download/v8.17/wekan-8.17-amd64.zip"
|
||||||
unzip wekan-8.19-amd64.zip
|
unzip wekan-8.17-amd64.zip
|
||||||
rm wekan-8.19-amd64.zip
|
rm wekan-8.17-amd64.zip
|
||||||
mv /home/wekan/app/bundle /build
|
mv /home/wekan/app/bundle /build
|
||||||
|
|
||||||
# Put back the original tar
|
# Put back the original tar
|
||||||
|
|
|
||||||
36
SECURITY.md
36
SECURITY.md
|
|
@ -1,20 +1,12 @@
|
||||||
|
About money, see [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||||
|
|
||||||
## Responsible Security Disclosure
|
Security is very important to us. If you discover any issue regarding security, please disclose
|
||||||
|
the information responsibly by sending an email from Protonmail to security@wekan.fi
|
||||||
|
that is Protomail email address, or by using this PGP key
|
||||||
|
[security-at-wekan.fi.asc](security-at-wekan.fi.asc) to security@wekan.fi
|
||||||
|
and not by creating a GitHub issue. We will respond swiftly to fix verifiable security issues.
|
||||||
|
|
||||||
- To send email, use [ProtonMail](https://proton.me) email address or use PGP key [security-at-wekan.fi.asc](security-at-wekan.fi.asc)
|
We thank you with a place at our hall of fame page, that is at https://wekan.fi/hall-of-fame
|
||||||
- Send info about security issue ONLY to security@wekan.fi (that is Protomail email address). NOT TO ANYWHERE ELSE. NO CC, NO BCC.
|
|
||||||
- Wait for new WeKan release that fixes security issue
|
|
||||||
- If you approve, we thank you by adding you to Hall of Fame: https://wekan.fi/hall-of-fame/
|
|
||||||
|
|
||||||
## Bonus Points
|
|
||||||
|
|
||||||
- If you include code for fixing security issue
|
|
||||||
|
|
||||||
## Losing Points
|
|
||||||
|
|
||||||
- If you ask about [bounty](CONTRIBUTING.md). There is no bounty. WeKan is NOT Big Tech. WeKan is FLOSS.
|
|
||||||
- If you forget to include vulnerability details.
|
|
||||||
- If you send info about security issue to somewhere else than security@wekan.fi
|
|
||||||
|
|
||||||
## How should reports be formatted?
|
## How should reports be formatted?
|
||||||
|
|
||||||
|
|
@ -34,7 +26,7 @@ CWSS (optional): %cwss
|
||||||
|
|
||||||
Anyone who reports a unique security issue in scope and does not disclose it to
|
Anyone who reports a unique security issue in scope and does not disclose it to
|
||||||
a third party before we have patched and updated may be upon their approval
|
a third party before we have patched and updated may be upon their approval
|
||||||
added to the WeKan Hall of Fame https://wekan.fi/hall-of-fame/
|
added to the Wekan Hall of Fame.
|
||||||
|
|
||||||
## Which domains are in scope?
|
## Which domains are in scope?
|
||||||
|
|
||||||
|
|
@ -71,6 +63,11 @@ and by by companies that have 30k users.
|
||||||
- If you are thinking about TLS MITM, look at https://github.com/caddyserver/caddy/issues/2530
|
- If you are thinking about TLS MITM, look at https://github.com/caddyserver/caddy/issues/2530
|
||||||
- Let's Encrypt TLS requires publicly accessible webserver, that Let's Encrypt TLS validation servers check.
|
- Let's Encrypt TLS requires publicly accessible webserver, that Let's Encrypt TLS validation servers check.
|
||||||
- If firewall limits to only allowed IP addresses, you may need non-Let's Encrypt TLS cert.
|
- If firewall limits to only allowed IP addresses, you may need non-Let's Encrypt TLS cert.
|
||||||
|
- For On Premise:
|
||||||
|
- https://caddyserver.com/docs/automatic-https#local-https
|
||||||
|
- https://github.com/wekan/wekan/wiki/Caddy-Webserver-Config
|
||||||
|
- https://github.com/wekan/wekan/wiki/Azure
|
||||||
|
- https://github.com/wekan/wekan/wiki/Traefik-and-self-signed-SSL-certs
|
||||||
|
|
||||||
## XSS
|
## XSS
|
||||||
|
|
||||||
|
|
@ -272,4 +269,9 @@ Typical already known or "no impact" bugs such as:
|
||||||
- Email spoofing, SPF, DMARC & DKIM. Wekan does not include email server.
|
- 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.
|
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).
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||||
appVersion: "v8.19.0"
|
appVersion: "v8.17.0"
|
||||||
files:
|
files:
|
||||||
userUploads:
|
userUploads:
|
||||||
- README.md
|
- README.md
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import '/client/lib/boardConverter';
|
||||||
import '/client/components/boardConversionProgress';
|
import '/client/components/boardConversionProgress';
|
||||||
|
|
||||||
// Import migration manager and progress UI
|
// Import migration manager and progress UI
|
||||||
import '/client/lib/attachmentMigrationManager';
|
import '/client/lib/migrationManager';
|
||||||
import '/client/components/settings/migrationProgress';
|
import '/client/components/migrationProgress';
|
||||||
|
|
||||||
// Import cron settings
|
// Import cron settings
|
||||||
import '/client/components/settings/cronSettings';
|
import '/client/components/settings/cronSettings';
|
||||||
|
|
@ -62,20 +62,3 @@ Meteor.startup(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Subscribe to per-user small publications
|
|
||||||
Meteor.startup(() => {
|
|
||||||
Tracker.autorun(() => {
|
|
||||||
if (Meteor.userId()) {
|
|
||||||
Meteor.subscribe('userGreyIcons');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize mobile mode on startup for iOS devices
|
|
||||||
// This ensures mobile mode is applied correctly on page load
|
|
||||||
Tracker.afterFlush(() => {
|
|
||||||
if (typeof Utils !== 'undefined' && Utils.initializeUserSettings) {
|
|
||||||
Utils.initializeUserSettings();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -108,12 +108,15 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
.comments .comment .comment-desc .reactions .open-comment-reaction-popup span {
|
.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o {
|
||||||
display: inline-block;
|
font-size: 17px;
|
||||||
font-size: clamp(14px, 2vw, 18px);
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1;
|
margin-left: 2px;
|
||||||
margin-left: 4px;
|
}
|
||||||
|
.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 {
|
.comments .comment .comment-desc .reactions .reaction {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ template(name="comment")
|
||||||
= text
|
= text
|
||||||
.edit-controls
|
.edit-controls
|
||||||
button.primary(type="submit") {{_ 'edit'}}
|
button.primary(type="submit") {{_ 'edit'}}
|
||||||
a.js-close-inlined-form(title="{{_ 'close' }}") ❌
|
.fa.fa-times-thin.js-close-inlined-form
|
||||||
else
|
else
|
||||||
.comment-text
|
.comment-text
|
||||||
+viewer
|
+viewer
|
||||||
|
|
@ -55,8 +55,8 @@ template(name="commentReactions")
|
||||||
span.reaction-count #{reaction.userIds.length}
|
span.reaction-count #{reaction.userIds.length}
|
||||||
if (currentUser.isBoardMember)
|
if (currentUser.isBoardMember)
|
||||||
a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
|
a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
|
||||||
span(title="{{_ 'reaction' }}") 😀
|
i.fa.fa-smile-o
|
||||||
span(title="{{_ 'add' }}") ➕
|
i.fa.fa-plus
|
||||||
|
|
||||||
template(name="addReactionPopup")
|
template(name="addReactionPopup")
|
||||||
.reactions-popup
|
.reactions-popup
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,8 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
BlazeComponent.extendComponent({
|
||||||
getComments() {
|
getComments() {
|
||||||
const data = this.data();
|
const ret = this.data().comments();
|
||||||
if (!data || typeof data.comments !== 'function') return [];
|
return ret;
|
||||||
return data.comments();
|
|
||||||
},
|
},
|
||||||
}).register("comments");
|
}).register("comments");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
template(name="archivedBoards")
|
template(name="archivedBoards")
|
||||||
h2
|
h2
|
||||||
span(title="{{_ 'archived-boards'}}") 📦
|
i.fa.fa-archive
|
||||||
| {{_ 'archived-boards'}}
|
| {{_ 'archived-boards'}}
|
||||||
|
|
||||||
ul.archived-lists
|
ul.archived-lists
|
||||||
|
|
@ -8,10 +8,10 @@ template(name="archivedBoards")
|
||||||
li.archived-lists-item
|
li.archived-lists-item
|
||||||
div.board-header-btns
|
div.board-header-btns
|
||||||
button.board-header-btn.js-delete-board
|
button.board-header-btn.js-delete-board
|
||||||
| 🗑️
|
i.fa.fa-trash-o
|
||||||
| {{_ 'delete-board'}}
|
| {{_ 'delete-board'}}
|
||||||
button.board-header-btn.js-restore-board
|
button.board-header-btn.js-restore-board
|
||||||
| ↩️
|
i.fa.fa-undo
|
||||||
| {{_ 'restore-board'}}
|
| {{_ 'restore-board'}}
|
||||||
= title
|
= title
|
||||||
span {{ moment archivedAt 'LLL' }}
|
span {{ moment archivedAt 'LLL' }}
|
||||||
|
|
|
||||||
|
|
@ -231,30 +231,6 @@
|
||||||
font-size: 1em !important; /* Keep original icon size */
|
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 */
|
/* Ensure scrollbars are positioned correctly */
|
||||||
#content[style*="overflow-x: auto"]::-webkit-scrollbar:vertical {
|
#content[style*="overflow-x: auto"]::-webkit-scrollbar:vertical {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
|
|
@ -287,35 +263,6 @@ body.mobile-mode.iphone-device .card-details .card-details-item-title {
|
||||||
animation: fadeIn 0.2s;
|
animation: fadeIn 0.2s;
|
||||||
z-index: 16;
|
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 .open-minicard-composer,
|
||||||
.board-wrapper .board-canvas.is-dragging-active .minicard-wrapper.is-checked {
|
.board-wrapper .board-canvas.is-dragging-active .minicard-wrapper.is-checked {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
template(name="board")
|
template(name="board")
|
||||||
|
|
||||||
if isConverting.get
|
if isMigrating.get
|
||||||
|
+migrationProgress
|
||||||
|
else if isConverting.get
|
||||||
+boardConversionProgress
|
+boardConversionProgress
|
||||||
else if isBoardReady.get
|
else if isBoardReady.get
|
||||||
if currentBoard
|
if currentBoard
|
||||||
|
|
@ -22,7 +24,7 @@ template(name="boardBody")
|
||||||
// Debug information (remove in production)
|
// Debug information (remove in production)
|
||||||
if debugBoardState
|
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;")
|
.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-wrapper(class=currentBoard.colorClass class="{{#if isMiniScreen}}mobile-view{{/if}}")
|
||||||
.board-canvas.js-swimlanes(
|
.board-canvas.js-swimlanes(
|
||||||
class="{{#if hasSwimlanes}}dragscroll{{/if}}"
|
class="{{#if hasSwimlanes}}dragscroll{{/if}}"
|
||||||
|
|
@ -47,8 +49,6 @@ template(name="boardBody")
|
||||||
+listsGroup(currentBoard)
|
+listsGroup(currentBoard)
|
||||||
else if isViewCalendar
|
else if isViewCalendar
|
||||||
+calendarView
|
+calendarView
|
||||||
else if isViewGantt
|
|
||||||
+ganttView
|
|
||||||
else
|
else
|
||||||
// Default view - show swimlanes if they exist, otherwise show lists
|
// Default view - show swimlanes if they exist, otherwise show lists
|
||||||
if hasSwimlanes
|
if hasSwimlanes
|
||||||
|
|
@ -56,10 +56,6 @@ template(name="boardBody")
|
||||||
+swimlane(this)
|
+swimlane(this)
|
||||||
else
|
else
|
||||||
+listsGroup(currentBoard)
|
+listsGroup(currentBoard)
|
||||||
//- Render multiple open cards in desktop mode
|
|
||||||
unless isMiniScreen
|
|
||||||
each openCards
|
|
||||||
+cardDetails(this cardIndex=@index)
|
|
||||||
+sidebar
|
+sidebar
|
||||||
|
|
||||||
template(name="calendarView")
|
template(name="calendarView")
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { ReactiveCache } from '/imports/reactiveCache';
|
import { ReactiveCache } from '/imports/reactiveCache';
|
||||||
import '../gantt/gantt.js';
|
|
||||||
import { TAPi18n } from '/imports/i18n';
|
import { TAPi18n } from '/imports/i18n';
|
||||||
import dragscroll from '@wekanteam/dragscroll';
|
import dragscroll from '@wekanteam/dragscroll';
|
||||||
import { boardConverter } from '/client/lib/boardConverter';
|
import { boardConverter } from '/client/lib/boardConverter';
|
||||||
import { formatDateByUserPreference } from '/imports/lib/dateUtils';
|
import { migrationManager } from '/client/lib/migrationManager';
|
||||||
|
import { attachmentMigrationManager } from '/client/lib/attachmentMigrationManager';
|
||||||
|
import { migrationProgressManager } from '/client/components/migrationProgress';
|
||||||
import Swimlanes from '/models/swimlanes';
|
import Swimlanes from '/models/swimlanes';
|
||||||
import Lists from '/models/lists';
|
import Lists from '/models/lists';
|
||||||
|
|
||||||
|
|
@ -15,6 +16,7 @@ BlazeComponent.extendComponent({
|
||||||
onCreated() {
|
onCreated() {
|
||||||
this.isBoardReady = new ReactiveVar(false);
|
this.isBoardReady = new ReactiveVar(false);
|
||||||
this.isConverting = 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._swimlaneCreated = new Set(); // Track boards where we've created swimlanes
|
||||||
this._boardProcessed = false; // Track if board has been processed
|
this._boardProcessed = false; // Track if board has been processed
|
||||||
this._lastProcessedBoardId = null; // Track last processed board ID
|
this._lastProcessedBoardId = null; // Track last processed board ID
|
||||||
|
|
@ -32,6 +34,7 @@ BlazeComponent.extendComponent({
|
||||||
// Use a separate autorun for subscription ready state to avoid reactive loops
|
// Use a separate autorun for subscription ready state to avoid reactive loops
|
||||||
this.subscriptionReadyAutorun = Tracker.autorun(() => {
|
this.subscriptionReadyAutorun = Tracker.autorun(() => {
|
||||||
if (handle.ready()) {
|
if (handle.ready()) {
|
||||||
|
// Only run conversion/migration logic once per board
|
||||||
if (!this._boardProcessed || this._lastProcessedBoardId !== currentBoardId) {
|
if (!this._boardProcessed || this._lastProcessedBoardId !== currentBoardId) {
|
||||||
this._boardProcessed = true;
|
this._boardProcessed = true;
|
||||||
this._lastProcessedBoardId = currentBoardId;
|
this._lastProcessedBoardId = currentBoardId;
|
||||||
|
|
@ -96,31 +99,422 @@ BlazeComponent.extendComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Automatic migration disabled - migrations must be run manually from sidebar
|
||||||
|
// Board admins can run migrations from the sidebar Migrations menu
|
||||||
this.isBoardReady.set(true);
|
this.isBoardReady.set(true);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error during board conversion check:', error);
|
console.error('Error during board conversion check:', error);
|
||||||
this.isConverting.set(false);
|
this.isConverting.set(false);
|
||||||
|
this.isMigrating.set(false);
|
||||||
this.isBoardReady.set(true); // Show board even if conversion check failed
|
this.isBoardReady.set(true); // Show board even if conversion check failed
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if board needs comprehensive migration
|
||||||
|
*/
|
||||||
|
async checkComprehensiveMigration(boardId) {
|
||||||
|
try {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
Meteor.call('comprehensiveBoardMigration.needsMigration', boardId, (error, result) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('Error checking comprehensive migration:', error);
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking comprehensive migration:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute comprehensive migration for a board
|
||||||
|
*/
|
||||||
|
async executeComprehensiveMigration(boardId) {
|
||||||
|
try {
|
||||||
|
// Start progress tracking
|
||||||
|
migrationProgressManager.startMigration();
|
||||||
|
|
||||||
|
// Simulate progress updates since we can't easily pass callbacks through Meteor methods
|
||||||
|
const progressSteps = [
|
||||||
|
{ step: 'analyze_board_structure', name: 'Analyze Board Structure', duration: 1000 },
|
||||||
|
{ step: 'fix_orphaned_cards', name: 'Fix Orphaned Cards', duration: 2000 },
|
||||||
|
{ step: 'convert_shared_lists', name: 'Convert Shared Lists', duration: 3000 },
|
||||||
|
{ step: 'ensure_per_swimlane_lists', name: 'Ensure Per-Swimlane Lists', duration: 1500 },
|
||||||
|
{ step: 'validate_migration', name: 'Validate Migration', duration: 1000 },
|
||||||
|
{ step: 'fix_avatar_urls', name: 'Fix Avatar URLs', duration: 1000 },
|
||||||
|
{ step: 'fix_attachment_urls', name: 'Fix Attachment URLs', duration: 1000 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Start the actual migration
|
||||||
|
const migrationPromise = new Promise((resolve, reject) => {
|
||||||
|
Meteor.call('comprehensiveBoardMigration.execute', boardId, (error, result) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('Error executing comprehensive migration:', error);
|
||||||
|
migrationProgressManager.failMigration(error);
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('Comprehensive migration completed for board:', boardId, result);
|
||||||
|
}
|
||||||
|
resolve(result.success);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate progress updates
|
||||||
|
const progressPromise = this.simulateMigrationProgress(progressSteps);
|
||||||
|
|
||||||
|
// Wait for both to complete
|
||||||
|
const [migrationResult] = await Promise.all([migrationPromise, progressPromise]);
|
||||||
|
|
||||||
|
migrationProgressManager.completeMigration();
|
||||||
|
return migrationResult;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error executing comprehensive migration:', error);
|
||||||
|
migrationProgressManager.failMigration(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulate migration progress updates
|
||||||
|
*/
|
||||||
|
async simulateMigrationProgress(progressSteps) {
|
||||||
|
const totalSteps = progressSteps.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < progressSteps.length; i++) {
|
||||||
|
const step = progressSteps[i];
|
||||||
|
const stepProgress = Math.round(((i + 1) / totalSteps) * 100);
|
||||||
|
|
||||||
|
// Update progress for this step
|
||||||
|
migrationProgressManager.updateProgress({
|
||||||
|
overallProgress: stepProgress,
|
||||||
|
currentStep: i + 1,
|
||||||
|
totalSteps,
|
||||||
|
stepName: step.step,
|
||||||
|
stepProgress: 0,
|
||||||
|
stepStatus: `Starting ${step.name}...`,
|
||||||
|
stepDetails: null,
|
||||||
|
boardId: Session.get('currentBoard')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate step progress
|
||||||
|
const stepDuration = step.duration;
|
||||||
|
const updateInterval = 100; // Update every 100ms
|
||||||
|
const totalUpdates = stepDuration / updateInterval;
|
||||||
|
|
||||||
|
for (let j = 0; j < totalUpdates; j++) {
|
||||||
|
const stepStepProgress = Math.round(((j + 1) / totalUpdates) * 100);
|
||||||
|
|
||||||
|
migrationProgressManager.updateProgress({
|
||||||
|
overallProgress: stepProgress,
|
||||||
|
currentStep: i + 1,
|
||||||
|
totalSteps,
|
||||||
|
stepName: step.step,
|
||||||
|
stepProgress: stepStepProgress,
|
||||||
|
stepStatus: `Processing ${step.name}...`,
|
||||||
|
stepDetails: { progress: `${stepStepProgress}%` },
|
||||||
|
boardId: Session.get('currentBoard')
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, updateInterval));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete the step
|
||||||
|
migrationProgressManager.updateProgress({
|
||||||
|
overallProgress: stepProgress,
|
||||||
|
currentStep: i + 1,
|
||||||
|
totalSteps,
|
||||||
|
stepName: step.step,
|
||||||
|
stepProgress: 100,
|
||||||
|
stepStatus: `${step.name} completed`,
|
||||||
|
stepDetails: { status: 'completed' },
|
||||||
|
boardId: Session.get('currentBoard')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async startBackgroundMigration(boardId) {
|
||||||
|
try {
|
||||||
|
// Start background migration using the cron system
|
||||||
|
Meteor.call('boardMigration.startBoardMigration', boardId, (error, result) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('Failed to start background migration:', error);
|
||||||
|
} else {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('Background migration started for board:', boardId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error starting background migration:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async convertSharedListsToPerSwimlane(boardId) {
|
||||||
|
try {
|
||||||
|
const board = ReactiveCache.getBoard(boardId);
|
||||||
|
if (!board) return;
|
||||||
|
|
||||||
|
// Check if board has already been processed for shared lists conversion
|
||||||
|
if (board.hasSharedListsConverted) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has already been processed for shared lists conversion`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all lists for this board
|
||||||
|
const allLists = board.lists();
|
||||||
|
const swimlanes = board.swimlanes();
|
||||||
|
|
||||||
|
if (swimlanes.length === 0) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has no swimlanes, skipping shared lists conversion`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find shared lists (lists with empty swimlaneId or null swimlaneId)
|
||||||
|
const sharedLists = allLists.filter(list => !list.swimlaneId || list.swimlaneId === '');
|
||||||
|
|
||||||
|
if (sharedLists.length === 0) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has no shared lists to convert`);
|
||||||
|
}
|
||||||
|
// Mark as processed even if no shared lists
|
||||||
|
Boards.update(boardId, { $set: { hasSharedListsConverted: true } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Converting ${sharedLists.length} shared lists to per-swimlane lists for board ${boardId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert each shared list to per-swimlane lists
|
||||||
|
for (const sharedList of sharedLists) {
|
||||||
|
// Create a copy of the list for each swimlane
|
||||||
|
for (const swimlane of swimlanes) {
|
||||||
|
// Check if this list already exists in this swimlane
|
||||||
|
const existingList = Lists.findOne({
|
||||||
|
boardId: boardId,
|
||||||
|
swimlaneId: swimlane._id,
|
||||||
|
title: sharedList.title
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingList) {
|
||||||
|
// Double-check to avoid race conditions
|
||||||
|
const doubleCheckList = ReactiveCache.getList({
|
||||||
|
boardId: boardId,
|
||||||
|
swimlaneId: swimlane._id,
|
||||||
|
title: sharedList.title
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!doubleCheckList) {
|
||||||
|
// Create a new list in this swimlane
|
||||||
|
const newListData = {
|
||||||
|
title: sharedList.title,
|
||||||
|
boardId: boardId,
|
||||||
|
swimlaneId: swimlane._id,
|
||||||
|
sort: sharedList.sort || 0,
|
||||||
|
archived: sharedList.archived || false, // Preserve archived state from original list
|
||||||
|
createdAt: new Date(),
|
||||||
|
modifiedAt: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy other properties if they exist
|
||||||
|
if (sharedList.color) newListData.color = sharedList.color;
|
||||||
|
if (sharedList.wipLimit) newListData.wipLimit = sharedList.wipLimit;
|
||||||
|
if (sharedList.wipLimitEnabled) newListData.wipLimitEnabled = sharedList.wipLimitEnabled;
|
||||||
|
if (sharedList.wipLimitSoft) newListData.wipLimitSoft = sharedList.wipLimitSoft;
|
||||||
|
|
||||||
|
Lists.insert(newListData);
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
const archivedStatus = sharedList.archived ? ' (archived)' : ' (active)';
|
||||||
|
console.log(`Created list "${sharedList.title}"${archivedStatus} for swimlane ${swimlane.title || swimlane._id}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`List "${sharedList.title}" already exists in swimlane ${swimlane.title || swimlane._id} (double-check), skipping`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`List "${sharedList.title}" already exists in swimlane ${swimlane.title || swimlane._id}, skipping`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the original shared list completely
|
||||||
|
Lists.remove(sharedList._id);
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Removed shared list "${sharedList.title}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark board as processed
|
||||||
|
Boards.update(boardId, { $set: { hasSharedListsConverted: true } });
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Successfully converted ${sharedLists.length} shared lists to per-swimlane lists for board ${boardId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error converting shared lists to per-swimlane:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fixMissingLists(boardId) {
|
||||||
|
try {
|
||||||
|
const board = ReactiveCache.getBoard(boardId);
|
||||||
|
if (!board) return;
|
||||||
|
|
||||||
|
// Check if board has already been processed for missing lists fix
|
||||||
|
if (board.fixMissingListsCompleted) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has already been processed for missing lists fix`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if migration is needed
|
||||||
|
const needsMigration = await new Promise((resolve, reject) => {
|
||||||
|
Meteor.call('fixMissingListsMigration.needsMigration', boardId, (error, result) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!needsMigration) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} does not need missing lists fix`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Starting fix missing lists migration for board ${boardId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the migration
|
||||||
|
const result = await new Promise((resolve, reject) => {
|
||||||
|
Meteor.call('fixMissingListsMigration.execute', boardId, (error, result) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result && result.success) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Successfully fixed missing lists for board ${boardId}: created ${result.createdLists} lists, updated ${result.updatedCards} cards`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fixing missing lists:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fixDuplicateLists(boardId) {
|
||||||
|
try {
|
||||||
|
const board = ReactiveCache.getBoard(boardId);
|
||||||
|
if (!board) return;
|
||||||
|
|
||||||
|
// Check if board has already been processed for duplicate lists fix
|
||||||
|
if (board.fixDuplicateListsCompleted) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has already been processed for duplicate lists fix`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Starting duplicate lists fix for board ${boardId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the duplicate lists fix
|
||||||
|
const result = await new Promise((resolve, reject) => {
|
||||||
|
Meteor.call('fixDuplicateLists.fixBoard', boardId, (error, result) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result && result.fixed > 0) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Successfully fixed ${result.fixed} duplicate lists for board ${boardId}: ${result.fixedSwimlanes} swimlanes, ${result.fixedLists} lists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark board as processed
|
||||||
|
Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } });
|
||||||
|
} else if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`No duplicate lists found for board ${boardId}`);
|
||||||
|
// Still mark as processed to avoid repeated checks
|
||||||
|
Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } });
|
||||||
|
} else {
|
||||||
|
// Still mark as processed to avoid repeated checks
|
||||||
|
Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } });
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fixing duplicate lists:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async startAttachmentMigrationIfNeeded(boardId) {
|
||||||
|
try {
|
||||||
|
// Check if board has already been migrated
|
||||||
|
if (attachmentMigrationManager.isBoardMigrated(boardId)) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has already been migrated, skipping`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are unconverted attachments
|
||||||
|
const unconvertedAttachments = attachmentMigrationManager.getUnconvertedAttachments(boardId);
|
||||||
|
|
||||||
|
if (unconvertedAttachments.length > 0) {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Starting attachment migration for ${unconvertedAttachments.length} attachments in board ${boardId}`);
|
||||||
|
}
|
||||||
|
await attachmentMigrationManager.startAttachmentMigration(boardId);
|
||||||
|
} else {
|
||||||
|
// No attachments to migrate, mark board as migrated
|
||||||
|
// This will be handled by the migration manager itself
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log(`Board ${boardId} has no attachments to migrate`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error starting attachment migration:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onlyShowCurrentCard() {
|
onlyShowCurrentCard() {
|
||||||
const isMiniScreen = Utils.isMiniScreen();
|
const isMiniScreen = Utils.isMiniScreen();
|
||||||
const currentCardId = Utils.getCurrentCardId(true);
|
const currentCardId = Utils.getCurrentCardId(true);
|
||||||
return isMiniScreen && currentCardId;
|
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() {
|
goHome() {
|
||||||
FlowRouter.go('home');
|
FlowRouter.go('home');
|
||||||
},
|
},
|
||||||
|
|
@ -129,6 +523,10 @@ BlazeComponent.extendComponent({
|
||||||
return this.isConverting.get();
|
return this.isConverting.get();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isMigrating() {
|
||||||
|
return this.isMigrating.get();
|
||||||
|
},
|
||||||
|
|
||||||
isBoardReady() {
|
isBoardReady() {
|
||||||
return this.isBoardReady.get();
|
return this.isBoardReady.get();
|
||||||
},
|
},
|
||||||
|
|
@ -580,19 +978,6 @@ BlazeComponent.extendComponent({
|
||||||
return boardView === 'board-view-cal';
|
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() {
|
hasSwimlanes() {
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
if (!currentBoard) {
|
if (!currentBoard) {
|
||||||
|
|
@ -636,6 +1021,7 @@ BlazeComponent.extendComponent({
|
||||||
const currentBoardId = Session.get('currentBoard');
|
const currentBoardId = Session.get('currentBoard');
|
||||||
const isBoardReady = this.isBoardReady.get();
|
const isBoardReady = this.isBoardReady.get();
|
||||||
const isConverting = this.isConverting.get();
|
const isConverting = this.isConverting.get();
|
||||||
|
const isMigrating = this.isMigrating.get();
|
||||||
const boardView = Utils.boardView();
|
const boardView = Utils.boardView();
|
||||||
|
|
||||||
if (process.env.DEBUG === 'true') {
|
if (process.env.DEBUG === 'true') {
|
||||||
|
|
@ -644,6 +1030,7 @@ BlazeComponent.extendComponent({
|
||||||
console.log('currentBoard:', !!currentBoard, currentBoard ? currentBoard.title : 'none');
|
console.log('currentBoard:', !!currentBoard, currentBoard ? currentBoard.title : 'none');
|
||||||
console.log('isBoardReady:', isBoardReady);
|
console.log('isBoardReady:', isBoardReady);
|
||||||
console.log('isConverting:', isConverting);
|
console.log('isConverting:', isConverting);
|
||||||
|
console.log('isMigrating:', isMigrating);
|
||||||
console.log('boardView:', boardView);
|
console.log('boardView:', boardView);
|
||||||
console.log('========================');
|
console.log('========================');
|
||||||
}
|
}
|
||||||
|
|
@ -654,6 +1041,7 @@ BlazeComponent.extendComponent({
|
||||||
currentBoardTitle: currentBoard ? currentBoard.title : 'none',
|
currentBoardTitle: currentBoard ? currentBoard.title : 'none',
|
||||||
isBoardReady,
|
isBoardReady,
|
||||||
isConverting,
|
isConverting,
|
||||||
|
isMigrating,
|
||||||
boardView
|
boardView
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -1020,8 +1408,3 @@ BlazeComponent.extendComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}).register('calendarView');
|
}).register('calendarView');
|
||||||
/**
|
|
||||||
* Gantt View Component
|
|
||||||
* Displays cards as a Gantt chart with start/due dates
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ template(name="boardHeaderBar")
|
||||||
| ❌
|
| ❌
|
||||||
|
|
||||||
a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
|
a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
|
||||||
span.emoji-icon 🔍
|
| 🔍
|
||||||
|
|
||||||
unless currentBoard.isTemplatesBoard
|
unless currentBoard.isTemplatesBoard
|
||||||
a.board-header-btn.js-toggle-board-view(
|
a.board-header-btn.js-toggle-board-view(
|
||||||
|
|
@ -121,8 +121,6 @@ template(name="boardHeaderBar")
|
||||||
| 📋
|
| 📋
|
||||||
if $eq boardView 'board-view-cal'
|
if $eq boardView 'board-view-cal'
|
||||||
| 📅
|
| 📅
|
||||||
if $eq boardView 'board-view-gantt'
|
|
||||||
| 📊
|
|
||||||
|
|
||||||
if canModifyBoard
|
if canModifyBoard
|
||||||
a.board-header-btn.js-multiselection-activate(
|
a.board-header-btn.js-multiselection-activate(
|
||||||
|
|
@ -210,13 +208,6 @@ template(name="boardChangeViewPopup")
|
||||||
| {{_ 'board-view-cal'}}
|
| {{_ 'board-view-cal'}}
|
||||||
if $eq Utils.boardView "board-view-cal"
|
if $eq Utils.boardView "board-view-cal"
|
||||||
| ✅
|
| ✅
|
||||||
li
|
|
||||||
with "board-view-gantt"
|
|
||||||
a.js-open-gantt-view
|
|
||||||
| 📊
|
|
||||||
| {{_ 'board-view-gantt'}}
|
|
||||||
if $eq Utils.boardView "board-view-gantt"
|
|
||||||
| ✅
|
|
||||||
|
|
||||||
template(name="createBoard")
|
template(name="createBoard")
|
||||||
form
|
form
|
||||||
|
|
|
||||||
|
|
@ -208,10 +208,6 @@ Template.boardChangeViewPopup.events({
|
||||||
Utils.setBoardView('board-view-cal');
|
Utils.setBoardView('board-view-cal');
|
||||||
Popup.back();
|
Popup.back();
|
||||||
},
|
},
|
||||||
'click .js-open-gantt-view'() {
|
|
||||||
Utils.setBoardView('board-view-gantt');
|
|
||||||
Popup.back();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const CreateBoard = BlazeComponent.extendComponent({
|
const CreateBoard = BlazeComponent.extendComponent({
|
||||||
|
|
|
||||||
|
|
@ -583,9 +583,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-list .board-list-item .multi-selection-checkbox.is-checked {
|
.board-list .board-list-item .multi-selection-checkbox.is-checked {
|
||||||
background: #3cb500;
|
background: #2196F3;
|
||||||
border-color: #3cb500;
|
border-color: #2196F3;
|
||||||
box-shadow: 0 2px 8px rgba(60, 181, 0, 0.6);
|
box-shadow: 0 2px 8px rgba(33, 150, 243, 0.6);
|
||||||
width: 24px !important;
|
width: 24px !important;
|
||||||
height: 24px !important;
|
height: 24px !important;
|
||||||
top: auto !important;
|
top: auto !important;
|
||||||
|
|
@ -601,22 +601,10 @@
|
||||||
font-weight: bold;
|
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 {
|
.board-list.is-multiselection-active .js-board.is-checked {
|
||||||
outline: 4px solid #3cb500;
|
outline: 4px solid #2196F3;
|
||||||
outline-offset: -4px;
|
outline-offset: -4px;
|
||||||
box-shadow: 0 4px 12px rgba(60, 181, 0, 0.4);
|
box-shadow: 0 4px 12px rgba(33, 150, 243, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Visual hint when multiselection is active */
|
/* Visual hint when multiselection is active */
|
||||||
|
|
@ -657,19 +645,8 @@ body.grey-icons-enabled .board-list.is-multiselection-active .js-board.is-checke
|
||||||
}
|
}
|
||||||
.board-backgrounds-list .board-background-select .background-box i.fa-check {
|
.board-backgrounds-list .board-background-select .background-box i.fa-check {
|
||||||
font-size: 25px;
|
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.) */
|
/* Mobile view styles - applied when isMiniScreen is true (iPhone, etc.) */
|
||||||
.board-list.mobile-view {
|
.board-list.mobile-view {
|
||||||
height: calc(100vh - 120px);
|
height: calc(100vh - 120px);
|
||||||
|
|
|
||||||
|
|
@ -8,26 +8,18 @@ template(name="boardList")
|
||||||
ul.menu
|
ul.menu
|
||||||
li(class="menu-item {{#if isSelectedMenu 'starred'}}active{{/if}}")
|
li(class="menu-item {{#if isSelectedMenu 'starred'}}active{{/if}}")
|
||||||
a.js-select-menu(data-type="starred")
|
a.js-select-menu(data-type="starred")
|
||||||
span.menu-label
|
span.menu-label ⭐ {{_ 'allboards.starred'}}
|
||||||
span.emoji-icon ⭐
|
|
||||||
| {{_ 'allboards.starred'}}
|
|
||||||
span.menu-count {{menuItemCount 'starred'}}
|
span.menu-count {{menuItemCount 'starred'}}
|
||||||
li(class="menu-item {{#if isSelectedMenu 'templates'}}active{{/if}}")
|
li(class="menu-item {{#if isSelectedMenu 'templates'}}active{{/if}}")
|
||||||
a.js-select-menu(data-type="templates")
|
a.js-select-menu(data-type="templates")
|
||||||
span.menu-label
|
span.menu-label 📋 {{_ 'allboards.templates'}}
|
||||||
span.emoji-icon 📋
|
|
||||||
| {{_ 'allboards.templates'}}
|
|
||||||
span.menu-count {{menuItemCount 'templates'}}
|
span.menu-count {{menuItemCount 'templates'}}
|
||||||
li(class="menu-item {{#if isSelectedMenu 'remaining'}}active{{/if}}")
|
li(class="menu-item {{#if isSelectedMenu 'remaining'}}active{{/if}}")
|
||||||
a.js-select-menu(data-type="remaining")
|
a.js-select-menu(data-type="remaining")
|
||||||
span.menu-label
|
span.menu-label 📂 {{_ 'allboards.remaining'}}
|
||||||
span.emoji-icon 📂
|
|
||||||
| {{_ 'allboards.remaining'}}
|
|
||||||
span.menu-count {{menuItemCount 'remaining'}}
|
span.menu-count {{menuItemCount 'remaining'}}
|
||||||
.workspaces-header
|
.workspaces-header
|
||||||
span
|
span 🗂️ {{_ 'allboards.workspaces'}}
|
||||||
span.emoji-icon 🗂️
|
|
||||||
| {{_ 'allboards.workspaces'}}
|
|
||||||
a.js-add-workspace(title="{{_ 'allboards.add-workspace'}}") +
|
a.js-add-workspace(title="{{_ 'allboards.add-workspace'}}") +
|
||||||
// Workspaces tree
|
// Workspaces tree
|
||||||
+workspaceTree(nodes=workspacesTree selectedWorkspaceId=selectedWorkspaceId)
|
+workspaceTree(nodes=workspacesTree selectedWorkspaceId=selectedWorkspaceId)
|
||||||
|
|
@ -51,49 +43,44 @@ template(name="boardList")
|
||||||
li.AllBoardBtns
|
li.AllBoardBtns
|
||||||
div.AllBoardButtonsContainer
|
div.AllBoardButtonsContainer
|
||||||
if userHasOrgsOrTeams
|
if userHasOrgsOrTeams
|
||||||
span.emoji-icon 🔍
|
span 🔍
|
||||||
input#filterBtn(type="button" value="{{_ 'filter'}}")
|
input#filterBtn(type="button" value="{{_ 'filter'}}")
|
||||||
button#resetBtn.filter-reset-btn
|
button#resetBtn.filter-reset-btn
|
||||||
span.reset-icon
|
span.reset-icon ❌
|
||||||
span.emoji-icon ❌
|
|
||||||
span {{_ 'filter-clear'}}
|
span {{_ 'filter-clear'}}
|
||||||
|
|
||||||
// Right boards grid
|
// Right boards grid
|
||||||
.boards-right-grid
|
.boards-right-grid
|
||||||
.boards-path-header
|
.boards-path-header
|
||||||
.path-left
|
.path-left
|
||||||
span.path-icon.emoji-icon {{currentMenuPath.icon}}
|
span.path-icon {{currentMenuPath.icon}}
|
||||||
span.path-text {{currentMenuPath.text}}
|
span.path-text {{currentMenuPath.text}}
|
||||||
if BoardMultiSelection.isActive
|
if BoardMultiSelection.isActive
|
||||||
span.multiselection-hint
|
span.multiselection-hint 📌 {{_ 'multi-selection-active'}}
|
||||||
span.emoji-icon 📌
|
|
||||||
| {{_ 'multi-selection-active'}}
|
|
||||||
.path-right
|
.path-right
|
||||||
if canModifyBoards
|
if canModifyBoards
|
||||||
if hasBoardsSelected
|
if hasBoardsSelected
|
||||||
button.js-archive-selected-boards.board-header-btn
|
button.js-archive-selected-boards.board-header-btn
|
||||||
span.emoji-icon 📦
|
span 📦
|
||||||
span {{_ 'archive-board'}}
|
span {{_ 'archive-board'}}
|
||||||
button.js-duplicate-selected-boards.board-header-btn
|
button.js-duplicate-selected-boards.board-header-btn
|
||||||
span.emoji-icon 📋
|
span 📋
|
||||||
span {{_ 'duplicate-board'}}
|
span {{_ 'duplicate-board'}}
|
||||||
a.board-header-btn.js-multiselection-activate(
|
a.board-header-btn.js-multiselection-activate(
|
||||||
title="{{#if BoardMultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}"
|
title="{{#if BoardMultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}"
|
||||||
class="{{#if BoardMultiSelection.isActive}}emphasis{{/if}}")
|
class="{{#if BoardMultiSelection.isActive}}emphasis{{/if}}")
|
||||||
span.emoji-icon ☑️
|
| ☑️
|
||||||
if BoardMultiSelection.isActive
|
if BoardMultiSelection.isActive
|
||||||
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
|
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
|
||||||
span.emoji-icon ✖
|
| ✖
|
||||||
ul.board-list.clearfix.js-boards(class="{{#if isMiniScreen}}mobile-view{{/if}} {{#if BoardMultiSelection.isActive}}is-multiselection-active{{/if}}")
|
ul.board-list.clearfix.js-boards(class="{{#if isMiniScreen}}mobile-view{{/if}} {{#if BoardMultiSelection.isActive}}is-multiselection-active{{/if}}")
|
||||||
li.js-add-board
|
li.js-add-board
|
||||||
if isSelectedMenu 'templates'
|
if isSelectedMenu 'templates'
|
||||||
a.board-list-item.label(title="{{_ 'add-template-container'}}")
|
a.board-list-item.label(title="{{_ 'add-template-container'}}")
|
||||||
span.emoji-icon ➕
|
| ➕ {{_ 'add-template-container'}}
|
||||||
| {{_ 'add-template-container'}}
|
|
||||||
else
|
else
|
||||||
a.board-list-item.label(title="{{_ 'add-board'}}")
|
a.board-list-item.label(title="{{_ 'add-board'}}")
|
||||||
span.emoji-icon ➕
|
| ➕ {{_ 'add-board'}}
|
||||||
| {{_ 'add-board'}}
|
|
||||||
each boards
|
each boards
|
||||||
li.js-board(class="{{_id}} {{#if isStarred}}starred{{/if}} {{colorClass}} {{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}", draggable="true")
|
li.js-board(class="{{_id}} {{#if isStarred}}starred{{/if}} {{colorClass}} {{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}", draggable="true")
|
||||||
if isInvited
|
if isInvited
|
||||||
|
|
@ -106,8 +93,7 @@ template(name="boardList")
|
||||||
span.js-star-board(
|
span.js-star-board(
|
||||||
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
|
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
|
||||||
title="{{_ 'star-board-title'}}")
|
title="{{_ 'star-board-title'}}")
|
||||||
span.emoji-icon
|
| {{#if isStarred}}⭐{{else}}☆{{/if}}
|
||||||
| {{#if isStarred}}⭐{{else}}☆{{/if}}
|
|
||||||
p.board-list-item-desc {{_ 'just-invited'}}
|
p.board-list-item-desc {{_ 'just-invited'}}
|
||||||
button.js-accept-invite.primary {{_ 'accept'}}
|
button.js-accept-invite.primary {{_ 'accept'}}
|
||||||
button.js-decline-invite {{_ 'decline'}}
|
button.js-decline-invite {{_ 'decline'}}
|
||||||
|
|
@ -117,9 +103,7 @@ template(name="boardList")
|
||||||
if BoardMultiSelection.isActive
|
if BoardMultiSelection.isActive
|
||||||
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
|
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
|
||||||
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
|
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||||
span.board-handle(title="{{_ 'drag-board'}}")
|
span.board-handle(title="{{_ 'drag-board'}}") ↕️
|
||||||
span.emoji-icon ↕️
|
|
||||||
|
|
||||||
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
|
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||||
span.details
|
span.details
|
||||||
span.board-list-item-name(title="{{_ 'template-container'}}")
|
span.board-list-item-name(title="{{_ 'template-container'}}")
|
||||||
|
|
@ -132,20 +116,17 @@ template(name="boardList")
|
||||||
span.js-has-spenttime-cards(
|
span.js-has-spenttime-cards(
|
||||||
class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}"
|
class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}"
|
||||||
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
|
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
|
||||||
span.emoji-icon ⏱️
|
| ⏱️
|
||||||
span.js-star-board(
|
span.js-star-board(
|
||||||
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
|
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
|
||||||
title="{{_ 'star-board-title'}}")
|
title="{{_ 'star-board-title'}}")
|
||||||
span.emoji-icon
|
| {{#if isStarred}}⭐{{else}}☆{{/if}}
|
||||||
| {{#if isStarred}}⭐{{else}}☆{{/if}}
|
|
||||||
else
|
else
|
||||||
.board-list-item
|
.board-list-item
|
||||||
if BoardMultiSelection.isActive
|
if BoardMultiSelection.isActive
|
||||||
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
|
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
|
||||||
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
|
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||||
span.board-handle(title="{{_ 'drag-board'}}")
|
span.board-handle(title="{{_ 'drag-board'}}") ↕️
|
||||||
span.emoji-icon ↕️
|
|
||||||
|
|
||||||
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
|
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||||
span.details
|
span.details
|
||||||
span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}")
|
span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}")
|
||||||
|
|
@ -170,12 +151,11 @@ template(name="boardList")
|
||||||
span.js-has-spenttime-cards(
|
span.js-has-spenttime-cards(
|
||||||
class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}"
|
class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}"
|
||||||
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
|
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
|
||||||
span.emoji-icon ⏱️
|
| ⏱️
|
||||||
a.js-star-board(
|
a.js-star-board(
|
||||||
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
|
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
|
||||||
title="{{_ 'star-board-title'}}")
|
title="{{_ 'star-board-title'}}")
|
||||||
span.emoji-icon
|
| {{#if isStarred}}⭐{{else}}☆{{/if}}
|
||||||
| {{#if isStarred}}⭐{{else}}☆{{/if}}
|
|
||||||
|
|
||||||
template(name="boardListHeaderBar")
|
template(name="boardListHeaderBar")
|
||||||
h1 {{_ title }}
|
h1 {{_ title }}
|
||||||
|
|
@ -194,19 +174,16 @@ template(name="workspaceTree")
|
||||||
each nodes
|
each nodes
|
||||||
li.workspace-node(class="{{#if $eq id selectedWorkspaceId}}active{{/if}}" data-workspace-id="{{id}}" draggable="true")
|
li.workspace-node(class="{{#if $eq id selectedWorkspaceId}}active{{/if}}" data-workspace-id="{{id}}" draggable="true")
|
||||||
.workspace-node-content
|
.workspace-node-content
|
||||||
span.workspace-drag-handle
|
span.workspace-drag-handle ↕️
|
||||||
span.emoji-icon ↕️
|
|
||||||
|
|
||||||
a.js-select-workspace(data-id="{{id}}")
|
a.js-select-workspace(data-id="{{id}}")
|
||||||
span.workspace-icon
|
span.workspace-icon
|
||||||
if icon
|
if icon
|
||||||
+viewer
|
+viewer
|
||||||
= icon
|
= icon
|
||||||
else
|
else
|
||||||
span.emoji-icon 📁
|
| 📁
|
||||||
span.workspace-name= name
|
span.workspace-name= name
|
||||||
a.js-edit-workspace(data-id="{{id}}" title="{{_ 'allboards.edit-workspace'}}")
|
a.js-edit-workspace(data-id="{{id}}" title="{{_ 'allboards.edit-workspace'}}") ✏️
|
||||||
span.emoji-icon ✏️
|
|
||||||
span.workspace-count {{workspaceCount id}}
|
span.workspace-count {{workspaceCount id}}
|
||||||
a.js-add-subworkspace(data-id="{{id}}" title="{{_ 'allboards.add-subworkspace'}}") +
|
a.js-add-subworkspace(data-id="{{id}}" title="{{_ 'allboards.add-subworkspace'}}") +
|
||||||
if children
|
if children
|
||||||
|
|
|
||||||
|
|
@ -232,18 +232,20 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
boards() {
|
boards() {
|
||||||
let query = {
|
let query = {
|
||||||
|
// { type: 'board' },
|
||||||
|
// { type: { $in: ['board','template-container'] } },
|
||||||
$and: [
|
$and: [
|
||||||
{ archived: false },
|
{ archived: false },
|
||||||
{ type: { $in: ['board', 'template-container'] } },
|
{ type: { $in: ['board', 'template-container'] } },
|
||||||
|
{ $or: [] },
|
||||||
{ title: { $not: { $regex: /^\^.*\^$/ } } }
|
{ title: { $not: { $regex: /^\^.*\^$/ } } }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
const membershipOrs = [];
|
|
||||||
|
|
||||||
let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly');
|
let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly');
|
||||||
|
|
||||||
if (FlowRouter.getRouteName() === 'home') {
|
if (FlowRouter.getRouteName() === 'home') {
|
||||||
membershipOrs.push({ 'members.userId': Meteor.userId() });
|
query.$and[2].$or.push({ 'members.userId': Meteor.userId() });
|
||||||
|
|
||||||
if (allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue) {
|
if (allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue) {
|
||||||
query.$and.push({ 'permission': 'private' });
|
query.$and.push({ 'permission': 'private' });
|
||||||
|
|
@ -258,7 +260,7 @@ BlazeComponent.extendComponent({
|
||||||
// }
|
// }
|
||||||
|
|
||||||
//query.$and[2].$or.push({'orgs': {$elemMatch : {orgId: orgsIds[0]}}});
|
//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() || '';
|
let teamIdsUserBelongs = currUser?.teamIdsUserBelongs() || '';
|
||||||
|
|
@ -268,11 +270,8 @@ BlazeComponent.extendComponent({
|
||||||
// query.$or[2].$or.push({'teams.teamId': teamsIds[i]});
|
// query.$or[2].$or.push({'teams.teamId': teamsIds[i]});
|
||||||
// }
|
// }
|
||||||
//query.$and[2].$or.push({'teams': { $elemMatch : {teamId: teamsIds[0]}}});
|
//query.$and[2].$or.push({'teams': { $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 = {
|
query = {
|
||||||
|
|
@ -546,18 +545,15 @@ BlazeComponent.extendComponent({
|
||||||
const query = {
|
const query = {
|
||||||
$and: [
|
$and: [
|
||||||
{ archived: false },
|
{ archived: false },
|
||||||
{ type: 'board' }
|
{ type: 'board' },
|
||||||
|
{ $or: [] }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
const ors = [];
|
|
||||||
if (selectedTeamsValues.length > 0) {
|
if (selectedTeamsValues.length > 0) {
|
||||||
ors.push({ 'teams.teamId': { $in: selectedTeamsValues } });
|
query.$and[2].$or.push({ 'teams.teamId': { $in: selectedTeamsValues } });
|
||||||
}
|
}
|
||||||
if (selectedOrgsValues.length > 0) {
|
if (selectedOrgsValues.length > 0) {
|
||||||
ors.push({ 'orgs.orgId': { $in: selectedOrgsValues } });
|
query.$and[2].$or.push({ 'orgs.orgId': { $in: selectedOrgsValues } });
|
||||||
}
|
|
||||||
if (ors.length) {
|
|
||||||
query.$and.push({ $or: ors });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let filteredBoards = ReactiveCache.getBoards(query, {});
|
let filteredBoards = ReactiveCache.getBoards(query, {});
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@ template(name="miniboard")
|
||||||
class="minicard-{{colorClass}}")
|
class="minicard-{{colorClass}}")
|
||||||
.minicard-title
|
.minicard-title
|
||||||
.handle
|
.handle
|
||||||
span.drag-handle(title="{{_ 'dragBoard'}}") ↕️
|
.fa.fa-arrows
|
||||||
+viewer
|
+viewer
|
||||||
= title
|
= title
|
||||||
|
|
|
||||||
|
|
@ -336,3 +336,36 @@
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Attachment migration styles */
|
||||||
|
.attachment-item.migrating {
|
||||||
|
position: relative;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-migration-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.migration-spinner {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #007cba;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.migration-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,10 @@ template(name="cardCustomField-number")
|
||||||
template(name="cardCustomField-checkbox")
|
template(name="cardCustomField-checkbox")
|
||||||
.js-checklist-item.checklist-item(class="{{#if data.value }}is-checked{{/if}}")
|
.js-checklist-item.checklist-item(class="{{#if data.value }}is-checked{{/if}}")
|
||||||
if canModifyCard
|
if canModifyCard
|
||||||
span.check-box-unicode {{#if data.value }}✅{{else}}⬜{{/if}}
|
.check-box-container
|
||||||
|
.check-box.materialCheckBox(class="{{#if data.value }}is-checked{{/if}}")
|
||||||
else
|
else
|
||||||
span.check-box-unicode {{#if data.value }}✅{{else}}⬜{{/if}}
|
.materialCheckBox(class="{{#if data.value }}is-checked{{/if}}")
|
||||||
|
|
||||||
template(name="cardCustomField-currency")
|
template(name="cardCustomField-currency")
|
||||||
if canModifyCard
|
if canModifyCard
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,6 @@ CardCustomField.register('cardCustomField');
|
||||||
events() {
|
events() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'click .js-checklist-item .check-box-unicode': this.toggleItem,
|
|
||||||
'click .js-checklist-item .check-box-container': this.toggleItem,
|
'click .js-checklist-item .check-box-container': this.toggleItem,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
float: left;
|
float: left;
|
||||||
height: clamp(24px, 3.5vw, 36px);
|
height: 30px;
|
||||||
width: clamp(24px, 3.5vw, 36px);
|
width: 30px;
|
||||||
margin: .3vh;
|
margin: .3vh;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
@ -118,65 +118,6 @@
|
||||||
transition: flex-basis 0.1s;
|
transition: flex-basis 0.1s;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Desktop mode: position card below board header */
|
|
||||||
body.desktop-mode .card-details:not(.card-details-popup) {
|
|
||||||
position: fixed;
|
|
||||||
width: auto;
|
|
||||||
max-width: 800px;
|
|
||||||
flex-basis: auto;
|
|
||||||
border-radius: 8px;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Default position for first card or when dragged */
|
|
||||||
body.desktop-mode .card-details:not(.card-details-popup):not([style*="left"]):not([style*="top"]) {
|
|
||||||
top: 50px;
|
|
||||||
left: 20px;
|
|
||||||
right: 20px;
|
|
||||||
bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stagger positions for multiple cards using nth-of-type */
|
|
||||||
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(1) {
|
|
||||||
top: 50px;
|
|
||||||
left: 20px;
|
|
||||||
}
|
|
||||||
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(2) {
|
|
||||||
top: 80px;
|
|
||||||
left: 50px;
|
|
||||||
}
|
|
||||||
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(3) {
|
|
||||||
top: 110px;
|
|
||||||
left: 80px;
|
|
||||||
}
|
|
||||||
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(4) {
|
|
||||||
top: 140px;
|
|
||||||
left: 110px;
|
|
||||||
}
|
|
||||||
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(5) {
|
|
||||||
top: 170px;
|
|
||||||
left: 140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For expanded cards, set dimensions */
|
|
||||||
body.desktop-mode .card-details:not(.card-details-popup):not(.card-details-collapsed) {
|
|
||||||
right: 20px;
|
|
||||||
bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Collapsed card state - hide content and set height to title row only */
|
|
||||||
.card-details.card-details-collapsed .card-details-canvas > *:not(.card-details-header) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.card-details.card-details-collapsed {
|
|
||||||
height: auto !important;
|
|
||||||
bottom: auto !important;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
body.desktop-mode .card-details.card-details-collapsed {
|
|
||||||
bottom: auto !important;
|
|
||||||
}
|
|
||||||
.card-details .mCustomScrollBox {
|
.card-details .mCustomScrollBox {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -198,30 +139,6 @@ body.desktop-mode .card-details.card-details-collapsed {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Collapse toggle triangle */
|
|
||||||
.card-details .card-details-header .card-collapse-toggle {
|
|
||||||
float: left;
|
|
||||||
font-size: 20px;
|
|
||||||
padding: 7px 10px;
|
|
||||||
margin-left: -10px;
|
|
||||||
margin-right: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Drag handle */
|
|
||||||
.card-details .card-details-header .card-drag-handle {
|
|
||||||
font-size: 20px;
|
|
||||||
padding: 8px 10px;
|
|
||||||
margin-right: 10px;
|
|
||||||
cursor: move;
|
|
||||||
user-select: none;
|
|
||||||
display: inline-block;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-details .card-details-header .close-card-details,
|
.card-details .card-details-header .close-card-details,
|
||||||
.card-details .card-details-header .maximize-card-details,
|
.card-details .card-details-header .maximize-card-details,
|
||||||
.card-details .card-details-header .minimize-card-details,
|
.card-details .card-details-header .minimize-card-details,
|
||||||
|
|
@ -239,16 +156,11 @@ body.desktop-mode .card-details.card-details-collapsed {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
padding: 5px 10px 5px 10px;
|
padding: 5px 10px 5px 10px;
|
||||||
margin-right: -8px;
|
margin-right: -8px;
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
}
|
||||||
.card-details .card-details-header .close-card-details-mobile-web,
|
.card-details .card-details-header .close-card-details-mobile-web {
|
||||||
.card-details .card-details-header .card-mobile-desktop-toggle {
|
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
margin-right: 5px;
|
margin-right: 40px;
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
}
|
||||||
.card-details .card-details-header .card-copy-button {
|
.card-details .card-details-header .card-copy-button {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
|
|
@ -269,36 +181,6 @@ body.desktop-mode .card-details.card-details-collapsed {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin-right: 30px;
|
margin-right: 30px;
|
||||||
}
|
}
|
||||||
.card-details .card-details-header .card-mobile-desktop-toggle,
|
|
||||||
.card-details .card-details-header .card-zoom-in,
|
|
||||||
.card-details .card-details-header .card-zoom-out {
|
|
||||||
font-size: 24px;
|
|
||||||
padding: 5px 10px 5px 10px;
|
|
||||||
margin-right: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Unify all card text to match title size */
|
|
||||||
.card-details {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
.card-details p,
|
|
||||||
.card-details span,
|
|
||||||
.card-details div,
|
|
||||||
.card-details a,
|
|
||||||
.card-details label,
|
|
||||||
.card-details input,
|
|
||||||
.card-details textarea,
|
|
||||||
.card-details select,
|
|
||||||
.card-details button,
|
|
||||||
.card-details .card-details-item-title,
|
|
||||||
.card-details .card-label,
|
|
||||||
.card-details .viewer {
|
|
||||||
font-size: inherit;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
.card-details .card-details-header .card-details-watch {
|
.card-details .card-details-header .card-details-watch {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
padding-left: 7px;
|
padding-left: 7px;
|
||||||
|
|
@ -402,19 +284,6 @@ body.desktop-mode .card-details.card-details-collapsed {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
resize: both;
|
resize: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Override for mobile mode even on larger screens */
|
|
||||||
body.mobile-mode .card-details {
|
|
||||||
width: 100vw !important;
|
|
||||||
top: 0 !important;
|
|
||||||
left: 0 !important;
|
|
||||||
right: 0 !important;
|
|
||||||
bottom: 0 !important;
|
|
||||||
height: 100vh !important;
|
|
||||||
max-height: 100vh !important;
|
|
||||||
resize: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-details-maximized {
|
.card-details-maximized {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
@ -466,53 +335,19 @@ input[type="submit"].attachment-add-link-submit {
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 800px) {
|
@media screen and (max-width: 800px) {
|
||||||
.card-details {
|
.card-details {
|
||||||
width: 100% !important;
|
width: calc(100% - 1px);
|
||||||
padding: 0px 0px 0px 0px !important;
|
padding: 0px 20px 0px 20px;
|
||||||
margin: 0px !important;
|
margin: 0px;
|
||||||
transition: none;
|
transition: none;
|
||||||
overflow-y: auto;
|
overflow-y: revert;
|
||||||
overflow-x: hidden;
|
overflow-x: revert;
|
||||||
/* iOS Safari specific fixes */
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
position: fixed !important;
|
|
||||||
top: 0 !important;
|
|
||||||
left: 0 !important;
|
|
||||||
right: 0 !important;
|
|
||||||
bottom: 0 !important;
|
|
||||||
z-index: 100 !important;
|
|
||||||
height: 100vh !important;
|
|
||||||
max-height: 100vh !important;
|
|
||||||
border-radius: 0 !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure card details are above everything on mobile */
|
|
||||||
body.mobile-mode .card-details {
|
|
||||||
z-index: 100 !important;
|
|
||||||
width: 100vw !important;
|
|
||||||
left: 0 !important;
|
|
||||||
right: 0 !important;
|
|
||||||
}
|
}
|
||||||
.card-details .card-details-canvas {
|
.card-details .card-details-canvas {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
padding: 0 15px;
|
|
||||||
}
|
}
|
||||||
.card-details .card-details-header .close-card-details {
|
.card-details .card-details-header .close-card-details {
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
.card-details .card-details-header .close-card-details-mobile-web {
|
|
||||||
display: block !important;
|
|
||||||
margin-right: 5px !important;
|
|
||||||
}
|
|
||||||
.card-details .card-details-header .card-mobile-desktop-toggle {
|
|
||||||
display: block !important;
|
|
||||||
margin-right: 5px !important;
|
|
||||||
}
|
|
||||||
.card-details .card-details-header .card-mobile-desktop-toggle {
|
|
||||||
display: block !important;
|
|
||||||
margin-right: 5px !important;
|
|
||||||
}
|
}
|
||||||
.card-details .card-details-header .card-details-menu {
|
.card-details .card-details-header .card-details-menu {
|
||||||
margin-right: 40px;
|
margin-right: 40px;
|
||||||
|
|
@ -538,62 +373,6 @@ input[type="submit"].attachment-add-link-submit {
|
||||||
.pop-over > .content-wrapper > .popup-container-depth-0 .card-details-header {
|
.pop-over > .content-wrapper > .popup-container-depth-0 .card-details-header {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
/* iPhone mobile: enlarge header buttons and increase spacing */
|
|
||||||
body.mobile-mode.iphone-device .card-details .card-details-header {
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
body.mobile-mode.iphone-device .card-details .card-details-header .close-card-details,
|
|
||||||
body.mobile-mode.iphone-device .card-details .card-details-header .maximize-card-details,
|
|
||||||
body.mobile-mode.iphone-device .card-details .card-details-header .minimize-card-details,
|
|
||||||
body.mobile-mode.iphone-device .card-details .card-details-header .card-details-menu-mobile-web,
|
|
||||||
body.mobile-mode.iphone-device .card-details .card-details-header .card-copy-mobile-button,
|
|
||||||
body.mobile-mode.iphone-device .card-details .card-details-header .card-mobile-desktop-toggle,
|
|
||||||
body.mobile-mode.iphone-device .card-details .card-details-header .card-zoom-in,
|
|
||||||
body.mobile-mode.iphone-device .card-details .card-details-header .card-zoom-out {
|
|
||||||
font-size: 2em !important; /* 2x bigger */
|
|
||||||
padding: 0.3em !important;
|
|
||||||
margin-right: 0.75em !important; /* 2x space compared to default */
|
|
||||||
margin-left: 0 !important;
|
|
||||||
}
|
|
||||||
/* Avoid clipping of the close button on the right edge */
|
|
||||||
body.mobile-mode.iphone-device .card-details .card-details-header .close-card-details {
|
|
||||||
margin-right: 0.75em !important;
|
|
||||||
}
|
|
||||||
/* Enlarge the header title too */
|
|
||||||
body.mobile-mode.iphone-device .card-details .card-details-header .card-details-title {
|
|
||||||
font-size: 1.2em !important;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile mode styles - apply when body has mobile-mode class regardless of screen size */
|
|
||||||
body.mobile-mode .card-details {
|
|
||||||
width: 100vw !important;
|
|
||||||
padding: 0px !important;
|
|
||||||
margin: 0px !important;
|
|
||||||
position: fixed !important;
|
|
||||||
top: 0 !important;
|
|
||||||
left: 0 !important;
|
|
||||||
right: 0 !important;
|
|
||||||
bottom: 0 !important;
|
|
||||||
z-index: 100 !important;
|
|
||||||
height: 100vh !important;
|
|
||||||
max-height: 100vh !important;
|
|
||||||
border-radius: 0 !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
overflow-y: auto !important;
|
|
||||||
overflow-x: hidden !important;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mobile-mode .card-details .card-details-canvas {
|
|
||||||
width: 100% !important;
|
|
||||||
padding: 0 15px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mobile-mode .card-details .card-details-header .close-card-details,
|
|
||||||
body.mobile-mode .card-details .card-details-header .close-card-details-mobile-web {
|
|
||||||
display: block !important;
|
|
||||||
}
|
}
|
||||||
.card-details-white {
|
.card-details-white {
|
||||||
background: #fff !important;
|
background: #fff !important;
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,13 @@ template(name="cardDetails")
|
||||||
|
|
||||||
+attachmentViewer
|
+attachmentViewer
|
||||||
|
|
||||||
section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}' class='{{#if cardCollapsed}}card-details-collapsed{{/if}}'): .card-details-canvas
|
section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}'): .card-details-canvas
|
||||||
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
|
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
|
||||||
+inlinedForm(classNames="js-card-details-title")
|
+inlinedForm(classNames="js-card-details-title")
|
||||||
+editCardTitleForm
|
+editCardTitleForm
|
||||||
else
|
else
|
||||||
unless isMiniScreen
|
unless isMiniScreen
|
||||||
unless isPopup
|
unless isPopup
|
||||||
span.card-collapse-toggle.js-card-collapse-toggle(title="{{_ 'collapse-card'}}")
|
|
||||||
if cardCollapsed
|
|
||||||
| ▶
|
|
||||||
else
|
|
||||||
| 🔽
|
|
||||||
a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
|
a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
|
||||||
| ❌
|
| ❌
|
||||||
if canModifyCard
|
if canModifyCard
|
||||||
|
|
@ -31,40 +26,24 @@ template(name="cardDetails")
|
||||||
| ☰
|
| ☰
|
||||||
a.card-copy-button.js-copy-link(
|
a.card-copy-button.js-copy-link(
|
||||||
id="cardURL_copy"
|
id="cardURL_copy"
|
||||||
|
class="fa-link"
|
||||||
title="{{_ 'copy-card-link-to-clipboard'}}"
|
title="{{_ 'copy-card-link-to-clipboard'}}"
|
||||||
href="{{ originRelativeUrl }}"
|
href="{{ originRelativeUrl }}"
|
||||||
)
|
)
|
||||||
span.emoji-icon 🔗
|
|
||||||
span.card-drag-handle.js-card-drag-handle(title="Drag card")
|
|
||||||
| ↕️
|
|
||||||
span.copied-tooltip {{_ 'copied'}}
|
span.copied-tooltip {{_ 'copied'}}
|
||||||
else
|
else
|
||||||
a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
|
unless isPopup
|
||||||
| ❌
|
a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
|
||||||
a.card-zoom-out.js-card-zoom-out(title="{{_ 'zoom-out'}}")
|
| ❌
|
||||||
| 🔍➖
|
|
||||||
a.card-zoom-in.js-card-zoom-in(title="{{_ 'zoom-in'}}")
|
|
||||||
| 🔍➕
|
|
||||||
a.card-mobile-desktop-toggle.js-card-mobile-desktop-toggle(title="{{_ 'mobile-desktop-toggle'}}")
|
|
||||||
if mobileMode
|
|
||||||
| 🖥️
|
|
||||||
else
|
|
||||||
| 📱
|
|
||||||
if canModifyCard
|
if canModifyCard
|
||||||
if cardMaximized
|
|
||||||
a.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
|
|
||||||
| 🔽
|
|
||||||
else
|
|
||||||
a.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
|
|
||||||
| 🔼
|
|
||||||
a.card-details-menu-mobile-web.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
|
a.card-details-menu-mobile-web.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
|
||||||
| ☰
|
| ☰
|
||||||
a.card-copy-mobile-button.js-copy-link(
|
a.card-copy-mobile-button.js-copy-link(
|
||||||
id="cardURL_copy"
|
id="cardURL_copy"
|
||||||
|
class="fa-link"
|
||||||
title="{{_ 'copy-card-link-to-clipboard'}}"
|
title="{{_ 'copy-card-link-to-clipboard'}}"
|
||||||
href="{{ originRelativeUrl }}"
|
href="{{ originRelativeUrl }}"
|
||||||
)
|
)
|
||||||
span.emoji-icon 🔗
|
|
||||||
span.copied-tooltip {{_ 'copied'}}
|
span.copied-tooltip {{_ 'copied'}}
|
||||||
h2.card-details-title.js-card-title(
|
h2.card-details-title.js-card-title(
|
||||||
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
|
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
|
||||||
|
|
@ -325,7 +304,7 @@ template(name="cardDetails")
|
||||||
hr
|
hr
|
||||||
.card-details-item.card-details-item-customfield
|
.card-details-item.card-details-item-customfield
|
||||||
h3.card-details-item-title
|
h3.card-details-item-title
|
||||||
| 📋
|
| 📋-alt
|
||||||
= definition.name
|
= definition.name
|
||||||
+cardCustomField
|
+cardCustomField
|
||||||
|
|
||||||
|
|
@ -699,7 +678,7 @@ template(name="cardDetailsActionsPopup")
|
||||||
| 👁️
|
| 👁️
|
||||||
| {{_ 'unwatch'}}
|
| {{_ 'unwatch'}}
|
||||||
else
|
else
|
||||||
| 👁️
|
| 👁️-slash
|
||||||
| {{_ 'watch'}}
|
| {{_ 'watch'}}
|
||||||
hr
|
hr
|
||||||
if canModifyCard
|
if canModifyCard
|
||||||
|
|
@ -719,7 +698,7 @@ template(name="cardDetailsActionsPopup")
|
||||||
if currentUser.isBoardAdmin
|
if currentUser.isBoardAdmin
|
||||||
li
|
li
|
||||||
a.js-custom-fields
|
a.js-custom-fields
|
||||||
| 📋
|
| 📋-alt
|
||||||
| {{_ 'card-edit-custom-fields'}}
|
| {{_ 'card-edit-custom-fields'}}
|
||||||
//li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
|
//li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
|
||||||
//li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
|
//li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
|
||||||
|
|
@ -739,7 +718,7 @@ template(name="cardDetailsActionsPopup")
|
||||||
| 👁️
|
| 👁️
|
||||||
| {{_ 'hide-list-on-minicard'}}
|
| {{_ 'hide-list-on-minicard'}}
|
||||||
else
|
else
|
||||||
| 👁️
|
| 👁️-slash
|
||||||
| {{_ 'show-list-on-minicard'}}
|
| {{_ 'show-list-on-minicard'}}
|
||||||
hr
|
hr
|
||||||
ul.pop-over-list
|
ul.pop-over-list
|
||||||
|
|
@ -788,7 +767,7 @@ template(name="cardDetailsActionsPopup")
|
||||||
ul.pop-over-list
|
ul.pop-over-list
|
||||||
li
|
li
|
||||||
a.js-more
|
a.js-more
|
||||||
span.emoji-icon 🔗
|
| 🔗
|
||||||
| {{_ 'cardMorePopup-title'}}
|
| {{_ 'cardMorePopup-title'}}
|
||||||
|
|
||||||
template(name="exportCardPopup")
|
template(name="exportCardPopup")
|
||||||
|
|
@ -824,29 +803,17 @@ template(name="copyAndMoveCard")
|
||||||
label {{_ 'boards'}}:
|
label {{_ 'boards'}}:
|
||||||
select.js-select-boards(autofocus)
|
select.js-select-boards(autofocus)
|
||||||
each boards
|
each boards
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{title}}
|
||||||
|
|
||||||
label {{_ 'swimlanes'}}:
|
label {{_ 'swimlanes'}}:
|
||||||
select.js-select-swimlanes
|
select.js-select-swimlanes
|
||||||
each swimlanes
|
each swimlanes
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{title}}
|
||||||
|
|
||||||
label {{_ 'lists'}}:
|
label {{_ 'lists'}}:
|
||||||
select.js-select-lists
|
select.js-select-lists
|
||||||
each lists
|
each lists
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{title}}
|
||||||
|
|
||||||
label {{_ 'cards'}}:
|
|
||||||
select.js-select-cards
|
|
||||||
each cards
|
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
div
|
|
||||||
input(type="radio" name="position" value="above" checked id="position-above" style="display: inline")
|
|
||||||
label(for="position-above") {{_ 'above-selected-card'}}
|
|
||||||
div
|
|
||||||
input(type="radio" name="position" value="below" id="position-below" style="display: inline")
|
|
||||||
label(for="position-below") {{_ 'below-selected-card'}}
|
|
||||||
|
|
||||||
.edit-controls.clearfix
|
.edit-controls.clearfix
|
||||||
button.primary.confirm.js-done {{_ 'done'}}
|
button.primary.confirm.js-done {{_ 'done'}}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import CardComments from '/models/cardComments';
|
||||||
import { ALLOWED_COLORS } from '/config/const';
|
import { ALLOWED_COLORS } from '/config/const';
|
||||||
import { UserAvatar } from '../users/userAvatar';
|
import { UserAvatar } from '../users/userAvatar';
|
||||||
import { DialogWithBoardSwimlaneList } from '/client/lib/dialogWithBoardSwimlaneList';
|
import { DialogWithBoardSwimlaneList } from '/client/lib/dialogWithBoardSwimlaneList';
|
||||||
import { DialogWithBoardSwimlaneListCard } from '/client/lib/dialogWithBoardSwimlaneListCard';
|
|
||||||
import { handleFileUpload } from './attachments';
|
import { handleFileUpload } from './attachments';
|
||||||
import uploadProgressManager from '../../lib/uploadProgressManager';
|
import uploadProgressManager from '../../lib/uploadProgressManager';
|
||||||
|
|
||||||
|
|
@ -64,11 +63,7 @@ BlazeComponent.extendComponent({
|
||||||
const boardBody = this.parentComponent().parentComponent();
|
const boardBody = this.parentComponent().parentComponent();
|
||||||
//in Miniview parent is Board, not BoardBody.
|
//in Miniview parent is Board, not BoardBody.
|
||||||
if (boardBody !== null) {
|
if (boardBody !== null) {
|
||||||
// Only show overlay in mobile mode, not in desktop mode
|
boardBody.showOverlay.set(true);
|
||||||
const isMobile = Utils.getMobileMode();
|
|
||||||
if (isMobile) {
|
|
||||||
boardBody.showOverlay.set(true);
|
|
||||||
}
|
|
||||||
boardBody.mouseHasEnterCardDetails = false;
|
boardBody.mouseHasEnterCardDetails = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +81,6 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
isWatching() {
|
isWatching() {
|
||||||
const card = this.currentData();
|
const card = this.currentData();
|
||||||
if (!card || typeof card.findWatcher !== 'function') return false;
|
|
||||||
return card.findWatcher(Meteor.userId());
|
return card.findWatcher(Meteor.userId());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -99,18 +93,6 @@ BlazeComponent.extendComponent({
|
||||||
return !Utils.getPopupCardId() && ReactiveCache.getCurrentUser().hasCardMaximized();
|
return !Utils.getPopupCardId() && ReactiveCache.getCurrentUser().hasCardMaximized();
|
||||||
},
|
},
|
||||||
|
|
||||||
cardCollapsed() {
|
|
||||||
const user = ReactiveCache.getCurrentUser();
|
|
||||||
if (user && user.profile) {
|
|
||||||
return !!user.profile.cardCollapsed;
|
|
||||||
}
|
|
||||||
if (Users.getPublicCardCollapsed) {
|
|
||||||
const stored = Users.getPublicCardCollapsed();
|
|
||||||
if (typeof stored === 'boolean') return stored;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
presentParentTask() {
|
presentParentTask() {
|
||||||
let result = this.currentBoard.presentParentTask;
|
let result = this.currentBoard.presentParentTask;
|
||||||
if (result === null || result === undefined) {
|
if (result === null || result === undefined) {
|
||||||
|
|
@ -163,9 +145,8 @@ BlazeComponent.extendComponent({
|
||||||
* @return is the list id the current list id ?
|
* @return is the list id the current list id ?
|
||||||
*/
|
*/
|
||||||
isCurrentListId(listId) {
|
isCurrentListId(listId) {
|
||||||
const data = this.data();
|
const ret = this.data().listId == listId;
|
||||||
if (!data || typeof data.listId === 'undefined') return false;
|
return ret;
|
||||||
return data.listId == listId;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onRendered() {
|
onRendered() {
|
||||||
|
|
@ -315,74 +296,8 @@ BlazeComponent.extendComponent({
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
...events,
|
...events,
|
||||||
'click .js-card-collapse-toggle'() {
|
|
||||||
const user = ReactiveCache.getCurrentUser();
|
|
||||||
const currentState = user && user.profile ? !!user.profile.cardCollapsed : !!Users.getPublicCardCollapsed();
|
|
||||||
if (user) {
|
|
||||||
Meteor.call('setCardCollapsed', !currentState);
|
|
||||||
} else if (Users.setPublicCardCollapsed) {
|
|
||||||
Users.setPublicCardCollapsed(!currentState);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'mousedown .js-card-drag-handle'(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const $card = $(event.target).closest('.card-details');
|
|
||||||
const startX = event.clientX;
|
|
||||||
const startY = event.clientY;
|
|
||||||
const startLeft = $card.offset().left;
|
|
||||||
const startTop = $card.offset().top;
|
|
||||||
|
|
||||||
const onMouseMove = (e) => {
|
|
||||||
const deltaX = e.clientX - startX;
|
|
||||||
const deltaY = e.clientY - startY;
|
|
||||||
$card.css({
|
|
||||||
left: startLeft + deltaX + 'px',
|
|
||||||
top: startTop + deltaY + 'px'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMouseUp = () => {
|
|
||||||
$(document).off('mousemove', onMouseMove);
|
|
||||||
$(document).off('mouseup', onMouseUp);
|
|
||||||
};
|
|
||||||
|
|
||||||
$(document).on('mousemove', onMouseMove);
|
|
||||||
$(document).on('mouseup', onMouseUp);
|
|
||||||
},
|
|
||||||
'click .js-close-card-details'() {
|
'click .js-close-card-details'() {
|
||||||
// Get board ID from either the card data or current board in session
|
Utils.goBoardId(this.data().boardId);
|
||||||
const card = this.currentData() || this.data();
|
|
||||||
const boardId = (card && card.boardId) || Utils.getCurrentBoard()._id;
|
|
||||||
const cardId = card && card._id;
|
|
||||||
|
|
||||||
if (boardId) {
|
|
||||||
// In desktop mode, remove from openCards array
|
|
||||||
const isMobile = Utils.getMobileMode();
|
|
||||||
if (!isMobile && cardId) {
|
|
||||||
const openCards = Session.get('openCards') || [];
|
|
||||||
const filtered = openCards.filter(id => id !== cardId);
|
|
||||||
Session.set('openCards', filtered);
|
|
||||||
|
|
||||||
// If this was the current card, clear it
|
|
||||||
if (Session.get('currentCard') === cardId) {
|
|
||||||
Session.set('currentCard', null);
|
|
||||||
}
|
|
||||||
// Don't navigate away in desktop mode - just close the card
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mobile mode: Clear the current card session to close the card
|
|
||||||
Session.set('currentCard', null);
|
|
||||||
|
|
||||||
// Navigate back to board without card
|
|
||||||
const board = ReactiveCache.getBoard(boardId);
|
|
||||||
if (board) {
|
|
||||||
FlowRouter.go('board', {
|
|
||||||
id: board._id,
|
|
||||||
slug: board.slug,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'click .js-copy-link'(event) {
|
'click .js-copy-link'(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
@ -396,34 +311,6 @@ BlazeComponent.extendComponent({
|
||||||
Meteor.call('changeDateFormat', dateFormat);
|
Meteor.call('changeDateFormat', dateFormat);
|
||||||
},
|
},
|
||||||
'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
|
'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
|
||||||
// Mobile: switch to desktop popup view (maximize)
|
|
||||||
'click .js-mobile-switch-to-desktop'(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
// Switch global mode to desktop so the card appears as desktop popup
|
|
||||||
Utils.setMobileMode(false);
|
|
||||||
},
|
|
||||||
'click .js-card-zoom-in'(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const current = Utils.getCardZoom();
|
|
||||||
const newZoom = Math.min(3.0, current + 0.1);
|
|
||||||
Utils.setCardZoom(newZoom);
|
|
||||||
},
|
|
||||||
'click .js-card-zoom-out'(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const current = Utils.getCardZoom();
|
|
||||||
const newZoom = Math.max(0.5, current - 0.1);
|
|
||||||
Utils.setCardZoom(newZoom);
|
|
||||||
},
|
|
||||||
'click .js-card-mobile-desktop-toggle'(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const currentMode = Utils.getMobileMode();
|
|
||||||
Utils.setMobileMode(!currentMode);
|
|
||||||
},
|
|
||||||
'click .js-card-mobile-desktop-toggle'(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const currentMode = Utils.getMobileMode();
|
|
||||||
Utils.setMobileMode(!currentMode);
|
|
||||||
},
|
|
||||||
'submit .js-card-description'(event) {
|
'submit .js-card-description'(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const description = this.currentComponent().getValue();
|
const description = this.currentComponent().getValue();
|
||||||
|
|
@ -796,7 +683,6 @@ Template.editCardSortOrderForm.onRendered(function () {
|
||||||
|
|
||||||
Template.cardDetailsActionsPopup.helpers({
|
Template.cardDetailsActionsPopup.helpers({
|
||||||
isWatching() {
|
isWatching() {
|
||||||
if (!this || typeof this.findWatcher !== 'function') return false;
|
|
||||||
return this.findWatcher(Meteor.userId());
|
return this.findWatcher(Meteor.userId());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -950,42 +836,26 @@ Template.editCardAssignerForm.events({
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Move Card Dialog */
|
/** Move Card Dialog */
|
||||||
(class extends DialogWithBoardSwimlaneListCard {
|
(class extends DialogWithBoardSwimlaneList {
|
||||||
getDialogOptions() {
|
getDialogOptions() {
|
||||||
const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions();
|
const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
setDone(cardId, options) {
|
setDone(boardId, swimlaneId, listId, options) {
|
||||||
ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options);
|
ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options);
|
||||||
const card = this.data();
|
const card = this.data();
|
||||||
let sortIndex = 0;
|
const minOrder = card.getMinSort(listId, swimlaneId);
|
||||||
|
card.move(boardId, swimlaneId, listId, minOrder - 1);
|
||||||
if (cardId) {
|
|
||||||
const targetCard = ReactiveCache.getCard(cardId);
|
|
||||||
if (targetCard) {
|
|
||||||
const position = this.$('input[name="position"]:checked').val();
|
|
||||||
if (position === 'above') {
|
|
||||||
sortIndex = targetCard.sort - 0.5;
|
|
||||||
} else {
|
|
||||||
sortIndex = targetCard.sort + 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If no card selected, move to end
|
|
||||||
sortIndex = card.getMaxSort(options.listId, options.swimlaneId) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
card.move(options.boardId, options.swimlaneId, options.listId, sortIndex);
|
|
||||||
}
|
}
|
||||||
}).register('moveCardPopup');
|
}).register('moveCardPopup');
|
||||||
|
|
||||||
/** Copy Card Dialog */
|
/** Copy Card Dialog */
|
||||||
(class extends DialogWithBoardSwimlaneListCard {
|
(class extends DialogWithBoardSwimlaneList {
|
||||||
getDialogOptions() {
|
getDialogOptions() {
|
||||||
const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions();
|
const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
setDone(cardId, options) {
|
setDone(boardId, swimlaneId, listId, options) {
|
||||||
ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options);
|
ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options);
|
||||||
const card = this.data();
|
const card = this.data();
|
||||||
|
|
||||||
|
|
@ -994,30 +864,8 @@ Template.editCardAssignerForm.events({
|
||||||
const title = textarea.val().trim();
|
const title = textarea.val().trim();
|
||||||
|
|
||||||
if (title) {
|
if (title) {
|
||||||
const newCardId = Meteor.call('copyCard', card._id, options.boardId, options.swimlaneId, options.listId, true, {title: title});
|
// insert new card to the top of new list
|
||||||
|
const newCardId = Meteor.call('copyCard', card._id, boardId, swimlaneId, listId, true, {title: title});
|
||||||
// Position the copied card
|
|
||||||
if (newCardId) {
|
|
||||||
const newCard = ReactiveCache.getCard(newCardId);
|
|
||||||
let sortIndex = 0;
|
|
||||||
|
|
||||||
if (cardId) {
|
|
||||||
const targetCard = ReactiveCache.getCard(cardId);
|
|
||||||
if (targetCard) {
|
|
||||||
const position = this.$('input[name="position"]:checked').val();
|
|
||||||
if (position === 'above') {
|
|
||||||
sortIndex = targetCard.sort - 0.5;
|
|
||||||
} else {
|
|
||||||
sortIndex = targetCard.sort + 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If no card selected, copy to end
|
|
||||||
sortIndex = newCard.getMaxSort(options.listId, options.swimlaneId) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case the filter is active we need to add the newly inserted card in
|
// In case the filter is active we need to add the newly inserted card in
|
||||||
// the list of exceptions -- cards that are not filtered. Otherwise the
|
// the list of exceptions -- cards that are not filtered. Otherwise the
|
||||||
|
|
@ -1029,12 +877,12 @@ Template.editCardAssignerForm.events({
|
||||||
}).register('copyCardPopup');
|
}).register('copyCardPopup');
|
||||||
|
|
||||||
/** Convert Checklist-Item to card dialog */
|
/** Convert Checklist-Item to card dialog */
|
||||||
(class extends DialogWithBoardSwimlaneListCard {
|
(class extends DialogWithBoardSwimlaneList {
|
||||||
getDialogOptions() {
|
getDialogOptions() {
|
||||||
const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions();
|
const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
setDone(cardId, options) {
|
setDone(boardId, swimlaneId, listId, options) {
|
||||||
ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options);
|
ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options);
|
||||||
const card = this.data();
|
const card = this.data();
|
||||||
|
|
||||||
|
|
@ -1044,29 +892,14 @@ Template.editCardAssignerForm.events({
|
||||||
if (title) {
|
if (title) {
|
||||||
const _id = Cards.insert({
|
const _id = Cards.insert({
|
||||||
title: title,
|
title: title,
|
||||||
listId: options.listId,
|
listId: listId,
|
||||||
boardId: options.boardId,
|
boardId: boardId,
|
||||||
swimlaneId: options.swimlaneId,
|
swimlaneId: swimlaneId,
|
||||||
sort: 0,
|
sort: 0,
|
||||||
});
|
});
|
||||||
const newCard = ReactiveCache.getCard(_id);
|
const card = ReactiveCache.getCard(_id);
|
||||||
|
const minOrder = card.getMinSort();
|
||||||
let sortIndex = 0;
|
card.move(card.boardId, card.swimlaneId, card.listId, minOrder - 1);
|
||||||
if (cardId) {
|
|
||||||
const targetCard = ReactiveCache.getCard(cardId);
|
|
||||||
if (targetCard) {
|
|
||||||
const position = this.$('input[name="position"]:checked').val();
|
|
||||||
if (position === 'above') {
|
|
||||||
sortIndex = targetCard.sort - 0.5;
|
|
||||||
} else {
|
|
||||||
sortIndex = targetCard.sort + 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sortIndex = newCard.getMaxSort(options.listId, options.swimlaneId) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex);
|
|
||||||
|
|
||||||
Filter.addException(_id);
|
Filter.addException(_id);
|
||||||
}
|
}
|
||||||
|
|
@ -1074,12 +907,12 @@ Template.editCardAssignerForm.events({
|
||||||
}).register('convertChecklistItemToCardPopup');
|
}).register('convertChecklistItemToCardPopup');
|
||||||
|
|
||||||
/** Copy many cards dialog */
|
/** Copy many cards dialog */
|
||||||
(class extends DialogWithBoardSwimlaneListCard {
|
(class extends DialogWithBoardSwimlaneList {
|
||||||
getDialogOptions() {
|
getDialogOptions() {
|
||||||
const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions();
|
const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
setDone(cardId, options) {
|
setDone(boardId, swimlaneId, listId, options) {
|
||||||
ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options);
|
ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options);
|
||||||
const card = this.data();
|
const card = this.data();
|
||||||
|
|
||||||
|
|
@ -1089,29 +922,7 @@ Template.editCardAssignerForm.events({
|
||||||
if (title) {
|
if (title) {
|
||||||
const titleList = JSON.parse(title);
|
const titleList = JSON.parse(title);
|
||||||
for (const obj of titleList) {
|
for (const obj of titleList) {
|
||||||
const newCardId = Meteor.call('copyCard', card._id, options.boardId, options.swimlaneId, options.listId, false, {title: obj.title, description: obj.description});
|
const newCardId = Meteor.call('copyCard', card._id, boardId, swimlaneId, listId, false, {title: obj.title, description: obj.description});
|
||||||
|
|
||||||
// Position the copied card
|
|
||||||
if (newCardId) {
|
|
||||||
const newCard = ReactiveCache.getCard(newCardId);
|
|
||||||
let sortIndex = 0;
|
|
||||||
|
|
||||||
if (cardId) {
|
|
||||||
const targetCard = ReactiveCache.getCard(cardId);
|
|
||||||
if (targetCard) {
|
|
||||||
const position = this.$('input[name="position"]:checked').val();
|
|
||||||
if (position === 'above') {
|
|
||||||
sortIndex = targetCard.sort - 0.5;
|
|
||||||
} else {
|
|
||||||
sortIndex = targetCard.sort + 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sortIndex = newCard.getMaxSort(options.listId, options.swimlaneId) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case the filter is active we need to add the newly inserted card in
|
// In case the filter is active we need to add the newly inserted card in
|
||||||
// the list of exceptions -- cards that are not filtered. Otherwise the
|
// the list of exceptions -- cards that are not filtered. Otherwise the
|
||||||
|
|
@ -1161,51 +972,6 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
}).register('setCardColorPopup');
|
}).register('setCardColorPopup');
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
|
||||||
onCreated() {
|
|
||||||
this.currentColor = new ReactiveVar(null);
|
|
||||||
},
|
|
||||||
|
|
||||||
colors() {
|
|
||||||
return ALLOWED_COLORS.map((color) => ({ color, name: '' }));
|
|
||||||
},
|
|
||||||
|
|
||||||
isSelected(color) {
|
|
||||||
return this.currentColor.get() === color;
|
|
||||||
},
|
|
||||||
|
|
||||||
events() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'click .js-palette-color'(event) {
|
|
||||||
// Extract color from class name like "card-details-red"
|
|
||||||
const classes = $(event.currentTarget).attr('class').split(' ');
|
|
||||||
const colorClass = classes.find(cls => cls.startsWith('card-details-'));
|
|
||||||
const color = colorClass ? colorClass.replace('card-details-', '') : null;
|
|
||||||
this.currentColor.set(color);
|
|
||||||
},
|
|
||||||
'click .js-submit'(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const color = this.currentColor.get();
|
|
||||||
// Use MultiSelection to get selected cards and set color on each
|
|
||||||
ReactiveCache.getCards(MultiSelection.getMongoSelector()).forEach(card => {
|
|
||||||
card.setColor(color);
|
|
||||||
});
|
|
||||||
Popup.back();
|
|
||||||
},
|
|
||||||
'click .js-remove-color'(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
// Use MultiSelection to get selected cards and remove color from each
|
|
||||||
ReactiveCache.getCards(MultiSelection.getMongoSelector()).forEach(card => {
|
|
||||||
card.setColor(null);
|
|
||||||
});
|
|
||||||
Popup.back();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
}).register('setSelectionColorPopup');
|
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
BlazeComponent.extendComponent({
|
||||||
onCreated() {
|
onCreated() {
|
||||||
this.currentCard = this.currentData();
|
this.currentCard = this.currentData();
|
||||||
|
|
|
||||||
|
|
@ -37,23 +37,14 @@ textarea.js-edit-checklist-item {
|
||||||
.checklist-progress-bar-container .checklist-progress-bar {
|
.checklist-progress-bar-container .checklist-progress-bar {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
background-color: #d6ebff !important;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
}
|
||||||
.checklist-progress-bar-container .checklist-progress-bar .checklist-progress {
|
.checklist-progress-bar-container .checklist-progress-bar .checklist-progress {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
background-color: #3cb500 !important;
|
background-color: #2196f3 !important;
|
||||||
padding: 0.01em 16px;
|
padding: 0.01em 16px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
/* Grey progress bar when grey icons setting is enabled */
|
|
||||||
body.grey-icons-enabled .checklist-progress-bar-container .checklist-progress-bar {
|
|
||||||
background-color: #d9d9d9;
|
|
||||||
}
|
|
||||||
body.grey-icons-enabled .checklist-progress-bar-container .checklist-progress-bar .checklist-progress {
|
|
||||||
background-color: #7a7a7a !important;
|
|
||||||
}
|
|
||||||
.checklist-title {
|
.checklist-title {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
@ -76,14 +67,14 @@ body.grey-icons-enabled .checklist-progress-bar-container .checklist-progress-ba
|
||||||
.checklist-title .checklist-stat.is-finished {
|
.checklist-title .checklist-stat.is-finished {
|
||||||
color: #3cb500;
|
color: #3cb500;
|
||||||
}
|
}
|
||||||
.checklist-title span.checklist-handle {
|
.checklist-title span.fa.checklist-handle {
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
float: left;
|
float: left;
|
||||||
display: inline-block;
|
}
|
||||||
width: 1.2em;
|
.checklist-title span.fa.checklist-handle.fa-arrows::before {
|
||||||
text-align: center;
|
content: "↕️" !important;
|
||||||
color: #999;
|
font-family: inherit !important;
|
||||||
}
|
}
|
||||||
#card-details-overlay {
|
#card-details-overlay {
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
@ -114,25 +105,6 @@ body.grey-icons-enabled .checklist-progress-bar-container .checklist-progress-ba
|
||||||
height: auto;
|
height: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* iPhone mobile: larger checklist titles and more spacing between items */
|
|
||||||
body.mobile-mode.iphone-device .checklist-title .title {
|
|
||||||
font-size: 1.3em !important;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mobile-mode.iphone-device .checklist-item {
|
|
||||||
margin-top: 12px !important;
|
|
||||||
margin-bottom: 8px !important;
|
|
||||||
padding: 8px 4px !important;
|
|
||||||
min-height: 44px; /* iOS recommended touch target size */
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mobile-mode.iphone-device .checklist-item span.checklistitem-handle {
|
|
||||||
font-size: 1.5em !important;
|
|
||||||
padding-right: 15px !important;
|
|
||||||
width: 1.5em !important;
|
|
||||||
}
|
|
||||||
.checklist-item.is-checked.invisible {
|
.checklist-item.is-checked.invisible {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
|
@ -162,27 +134,6 @@ body.mobile-mode.iphone-device .checklist-item span.checklistitem-handle {
|
||||||
border-bottom: 2px solid #3cb500;
|
border-bottom: 2px solid #3cb500;
|
||||||
border-right: 2px solid #3cb500;
|
border-right: 2px solid #3cb500;
|
||||||
}
|
}
|
||||||
/* Unicode checkbox icons styling */
|
|
||||||
.checklist-item .check-box-unicode,
|
|
||||||
.cardCustomField-checkbox .check-box-unicode {
|
|
||||||
font-size: 1.3em;
|
|
||||||
margin-right: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
/* Grey checkmarks when grey icons setting is enabled */
|
|
||||||
body.grey-icons-enabled .checklist-item .check-box.is-checked {
|
|
||||||
border-bottom: 2px solid #7a7a7a;
|
|
||||||
border-right: 2px solid #7a7a7a;
|
|
||||||
}
|
|
||||||
body.grey-icons-enabled .checklist-item .check-box-unicode,
|
|
||||||
body.grey-icons-enabled .cardCustomField-checkbox .check-box-unicode {
|
|
||||||
filter: grayscale(100%);
|
|
||||||
-webkit-filter: grayscale(100%);
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
.checklist-item .item-title {
|
.checklist-item .item-title {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
@ -197,14 +148,13 @@ body.grey-icons-enabled .cardCustomField-checkbox .check-box-unicode {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
max-width: 420px;
|
max-width: 420px;
|
||||||
}
|
}
|
||||||
.checklist-item span.checklistitem-handle {
|
.checklist-item span.fa.checklistitem-handle {
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
display: inline-block;
|
}
|
||||||
width: 1.2em;
|
.checklist-item span.fa.checklistitem-handle.fa-arrows::before {
|
||||||
text-align: center;
|
content: "↕️" !important;
|
||||||
color: #999;
|
font-family: inherit !important;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
.js-delete-checklist-item,
|
.js-delete-checklist-item,
|
||||||
.js-convert-checklist-item-to-card {
|
.js-convert-checklist-item-to-card {
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,19 @@ template(name="checklists")
|
||||||
else
|
else
|
||||||
a.add-checklist-top.js-open-inlined-form(title="{{_ 'add-checklist'}}")
|
a.add-checklist-top.js-open-inlined-form(title="{{_ 'add-checklist'}}")
|
||||||
| ➕
|
| ➕
|
||||||
|
if currentUser.isBoardMember
|
||||||
|
.material-toggle-switch(title="{{_ 'hide-finished-checklist'}}")
|
||||||
|
//span.toggle-switch-title
|
||||||
|
if card.hideFinishedChecklistIfItemsAreHidden
|
||||||
|
input.toggle-switch(type="checkbox" id="toggleHideFinishedChecklist" checked="checked")
|
||||||
|
else
|
||||||
|
input.toggle-switch(type="checkbox" id="toggleHideFinishedChecklist")
|
||||||
|
label.toggle-label(for="toggleHideFinishedChecklist")
|
||||||
|
|
||||||
.card-checklist-items
|
.card-checklist-items
|
||||||
each checklist in checklists
|
each checklist in checklists
|
||||||
+checklistDetail(checklist = checklist card = card)
|
if checklist.showChecklist card.hideFinishedChecklistIfItemsAreHidden
|
||||||
|
+checklistDetail(checklist = checklist card = card)
|
||||||
|
|
||||||
if canModifyCard
|
if canModifyCard
|
||||||
+inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId)
|
+inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId)
|
||||||
|
|
@ -29,12 +38,12 @@ template(name="checklistDetail")
|
||||||
.checklist-title
|
.checklist-title
|
||||||
span
|
span
|
||||||
if canModifyCard
|
if canModifyCard
|
||||||
a.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}") ☰
|
a.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}")
|
||||||
|
|
||||||
if canModifyCard
|
if canModifyCard
|
||||||
h4.title.js-open-inlined-form.is-editable
|
h4.title.js-open-inlined-form.is-editable
|
||||||
if isTouchScreenOrShowDesktopDragHandles
|
if isTouchScreenOrShowDesktopDragHandles
|
||||||
span.checklist-handle(title="{{_ 'dragChecklist'}}") ↕️
|
span.fa.checklist-handle(class="fa-arrows" title="{{_ 'dragChecklist'}}")
|
||||||
+viewer
|
+viewer
|
||||||
= checklist.title
|
= checklist.title
|
||||||
else
|
else
|
||||||
|
|
@ -53,10 +62,6 @@ template(name="checklistDeletePopup")
|
||||||
p {{_ 'confirm-checklist-delete-popup'}}
|
p {{_ 'confirm-checklist-delete-popup'}}
|
||||||
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
|
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
|
||||||
|
|
||||||
template(name="checklistItemDeletePopup")
|
|
||||||
p {{_ 'confirm-checklist-delete-popup'}}
|
|
||||||
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
|
|
||||||
|
|
||||||
template(name="addChecklistItemForm")
|
template(name="addChecklistItemForm")
|
||||||
a(title="{{_ 'copy-text-to-clipboard'}}")
|
a(title="{{_ 'copy-text-to-clipboard'}}")
|
||||||
span.copied-tooltip {{_ 'copied'}}
|
span.copied-tooltip {{_ 'copied'}}
|
||||||
|
|
@ -90,13 +95,10 @@ template(name="editChecklistItemForm")
|
||||||
| ❌
|
| ❌
|
||||||
span(title=createdAt) {{ moment createdAt }}
|
span(title=createdAt) {{ moment createdAt }}
|
||||||
if canModifyCard
|
if canModifyCard
|
||||||
if $eq type 'item'
|
a.js-delete-checklist-item {{_ "delete"}}...
|
||||||
a.js-delete-checklist-item {{_ "delete"}}...
|
a.js-convert-checklist-item-to-card
|
||||||
a.js-convert-checklist-item-to-card
|
| 📋
|
||||||
| 📋
|
| {{_ 'convertChecklistItemToCardPopup-title'}}
|
||||||
| {{_ 'convertChecklistItemToCardPopup-title'}}
|
|
||||||
else
|
|
||||||
a.js-delete-checklist {{_ "delete"}}...
|
|
||||||
|
|
||||||
template(name="checklistItems")
|
template(name="checklistItems")
|
||||||
if checklist.items.length
|
if checklist.items.length
|
||||||
|
|
@ -123,13 +125,14 @@ template(name='checklistItemDetail')
|
||||||
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.hideCheckedChecklistItems}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}"
|
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.hideCheckedChecklistItems}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}"
|
||||||
role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0")
|
role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0")
|
||||||
if canModifyCard
|
if canModifyCard
|
||||||
span.check-box-unicode {{#if item.isFinished }}✅{{else}}⬜{{/if}}
|
.check-box-container
|
||||||
span.checklistitem-handle(title="{{_ 'dragChecklistItem'}}") ↕️
|
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
|
||||||
|
span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}")
|
||||||
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
|
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
|
||||||
+viewer
|
+viewer
|
||||||
= item.title
|
= item.title
|
||||||
else
|
else
|
||||||
span.check-box-unicode {{#if item.isFinished }}✅{{else}}⬜{{/if}}
|
.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
|
||||||
.item-title(class="{{#if item.isFinished }}is-checked{{/if}}")
|
.item-title(class="{{#if item.isFinished }}is-checked{{/if}}")
|
||||||
+viewer
|
+viewer
|
||||||
= item.title
|
= item.title
|
||||||
|
|
@ -164,62 +167,34 @@ template(name="checklistActionsPopup")
|
||||||
else
|
else
|
||||||
input.toggle-switch(type="checkbox" id="toggleHideAllChecklistItems_{{checklist._id}}")
|
input.toggle-switch(type="checkbox" id="toggleHideAllChecklistItems_{{checklist._id}}")
|
||||||
label.toggle-label(for="toggleHideAllChecklistItems_{{checklist._id}}")
|
label.toggle-label(for="toggleHideAllChecklistItems_{{checklist._id}}")
|
||||||
a.js-toggle-show-checklist-at-minicard
|
|
||||||
| 📋
|
|
||||||
| {{_ "showChecklistAtMinicard"}} ...
|
|
||||||
.material-toggle-switch(title="{{_ 'showChecklistAtMinicard'}}")
|
|
||||||
if checklist.showChecklistAtMinicard
|
|
||||||
input.toggle-switch(type="checkbox" id="toggleShowChecklistAtMinicard_{{checklist._id}}" checked="checked")
|
|
||||||
else
|
|
||||||
input.toggle-switch(type="checkbox" id="toggleShowChecklistAtMinicard_{{checklist._id}}")
|
|
||||||
label.toggle-label(for="toggleShowChecklistAtMinicard_{{checklist._id}}")
|
|
||||||
|
|
||||||
template(name="copyChecklistPopup")
|
template(name="copyChecklistPopup")
|
||||||
unless currentUser.isWorker
|
+copyAndMoveChecklist
|
||||||
label {{_ 'boards'}}:
|
|
||||||
select.js-select-boards(autofocus)
|
|
||||||
each boards
|
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
label {{_ 'swimlanes'}}:
|
|
||||||
select.js-select-swimlanes
|
|
||||||
each swimlanes
|
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
label {{_ 'lists'}}:
|
|
||||||
select.js-select-lists
|
|
||||||
each lists
|
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
label {{_ 'card'}}:
|
|
||||||
select.js-select-cards
|
|
||||||
each cards
|
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
.edit-controls.clearfix
|
|
||||||
button.primary.confirm.js-done {{_ 'done'}}
|
|
||||||
|
|
||||||
template(name="moveChecklistPopup")
|
template(name="moveChecklistPopup")
|
||||||
|
+copyAndMoveChecklist
|
||||||
|
|
||||||
|
template(name="copyAndMoveChecklist")
|
||||||
unless currentUser.isWorker
|
unless currentUser.isWorker
|
||||||
label {{_ 'boards'}}:
|
label {{_ 'boards'}}:
|
||||||
select.js-select-boards(autofocus)
|
select.js-select-boards(autofocus)
|
||||||
each boards
|
each boards
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{title}}
|
||||||
|
|
||||||
label {{_ 'swimlanes'}}:
|
label {{_ 'swimlanes'}}:
|
||||||
select.js-select-swimlanes
|
select.js-select-swimlanes
|
||||||
each swimlanes
|
each swimlanes
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{title}}
|
||||||
|
|
||||||
label {{_ 'lists'}}:
|
label {{_ 'lists'}}:
|
||||||
select.js-select-lists
|
select.js-select-lists
|
||||||
each lists
|
each lists
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{title}}
|
||||||
|
|
||||||
label {{_ 'card'}}:
|
label {{_ 'cards'}}:
|
||||||
select.js-select-cards
|
select.js-select-cards
|
||||||
each cards
|
each cards
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{title}}
|
||||||
|
|
||||||
.edit-controls.clearfix
|
.edit-controls.clearfix
|
||||||
button.primary.confirm.js-done {{_ 'done'}}
|
button.primary.confirm.js-done {{_ 'done'}}
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ BlazeComponent.extendComponent({
|
||||||
$(self.itemsDom).sortable('option', 'disabled', !userIsMember());
|
$(self.itemsDom).sortable('option', 'disabled', !userIsMember());
|
||||||
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
||||||
$(self.itemsDom).sortable({
|
$(self.itemsDom).sortable({
|
||||||
handle: 'span.checklistitem-handle',
|
handle: 'span.fa.checklistitem-handle',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -157,6 +157,14 @@ BlazeComponent.extendComponent({
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
deleteItem() {
|
||||||
|
const checklist = this.currentData().checklist;
|
||||||
|
const item = this.currentData().item;
|
||||||
|
if (checklist && item && item._id) {
|
||||||
|
ChecklistItems.remove(item._id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
editChecklist(event) {
|
editChecklist(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const textarea = this.find('textarea.js-edit-checklist-item');
|
const textarea = this.find('textarea.js-edit-checklist-item');
|
||||||
|
|
@ -208,28 +216,14 @@ BlazeComponent.extendComponent({
|
||||||
'submit .js-add-checklist-item': this.addChecklistItem,
|
'submit .js-add-checklist-item': this.addChecklistItem,
|
||||||
'submit .js-edit-checklist-item': this.editChecklistItem,
|
'submit .js-edit-checklist-item': this.editChecklistItem,
|
||||||
'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'),
|
'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'),
|
||||||
'click .js-delete-checklist-item'(event) {
|
'click .js-delete-checklist-item': this.deleteItem,
|
||||||
const item = this.currentData().item;
|
|
||||||
const confirmFunc = Popup.afterConfirm('checklistItemDelete', function () {
|
|
||||||
if (item && item._id) {
|
|
||||||
ChecklistItems.remove(item._id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
confirmFunc.call(this, event);
|
|
||||||
},
|
|
||||||
'click .js-delete-checklist'(event) {
|
|
||||||
const checklist = this.currentData().checklist;
|
|
||||||
const confirmFunc = Popup.afterConfirm('checklistDelete', function () {
|
|
||||||
Popup.back(2);
|
|
||||||
if (checklist && checklist._id) {
|
|
||||||
Checklists.remove(checklist._id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
confirmFunc.call(this, event);
|
|
||||||
},
|
|
||||||
'focus .js-add-checklist-item': this.focusChecklistItem,
|
'focus .js-add-checklist-item': this.focusChecklistItem,
|
||||||
// add and delete checklist / checklist-item
|
// add and delete checklist / checklist-item
|
||||||
'click .js-open-inlined-form': this.closeAllInlinedForms,
|
'click .js-open-inlined-form': this.closeAllInlinedForms,
|
||||||
|
'click #toggleHideFinishedChecklist'(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.data().card.toggleHideFinishedChecklist();
|
||||||
|
},
|
||||||
keydown: this.pressKey,
|
keydown: this.pressKey,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -281,8 +275,8 @@ BlazeComponent.extendComponent({
|
||||||
Template.checklists.helpers({
|
Template.checklists.helpers({
|
||||||
checklists() {
|
checklists() {
|
||||||
const card = ReactiveCache.getCard(this.cardId);
|
const card = ReactiveCache.getCard(this.cardId);
|
||||||
if (!card || typeof card.checklists !== 'function') return [];
|
const ret = card.checklists();
|
||||||
return card.checklists();
|
return ret;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -309,16 +303,13 @@ BlazeComponent.extendComponent({
|
||||||
events() {
|
events() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'click .js-delete-checklist'(event) {
|
'click .js-delete-checklist': Popup.afterConfirm('checklistDelete', function () {
|
||||||
const checklist = this.data().checklist;
|
Popup.back(2);
|
||||||
const confirmFunc = Popup.afterConfirm('checklistDelete', function () {
|
const checklist = this.checklist;
|
||||||
Popup.back(2);
|
if (checklist && checklist._id) {
|
||||||
if (checklist && checklist._id) {
|
Checklists.remove(checklist._id);
|
||||||
Checklists.remove(checklist._id);
|
}
|
||||||
}
|
}),
|
||||||
});
|
|
||||||
confirmFunc.call(this, event);
|
|
||||||
},
|
|
||||||
'click .js-move-checklist': Popup.open('moveChecklist'),
|
'click .js-move-checklist': Popup.open('moveChecklist'),
|
||||||
'click .js-copy-checklist': Popup.open('copyChecklist'),
|
'click .js-copy-checklist': Popup.open('copyChecklist'),
|
||||||
'click .js-hide-checked-checklist-items'(event) {
|
'click .js-hide-checked-checklist-items'(event) {
|
||||||
|
|
@ -331,12 +322,6 @@ BlazeComponent.extendComponent({
|
||||||
this.data().checklist.toggleHideAllChecklistItems();
|
this.data().checklist.toggleHideAllChecklistItems();
|
||||||
Popup.back();
|
Popup.back();
|
||||||
},
|
},
|
||||||
'click .js-toggle-show-checklist-at-minicard'(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const checklist = this.data().checklist;
|
|
||||||
checklist.toggleShowChecklistAtMinicard();
|
|
||||||
Popup.back();
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -375,7 +360,6 @@ BlazeComponent.extendComponent({
|
||||||
events() {
|
events() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'click .js-checklist-item .check-box-unicode': this.toggleItem,
|
|
||||||
'click .js-checklist-item .check-box-container': this.toggleItem,
|
'click .js-checklist-item .check-box-container': this.toggleItem,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -390,12 +374,7 @@ BlazeComponent.extendComponent({
|
||||||
}
|
}
|
||||||
setDone(cardId, options) {
|
setDone(cardId, options) {
|
||||||
ReactiveCache.getCurrentUser().setMoveChecklistDialogOption(this.currentBoardId, options);
|
ReactiveCache.getCurrentUser().setMoveChecklistDialogOption(this.currentBoardId, options);
|
||||||
const checklist = this.data().checklist;
|
this.data().checklist.move(cardId);
|
||||||
Meteor.call('moveChecklist', checklist._id, cardId, (error) => {
|
|
||||||
if (error) {
|
|
||||||
console.error('Error moving checklist:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}).register('moveChecklistPopup');
|
}).register('moveChecklistPopup');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -223,13 +223,9 @@
|
||||||
.card-label-edit-button:hover {
|
.card-label-edit-button:hover {
|
||||||
background: #dbdbdb;
|
background: #dbdbdb;
|
||||||
}
|
}
|
||||||
ul.edit-labels-pop-over span.label-handle {
|
ul.edit-labels-pop-over span.fa.label-handle {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
display: inline-block;
|
|
||||||
width: 1.2em;
|
|
||||||
text-align: center;
|
|
||||||
color: #999;
|
|
||||||
}
|
}
|
||||||
ul.edit-labels-pop-over span.label-handle + .card-label {
|
ul.edit-labels-pop-over span.fa.label-handle + .card-label {
|
||||||
max-width: 180px;
|
max-width: 180px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ template(name="cardLabelsPopup")
|
||||||
a.card-label-edit-button.js-edit-label
|
a.card-label-edit-button.js-edit-label
|
||||||
| ✏️
|
| ✏️
|
||||||
if isTouchScreenOrShowDesktopDragHandles
|
if isTouchScreenOrShowDesktopDragHandles
|
||||||
span.label-handle(title="{{_ 'dragLabel'}}") ↕️
|
span.fa.label-handle(class="fa-arrows" title="{{_ 'dragLabel'}}")
|
||||||
span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="card-label-{{color}}"
|
span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="card-label-{{color}}"
|
||||||
class="{{# if isLabelSelected ../_id }}active{{/if}}")
|
class="{{# if isLabelSelected ../_id }}active{{/if}}")
|
||||||
+viewer
|
+viewer
|
||||||
|
|
|
||||||
|
|
@ -142,12 +142,9 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.minicard .handle .drag-handle {
|
.minicard .handle .fa-arrows {
|
||||||
font-size: clamp(16px, 3vw, 20px);
|
font-size: clamp(16px, 3vw, 20px);
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
display: inline-block;
|
|
||||||
width: 1.4em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
.minicard .minicard-title .card-number {
|
.minicard .minicard-title .card-number {
|
||||||
color: #b3b3b3;
|
color: #b3b3b3;
|
||||||
|
|
@ -300,6 +297,19 @@
|
||||||
background-color: #1976d2 !important;
|
background-color: #1976d2 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Font Awesome icons in minicard dates */
|
||||||
|
.minicard .card-date i.fa {
|
||||||
|
margin-right: 0.3vw;
|
||||||
|
font-size: 0.9em;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Font Awesome icons in minicard spent time */
|
||||||
|
.minicard .card-time i.fa {
|
||||||
|
margin-right: 0.3vw;
|
||||||
|
font-size: 0.9em;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
.minicard .badges {
|
.minicard .badges {
|
||||||
float: left;
|
float: left;
|
||||||
margin-top: 1vh;
|
margin-top: 1vh;
|
||||||
|
|
@ -731,80 +741,7 @@
|
||||||
gap: 0.3vw;
|
gap: 0.3vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checklist display on minicard */
|
.minicard-list-name i.fa {
|
||||||
.minicard-checklist {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 0.5vh;
|
|
||||||
margin-bottom: 0.5vh;
|
|
||||||
padding: 0.3vh 0.5vw;
|
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
|
||||||
border-radius: 0.3vw;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minicard-checklist .checklist-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 0.3vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minicard-checklist .checklist-title {
|
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
font-weight: bold;
|
opacity: 0.7;
|
||||||
color: #4d4d4d;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minicard-checklist .checklist-menu {
|
|
||||||
font-size: 1.2em;
|
|
||||||
color: #666;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0 0.3vw;
|
|
||||||
border-radius: 0.2vw;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minicard-checklist .checklist-menu:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.minicard-checklist .checklist-item {
|
|
||||||
font-size: 0.75em;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 0.2vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0.3vw;
|
|
||||||
line-height: 1.2;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.2vh 0;
|
|
||||||
border-radius: 0.2vw;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minicard-checklist .checklist-item:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.minicard-checklist .checklist-item.is-checked {
|
|
||||||
text-decoration: line-through;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minicard-checklist .checklist-item .check-box-unicode {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 0.8em;
|
|
||||||
margin-top: 0.1vh;
|
|
||||||
transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.minicard-checklist .checklist-item:hover .check-box-unicode {
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.minicard-checklist .checklist-item .item-title {
|
|
||||||
flex: 1;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,10 @@ template(name="minicard")
|
||||||
.badge
|
.badge
|
||||||
span.badge-icon 📎
|
span.badge-icon 📎
|
||||||
span.badge-text= attachments.length
|
span.badge-text= attachments.length
|
||||||
|
if checklists.length
|
||||||
|
.badge(class="{{#if checklistFinished}}is-finished{{/if}}")
|
||||||
|
span.badge-icon ☑️
|
||||||
|
span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}}
|
||||||
if allSubtasks.count
|
if allSubtasks.count
|
||||||
.badge
|
.badge
|
||||||
span.badge-icon 🌐
|
span.badge-icon 🌐
|
||||||
|
|
@ -177,9 +181,6 @@ template(name="minicard")
|
||||||
.badge
|
.badge
|
||||||
span.badge-icon 🔢
|
span.badge-icon 🔢
|
||||||
span.badge-text.check-list-sort {{ sort }}
|
span.badge-text.check-list-sort {{ sort }}
|
||||||
if shouldShowChecklistAtMinicard
|
|
||||||
each shouldShowChecklistAtMinicard
|
|
||||||
+minicardChecklist(checklist=. card=..)
|
|
||||||
if currentBoard.allowsDescriptionTextOnMinicard
|
if currentBoard.allowsDescriptionTextOnMinicard
|
||||||
if getDescription
|
if getDescription
|
||||||
.minicard-description
|
.minicard-description
|
||||||
|
|
@ -201,12 +202,55 @@ template(name="editCardSortOrderPopup")
|
||||||
.edit-controls.clearfix
|
.edit-controls.clearfix
|
||||||
button.primary.confirm.js-submit-edit-card-sort-popup(type="submit") {{_ 'save'}}
|
button.primary.confirm.js-submit-edit-card-sort-popup(type="submit") {{_ 'save'}}
|
||||||
|
|
||||||
template(name="minicardChecklist")
|
template(name="minicardDetailsActionsPopup")
|
||||||
.minicard-checklist
|
ul.pop-over-list
|
||||||
.checklist-header
|
if canModifyCard
|
||||||
.checklist-title= checklist.title
|
li
|
||||||
if canModifyCard
|
a.js-move-card
|
||||||
a.checklist-menu.js-open-checklist-menu(title="{{_ 'checklistActionsPopup-title'}}") ☰
|
| ➡️
|
||||||
each visibleItems
|
| {{_ 'moveCardPopup-title'}}
|
||||||
+checklistItemDetail(item = . checklist = checklist card = card)
|
li
|
||||||
|
a.js-copy-card
|
||||||
|
| 📋
|
||||||
|
| {{_ 'copyCardPopup-title'}}
|
||||||
|
hr
|
||||||
|
li
|
||||||
|
a.js-archive
|
||||||
|
| ➡️
|
||||||
|
| 📦
|
||||||
|
| {{_ 'archive-card'}}
|
||||||
|
hr
|
||||||
|
li
|
||||||
|
a.js-move-card-to-top
|
||||||
|
| ⬆️
|
||||||
|
| {{_ 'moveCardToTop-title'}}
|
||||||
|
li
|
||||||
|
a.js-move-card-to-bottom
|
||||||
|
| ⬇️
|
||||||
|
| {{_ 'moveCardToBottom-title'}}
|
||||||
|
hr
|
||||||
|
li
|
||||||
|
a.js-add-labels
|
||||||
|
| 🏷️
|
||||||
|
| {{_ 'card-edit-labels'}}
|
||||||
|
li
|
||||||
|
a.js-due-date
|
||||||
|
| 📥
|
||||||
|
| {{_ 'editCardDueDatePopup-title'}}
|
||||||
|
li
|
||||||
|
a.js-set-card-color
|
||||||
|
| 🎨
|
||||||
|
| {{_ 'setCardColorPopup-title'}}
|
||||||
|
li
|
||||||
|
a.js-link
|
||||||
|
| 🔗
|
||||||
|
| {{_ 'link-card'}}
|
||||||
|
li
|
||||||
|
a.js-toggle-watch-card
|
||||||
|
if isWatching
|
||||||
|
| 👁️
|
||||||
|
| {{_ 'unwatch'}}
|
||||||
|
else
|
||||||
|
| 👁️-slash
|
||||||
|
| {{_ 'watch'}}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,13 +91,6 @@ BlazeComponent.extendComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleChecklistItem() {
|
|
||||||
const item = this.currentData();
|
|
||||||
if (item && item._id) {
|
|
||||||
item.toggleItem();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
events() {
|
events() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
@ -115,7 +108,7 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
'click span.badge-icon.fa.fa-sort, click span.badge-text.check-list-sort' : Popup.open("editCardSortOrder"),
|
'click span.badge-icon.fa.fa-sort, click span.badge-text.check-list-sort' : Popup.open("editCardSortOrder"),
|
||||||
'click .minicard-labels' : this.cardLabelsPopup,
|
'click .minicard-labels' : this.cardLabelsPopup,
|
||||||
'click .js-open-minicard-details-menu': Popup.open('cardDetailsActions'),
|
'click .js-open-minicard-details-menu': Popup.open('minicardDetailsActions'),
|
||||||
// Drag and drop file upload handlers
|
// Drag and drop file upload handlers
|
||||||
'dragover .minicard'(event) {
|
'dragover .minicard'(event) {
|
||||||
// Only prevent default for file drags to avoid interfering with sortable
|
// Only prevent default for file drags to avoid interfering with sortable
|
||||||
|
|
@ -177,43 +170,6 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
}).register('minicard');
|
}).register('minicard');
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
|
||||||
template() {
|
|
||||||
return 'minicardChecklist';
|
|
||||||
},
|
|
||||||
|
|
||||||
events() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'click .js-open-checklist-menu'(event) {
|
|
||||||
const data = this.currentData();
|
|
||||||
const checklist = data.checklist || data;
|
|
||||||
const card = data.card || this.data();
|
|
||||||
const context = { currentData: () => ({ checklist, card }) };
|
|
||||||
Popup.open('checklistActions').call(context, event);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
visibleItems() {
|
|
||||||
const checklist = this.currentData().checklist || this.currentData();
|
|
||||||
const items = checklist.items();
|
|
||||||
|
|
||||||
return items.filter(item => {
|
|
||||||
// Hide finished items if hideCheckedChecklistItems is true
|
|
||||||
if (item.isFinished && checklist.hideCheckedChecklistItems) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Hide all items if hideAllChecklistItems is true
|
|
||||||
if (checklist.hideAllChecklistItems) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}).register('minicardChecklist');
|
|
||||||
|
|
||||||
Template.minicard.helpers({
|
Template.minicard.helpers({
|
||||||
hiddenMinicardLabelText() {
|
hiddenMinicardLabelText() {
|
||||||
const currentUser = ReactiveCache.getCurrentUser();
|
const currentUser = ReactiveCache.getCurrentUser();
|
||||||
|
|
@ -253,29 +209,9 @@ Template.minicard.helpers({
|
||||||
// Show list name if either:
|
// Show list name if either:
|
||||||
// 1. Board-wide setting is enabled, OR
|
// 1. Board-wide setting is enabled, OR
|
||||||
// 2. This specific card has the setting enabled
|
// 2. This specific card has the setting enabled
|
||||||
const currentBoard = this.board();
|
const currentBoard = this.currentBoard;
|
||||||
if (!currentBoard) return false;
|
if (!currentBoard) return false;
|
||||||
return currentBoard.allowsShowListsOnMinicard || this.showListOnMinicard;
|
return currentBoard.allowsShowListsOnMinicard || this.showListOnMinicard;
|
||||||
},
|
|
||||||
|
|
||||||
shouldShowChecklistAtMinicard() {
|
|
||||||
// Return checklists that should be shown on minicard
|
|
||||||
const currentBoard = this.board();
|
|
||||||
if (!currentBoard) return [];
|
|
||||||
|
|
||||||
const checklists = this.checklists();
|
|
||||||
const visibleChecklists = [];
|
|
||||||
|
|
||||||
checklists.forEach(checklist => {
|
|
||||||
// Show checklist if either:
|
|
||||||
// 1. Board-wide setting is enabled, OR
|
|
||||||
// 2. This specific checklist has the setting enabled
|
|
||||||
if (currentBoard.allowsChecklistAtMinicard || checklist.showChecklistAtMinicard) {
|
|
||||||
visibleChecklists.push(checklist);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return visibleChecklists;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -306,7 +242,7 @@ BlazeComponent.extendComponent({
|
||||||
}
|
}
|
||||||
}).register('editCardSortOrderPopup');
|
}).register('editCardSortOrderPopup');
|
||||||
|
|
||||||
Template.cardDetailsActionsPopup.events({
|
Template.minicardDetailsActionsPopup.events({
|
||||||
'click .js-due-date': Popup.open('editCardDueDate'),
|
'click .js-due-date': Popup.open('editCardDueDate'),
|
||||||
'click .js-move-card': Popup.open('moveCard'),
|
'click .js-move-card': Popup.open('moveCard'),
|
||||||
'click .js-copy-card': Popup.open('copyCard'),
|
'click .js-copy-card': Popup.open('copyCard'),
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ template(name="resultCard")
|
||||||
= getBoard.title
|
= getBoard.title
|
||||||
else
|
else
|
||||||
.broken-cards-null
|
.broken-cards-null
|
||||||
| {{_ 'no-name'}}
|
| NULL
|
||||||
if getBoard.archived
|
if getBoard.archived
|
||||||
| 📦
|
| 📦
|
||||||
li.result-card-context.result-card-context-separator
|
li.result-card-context.result-card-context-separator
|
||||||
|
|
@ -25,7 +25,7 @@ template(name="resultCard")
|
||||||
= getSwimlane.title
|
= getSwimlane.title
|
||||||
else
|
else
|
||||||
.broken-cards-null
|
.broken-cards-null
|
||||||
| {{_ 'no-name'}}
|
| NULL
|
||||||
if getSwimlane.archived
|
if getSwimlane.archived
|
||||||
| 📦
|
| 📦
|
||||||
li.result-card-context.result-card-context-separator
|
li.result-card-context.result-card-context-separator
|
||||||
|
|
@ -39,6 +39,6 @@ template(name="resultCard")
|
||||||
= getList.title
|
= getList.title
|
||||||
else
|
else
|
||||||
.broken-cards-null
|
.broken-cards-null
|
||||||
| {{_ 'no-name'}}
|
| NULL
|
||||||
if getList.archived
|
if getList.archived
|
||||||
| 📦
|
| 📦
|
||||||
|
|
|
||||||
|
|
@ -87,15 +87,6 @@ textarea.js-edit-subtask-item {
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: -600px;
|
bottom: -600px;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fix for mobile Safari: ensure this doesn't block card interaction */
|
|
||||||
@media screen and (max-width: 800px) {
|
|
||||||
#card-details-overlay {
|
|
||||||
z-index: 15;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.subtasks {
|
.subtasks {
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
|
|
@ -136,25 +127,6 @@ textarea.js-edit-subtask-item {
|
||||||
border-bottom: 2px solid #3cb500;
|
border-bottom: 2px solid #3cb500;
|
||||||
border-right: 2px solid #3cb500;
|
border-right: 2px solid #3cb500;
|
||||||
}
|
}
|
||||||
/* Unicode checkbox icons styling */
|
|
||||||
.subtasks-item .check-box-unicode {
|
|
||||||
font-size: 1.3em;
|
|
||||||
margin-right: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
/* Grey checkmarks when grey icons setting is enabled */
|
|
||||||
body.grey-icons-enabled .subtasks-item .check-box.is-checked {
|
|
||||||
border-bottom: 2px solid #7a7a7a;
|
|
||||||
border-right: 2px solid #7a7a7a;
|
|
||||||
}
|
|
||||||
body.grey-icons-enabled .subtasks-item .check-box-unicode {
|
|
||||||
filter: grayscale(100%);
|
|
||||||
-webkit-filter: grayscale(100%);
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
.subtasks-item .item-title {
|
.subtasks-item .item-title {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
|
|
|
||||||
|
|
@ -74,12 +74,12 @@ template(name="subtasksItems")
|
||||||
template(name='subtaskItemDetail')
|
template(name='subtaskItemDetail')
|
||||||
.js-subtasks-item.subtasks-item
|
.js-subtasks-item.subtasks-item
|
||||||
if canModifyCard
|
if canModifyCard
|
||||||
span.check-box-unicode {{#if item.isFinished }}✅{{else}}⬜{{/if}}
|
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
|
||||||
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
|
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
|
||||||
+viewer
|
+viewer
|
||||||
= item.title
|
= item.title
|
||||||
else
|
else
|
||||||
span.check-box-unicode {{#if item.isFinished }}✅{{else}}⬜{{/if}}
|
.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
|
||||||
.item-title(class="{{#if item.isFinished }}is-checked{{/if}}")
|
.item-title(class="{{#if item.isFinished }}is-checked{{/if}}")
|
||||||
+viewer
|
+viewer
|
||||||
= item.title
|
= item.title
|
||||||
|
|
|
||||||
|
|
@ -104,19 +104,7 @@ BlazeComponent.extendComponent({
|
||||||
}).register('subtasks');
|
}).register('subtasks');
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
BlazeComponent.extendComponent({
|
||||||
toggleItem() {
|
// ...
|
||||||
const item = this.currentData().item;
|
|
||||||
if (item && item._id) {
|
|
||||||
item.toggleItem();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
events() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'click .js-subtasks-item .check-box-unicode': this.toggleItem,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
}).register('subtaskItemDetail');
|
}).register('subtaskItemDetail');
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
BlazeComponent.extendComponent({
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,19 @@
|
||||||
<div class="original-position-info">
|
<div class="original-position-info">
|
||||||
{{#if isLoading}}
|
{{#if isLoading}}
|
||||||
<div class="original-position-loading">
|
<div class="original-position-loading">
|
||||||
⏳ Loading original position...
|
<i class="fa fa-spinner fa-spin"></i> Loading original position...
|
||||||
</div>
|
</div>
|
||||||
{{else if showOriginalPosition}}
|
{{else if showOriginalPosition}}
|
||||||
<div class="original-position-details">
|
<div class="original-position-details">
|
||||||
{{#if hasMovedFromOriginal}}
|
{{#if hasMovedFromOriginal}}
|
||||||
<div class="original-position-moved">
|
<div class="original-position-moved">
|
||||||
<span class="original-position-text">ℹ️ {{getOriginalPositionDescription}}</span>
|
<i class="fa fa-info-circle"></i>
|
||||||
|
<span class="original-position-text">{{getOriginalPositionDescription}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="original-position-unchanged">
|
<div class="original-position-unchanged">
|
||||||
<span class="original-position-text">✅ In original position</span>
|
<i class="fa fa-check-circle"></i>
|
||||||
|
<span class="original-position-text">In original position</span>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -315,18 +315,11 @@ textarea::-moz-placeholder {
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
border-top: 2px solid transparent;
|
border-top: 2px solid transparent;
|
||||||
border-left: 2px solid transparent;
|
border-left: 2px solid transparent;
|
||||||
border-bottom: 2px solid #3cb500;
|
|
||||||
border-right: 2px solid #3cb500;
|
|
||||||
transform: rotate(40deg);
|
transform: rotate(40deg);
|
||||||
-webkit-backface-visibility: hidden;
|
-webkit-backface-visibility: hidden;
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
transform-origin: 100% 100%;
|
transform-origin: 100% 100%;
|
||||||
}
|
}
|
||||||
/* Grey checkmarks when grey icons setting is enabled */
|
|
||||||
body.grey-icons-enabled .materialCheckBox.is-checked {
|
|
||||||
border-bottom: 2px solid #7a7a7a;
|
|
||||||
border-right: 2px solid #7a7a7a;
|
|
||||||
}
|
|
||||||
.button-link {
|
.button-link {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
background: linear-gradient(#fff, #f5f5f5);
|
background: linear-gradient(#fff, #f5f5f5);
|
||||||
|
|
|
||||||
|
|
@ -1,203 +0,0 @@
|
||||||
/* Gantt chart cell background colors for Received, Start, Due, End (matching cardDetails) */
|
|
||||||
.ganttview-received {
|
|
||||||
background-color: #dbdbdb !important;
|
|
||||||
color: #000 !important;
|
|
||||||
font-size: 18px !important;
|
|
||||||
font-weight: bold !important;
|
|
||||||
}
|
|
||||||
.ganttview-start {
|
|
||||||
background-color: #90ee90 !important;
|
|
||||||
color: #000 !important;
|
|
||||||
font-size: 18px !important;
|
|
||||||
font-weight: bold !important;
|
|
||||||
}
|
|
||||||
.ganttview-due {
|
|
||||||
background-color: #ffd700 !important;
|
|
||||||
color: #000 !important;
|
|
||||||
font-size: 18px !important;
|
|
||||||
font-weight: bold !important;
|
|
||||||
}
|
|
||||||
.ganttview-end {
|
|
||||||
background-color: #ffb3b3 !important;
|
|
||||||
color: #000 !important;
|
|
||||||
font-size: 18px !important;
|
|
||||||
font-weight: bold !important;
|
|
||||||
}
|
|
||||||
/* Gantt View Styles */
|
|
||||||
|
|
||||||
.gantt-view {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
overflow: visible;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-view.swimlane {
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container {
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: visible;
|
|
||||||
background-color: #fff;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container table,
|
|
||||||
.gantt-table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
min-width: 800px;
|
|
||||||
border: 2px solid #666;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 13px;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container thead {
|
|
||||||
background-color: #e8e8e8;
|
|
||||||
border-bottom: 2px solid #666;
|
|
||||||
font-weight: bold;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container thead th,
|
|
||||||
.gantt-container thead tr > td:first-child {
|
|
||||||
border-right: 2px solid #666;
|
|
||||||
padding: 4px; /* half of 8px */
|
|
||||||
width: 100px; /* half of 200px */
|
|
||||||
text-align: left;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: #e8e8e8;
|
|
||||||
min-width: 100px; /* half of 200px */
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container thead td {
|
|
||||||
border-right: 1px solid #999;
|
|
||||||
padding: 2px 1px; /* half */
|
|
||||||
text-align: center;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
font-size: 11px;
|
|
||||||
min-width: 15px; /* half of 30px */
|
|
||||||
font-weight: bold;
|
|
||||||
height: auto;
|
|
||||||
line-height: 1.2;
|
|
||||||
white-space: normal;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container tbody tr {
|
|
||||||
border-bottom: 1px solid #999;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container tbody tr:hover {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container tbody tr:hover td {
|
|
||||||
background-color: #f9f9f9 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container tbody td {
|
|
||||||
border-right: 1px solid #ccc;
|
|
||||||
padding: 1px; /* half */
|
|
||||||
text-align: center;
|
|
||||||
min-width: 15px; /* half of 30px */
|
|
||||||
height: 32px;
|
|
||||||
vertical-align: middle;
|
|
||||||
line-height: 28px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container tbody td:nth-child(even) {
|
|
||||||
background-color: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container tbody td:first-child {
|
|
||||||
border-right: 2px solid #666;
|
|
||||||
padding: 4px; /* half of 8px */
|
|
||||||
font-weight: 500;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #fafafa !important;
|
|
||||||
text-align: left;
|
|
||||||
width: 100px; /* half of 200px */
|
|
||||||
min-width: 100px; /* half of 200px */
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
height: auto;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container tbody td:first-child:hover {
|
|
||||||
background-color: #f0f0f0 !important;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.js-gantt-task-cell {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.js-gantt-date-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container .ganttview-weekend {
|
|
||||||
background-color: #efefef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container .ganttview-today {
|
|
||||||
background-color: #fcf8e3;
|
|
||||||
border-right: 2px solid #ffb347;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Task bar styling - VERY VISIBLE */
|
|
||||||
.gantt-container tbody td.ganttview-block {
|
|
||||||
background-color: #4CAF50 !important;
|
|
||||||
color: #fff !important;
|
|
||||||
font-size: 18px !important;
|
|
||||||
font-weight: bold !important;
|
|
||||||
padding: 2px !important;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive adjustments */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.gantt-container table {
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container thead td {
|
|
||||||
min-width: 20px;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container tbody td {
|
|
||||||
min-width: 20px;
|
|
||||||
padding: 1px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container tbody td:first-child {
|
|
||||||
width: 100px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Print styles */
|
|
||||||
@media print {
|
|
||||||
.gantt-container {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-container table {
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
//- Gantt Chart View Template
|
|
||||||
template(name="ganttView")
|
|
||||||
link(rel="stylesheet" href="/client/components/gantt/gantt.css")
|
|
||||||
link(rel="stylesheet" href="/client/components/gantt/ganttCard.css")
|
|
||||||
.gantt-view
|
|
||||||
h2 {{_ 'board-view-gantt'}}
|
|
||||||
if hasSelectedCard
|
|
||||||
+ganttCard(selectedCard)
|
|
||||||
each weeks
|
|
||||||
table.gantt-table
|
|
||||||
thead
|
|
||||||
tr
|
|
||||||
th {{_ 'task'}} {{_ 'predicate-week'}} {{week}}
|
|
||||||
each weekDays this
|
|
||||||
th
|
|
||||||
| {{formattedDate .}} {{weekdayLabel .}}
|
|
||||||
tbody
|
|
||||||
each cardsInWeek this
|
|
||||||
tr(data-card-id="{{cardId .}}")
|
|
||||||
td.js-gantt-task-cell
|
|
||||||
a.js-gantt-card-title(href="#")
|
|
||||||
+viewer
|
|
||||||
| {{cardTitle .}}
|
|
||||||
each weekDays ..
|
|
||||||
td(class="{{cellClasses .. .}}" data-card-id="{{cardId ..}}" data-date-type="{{cellContentClass .. .}}")
|
|
||||||
| {{cellContent .. .}}
|
|
||||||
|
|
||||||
|
|
@ -1,217 +0,0 @@
|
||||||
// Add click handler to ganttView for card titles
|
|
||||||
Template.ganttView.events({
|
|
||||||
'click .js-gantt-card-title'(event, template) {
|
|
||||||
event.preventDefault();
|
|
||||||
// Get card ID from the closest row's data attribute
|
|
||||||
const $row = template.$(event.currentTarget).closest('tr');
|
|
||||||
const cardId = $row.data('card-id');
|
|
||||||
|
|
||||||
if (cardId) {
|
|
||||||
template.selectedCardId.set(cardId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
import { Template } from 'meteor/templating';
|
|
||||||
|
|
||||||
// Blaze template helpers for ganttView
|
|
||||||
function getISOWeekInfo(d) {
|
|
||||||
const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
||||||
const dayNum = date.getUTCDay() || 7;
|
|
||||||
date.setUTCDate(date.getUTCDate() + 4 - dayNum);
|
|
||||||
const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
|
|
||||||
const week = Math.ceil((((date - yearStart) / 86400000) + 1) / 7);
|
|
||||||
return { year: date.getUTCFullYear(), week };
|
|
||||||
}
|
|
||||||
function startOfISOWeek(d) {
|
|
||||||
const date = new Date(d);
|
|
||||||
const day = date.getDay() || 7;
|
|
||||||
if (day !== 1) date.setDate(date.getDate() - (day - 1));
|
|
||||||
date.setHours(0,0,0,0);
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
Template.ganttView.helpers({
|
|
||||||
weeks() {
|
|
||||||
const board = Utils.getCurrentBoard();
|
|
||||||
if (!board) return [];
|
|
||||||
const cards = Cards.find({ boardId: board._id }, { sort: { startAt: 1, dueAt: 1 } }).fetch();
|
|
||||||
const weeksMap = new Map();
|
|
||||||
const relevantCards = cards.filter(c => c.receivedAt || c.startAt || c.dueAt || c.endAt);
|
|
||||||
relevantCards.forEach(card => {
|
|
||||||
['receivedAt','startAt','dueAt','endAt'].forEach(field => {
|
|
||||||
if (card[field]) {
|
|
||||||
const dt = new Date(card[field]);
|
|
||||||
const info = getISOWeekInfo(dt);
|
|
||||||
const key = `${info.year}-W${info.week}`;
|
|
||||||
if (!weeksMap.has(key)) {
|
|
||||||
weeksMap.set(key, { year: info.year, week: info.week, start: startOfISOWeek(dt) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return Array.from(weeksMap.values()).sort((a,b) => a.start - b.start);
|
|
||||||
},
|
|
||||||
weekDays(week) {
|
|
||||||
const weekStart = new Date(week.start);
|
|
||||||
return Array.from({length:7}, (_,i) => {
|
|
||||||
const d = new Date(weekStart);
|
|
||||||
d.setDate(d.getDate() + i);
|
|
||||||
d.setHours(0,0,0,0);
|
|
||||||
return d;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
weekdayLabel(day) {
|
|
||||||
const weekdayKeys = ['monday','tuesday','wednesday','thursday','friday','saturday','sunday'];
|
|
||||||
return TAPi18n.__(weekdayKeys[day.getDay() === 0 ? 6 : day.getDay() - 1]);
|
|
||||||
},
|
|
||||||
formattedDate(day) {
|
|
||||||
const currentUser = ReactiveCache.getCurrentUser && ReactiveCache.getCurrentUser();
|
|
||||||
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
|
|
||||||
return formatDateByUserPreference(day, dateFormat, false);
|
|
||||||
},
|
|
||||||
cardsInWeek(week) {
|
|
||||||
const board = Utils.getCurrentBoard();
|
|
||||||
if (!board) return [];
|
|
||||||
const cards = Cards.find({ boardId: board._id }).fetch();
|
|
||||||
return cards.filter(card => {
|
|
||||||
return ['receivedAt','startAt','dueAt','endAt'].some(field => {
|
|
||||||
if (card[field]) {
|
|
||||||
const dt = new Date(card[field]);
|
|
||||||
const info = getISOWeekInfo(dt);
|
|
||||||
return info.week === week.week && info.year === week.year;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
cardTitle(card) {
|
|
||||||
return card.title;
|
|
||||||
},
|
|
||||||
cardId(card) {
|
|
||||||
return card._id;
|
|
||||||
},
|
|
||||||
cardUrl(card) {
|
|
||||||
if (!card) return '#';
|
|
||||||
const board = ReactiveCache.getBoard(card.boardId);
|
|
||||||
if (!board) return '#';
|
|
||||||
return FlowRouter.path('card', {
|
|
||||||
boardId: card.boardId,
|
|
||||||
slug: board.slug,
|
|
||||||
cardId: card._id,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
cellContentClass(card, day) {
|
|
||||||
const cardDates = {
|
|
||||||
receivedAt: card.receivedAt ? new Date(card.receivedAt) : null,
|
|
||||||
startAt: card.startAt ? new Date(card.startAt) : null,
|
|
||||||
dueAt: card.dueAt ? new Date(card.dueAt) : null,
|
|
||||||
endAt: card.endAt ? new Date(card.endAt) : null,
|
|
||||||
};
|
|
||||||
if (cardDates.receivedAt && cardDates.receivedAt.toDateString() === day.toDateString()) return 'ganttview-received';
|
|
||||||
if (cardDates.startAt && cardDates.startAt.toDateString() === day.toDateString()) return 'ganttview-start';
|
|
||||||
if (cardDates.dueAt && cardDates.dueAt.toDateString() === day.toDateString()) return 'ganttview-due';
|
|
||||||
if (cardDates.endAt && cardDates.endAt.toDateString() === day.toDateString()) return 'ganttview-end';
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
cellContent(card, day) {
|
|
||||||
const cardDates = {
|
|
||||||
receivedAt: card.receivedAt ? new Date(card.receivedAt) : null,
|
|
||||||
startAt: card.startAt ? new Date(card.startAt) : null,
|
|
||||||
dueAt: card.dueAt ? new Date(card.dueAt) : null,
|
|
||||||
endAt: card.endAt ? new Date(card.endAt) : null,
|
|
||||||
};
|
|
||||||
if (cardDates.receivedAt && cardDates.receivedAt.toDateString() === day.toDateString()) return '📥';
|
|
||||||
if (cardDates.startAt && cardDates.startAt.toDateString() === day.toDateString()) return '🚀';
|
|
||||||
if (cardDates.dueAt && cardDates.dueAt.toDateString() === day.toDateString()) return '⏰';
|
|
||||||
if (cardDates.endAt && cardDates.endAt.toDateString() === day.toDateString()) return '🏁';
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
isToday(day) {
|
|
||||||
const today = new Date();
|
|
||||||
return day.toDateString() === today.toDateString();
|
|
||||||
},
|
|
||||||
isWeekend(day) {
|
|
||||||
const idx = day.getDay();
|
|
||||||
return idx === 0 || idx === 6;
|
|
||||||
},
|
|
||||||
hasSelectedCard() {
|
|
||||||
return Template.instance().selectedCardId.get() !== null;
|
|
||||||
},
|
|
||||||
selectedCard() {
|
|
||||||
const cardId = Template.instance().selectedCardId.get();
|
|
||||||
return cardId ? ReactiveCache.getCard(cardId) : null;
|
|
||||||
},
|
|
||||||
cellClasses(card, day) {
|
|
||||||
// Get the base class from cellContentClass logic
|
|
||||||
const cardDates = {
|
|
||||||
receivedAt: card.receivedAt ? new Date(card.receivedAt) : null,
|
|
||||||
startAt: card.startAt ? new Date(card.startAt) : null,
|
|
||||||
dueAt: card.dueAt ? new Date(card.dueAt) : null,
|
|
||||||
endAt: card.endAt ? new Date(card.endAt) : null,
|
|
||||||
};
|
|
||||||
let classes = '';
|
|
||||||
if (cardDates.receivedAt && cardDates.receivedAt.toDateString() === day.toDateString()) classes = 'ganttview-received';
|
|
||||||
else if (cardDates.startAt && cardDates.startAt.toDateString() === day.toDateString()) classes = 'ganttview-start';
|
|
||||||
else if (cardDates.dueAt && cardDates.dueAt.toDateString() === day.toDateString()) classes = 'ganttview-due';
|
|
||||||
else if (cardDates.endAt && cardDates.endAt.toDateString() === day.toDateString()) classes = 'ganttview-end';
|
|
||||||
|
|
||||||
// Add conditional classes
|
|
||||||
const today = new Date();
|
|
||||||
if (day.toDateString() === today.toDateString()) classes += ' ganttview-today';
|
|
||||||
const idx = day.getDay();
|
|
||||||
if (idx === 0 || idx === 6) classes += ' ganttview-weekend';
|
|
||||||
if (classes.trim()) classes += ' js-gantt-date-icon';
|
|
||||||
|
|
||||||
return classes.trim();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.ganttView.onCreated(function() {
|
|
||||||
this.selectedCardId = new ReactiveVar(null);
|
|
||||||
// Provide properties expected by cardDetails component
|
|
||||||
this.showOverlay = new ReactiveVar(false);
|
|
||||||
this.mouseHasEnterCardDetails = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Blaze onRendered logic for ganttView
|
|
||||||
Template.ganttView.onRendered(function() {
|
|
||||||
const self = this;
|
|
||||||
this.autorun(() => {
|
|
||||||
// If you have legacy imperative rendering, keep it here
|
|
||||||
if (typeof renderGanttChart === 'function') {
|
|
||||||
renderGanttChart();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Add click handler for date cells (Received, Start, Due, End)
|
|
||||||
this.$('.gantt-table').on('click', '.js-gantt-date-icon', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
const $cell = self.$(this);
|
|
||||||
const cardId = $cell.data('card-id');
|
|
||||||
let dateType = $cell.data('date-type');
|
|
||||||
// Remove 'ganttview-' prefix to match popup map
|
|
||||||
if (typeof dateType === 'string' && dateType.startsWith('ganttview-')) {
|
|
||||||
dateType = dateType.replace('ganttview-', '');
|
|
||||||
}
|
|
||||||
const popupMap = {
|
|
||||||
received: 'editCardReceivedDate',
|
|
||||||
start: 'editCardStartDate',
|
|
||||||
due: 'editCardDueDate',
|
|
||||||
end: 'editCardEndDate',
|
|
||||||
};
|
|
||||||
const popupName = popupMap[dateType];
|
|
||||||
if (!popupName || typeof Popup === 'undefined' || typeof Popup.open !== 'function') return;
|
|
||||||
const card = ReactiveCache.getCard(cardId);
|
|
||||||
if (!card) return;
|
|
||||||
const openFn = Popup.open(popupName);
|
|
||||||
openFn.call({ currentData: () => card }, e, { dataContextIfCurrentDataIsUndefined: card });
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
import markdownit from 'markdown-it';
|
|
||||||
import { TAPi18n } from '/imports/i18n';
|
|
||||||
import { formatDateByUserPreference } from '/imports/lib/dateUtils';
|
|
||||||
import { ReactiveCache } from '/imports/reactiveCache';
|
|
||||||
|
|
||||||
const md = markdownit({ breaks: true, linkify: true });
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
.gantt-card-wrapper {
|
|
||||||
background: white;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin: 1rem 0;
|
|
||||||
padding: 1rem;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-card-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-card-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #666;
|
|
||||||
padding: 0;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-button:hover {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gantt-card-content {
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
template(name="ganttCard")
|
|
||||||
+cardDetails(selectedCard)
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
BlazeComponent.extendComponent({
|
|
||||||
onCreated() {
|
|
||||||
// Provide the expected parent component properties for cardDetails
|
|
||||||
this.showOverlay = new ReactiveVar(false);
|
|
||||||
this.mouseHasEnterCardDetails = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
selectedCard() {
|
|
||||||
// The selected card is now passed as a parameter to the component
|
|
||||||
return this.currentData();
|
|
||||||
},
|
|
||||||
|
|
||||||
events() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'click .js-close-card-details'(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
// Find the ganttView template instance and clear selectedCardId
|
|
||||||
let view = Blaze.currentView;
|
|
||||||
while (view) {
|
|
||||||
if (view.templateInstance && view.templateInstance().selectedCardId) {
|
|
||||||
view.templateInstance().selectedCardId.set(null);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
view = view.parentView;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
}).register('ganttCard');
|
|
||||||
|
|
||||||
// Add click handler to ganttView for card titles
|
|
||||||
Template.ganttView.events({
|
|
||||||
'click .js-gantt-card-title'(event, template) {
|
|
||||||
event.preventDefault();
|
|
||||||
// Get card ID from the closest row's data attribute
|
|
||||||
const $row = template.$(event.currentTarget).closest('tr');
|
|
||||||
const cardId = $row.data('card-id');
|
|
||||||
|
|
||||||
if (cardId) {
|
|
||||||
template.selectedCardId.set(cardId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
template(name="importHeaderBar")
|
template(name="importHeaderBar")
|
||||||
h1
|
h1
|
||||||
a.back-btn(href="{{pathFor 'home'}}")
|
a.back-btn(href="{{pathFor 'home'}}")
|
||||||
| ⬅️
|
i.fa.fa-chevron-left
|
||||||
| {{_ title}}
|
| {{_ title}}
|
||||||
|
|
||||||
template(name="import")
|
template(name="import")
|
||||||
|
|
@ -36,7 +36,7 @@ template(name="importMapMembers")
|
||||||
+userAvatar(userId=wekanId)
|
+userAvatar(userId=wekanId)
|
||||||
else
|
else
|
||||||
a.member.add-member
|
a.member.add-member
|
||||||
| ➕
|
i.fa.fa-plus
|
||||||
//-
|
//-
|
||||||
Due to the way the flewbox layout is working, we need to set some
|
Due to the way the flewbox layout is working, we need to set some
|
||||||
invisible items so that the last row items have a consistent width.
|
invisible items so that the last row items have a consistent width.
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,7 @@ body.list-resizing-active * {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
.list.list-collapsed .list-header .js-collapse {
|
.list.list-collapsed .list-header .js-collapse {
|
||||||
margin: 0 auto 0 auto;
|
margin: 0 auto 20px auto;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
@ -290,12 +290,6 @@ body.list-resizing-active * {
|
||||||
display: block;
|
display: block;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
.list.list-collapsed .list-header .list-header-handle {
|
|
||||||
position: absolute !important;
|
|
||||||
top: 30px !important;
|
|
||||||
right: 1.5vw !important;
|
|
||||||
z-index: 15 !important;
|
|
||||||
}
|
|
||||||
.list.list-collapsed .list-header .list-rotated {
|
.list.list-collapsed .list-header .list-rotated {
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
|
|
@ -303,6 +297,7 @@ body.list-resizing-active * {
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
|
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
|
@ -313,15 +308,15 @@ body.list-resizing-active * {
|
||||||
color: #333;
|
color: #333;
|
||||||
background-color: rgba(255, 255, 255, 0.95);
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
padding: 0;
|
padding: 8px 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 0;
|
margin: 0 auto;
|
||||||
width: 100vh;
|
width: 25vh;
|
||||||
height: 30px;
|
height: 60vh;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 40px;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(calc(-50% + 20px)) rotate(0deg);
|
transform: translate(calc(-50% + 50px), -50%) rotate(0deg);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
|
|
@ -373,18 +368,6 @@ body.list-resizing-active * {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
/* Sum badge shown before list title */
|
|
||||||
.list-header .list-sum-badge {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 8px;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
background: transparent;
|
|
||||||
color: #8c8c8c;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 12px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.list-rotated {
|
.list-rotated {
|
||||||
width: 1.3vw;
|
width: 1.3vw;
|
||||||
height: 35vh;
|
height: 35vh;
|
||||||
|
|
@ -420,42 +403,22 @@ body.list-resizing-active * {
|
||||||
color: #a6a6a6;
|
color: #a6a6a6;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
/* List header collapse button styling */
|
|
||||||
.list-header .list-header-collapse-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 10px;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-header .js-collapse {
|
.list-header .js-collapse {
|
||||||
color: #a6a6a6;
|
color: #a6a6a6;
|
||||||
|
margin-right: 15px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
padding: 5px 8px;
|
padding: 5px 8px;
|
||||||
border: none;
|
border: 1px solid #ccc;
|
||||||
border-radius: 0;
|
border-radius: 4px;
|
||||||
background-color: transparent;
|
background-color: #f5f5f5;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 18px;
|
font-size: 14px;
|
||||||
line-height: 1;
|
|
||||||
min-width: 30px;
|
|
||||||
text-align: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
text-decoration: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
.list-header .js-collapse:hover {
|
.list-header .js-collapse:hover {
|
||||||
background-color: transparent;
|
background-color: #e0e0e0;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-header .list-header-collapse-container > div {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.list.list-collapsed .list-header .js-collapse {
|
.list.list-collapsed .list-header .js-collapse {
|
||||||
display: inline-block !important;
|
display: inline-block !important;
|
||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
|
|
@ -484,18 +447,17 @@ body.list-resizing-active * {
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
}
|
}
|
||||||
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
|
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
|
||||||
width: 100vh;
|
width: 15vh;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
padding: 0;
|
padding: 8px 4px;
|
||||||
margin: 0;
|
margin: 0 auto;
|
||||||
overflow: visible;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 40px;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(calc(-50% + 120px)) rotate(0deg);
|
transform: translate(calc(-50% + 50px), -50%) rotate(0deg);
|
||||||
text-align: center;
|
text-align: left;
|
||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
display: block !important;
|
display: block !important;
|
||||||
|
|
@ -525,18 +487,17 @@ body.list-resizing-active * {
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
}
|
}
|
||||||
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
|
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
|
||||||
width: 100vh;
|
width: 15vh;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
padding: 0;
|
padding: 8px 4px;
|
||||||
margin: 0;
|
margin: 0 auto;
|
||||||
overflow: visible;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 40px;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(calc(-50% + 120px)) rotate(0deg);
|
transform: translate(calc(-50% + 50px), -50%) rotate(0deg);
|
||||||
text-align: center;
|
text-align: left;
|
||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
display: block !important;
|
display: block !important;
|
||||||
|
|
@ -566,17 +527,16 @@ body.list-resizing-active * {
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
}
|
}
|
||||||
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
|
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
|
||||||
width: 100vh;
|
width: 15vh;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
padding: 0;
|
padding: 8px 4px;
|
||||||
margin: 0;
|
margin: 0 auto;
|
||||||
overflow: visible;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 40px;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(calc(-50% + 40px)) rotate(0deg);
|
transform: translate(calc(-50% + 50px), -50%) rotate(0deg);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
visibility: visible !important;
|
visibility: visible !important;
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
|
|
@ -790,9 +750,6 @@ body.list-resizing-active * {
|
||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
align-self: start;
|
align-self: start;
|
||||||
text-align: left;
|
|
||||||
padding-left: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
font-size: 16px !important;
|
font-size: 16px !important;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
@ -1007,9 +964,6 @@ body.list-resizing-active * {
|
||||||
grid-row: 2 !important;
|
grid-row: 2 !important;
|
||||||
grid-column: 2 !important;
|
grid-column: 2 !important;
|
||||||
align-self: start !important;
|
align-self: start !important;
|
||||||
text-align: left !important;
|
|
||||||
padding-left: 0 !important;
|
|
||||||
margin-left: 0 !important;
|
|
||||||
font-size: 16px !important;
|
font-size: 16px !important;
|
||||||
line-height: 1.2 !important;
|
line-height: 1.2 !important;
|
||||||
}
|
}
|
||||||
|
|
@ -1081,23 +1035,6 @@ body.list-resizing-active * {
|
||||||
grid-row: 1/3 !important;
|
grid-row: 1/3 !important;
|
||||||
grid-column: 1 !important;
|
grid-column: 1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Allow long list titles to expand on desktop (non-mobile, non-collapsed) */
|
|
||||||
.list:not(.mobile-view):not(.list-collapsed) .list-header {
|
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list:not(.mobile-view):not(.list-collapsed) .list-header .list-header-name {
|
|
||||||
/* Permit wrapping and full visibility */
|
|
||||||
white-space: normal !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
text-overflow: clip !important;
|
|
||||||
display: inline-block !important;
|
|
||||||
/* Reserve space for right-side controls (menu, handle, count) */
|
|
||||||
max-width: calc(100% - 120px) !important;
|
|
||||||
/* Break long words to avoid overflow */
|
|
||||||
word-break: break-word !important;
|
|
||||||
}
|
|
||||||
.link-board-wrapper {
|
.link-board-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@ template(name='list')
|
||||||
style="{{#unless collapsed}}min-width:{{listWidth}}px;max-width:{{listConstraint}}px;{{/unless}}"
|
style="{{#unless collapsed}}min-width:{{listWidth}}px;max-width:{{listConstraint}}px;{{/unless}}"
|
||||||
class="{{#if collapsed}}list-collapsed{{/if}} {{#if autoWidth}}list-auto-width{{/if}} {{#if isMiniScreen}}mobile-view{{/if}}")
|
class="{{#if collapsed}}list-collapsed{{/if}} {{#if autoWidth}}list-auto-width{{/if}} {{#if isMiniScreen}}mobile-view{{/if}}")
|
||||||
+listHeader
|
+listHeader
|
||||||
unless collapsed
|
+listBody
|
||||||
+listBody
|
.list-resize-handle.js-list-resize-handle.nodragscroll
|
||||||
.list-resize-handle.js-list-resize-handle.nodragscroll
|
|
||||||
|
|
||||||
template(name='miniList')
|
template(name='miniList')
|
||||||
a.mini-list.js-select-list.js-list(id="js-list-{{_id}}" class="{{#if isMiniScreen}}mobile-view{{/if}}")
|
a.mini-list.js-select-list.js-list(id="js-list-{{_id}}" class="{{#if isMiniScreen}}mobile-view{{/if}}")
|
||||||
|
|
|
||||||
|
|
@ -279,8 +279,7 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
// Only enable resize for non-collapsed, non-auto-width lists
|
// Only enable resize for non-collapsed, non-auto-width lists
|
||||||
const isAutoWidth = this.autoWidth();
|
const isAutoWidth = this.autoWidth();
|
||||||
const isCollapsed = Utils.getListCollapseState(list);
|
if (list.collapsed || isAutoWidth) {
|
||||||
if (isCollapsed || isAutoWidth) {
|
|
||||||
$resizeHandle.hide();
|
$resizeHandle.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -434,10 +433,9 @@ BlazeComponent.extendComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Reactively update resize handle visibility when auto-width or collapse changes
|
// Reactively update resize handle visibility when auto-width changes
|
||||||
component.autorun(() => {
|
component.autorun(() => {
|
||||||
const collapsed = Utils.getListCollapseState(list);
|
if (component.autoWidth()) {
|
||||||
if (component.autoWidth() || collapsed) {
|
|
||||||
$resizeHandle.hide();
|
$resizeHandle.hide();
|
||||||
} else {
|
} else {
|
||||||
$resizeHandle.show();
|
$resizeHandle.show();
|
||||||
|
|
@ -454,12 +452,6 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
}).register('list');
|
}).register('list');
|
||||||
|
|
||||||
Template.list.helpers({
|
|
||||||
collapsed() {
|
|
||||||
return Utils.getListCollapseState(this);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.miniList.events({
|
Template.miniList.events({
|
||||||
'click .js-select-list'() {
|
'click .js-select-list'() {
|
||||||
const listId = this._id;
|
const listId = this._id;
|
||||||
|
|
|
||||||
|
|
@ -85,19 +85,16 @@ template(name="linkCardPopup")
|
||||||
|
|
||||||
label {{_ 'swimlanes'}}:
|
label {{_ 'swimlanes'}}:
|
||||||
select.js-select-swimlanes
|
select.js-select-swimlanes
|
||||||
option(value="") {{_ 'custom-field-dropdown-none'}}
|
|
||||||
each swimlanes
|
each swimlanes
|
||||||
option(value="{{_id}}") {{isTitleDefault title}}
|
option(value="{{_id}}") {{isTitleDefault title}}
|
||||||
|
|
||||||
label {{_ 'lists'}}:
|
label {{_ 'lists'}}:
|
||||||
select.js-select-lists
|
select.js-select-lists
|
||||||
option(value="") {{_ 'custom-field-dropdown-none'}}
|
|
||||||
each lists
|
each lists
|
||||||
option(value="{{_id}}") {{isTitleDefault title}}
|
option(value="{{_id}}") {{isTitleDefault title}}
|
||||||
|
|
||||||
label {{_ 'cards'}}:
|
label {{_ 'cards'}}:
|
||||||
select.js-select-cards
|
select.js-select-cards
|
||||||
option(value="") {{_ 'custom-field-dropdown-none'}}
|
|
||||||
each cards
|
each cards
|
||||||
option(value="{{getRealId}}") {{getTitle}}
|
option(value="{{getRealId}}") {{getTitle}}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,50 +16,11 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
customFieldsSum() {
|
customFieldsSum() {
|
||||||
const list = Template.currentData();
|
const ret = ReactiveCache.getCustomFields({
|
||||||
if (!list) return [];
|
boardIds: { $in: [Session.get('currentBoard')] },
|
||||||
const boardId = Session.get('currentBoard');
|
|
||||||
const fields = ReactiveCache.getCustomFields({
|
|
||||||
boardIds: { $in: [boardId] },
|
|
||||||
showSumAtTopOfList: true,
|
showSumAtTopOfList: true,
|
||||||
});
|
});
|
||||||
|
return ret;
|
||||||
if (!fields || !fields.length) return [];
|
|
||||||
|
|
||||||
const cards = ReactiveCache.getCards({
|
|
||||||
listId: list._id,
|
|
||||||
archived: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = fields.map(field => {
|
|
||||||
let sum = 0;
|
|
||||||
if (cards && cards.length) {
|
|
||||||
cards.forEach(card => {
|
|
||||||
const cfs = (card.customFields || []);
|
|
||||||
const cf = cfs.find(f => f && f._id === field._id);
|
|
||||||
if (!cf || cf.value === null || cf.value === undefined) return;
|
|
||||||
let v = cf.value;
|
|
||||||
if (typeof v === 'string') {
|
|
||||||
// try to parse string numbers, accept comma decimal
|
|
||||||
const parsed = parseFloat(v.replace(',', '.'));
|
|
||||||
if (isNaN(parsed)) return;
|
|
||||||
v = parsed;
|
|
||||||
}
|
|
||||||
if (typeof v === 'number' && isFinite(v)) {
|
|
||||||
sum += v;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
_id: field._id,
|
|
||||||
name: field.name,
|
|
||||||
type: field.type,
|
|
||||||
settings: field.settings || {},
|
|
||||||
value: sum,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
openForm(options) {
|
openForm(options) {
|
||||||
|
|
@ -293,22 +254,6 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
}).register('listBody');
|
}).register('listBody');
|
||||||
|
|
||||||
// Helpers for listBody template context
|
|
||||||
Template.listBody.helpers({
|
|
||||||
formattedCurrencyCustomFieldValue(val) {
|
|
||||||
// `this` is the custom field sum object from customFieldsSum each-iteration
|
|
||||||
const field = this || {};
|
|
||||||
const code = (field.settings && field.settings.currencyCode) || 'USD';
|
|
||||||
try {
|
|
||||||
const n = typeof val === 'number' ? val : parseFloat(val);
|
|
||||||
if (!isFinite(n)) return val;
|
|
||||||
return new Intl.NumberFormat(undefined, { style: 'currency', currency: code }).format(n);
|
|
||||||
} catch (e) {
|
|
||||||
return `${code} ${val}`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function toggleValueInReactiveArray(reactiveValue, value) {
|
function toggleValueInReactiveArray(reactiveValue, value) {
|
||||||
const array = reactiveValue.get();
|
const array = reactiveValue.get();
|
||||||
const valueIndex = array.indexOf(value);
|
const valueIndex = array.indexOf(value);
|
||||||
|
|
@ -542,6 +487,8 @@ BlazeComponent.extendComponent({
|
||||||
{
|
{
|
||||||
sort: { sort: 1 },
|
sort: { sort: 1 },
|
||||||
});
|
});
|
||||||
|
if (swimlanes.length)
|
||||||
|
this.selectedSwimlaneId.set(swimlanes[0]._id);
|
||||||
return swimlanes;
|
return swimlanes;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -556,6 +503,7 @@ BlazeComponent.extendComponent({
|
||||||
{
|
{
|
||||||
sort: { sort: 1 },
|
sort: { sort: 1 },
|
||||||
});
|
});
|
||||||
|
if (lists.length) this.selectedListId.set(lists[0]._id);
|
||||||
return lists;
|
return lists;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -564,17 +512,19 @@ BlazeComponent.extendComponent({
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const ownCardsIds = this.board.cards().map(card => card.getRealId());
|
const ownCardsIds = this.board.cards().map(card => card.getRealId());
|
||||||
const selector = {
|
const ret = ReactiveCache.getCards(
|
||||||
|
{
|
||||||
|
boardId: this.selectedBoardId.get(),
|
||||||
|
swimlaneId: this.selectedSwimlaneId.get(),
|
||||||
|
listId: this.selectedListId.get(),
|
||||||
archived: false,
|
archived: false,
|
||||||
linkedId: { $nin: ownCardsIds },
|
linkedId: { $nin: ownCardsIds },
|
||||||
_id: { $nin: ownCardsIds },
|
_id: { $nin: ownCardsIds },
|
||||||
type: { $nin: ['template-card'] },
|
type: { $nin: ['template-card'] },
|
||||||
};
|
},
|
||||||
if (this.selectedBoardId.get()) selector.boardId = this.selectedBoardId.get();
|
{
|
||||||
if (this.selectedSwimlaneId.get()) selector.swimlaneId = this.selectedSwimlaneId.get();
|
sort: { sort: 1 },
|
||||||
if (this.selectedListId.get()) selector.listId = this.selectedListId.get();
|
});
|
||||||
|
|
||||||
const ret = ReactiveCache.getCards(selector, { sort: { sort: 1 } });
|
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -595,12 +545,8 @@ BlazeComponent.extendComponent({
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'change .js-select-boards'(evt) {
|
'change .js-select-boards'(evt) {
|
||||||
const val = $(evt.currentTarget).val();
|
subManager.subscribe('board', $(evt.currentTarget).val(), false);
|
||||||
subManager.subscribe('board', val, false);
|
this.selectedBoardId.set($(evt.currentTarget).val());
|
||||||
// Clear selections to allow linking only board or re-choose swimlane/list
|
|
||||||
this.selectedSwimlaneId.set('');
|
|
||||||
this.selectedListId.set('');
|
|
||||||
this.selectedBoardId.set(val);
|
|
||||||
},
|
},
|
||||||
'change .js-select-swimlanes'(evt) {
|
'change .js-select-swimlanes'(evt) {
|
||||||
this.selectedSwimlaneId.set($(evt.currentTarget).val());
|
this.selectedSwimlaneId.set($(evt.currentTarget).val());
|
||||||
|
|
|
||||||
|
|
@ -26,32 +26,24 @@ template(name="listHeader")
|
||||||
|/#{wipLimit.value})
|
|/#{wipLimit.value})
|
||||||
if showCardsCountForList cards.length
|
if showCardsCountForList cards.length
|
||||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
||||||
if hasNumberFieldsSum
|
|
||||||
|
|
|
||||||
span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}}
|
|
||||||
else
|
else
|
||||||
div.list-header-collapse-container
|
if collapsed
|
||||||
a.list-collapse-indicator.js-collapse(title="{{_ 'collapse'}}")
|
a.js-collapse(title="{{_ 'uncollapse'}}")
|
||||||
if collapsed
|
| ⬅️
|
||||||
| ▶
|
| ➡️
|
||||||
else
|
div(class="{{#if collapsed}}list-rotated{{/if}}")
|
||||||
| 🔽
|
h2.list-header-name(
|
||||||
div(class="{{#if collapsed}}list-rotated{{/if}}")
|
title="{{ moment modifiedAt 'LLL' }}"
|
||||||
h2.list-header-name(
|
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
|
||||||
title="{{ moment modifiedAt 'LLL' }}"
|
+viewer
|
||||||
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
|
= title
|
||||||
+viewer
|
if wipLimit.enabled
|
||||||
= title
|
| (
|
||||||
if wipLimit.enabled
|
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|
||||||
| (
|
|/#{wipLimit.value})
|
||||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|
|
||||||
|/#{wipLimit.value})
|
|
||||||
unless collapsed
|
unless collapsed
|
||||||
if showCardsCountForList cards.length
|
if showCardsCountForList cards.length
|
||||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
||||||
if hasNumberFieldsSum
|
|
||||||
|
|
|
||||||
span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}}
|
|
||||||
if isMiniScreen
|
if isMiniScreen
|
||||||
if currentList
|
if currentList
|
||||||
if isWatching
|
if isWatching
|
||||||
|
|
@ -66,10 +58,6 @@ template(name="listHeader")
|
||||||
unless currentUser.isWorker
|
unless currentUser.isWorker
|
||||||
a.list-header-handle.handle.js-list-handle ↕️
|
a.list-header-handle.handle.js-list-handle ↕️
|
||||||
else if currentUser.isBoardMember
|
else if currentUser.isBoardMember
|
||||||
if currentUser.isBoardMember
|
|
||||||
unless currentUser.isCommentOnly
|
|
||||||
unless currentUser.isWorker
|
|
||||||
a.list-header-handle.handle.js-list-handle ↕️
|
|
||||||
if isWatching
|
if isWatching
|
||||||
i.list-header-watch-icon | 👁️
|
i.list-header-watch-icon | 👁️
|
||||||
unless collapsed
|
unless collapsed
|
||||||
|
|
@ -79,8 +67,14 @@ template(name="listHeader")
|
||||||
// a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}")
|
// a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}")
|
||||||
if canSeeAddCard
|
if canSeeAddCard
|
||||||
a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") ➕
|
a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") ➕
|
||||||
|
a.js-collapse(title="{{_ 'collapse'}}")
|
||||||
|
| ⬅️
|
||||||
|
| ➡️
|
||||||
a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") ☰
|
a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") ☰
|
||||||
|
if currentUser.isBoardMember
|
||||||
|
unless currentUser.isCommentOnly
|
||||||
|
unless currentUser.isWorker
|
||||||
|
a.list-header-handle.handle.js-list-handle ↕️
|
||||||
|
|
||||||
template(name="editListTitleForm")
|
template(name="editListTitleForm")
|
||||||
.list-composer
|
.list-composer
|
||||||
|
|
|
||||||
|
|
@ -34,14 +34,13 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
collapsed(check = undefined) {
|
collapsed(check = undefined) {
|
||||||
const list = Template.currentData();
|
const list = Template.currentData();
|
||||||
const status = Utils.getListCollapseState(list);
|
const status = list.isCollapsed();
|
||||||
if (check === undefined) {
|
if (check === undefined) {
|
||||||
// just check
|
// just check
|
||||||
return status;
|
return status;
|
||||||
} else {
|
} else {
|
||||||
const next = typeof check === 'boolean' ? check : !status;
|
list.collapse(!status);
|
||||||
Utils.setListCollapseState(list, next);
|
return !status;
|
||||||
return next;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
editTitle(event) {
|
editTitle(event) {
|
||||||
|
|
@ -143,48 +142,7 @@ BlazeComponent.extendComponent({
|
||||||
Template.listHeader.helpers({
|
Template.listHeader.helpers({
|
||||||
isBoardAdmin() {
|
isBoardAdmin() {
|
||||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||||
},
|
}
|
||||||
numberFieldsSum() {
|
|
||||||
const list = Template.currentData();
|
|
||||||
if (!list) return 0;
|
|
||||||
const boardId = Session.get('currentBoard');
|
|
||||||
const fields = ReactiveCache.getCustomFields({
|
|
||||||
boardIds: { $in: [boardId] },
|
|
||||||
showSumAtTopOfList: true,
|
|
||||||
type: 'number',
|
|
||||||
});
|
|
||||||
if (!fields || !fields.length) return 0;
|
|
||||||
const cards = ReactiveCache.getCards({ listId: list._id, archived: false });
|
|
||||||
let total = 0;
|
|
||||||
if (cards && cards.length) {
|
|
||||||
cards.forEach(card => {
|
|
||||||
const cfs = (card.customFields || []);
|
|
||||||
fields.forEach(field => {
|
|
||||||
const cf = cfs.find(f => f && f._id === field._id);
|
|
||||||
if (!cf || cf.value === null || cf.value === undefined) return;
|
|
||||||
let v = cf.value;
|
|
||||||
if (typeof v === 'string') {
|
|
||||||
const parsed = parseFloat(v.replace(',', '.'));
|
|
||||||
if (isNaN(parsed)) return;
|
|
||||||
v = parsed;
|
|
||||||
}
|
|
||||||
if (typeof v === 'number' && isFinite(v)) {
|
|
||||||
total += v;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
},
|
|
||||||
hasNumberFieldsSum() {
|
|
||||||
const boardId = Session.get('currentBoard');
|
|
||||||
const fields = ReactiveCache.getCustomFields({
|
|
||||||
boardIds: { $in: [boardId] },
|
|
||||||
showSumAtTopOfList: true,
|
|
||||||
type: 'number',
|
|
||||||
});
|
|
||||||
return !!(fields && fields.length);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.listActionPopup.helpers({
|
Template.listActionPopup.helpers({
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@ template(name="minilist")
|
||||||
class="minicard-{{colorClass}}")
|
class="minicard-{{colorClass}}")
|
||||||
.minicard-title
|
.minicard-title
|
||||||
.handle
|
.handle
|
||||||
span.drag-handle(title="{{_ 'dragList'}}") ↕️
|
.fa.fa-arrows
|
||||||
+viewer
|
+viewer
|
||||||
= title
|
= title
|
||||||
|
|
|
||||||
|
|
@ -232,24 +232,6 @@ class DueCardsComponent extends BlazeComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize dueAt to timestamps for stable client-side ordering
|
|
||||||
const future = new Date('2100-12-31').getTime();
|
|
||||||
const toTime = v => {
|
|
||||||
if (v === null || v === undefined || v === '') return future;
|
|
||||||
if (v instanceof Date) return v.getTime();
|
|
||||||
const t = new Date(v);
|
|
||||||
if (!isNaN(t.getTime())) return t.getTime();
|
|
||||||
return future;
|
|
||||||
};
|
|
||||||
|
|
||||||
filteredCards.sort((a, b) => {
|
|
||||||
const x = toTime(a.dueAt);
|
|
||||||
const y = toTime(b.dueAt);
|
|
||||||
if (x > y) return 1;
|
|
||||||
if (x < y) return -1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (process.env.DEBUG === 'true') {
|
if (process.env.DEBUG === 'true') {
|
||||||
console.log('dueCards client: filtered to', filteredCards.length, 'cards');
|
console.log('dueCards client: filtered to', filteredCards.length, 'cards');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -339,20 +339,15 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 3vw;
|
min-width: 3vw;
|
||||||
font-size: clamp(12px, 2vw, 14px);
|
font-size: clamp(12px, 2vw, 14px);
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make zoom input wider on all mobile screens */
|
/* Make zoom input wider on all mobile screens */
|
||||||
@media screen and (max-width: 800px),
|
@media screen and (max-width: 800px),
|
||||||
screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) {
|
screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) {
|
||||||
#header-quick-access .zoom-controls .zoom-input {
|
#header-quick-access .zoom-controls .zoom-input {
|
||||||
min-width: 80px !important; /* Wider on mobile to show 3 digits */
|
min-width: 50px !important; /* Wider on mobile */
|
||||||
width: 80px !important; /* Fixed width to show 100 fully */
|
width: 50px !important; /* Fixed width to show all numbers */
|
||||||
font-size: 16px !important; /* Slightly larger text */
|
font-size: 14px !important; /* Slightly larger text */
|
||||||
flex: 0 0 80px !important; /* Prevent shrinking in flex */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -855,9 +850,8 @@
|
||||||
#header-quick-access .zoom-controls .zoom-input {
|
#header-quick-access .zoom-controls .zoom-input {
|
||||||
font-size: 16px !important; /* Larger input text */
|
font-size: 16px !important; /* Larger input text */
|
||||||
padding: 0.5vh 0.8vw !important;
|
padding: 0.5vh 0.8vw !important;
|
||||||
min-width: 80px !important; /* Wider to fit 100 */
|
min-width: 6vw !important; /* Much wider for mobile */
|
||||||
width: 80px !important; /* Fixed width to show 100 fully */
|
width: 60px !important; /* Fixed width to show all numbers */
|
||||||
flex: 0 0 80px !important; /* Prevent shrinking in flex */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make mobile mode toggle larger */
|
/* Make mobile mode toggle larger */
|
||||||
|
|
|
||||||
|
|
@ -111,9 +111,7 @@ template(name="header")
|
||||||
| 📢
|
| 📢
|
||||||
+viewer
|
+viewer
|
||||||
| #{announcement}
|
| #{announcement}
|
||||||
a
|
| ❌
|
||||||
.js-close-announcement
|
|
||||||
| ❌
|
|
||||||
|
|
||||||
template(name="offlineWarning")
|
template(name="offlineWarning")
|
||||||
.offline-warning
|
.offline-warning
|
||||||
|
|
|
||||||
|
|
@ -81,27 +81,6 @@ body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
/* iOS Safari fixes */
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile mode specific fixes for iOS Safari */
|
|
||||||
body.mobile-mode {
|
|
||||||
overflow-x: hidden;
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
/* Prevent iOS Safari bounce scroll */
|
|
||||||
overscroll-behavior: none;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure content area is scrollable in mobile mode */
|
|
||||||
body.mobile-mode #content {
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
height: calc(100vh - 48px);
|
|
||||||
}
|
}
|
||||||
#content {
|
#content {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -548,7 +527,7 @@ a:not(.disabled).is-active i.fa {
|
||||||
|
|
||||||
/* Board canvas */
|
/* Board canvas */
|
||||||
.board-canvas {
|
.board-canvas {
|
||||||
padding: 0 8px 8px 0;
|
padding: 8px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
@ -696,7 +675,7 @@ a:not(.disabled).is-active i.fa {
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-canvas {
|
.board-canvas {
|
||||||
padding: 0 12px 12px 0;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#header {
|
#header {
|
||||||
|
|
@ -777,7 +756,7 @@ a:not(.disabled).is-active i.fa {
|
||||||
.inline-input {
|
.inline-input {
|
||||||
height: 37px;
|
height: 37px;
|
||||||
margin: 8px 10px 0 0;
|
margin: 8px 10px 0 0;
|
||||||
width: 100px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
.select-authentication {
|
.select-authentication {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -920,40 +899,6 @@ a:not(.disabled).is-active i.fa {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* iOS Safari Mobile Mode Fixes */
|
|
||||||
@media screen and (max-width: 800px) {
|
|
||||||
/* Prevent scrolling issues on iOS Safari when card popup is open */
|
|
||||||
body.mobile-mode {
|
|
||||||
overflow: hidden;
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fix z-index stacking for mobile Safari */
|
|
||||||
body.mobile-mode .board-wrapper {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mobile-mode .board-wrapper .board-canvas .board-overlay {
|
|
||||||
z-index: 17 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mobile-mode .card-details {
|
|
||||||
z-index: 100 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.mobile-mode .pop-over {
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure smooth scrolling on iOS */
|
|
||||||
body.mobile-mode .card-details,
|
|
||||||
body.mobile-mode .pop-over .content-wrapper {
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@-moz-keyframes lds-roller {
|
@-moz-keyframes lds-roller {
|
||||||
0% {
|
0% {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,8 @@ template(name="main")
|
||||||
html(lang="{{TAPi18n.getLanguage}}")
|
html(lang="{{TAPi18n.getLanguage}}")
|
||||||
head
|
head
|
||||||
title
|
title
|
||||||
meta(name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=yes, viewport-fit=cover")
|
meta(name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=yes")
|
||||||
meta(http-equiv="X-UA-Compatible" content="IE=edge")
|
meta(http-equiv="X-UA-Compatible" content="IE=edge")
|
||||||
meta(name="apple-mobile-web-app-capable" content="yes")
|
|
||||||
meta(name="apple-mobile-web-app-status-bar-style" content="black-translucent")
|
|
||||||
//- XXX We should use pathFor in the following `href` to support the case
|
//- XXX We should use pathFor in the following `href` to support the case
|
||||||
where the application is deployed with a path prefix, but it seems to be
|
where the application is deployed with a path prefix, but it seems to be
|
||||||
difficult to do that cleanly with Blaze -- at least without adding extra
|
difficult to do that cleanly with Blaze -- at least without adding extra
|
||||||
|
|
@ -79,6 +77,7 @@ template(name="defaultLayout")
|
||||||
| {{{afterBodyStart}}}
|
| {{{afterBodyStart}}}
|
||||||
+Template.dynamic(template=content)
|
+Template.dynamic(template=content)
|
||||||
| {{{beforeBodyEnd}}}
|
| {{{beforeBodyEnd}}}
|
||||||
|
+migrationProgress
|
||||||
+boardConversionProgress
|
+boardConversionProgress
|
||||||
if (Modal.isOpen)
|
if (Modal.isOpen)
|
||||||
#modal
|
#modal
|
||||||
|
|
|
||||||
|
|
@ -94,24 +94,24 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Admin edit popups: use full height */
|
/* Admin edit popups: use full height */
|
||||||
.pop-over[data-popup="editUserPopup"],
|
.pop-over[data-popup="editUser"],
|
||||||
.pop-over[data-popup="editOrgPopup"],
|
.pop-over[data-popup="editOrg"],
|
||||||
.pop-over[data-popup="editTeamPopup"] {
|
.pop-over[data-popup="editTeam"] {
|
||||||
height: calc(100vh - 20px) !important;
|
height: calc(100vh - 20px) !important;
|
||||||
max-height: calc(100vh - 20px) !important;
|
max-height: calc(100vh - 20px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pop-over[data-popup="editUserPopup"] .content-wrapper,
|
.pop-over[data-popup="editUser"] .content-wrapper,
|
||||||
.pop-over[data-popup="editOrgPopup"] .content-wrapper,
|
.pop-over[data-popup="editOrg"] .content-wrapper,
|
||||||
.pop-over[data-popup="editTeamPopup"] .content-wrapper {
|
.pop-over[data-popup="editTeam"] .content-wrapper {
|
||||||
max-height: calc(100vh - 80px) !important; /* Subtract header height */
|
max-height: calc(100vh - 80px) !important; /* Subtract header height */
|
||||||
height: calc(100vh - 80px) !important;
|
height: calc(100vh - 80px) !important;
|
||||||
overflow-y: auto !important;
|
overflow-y: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pop-over[data-popup="editUserPopup"] .content-container,
|
.pop-over[data-popup="editUser"] .content-container,
|
||||||
.pop-over[data-popup="editOrgPopup"] .content-container,
|
.pop-over[data-popup="editOrg"] .content-container,
|
||||||
.pop-over[data-popup="editTeamPopup"] .content-container {
|
.pop-over[data-popup="editTeam"] .content-container {
|
||||||
max-height: calc(100vh - 80px) !important; /* Subtract header height */
|
max-height: calc(100vh - 80px) !important; /* Subtract header height */
|
||||||
height: calc(100vh - 80px) !important;
|
height: calc(100vh - 80px) !important;
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +123,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Specific styling for language popup list */
|
/* Specific styling for language popup list */
|
||||||
.pop-over[data-popup="changeLanguagePopup"] .pop-over-list {
|
.pop-over[data-popup="changeLanguage"] .pop-over-list {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
@ -131,69 +131,46 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure content div in language popup contains all items */
|
/* Ensure content div in language popup contains all items */
|
||||||
.pop-over[data-popup="changeLanguagePopup"] .content {
|
.pop-over[data-popup="changeLanguage"] .content {
|
||||||
height: auto;
|
height: auto;
|
||||||
/* Remove forced min-height to avoid top gap */
|
min-height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure hidden stack pages truly take no space */
|
/* Allow dynamic height for Change Language popup */
|
||||||
.pop-over[data-popup="changeLanguagePopup"] .content.no-height {
|
.pop-over[data-popup="changeLanguage"] .content-wrapper {
|
||||||
min-height: 0 !important;
|
max-height: inherit; /* Use dynamic height from JavaScript */
|
||||||
height: 0 !important;
|
}
|
||||||
padding: 0 !important;
|
|
||||||
margin: 0 !important;
|
.pop-over[data-popup="changeLanguage"] .content-container {
|
||||||
visibility: hidden !important;
|
max-height: inherit; /* Use dynamic height from JavaScript */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make language popup extend to bottom of browser window */
|
/* Make language popup extend to bottom of browser window */
|
||||||
.pop-over[data-popup="changeLanguagePopup"] {
|
.pop-over[data-popup="changeLanguage"] {
|
||||||
position: fixed !important;
|
height: calc(100vh - 30px);
|
||||||
bottom: 0 !important;
|
min-height: 300px;
|
||||||
top: auto !important;
|
/* Adjust positioning to move popup 30px higher */
|
||||||
left: auto !important;
|
transform: translateY(-30px);
|
||||||
right: 20px !important;
|
|
||||||
width: auto !important;
|
|
||||||
max-width: 450px !important;
|
|
||||||
height: 100vh !important;
|
|
||||||
max-height: 100vh !important;
|
|
||||||
min-height: 300px !important;
|
|
||||||
display: flex !important;
|
|
||||||
flex-direction: column !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Allow dynamic height for Change Language popup */
|
.pop-over[data-popup="changeLanguage"] .content-wrapper {
|
||||||
.pop-over[data-popup="changeLanguagePopup"] .header {
|
height: calc(100% - 50px); /* Subtract header height more precisely */
|
||||||
flex-shrink: 0 !important;
|
min-height: 250px;
|
||||||
height: auto !important;
|
overflow-y: auto;
|
||||||
|
max-height: none; /* Remove any max-height constraints */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pop-over[data-popup="changeLanguagePopup"] .content-wrapper {
|
.pop-over[data-popup="changeLanguage"] .content-container {
|
||||||
flex: 1 !important;
|
height: auto; /* Let content determine height */
|
||||||
overflow-y: auto !important;
|
min-height: 250px;
|
||||||
overflow-x: hidden !important;
|
max-height: none; /* Remove any max-height constraints */
|
||||||
min-height: 0 !important;
|
flex: 1;
|
||||||
max-height: none !important;
|
display: flex;
|
||||||
height: auto !important;
|
flex-direction: column;
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pop-over[data-popup="changeLanguagePopup"] .content-container {
|
|
||||||
height: auto !important;
|
|
||||||
max-height: none !important;
|
|
||||||
flex: 1 !important;
|
|
||||||
display: flex !important;
|
|
||||||
flex-direction: column !important;
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pop-over[data-popup="changeLanguagePopup"] .content {
|
|
||||||
height: auto !important;
|
|
||||||
max-height: none !important;
|
|
||||||
padding-bottom: 50px !important;
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Date popup sizing for native HTML inputs */
|
/* Date popup sizing for native HTML inputs */
|
||||||
|
|
@ -538,7 +515,6 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 6px;
|
top: 6px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
color: #3cb500;
|
|
||||||
}
|
}
|
||||||
.pop-over-list .pop-over-list.checkable li.active a {
|
.pop-over-list .pop-over-list.checkable li.active a {
|
||||||
padding-right: 28px;
|
padding-right: 28px;
|
||||||
|
|
@ -546,10 +522,6 @@
|
||||||
.pop-over-list .pop-over-list.checkable li.active a .fa-check {
|
.pop-over-list .pop-over-list.checkable li.active a .fa-check {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
/* Grey check icons when grey icons setting is enabled */
|
|
||||||
body.grey-icons-enabled .pop-over-list .pop-over-list.checkable .fa-check {
|
|
||||||
color: #7a7a7a;
|
|
||||||
}
|
|
||||||
.pop-over.miniprofile .header {
|
.pop-over.miniprofile .header {
|
||||||
border-bottom-color: transparent;
|
border-bottom-color: transparent;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
|
@ -595,10 +567,6 @@ body.grey-icons-enabled .pop-over-list .pop-over-list.checkable .fa-check {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
border: 0px solid #dbdbdb;
|
border: 0px solid #dbdbdb;
|
||||||
/* Ensure popups appear above card details on mobile */
|
|
||||||
z-index: 999999 !important;
|
|
||||||
/* iOS Safari scrolling fix */
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
}
|
||||||
.pop-over .header {
|
.pop-over .header {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
@ -683,23 +651,3 @@ body.grey-icons-enabled .pop-over-list .pop-over-list.checkable .fa-check {
|
||||||
transform: none !important;
|
transform: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Force full-screen popups in mobile mode regardless of screen width */
|
|
||||||
body.mobile-mode .pop-over {
|
|
||||||
position: fixed !important;
|
|
||||||
top: 0 !important;
|
|
||||||
left: 0 !important;
|
|
||||||
right: 0 !important;
|
|
||||||
bottom: 0 !important;
|
|
||||||
width: 100vw !important;
|
|
||||||
height: 100vh !important;
|
|
||||||
max-width: 100vw !important;
|
|
||||||
max-height: 100vh !important;
|
|
||||||
}
|
|
||||||
body.mobile-mode .pop-over .content-wrapper {
|
|
||||||
width: 100% !important;
|
|
||||||
height: calc(100vh - 48px) !important;
|
|
||||||
max-height: calc(100vh - 48px) !important;
|
|
||||||
overflow-y: auto !important;
|
|
||||||
overflow-x: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
class="{{#unless title}}miniprofile{{/unless}}"
|
class="{{#unless title}}miniprofile{{/unless}}"
|
||||||
class=currentBoard.colorClass
|
class=currentBoard.colorClass
|
||||||
class="{{#unless title}}no-title{{/unless}}"
|
class="{{#unless title}}no-title{{/unless}}"
|
||||||
data-popup="{{popupName}}"
|
|
||||||
style="left:{{offset.left}}px; top:{{offset.top}}px;{{#if offset.maxHeight}} max-height:{{offset.maxHeight}}px;{{/if}}")
|
style="left:{{offset.left}}px; top:{{offset.top}}px;{{#if offset.maxHeight}} max-height:{{offset.maxHeight}}px;{{/if}}")
|
||||||
.header
|
.header
|
||||||
a.back-btn.js-back-view(class="{{#unless hasPopupParent}}is-hidden{{/unless}}")
|
a.back-btn.js-back-view(class="{{#unless hasPopupParent}}is-hidden{{/unless}}")
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
template(name="supportHeaderBar")
|
|
||||||
h1
|
|
||||||
if isSupportEnabled
|
|
||||||
= supportTitle
|
|
||||||
else
|
|
||||||
| {{_ 'support'}}
|
|
||||||
|
|
||||||
template(name="support")
|
|
||||||
.support-page
|
|
||||||
if isSupportPublic
|
|
||||||
if isSupportEnabled
|
|
||||||
.support-page-content
|
|
||||||
+viewer
|
|
||||||
| {{supportContent}}
|
|
||||||
else
|
|
||||||
.support-page-content
|
|
||||||
| {{_ 'support-info-not-added-yet'}}
|
|
||||||
else
|
|
||||||
if currentUser
|
|
||||||
if isSupportEnabled
|
|
||||||
.support-page-content
|
|
||||||
+viewer
|
|
||||||
| {{supportContent}}
|
|
||||||
else
|
|
||||||
.support-page-content
|
|
||||||
| {{_ 'support-info-not-added-yet'}}
|
|
||||||
else
|
|
||||||
.support-page-content
|
|
||||||
| {{_ 'support-info-only-for-logged-in-users'}}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import { ReactiveCache } from '/imports/reactiveCache';
|
|
||||||
import { TAPi18n } from '/imports/i18n';
|
|
||||||
|
|
||||||
// Shared helpers for both support templates
|
|
||||||
const supportHelpers = {
|
|
||||||
supportTitle() {
|
|
||||||
const setting = ReactiveCache.getCurrentSetting();
|
|
||||||
return setting && setting.supportTitle ? setting.supportTitle : TAPi18n.__('support');
|
|
||||||
},
|
|
||||||
supportContent() {
|
|
||||||
const setting = ReactiveCache.getCurrentSetting();
|
|
||||||
return setting && setting.supportPageText ? setting.supportPageText : TAPi18n.__('support-info-not-added-yet');
|
|
||||||
},
|
|
||||||
isSupportEnabled() {
|
|
||||||
const setting = ReactiveCache.getCurrentSetting();
|
|
||||||
return setting && setting.supportPageEnabled;
|
|
||||||
},
|
|
||||||
isSupportPublic() {
|
|
||||||
const setting = ReactiveCache.getCurrentSetting();
|
|
||||||
return setting && setting.supportPagePublic;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Main support page component
|
|
||||||
BlazeComponent.extendComponent({
|
|
||||||
onCreated() {
|
|
||||||
this.error = new ReactiveVar('');
|
|
||||||
this.loading = new ReactiveVar(false);
|
|
||||||
|
|
||||||
Meteor.subscribe('setting');
|
|
||||||
},
|
|
||||||
...supportHelpers
|
|
||||||
}).register('support');
|
|
||||||
|
|
||||||
// Header bar component
|
|
||||||
BlazeComponent.extendComponent({
|
|
||||||
onCreated() {
|
|
||||||
Meteor.subscribe('setting');
|
|
||||||
},
|
|
||||||
...supportHelpers
|
|
||||||
}).register('supportHeaderBar');
|
|
||||||
|
|
@ -267,35 +267,3 @@
|
||||||
color: #a0aec0;
|
color: #a0aec0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Attachment migration styles */
|
|
||||||
.attachment-item.migrating {
|
|
||||||
position: relative;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachment-migration-overlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 10;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.migration-spinner {
|
|
||||||
font-size: 24px;
|
|
||||||
color: #007cba;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.migration-text {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
@ -4,14 +4,14 @@ template(name="migrationProgress")
|
||||||
.migration-progress-modal
|
.migration-progress-modal
|
||||||
.migration-progress-header
|
.migration-progress-header
|
||||||
h3.migration-progress-title
|
h3.migration-progress-title
|
||||||
| 🔄 {{_ 'migration-progress-title'}}
|
| 🔄 Board Migration in Progress
|
||||||
.migration-progress-close.js-close-migration-progress
|
.migration-progress-close.js-close-migration-progress
|
||||||
| ❌
|
| ❌
|
||||||
|
|
||||||
.migration-progress-content
|
.migration-progress-content
|
||||||
.migration-progress-overall
|
.migration-progress-overall
|
||||||
.migration-progress-overall-label
|
.migration-progress-overall-label
|
||||||
| {{_ 'migration-progress-overall'}}: {{currentStep}} {{_ 'of'}} {{totalSteps}} {{_ 'steps'}}
|
| Overall Progress: {{currentStep}} of {{totalSteps}} steps
|
||||||
.migration-progress-overall-bar
|
.migration-progress-overall-bar
|
||||||
.migration-progress-overall-fill(style="{{progressBarStyle}}")
|
.migration-progress-overall-fill(style="{{progressBarStyle}}")
|
||||||
.migration-progress-overall-percentage
|
.migration-progress-overall-percentage
|
||||||
|
|
@ -19,7 +19,7 @@ template(name="migrationProgress")
|
||||||
|
|
||||||
.migration-progress-current-step
|
.migration-progress-current-step
|
||||||
.migration-progress-step-label
|
.migration-progress-step-label
|
||||||
| {{_ 'migration-progress-current-step'}}: {{stepNameFormatted}}
|
| Current Step: {{stepNameFormatted}}
|
||||||
.migration-progress-step-bar
|
.migration-progress-step-bar
|
||||||
.migration-progress-step-fill(style="{{stepProgressBarStyle}}")
|
.migration-progress-step-fill(style="{{stepProgressBarStyle}}")
|
||||||
.migration-progress-step-percentage
|
.migration-progress-step-percentage
|
||||||
|
|
@ -27,17 +27,17 @@ template(name="migrationProgress")
|
||||||
|
|
||||||
.migration-progress-status
|
.migration-progress-status
|
||||||
.migration-progress-status-label
|
.migration-progress-status-label
|
||||||
| {{_ 'migration-progress-status'}}:
|
| Status:
|
||||||
.migration-progress-status-text
|
.migration-progress-status-text
|
||||||
| {{stepStatus}}
|
| {{stepStatus}}
|
||||||
|
|
||||||
if stepDetailsFormatted
|
if stepDetailsFormatted
|
||||||
.migration-progress-details
|
.migration-progress-details
|
||||||
.migration-progress-details-label
|
.migration-progress-details-label
|
||||||
| {{_ 'migration-progress-details'}}:
|
| Details:
|
||||||
.migration-progress-details-text
|
.migration-progress-details-text
|
||||||
| {{stepDetailsFormatted}}
|
| {{stepDetailsFormatted}}
|
||||||
|
|
||||||
.migration-progress-footer
|
.migration-progress-footer
|
||||||
.migration-progress-note
|
.migration-progress-note
|
||||||
| {{_ 'migration-progress-note'}}
|
| Please wait while we migrate your board to the latest structure...
|
||||||
|
|
@ -20,10 +20,10 @@
|
||||||
height: 3vw;
|
height: 3vw;
|
||||||
}
|
}
|
||||||
#notifications-drawer .notification .read-status .activity-type {
|
#notifications-drawer .notification .read-status .activity-type {
|
||||||
margin: 8px 0 0;
|
margin: 2vh 0 0;
|
||||||
width: 1.2em;
|
width: 2.2vw;
|
||||||
height: 1.2em;
|
height: 2.2vw;
|
||||||
font-size: clamp(14px, 2vw, 17px);
|
font-size: clamp(14px, 2.5vw, 17px);
|
||||||
display: block;
|
display: block;
|
||||||
color: #bbb;
|
color: #bbb;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ template(name='notification')
|
||||||
+activity(activity=activityData mode='none')
|
+activity(activity=activityData mode='none')
|
||||||
if read
|
if read
|
||||||
.remove
|
.remove
|
||||||
a(title="{{_ 'delete'}}") 🗑️
|
a.fa.fa-trash
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
template(name='notificationIcon')
|
template(name='notificationIcon')
|
||||||
if($in activityType 'deleteAttachment' 'addAttachment')
|
if($in activityType 'deleteAttachment' 'addAttachment')
|
||||||
span.activity-type(title="attachment") 📎
|
i.fa.fa-paperclip.activity-type(title="attachment")
|
||||||
else if($in activityType 'createBoard' 'importBoard')
|
else if($in activityType 'createBoard' 'importBoard')
|
||||||
span.activity-type(title="board") 🗂️
|
i.fa.fa-chalkboard.activity-type(title="board")
|
||||||
|
|
||||||
else if($in activityType 'createCard' 'importCard' 'moveCard')
|
else if($in activityType 'createCard' 'importCard' 'moveCard')
|
||||||
+cardNotificationIcon
|
+cardNotificationIcon
|
||||||
|
|
@ -19,17 +19,17 @@ template(name='notificationIcon')
|
||||||
//- DRY and consistant
|
//- DRY and consistant
|
||||||
|
|
||||||
else if($in activityType 'checkedItem' 'uncheckedItem' 'addChecklistItem' 'removedChecklistItem')
|
else if($in activityType 'checkedItem' 'uncheckedItem' 'addChecklistItem' 'removedChecklistItem')
|
||||||
span.activity-type(title="checklist item") ☑️
|
i.fa.fa-check-square.activity-type(title="checklist item")
|
||||||
else if($in activityType 'addComment')
|
else if($in activityType 'addComment')
|
||||||
span.activity-type(title="comment") 💬
|
i.fa.fa-comment-o.activity-type(title="comment")
|
||||||
else if($in activityType 'createCustomField' 'setCustomField' 'unsetCustomField')
|
else if($in activityType 'createCustomField' 'setCustomField' 'unsetCustomField')
|
||||||
span.activity-type(title="custom field") 🧩
|
i.fa.fa-code.activity-type(title="custom field")
|
||||||
else if($in activityType 'addedLabel' 'removedLabel')
|
else if($in activityType 'addedLabel' 'removedLabel')
|
||||||
span.activity-type(title="label") 🏷️
|
i.fa.fa-tag.activity-type(title="label")
|
||||||
else if($in activityType 'a-startAt' 'a-receivedAt')
|
else if($in activityType 'a-startAt' 'a-receivedAt')
|
||||||
span.activity-type(title="date") ⏰
|
i.fa.fa-clock-o.activity-type(title="date")
|
||||||
else if($in activityType 'a-dueAt' 'a-endAt')
|
else if($in activityType 'a-dueAt' 'a-endAt')
|
||||||
span.activity-type(title="date") ⏰
|
i.fa.fa-clock-o.activity-type(title="date")
|
||||||
|
|
||||||
else if($in activityType 'createList' 'removeList' 'archivedList')
|
else if($in activityType 'createList' 'removeList' 'archivedList')
|
||||||
+listNotificationIcon
|
+listNotificationIcon
|
||||||
|
|
@ -41,17 +41,17 @@ template(name='notificationIcon')
|
||||||
//- elswhere in the app we use fa-trello to indicate lists...
|
//- elswhere in the app we use fa-trello to indicate lists...
|
||||||
//- i personally like fa-columns a bit better
|
//- i personally like fa-columns a bit better
|
||||||
else if($in activityType 'unjoinMember' 'addBoardMember' 'joinMember' 'removeBoardMember')
|
else if($in activityType 'unjoinMember' 'addBoardMember' 'joinMember' 'removeBoardMember')
|
||||||
span.activity-type(title="member") 👤
|
i.fa.fa-user.activity-type(title="member")
|
||||||
else if($in activityType 'createSwimlane' 'archivedSwimlane')
|
else if($in activityType 'createSwimlane' 'archivedSwimlane')
|
||||||
span.activity-type(title="swimlane") 🧭
|
i.fa.fa-th-large.activity-type(title="swimlane")
|
||||||
else
|
else
|
||||||
span.activity-type(title="can't find icon for #{activityType}") 🐞
|
i.fa.fa-bug.activity-type(title="can't find icon for #{activityType}")
|
||||||
|
|
||||||
template(name='cardNotificationIcon')
|
template(name='cardNotificationIcon')
|
||||||
span.activity-type(title="card") 🗒️
|
i.fa.fa-clone.activity-type(title="card")
|
||||||
|
|
||||||
template(name='checklistNotificationIcon')
|
template(name='checklistNotificationIcon')
|
||||||
span.activity-type(title="checklist") 📝
|
i.fa.fa-list.activity-type(title="checklist")
|
||||||
|
|
||||||
template(name='listNotificationIcon')
|
template(name='listNotificationIcon')
|
||||||
span.activity-type(title="list") 📋
|
i.fa.fa-columns.activity-type(title="list")
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,9 @@ section#notifications-drawer .remove-read {
|
||||||
section#notifications-drawer .remove-read:hover {
|
section#notifications-drawer .remove-read:hover {
|
||||||
color: #eb4646 !important;
|
color: #eb4646 !important;
|
||||||
}
|
}
|
||||||
|
section#notifications-drawer .remove-read:hover i.fa {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
section#notifications-drawer ul.notifications {
|
section#notifications-drawer ul.notifications {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0px 16px 0px 16px;
|
padding: 0px 16px 0px 16px;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ template(name='notificationsDrawer')
|
||||||
h5 {{_ 'notifications'}}
|
h5 {{_ 'notifications'}}
|
||||||
if($gt unreadNotifications 0)
|
if($gt unreadNotifications 0)
|
||||||
|(#{unreadNotifications})
|
|(#{unreadNotifications})
|
||||||
a.close ❌
|
a.fa.fa-times-thin.close
|
||||||
ul.notifications
|
ul.notifications
|
||||||
each transformedProfile.notifications
|
each transformedProfile.notifications
|
||||||
+notification(activityData=activityObj index=dbIndex read=read)
|
+notification(activityData=activityObj index=dbIndex read=read)
|
||||||
|
|
@ -16,5 +16,5 @@ template(name='notificationsDrawer')
|
||||||
a.all-read {{_ 'mark-all-as-read'}}
|
a.all-read {{_ 'mark-all-as-read'}}
|
||||||
if ($and ($.Session.get 'showReadNotifications') ($gt readNotifications 0))
|
if ($and ($.Session.get 'showReadNotifications') ($gt readNotifications 0))
|
||||||
a.remove-read
|
a.remove-read
|
||||||
| 🗑️
|
i.fa.fa-trash
|
||||||
| {{_ 'remove-all-read'}}
|
| {{_ 'remove-all-read'}}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ template(name="boardActions")
|
||||||
| {{_'r-the-board'}}
|
| {{_'r-the-board'}}
|
||||||
div.trigger-dropdown
|
div.trigger-dropdown
|
||||||
select(id="board-id")
|
select(id="board-id")
|
||||||
option(value="" disabled selected if=not boards.length) {{loadingBoardsLabel}}
|
|
||||||
each boards
|
each boards
|
||||||
if $eq _id currentBoard._id
|
if $eq _id currentBoard._id
|
||||||
option(value="{{_id}}" selected) {{_ 'current'}}
|
option(value="{{_id}}" selected) {{_ 'current'}}
|
||||||
|
|
@ -86,7 +85,6 @@ template(name="boardActions")
|
||||||
| {{_'r-the-board'}}
|
| {{_'r-the-board'}}
|
||||||
div.trigger-dropdown
|
div.trigger-dropdown
|
||||||
select(id="board-id-link")
|
select(id="board-id-link")
|
||||||
option(value="" disabled selected if=not boards.length) {{loadingBoardsLabel}}
|
|
||||||
each boards
|
each boards
|
||||||
if $eq _id currentBoard._id
|
if $eq _id currentBoard._id
|
||||||
option(value="{{_id}}" selected) {{_ 'current'}}
|
option(value="{{_id}}" selected) {{_ 'current'}}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
import { ReactiveCache } from '/imports/reactiveCache';
|
import { ReactiveCache } from '/imports/reactiveCache';
|
||||||
import { TAPi18n } from '/imports/i18n';
|
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
BlazeComponent.extendComponent({
|
||||||
onCreated() {
|
onCreated() {},
|
||||||
// Ensure boards are available for action dropdowns
|
|
||||||
this.subscribe('boards');
|
|
||||||
},
|
|
||||||
|
|
||||||
boards() {
|
boards() {
|
||||||
const ret = ReactiveCache.getBoards(
|
const ret = ReactiveCache.getBoards(
|
||||||
|
|
@ -23,16 +19,6 @@ BlazeComponent.extendComponent({
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
loadingBoardsLabel() {
|
|
||||||
try {
|
|
||||||
const txt = TAPi18n.__('loading-boards');
|
|
||||||
if (txt && !txt.startsWith("key '")) return txt;
|
|
||||||
} catch (e) {
|
|
||||||
// ignore translation lookup errors
|
|
||||||
}
|
|
||||||
return 'Loading boards...';
|
|
||||||
},
|
|
||||||
|
|
||||||
events() {
|
events() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ BlazeComponent.extendComponent({
|
||||||
this.subscribe('allRules');
|
this.subscribe('allRules');
|
||||||
this.subscribe('allTriggers');
|
this.subscribe('allTriggers');
|
||||||
this.subscribe('allActions');
|
this.subscribe('allActions');
|
||||||
this.subscribe('boards');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
trigger() {
|
trigger() {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
template(name="rulesActions")
|
template(name="rulesActions")
|
||||||
h2
|
h2
|
||||||
| ✨
|
| ✨
|
||||||
| {{_ 'r-rule' }} "{{ruleNameStr}}" - {{_ 'r-add-action'}}
|
| {{_ 'r-rule' }} "#{data.ruleName.get}" - {{_ 'r-add-action'}}
|
||||||
.triggers-content
|
.triggers-content
|
||||||
.triggers-body
|
.triggers-body
|
||||||
.triggers-side-menu
|
.triggers-side-menu
|
||||||
|
|
@ -15,13 +15,13 @@ template(name="rulesActions")
|
||||||
li.js-set-mail-actions
|
li.js-set-mail-actions
|
||||||
| @
|
| @
|
||||||
.triggers-main-body
|
.triggers-main-body
|
||||||
if $eq currentActions.get 'board'
|
if ($eq currentActions.get 'board')
|
||||||
+boardActions(ruleName=data.ruleName triggerVar=data.triggerVar)
|
+boardActions(ruleName=data.ruleName triggerVar=data.triggerVar)
|
||||||
else if $eq currentActions.get 'card'
|
else if ($eq currentActions.get 'card')
|
||||||
+cardActions(ruleName=data.ruleName triggerVar=data.triggerVar)
|
+cardActions(ruleName=data.ruleName triggerVar=data.triggerVar)
|
||||||
else if $eq currentActions.get 'checklist'
|
else if ($eq currentActions.get 'checklist')
|
||||||
+checklistActions(ruleName=data.ruleName triggerVar=data.triggerVar)
|
+checklistActions(ruleName=data.ruleName triggerVar=data.triggerVar)
|
||||||
else if $eq currentActions.get 'mail'
|
else if ($eq currentActions.get 'mail')
|
||||||
+mailActions(ruleName=data.ruleName triggerVar=data.triggerVar)
|
+mailActions(ruleName=data.ruleName triggerVar=data.triggerVar)
|
||||||
div.rules-back
|
div.rules-back
|
||||||
button.js-goback
|
button.js-goback
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,6 @@ BlazeComponent.extendComponent({
|
||||||
this.currentActions = new ReactiveVar('board');
|
this.currentActions = new ReactiveVar('board');
|
||||||
},
|
},
|
||||||
|
|
||||||
ruleNameStr() {
|
|
||||||
const rn = this.data() && this.data().ruleName;
|
|
||||||
try {
|
|
||||||
return rn && typeof rn.get === 'function' ? rn.get() : '';
|
|
||||||
} catch (_) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setBoardActions() {
|
setBoardActions() {
|
||||||
this.currentActions.set('board');
|
this.currentActions.set('board');
|
||||||
$('.js-set-card-actions').removeClass('active');
|
$('.js-set-card-actions').removeClass('active');
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
template(name="rulesTriggers")
|
template(name="rulesTriggers")
|
||||||
h2
|
h2
|
||||||
| ✨
|
| ✨
|
||||||
| {{_ 'r-rule' }} "{{ruleNameStr}}" - {{_ 'r-add-trigger'}}
|
| {{_ 'r-rule' }} "#{data.ruleName.get}" - {{_ 'r-add-trigger'}}
|
||||||
.triggers-content
|
.triggers-content
|
||||||
.triggers-body
|
.triggers-body
|
||||||
.triggers-side-menu
|
.triggers-side-menu
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,6 @@ BlazeComponent.extendComponent({
|
||||||
this.showChecklistTrigger = new ReactiveVar(false);
|
this.showChecklistTrigger = new ReactiveVar(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
ruleNameStr() {
|
|
||||||
const rn = this.data() && this.data().ruleName;
|
|
||||||
try {
|
|
||||||
return rn && typeof rn.get === 'function' ? rn.get() : '';
|
|
||||||
} catch (_) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setBoardTriggers() {
|
setBoardTriggers() {
|
||||||
this.showBoardTrigger.set(true);
|
this.showBoardTrigger.set(true);
|
||||||
this.showCardTrigger.set(false);
|
this.showCardTrigger.set(false);
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ template(name="boardTriggers")
|
||||||
template(name="boardCardTitlePopup")
|
template(name="boardCardTitlePopup")
|
||||||
form
|
form
|
||||||
label
|
label
|
||||||
| {{_ 'boardCardTitlePopup-title'}}
|
| Card Title Filter
|
||||||
input.js-card-filter-name(type="text" value=title autofocus)
|
input.js-card-filter-name(type="text" value=title autofocus)
|
||||||
input.js-card-filter-button.primary.wide(type="submit" value="{{_ 'set-filter'}}")
|
input.js-card-filter-button.primary.wide(type="submit" value="{{_ 'set-filter'}}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,27 @@ template(name="adminReports")
|
||||||
ul
|
ul
|
||||||
li
|
li
|
||||||
a.js-report-broken(data-id="report-broken")
|
a.js-report-broken(data-id="report-broken")
|
||||||
span.emoji-icon 🔗
|
| 🔗
|
||||||
| {{_ 'broken-cards'}}
|
| {{_ 'broken-cards'}}
|
||||||
|
|
||||||
li
|
li
|
||||||
a.js-report-files(data-id="report-files")
|
a.js-report-files(data-id="report-files")
|
||||||
span.emoji-icon 📎
|
| 📎
|
||||||
| {{_ 'filesReportTitle'}}
|
| {{_ 'filesReportTitle'}}
|
||||||
|
|
||||||
li
|
li
|
||||||
a.js-report-rules(data-id="report-rules")
|
a.js-report-rules(data-id="report-rules")
|
||||||
span.emoji-icon ✨
|
| ✨
|
||||||
| {{_ 'rulesReportTitle'}}
|
| {{_ 'rulesReportTitle'}}
|
||||||
|
|
||||||
li
|
li
|
||||||
a.js-report-boards(data-id="report-boards")
|
a.js-report-boards(data-id="report-boards")
|
||||||
span.emoji-icon ✨
|
| ✨
|
||||||
| {{_ 'boardsReportTitle'}}
|
| {{_ 'boardsReportTitle'}}
|
||||||
|
|
||||||
li
|
li
|
||||||
a.js-report-cards(data-id="report-cards")
|
a.js-report-cards(data-id="report-cards")
|
||||||
span.emoji-icon ✨
|
| ✨
|
||||||
| {{_ 'cardsReportTitle'}}
|
| {{_ 'cardsReportTitle'}}
|
||||||
|
|
||||||
.main-body
|
.main-body
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ template(name="people")
|
||||||
option(value="inactive") {{_ 'admin-people-filter-inactive'}}
|
option(value="inactive") {{_ 'admin-people-filter-inactive'}}
|
||||||
option(value="admin") Admin
|
option(value="admin") Admin
|
||||||
button#unlockAllUsers.unlock-all-btn
|
button#unlockAllUsers.unlock-all-btn
|
||||||
span.emoji-icon 🔓
|
| 🔓
|
||||||
| {{_ 'accounts-lockout-unlock-all'}}
|
| {{_ 'accounts-lockout-unlock-all'}}
|
||||||
.ext-box-right
|
.ext-box-right
|
||||||
span {{#unless isMiniScreen}}{{_ 'people-number'}}{{/unless}} #{peopleNumber}
|
span {{#unless isMiniScreen}}{{_ 'people-number'}}{{/unless}} #{peopleNumber}
|
||||||
|
|
@ -58,7 +58,7 @@ template(name="people")
|
||||||
| {{_ 'add'}} / {{_ 'delete'}} {{_ 'teams'}}
|
| {{_ 'add'}} / {{_ 'delete'}} {{_ 'teams'}}
|
||||||
else if lockedUsersSetting.get
|
else if lockedUsersSetting.get
|
||||||
span
|
span
|
||||||
span.emoji-icon.text-red 🔒
|
span.text-red 🔒
|
||||||
unless isMiniScreen
|
unless isMiniScreen
|
||||||
| {{_ 'accounts-lockout-locked-users'}}
|
| {{_ 'accounts-lockout-locked-users'}}
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ template(name="people")
|
||||||
| {{_ 'people'}}
|
| {{_ 'people'}}
|
||||||
li
|
li
|
||||||
a.js-locked-users-menu(data-id="locked-users-setting")
|
a.js-locked-users-menu(data-id="locked-users-setting")
|
||||||
span.emoji-icon.text-red 🔒
|
span.text-red 🔒
|
||||||
| {{_ 'accounts-lockout-locked-users'}}
|
| {{_ 'accounts-lockout-locked-users'}}
|
||||||
.main-body
|
.main-body
|
||||||
if loading.get
|
if loading.get
|
||||||
|
|
@ -247,9 +247,9 @@ template(name="peopleRow")
|
||||||
input.selectUserChkBox(type="checkbox", id="{{userData._id}}")
|
input.selectUserChkBox(type="checkbox", id="{{userData._id}}")
|
||||||
td.account-status
|
td.account-status
|
||||||
if isUserLocked
|
if isUserLocked
|
||||||
span.text-red.js-toggle-lock-status.emoji-icon(data-user-id=userData._id, data-is-locked="true", title="{{_ 'accounts-lockout-click-to-unlock'}}") 🔒
|
span.text-red.js-toggle-lock-status(data-user-id=userData._id, data-is-locked="true", title="{{_ 'accounts-lockout-click-to-unlock'}}") 🔒
|
||||||
else
|
else
|
||||||
span.text-green.js-toggle-lock-status.emoji-icon(data-user-id=userData._id, data-is-locked="false", title="{{_ 'accounts-lockout-user-unlocked'}}") 🔓
|
span.text-green.js-toggle-lock-status(data-user-id=userData._id, data-is-locked="false", title="{{_ 'accounts-lockout-user-unlocked'}}") 🔓
|
||||||
td.account-active-status
|
td.account-active-status
|
||||||
if userData.loginDisabled
|
if userData.loginDisabled
|
||||||
span.text-red.js-toggle-active-status(data-user-id=userData._id, data-is-active="false", title="{{_ 'admin-people-user-inactive'}}") 🚫
|
span.text-red.js-toggle-active-status(data-user-id=userData._id, data-is-active="false", title="{{_ 'admin-people-user-inactive'}}") 🚫
|
||||||
|
|
|
||||||
|
|
@ -137,13 +137,8 @@
|
||||||
padding: 0.5rem 0.5rem;
|
padding: 0.5rem 0.5rem;
|
||||||
}
|
}
|
||||||
.setting-content .content-body .main-body ul li a .is-checked {
|
.setting-content .content-body .main-body ul li a .is-checked {
|
||||||
border-bottom: 2px solid #3cb500;
|
border-bottom: 2px solid #2980b9;
|
||||||
border-right: 2px solid #3cb500;
|
border-right: 2px solid #2980b9;
|
||||||
}
|
|
||||||
/* Grey checkmarks when grey icons setting is enabled */
|
|
||||||
body.grey-icons-enabled .setting-content .content-body .main-body ul li a .is-checked {
|
|
||||||
border-bottom: 2px solid #7a7a7a;
|
|
||||||
border-right: 2px solid #7a7a7a;
|
|
||||||
}
|
}
|
||||||
.setting-content .content-body .main-body ul li a span {
|
.setting-content .content-body .main-body ul li a span {
|
||||||
padding: 0 0.5rem;
|
padding: 0 0.5rem;
|
||||||
|
|
|
||||||
|
|
@ -6,87 +6,87 @@ template(name="setting")
|
||||||
.content-title.ext-box
|
.content-title.ext-box
|
||||||
if isGeneralSetting
|
if isGeneralSetting
|
||||||
span
|
span
|
||||||
span.emoji-icon 🔑
|
| 🔑
|
||||||
| {{_ 'registration'}}
|
| {{_ 'registration'}}
|
||||||
else if isEmailSetting
|
else if isEmailSetting
|
||||||
span
|
span
|
||||||
span.emoji-icon ✉️
|
| ✉️
|
||||||
| {{_ 'email'}}
|
| {{_ 'email'}}
|
||||||
else if isAccountSetting
|
else if isAccountSetting
|
||||||
span
|
span
|
||||||
span.emoji-icon 👥
|
| 👥
|
||||||
| {{_ 'accounts'}}
|
| {{_ 'accounts'}}
|
||||||
else if isTableVisibilityModeSetting
|
else if isTableVisibilityModeSetting
|
||||||
span
|
span
|
||||||
span.emoji-icon 👁️
|
| 👁️
|
||||||
| {{_ 'tableVisibilityMode'}}
|
| {{_ 'tableVisibilityMode'}}
|
||||||
else if isAnnouncementSetting
|
else if isAnnouncementSetting
|
||||||
span
|
span
|
||||||
span.emoji-icon 📢
|
| 📢
|
||||||
| {{_ 'admin-announcement'}}
|
| {{_ 'admin-announcement'}}
|
||||||
else if isAccessibilitySetting
|
else if isAccessibilitySetting
|
||||||
span
|
span
|
||||||
span.emoji-icon ♿
|
| ♿
|
||||||
| {{_ 'accessibility'}}
|
| {{_ 'accessibility'}}
|
||||||
else if isLayoutSetting
|
else if isLayoutSetting
|
||||||
span
|
span
|
||||||
span.emoji-icon 🔗
|
| 🔗
|
||||||
| {{_ 'layout'}}
|
| {{_ 'layout'}}
|
||||||
else if isWebhookSetting
|
else if isWebhookSetting
|
||||||
span
|
span
|
||||||
span.emoji-icon 🌐
|
| 🌐
|
||||||
| {{_ 'global-webhook'}}
|
| {{_ 'global-webhook'}}
|
||||||
else if isAttachmentSettings
|
else if isAttachmentSettings
|
||||||
span
|
span
|
||||||
span.emoji-iconpan.emoji-icon 📎
|
| 📎
|
||||||
| {{_ 'attachments'}}
|
| {{_ 'attachments'}}
|
||||||
else if isCronSettings
|
else if isCronSettings
|
||||||
span
|
span
|
||||||
span.emoji-icon ⏰
|
| ⏰
|
||||||
| {{_ 'cron'}}
|
| {{_ 'cron'}}
|
||||||
.content-body
|
.content-body
|
||||||
.side-menu
|
.side-menu
|
||||||
ul
|
ul
|
||||||
li(class="{{#if isGeneralSetting}}active{{/if}}")
|
li(class="{{#if isGeneralSetting}}active{{/if}}")
|
||||||
a.js-setting-menu(data-id="registration-setting")
|
a.js-setting-menu(data-id="registration-setting")
|
||||||
span.emoji-icon 🔑
|
| 🔑
|
||||||
| {{_ 'registration'}}
|
| {{_ 'registration'}}
|
||||||
unless isSandstorm
|
unless isSandstorm
|
||||||
li(class="{{#if isEmailSetting}}active{{/if}}")
|
li(class="{{#if isEmailSetting}}active{{/if}}")
|
||||||
a.js-setting-menu(data-id="email-setting")
|
a.js-setting-menu(data-id="email-setting")
|
||||||
span.emoji-icon ✉️
|
| ✉️
|
||||||
| {{_ 'email'}}
|
| {{_ 'email'}}
|
||||||
li(class="{{#if isAccountSetting}}active{{/if}}")
|
li(class="{{#if isAccountSetting}}active{{/if}}")
|
||||||
a.js-setting-menu(data-id="account-setting")
|
a.js-setting-menu(data-id="account-setting")
|
||||||
span.emoji-icon 👥
|
| 👥
|
||||||
| {{_ 'accounts'}}
|
| {{_ 'accounts'}}
|
||||||
li(class="{{#if isTableVisibilityModeSetting}}active{{/if}}")
|
li(class="{{#if isTableVisibilityModeSetting}}active{{/if}}")
|
||||||
a.js-setting-menu(data-id="tableVisibilityMode-setting")
|
a.js-setting-menu(data-id="tableVisibilityMode-setting")
|
||||||
span.emoji-icon 👁️
|
| 👁️
|
||||||
| {{_ 'tableVisibilityMode'}}
|
| {{_ 'tableVisibilityMode'}}
|
||||||
li(class="{{#if isAnnouncementSetting}}active{{/if}}")
|
li(class="{{#if isAnnouncementSetting}}active{{/if}}")
|
||||||
a.js-setting-menu(data-id="announcement-setting")
|
a.js-setting-menu(data-id="announcement-setting")
|
||||||
span.emoji-icon 📢
|
| 📢
|
||||||
| {{_ 'admin-announcement'}}
|
| {{_ 'admin-announcement'}}
|
||||||
li(class="{{#if isAccessibilitySetting}}active{{/if}}")
|
li(class="{{#if isAccessibilitySetting}}active{{/if}}")
|
||||||
a.js-setting-menu(data-id="accessibility-setting")
|
a.js-setting-menu(data-id="accessibility-setting")
|
||||||
span.emoji-icon ♿
|
| ♿
|
||||||
| {{_ 'accessibility'}}
|
| {{_ 'accessibility'}}
|
||||||
li(class="{{#if isLayoutSetting}}active{{/if}}")
|
li(class="{{#if isLayoutSetting}}active{{/if}}")
|
||||||
a.js-setting-menu(data-id="layout-setting")
|
a.js-setting-menu(data-id="layout-setting")
|
||||||
span.emoji-icon 🔗
|
| 🔗
|
||||||
| {{_ 'layout'}}
|
| {{_ 'layout'}}
|
||||||
li(class="{{#if isWebhookSetting}}active{{/if}}")
|
li(class="{{#if isWebhookSetting}}active{{/if}}")
|
||||||
a.js-setting-menu(data-id="webhook-setting")
|
a.js-setting-menu(data-id="webhook-setting")
|
||||||
span.emoji-icon 🌐
|
| 🌐
|
||||||
| {{_ 'global-webhook'}}
|
| {{_ 'global-webhook'}}
|
||||||
li(class="{{#if isAttachmentSettings}}active{{/if}}")
|
li(class="{{#if isAttachmentSettings}}active{{/if}}")
|
||||||
a.js-setting-menu(data-id="attachment-settings")
|
a.js-setting-menu(data-id="attachment-settings")
|
||||||
span.emoji-icon 📎
|
| 📎
|
||||||
| {{_ 'attachments'}}
|
| {{_ 'attachments'}}
|
||||||
li(class="{{#if isCronSettings}}active{{/if}}")
|
li(class="{{#if isCronSettings}}active{{/if}}")
|
||||||
a.js-setting-menu(data-id="cron-settings")
|
a.js-setting-menu(data-id="cron-settings")
|
||||||
span.emoji-icon ⏰
|
| ⏰
|
||||||
| {{_ 'cron'}}
|
| {{_ 'cron'}}
|
||||||
.main-body
|
.main-body
|
||||||
if isLoading
|
if isLoading
|
||||||
|
|
@ -170,10 +170,7 @@ template(name="setting")
|
||||||
label {{_ 'migration-status'}}
|
label {{_ 'migration-status'}}
|
||||||
.status-indicator
|
.status-indicator
|
||||||
span.status-label {{_ 'status'}}:
|
span.status-label {{_ 'status'}}:
|
||||||
span.status-value {{#if isMigrating}}{{migrationStatus}}{{else}}{{_ 'idle'}}{{/if}}
|
span.status-value {{migrationStatus}}
|
||||||
.current-step(class="{{#unless migrationCurrentStep}}hide{{/unless}}")
|
|
||||||
span.step-label {{_ 'current-step'}}:
|
|
||||||
span.step-value {{migrationCurrentStep}}
|
|
||||||
.progress-section
|
.progress-section
|
||||||
.progress
|
.progress
|
||||||
.progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100")
|
.progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100")
|
||||||
|
|
@ -182,13 +179,9 @@ template(name="setting")
|
||||||
| {{migrationProgress}}% {{_ 'complete'}}
|
| {{migrationProgress}}% {{_ 'complete'}}
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
button.js-start-all-migrations.btn.btn-primary {{#if isMigrating}}disabled{{/if}} {{_ 'start-all-migrations'}}
|
button.js-start-all-migrations.btn.btn-primary {{_ 'start-all-migrations'}}
|
||||||
button.js-pause-all-migrations.btn.btn-warning {{#unless isMigrating}}disabled{{/unless}} {{_ 'pause-all-migrations'}}
|
button.js-pause-all-migrations.btn.btn-warning {{_ 'pause-all-migrations'}}
|
||||||
button.js-stop-all-migrations.btn.btn-danger {{#unless isMigrating}}disabled{{/unless}} {{_ 'stop-all-migrations'}}
|
button.js-stop-all-migrations.btn.btn-danger {{_ 'stop-all-migrations'}}
|
||||||
|
|
||||||
li
|
|
||||||
h3 {{_ 'migration-steps'}}
|
|
||||||
p Migration steps section temporarily removed
|
|
||||||
|
|
||||||
li
|
li
|
||||||
h3 {{_ 'board-operations'}}
|
h3 {{_ 'board-operations'}}
|
||||||
|
|
@ -207,7 +200,7 @@ template(name="setting")
|
||||||
.job-info
|
.job-info
|
||||||
.job-name {{name}}
|
.job-name {{name}}
|
||||||
.job-schedule {{schedule}}
|
.job-schedule {{schedule}}
|
||||||
.job-status {{status}}
|
.job-description {{description}}
|
||||||
.job-actions
|
.job-actions
|
||||||
button.js-pause-job.btn.btn-sm.btn-warning(data-job-id="{{_id}}") {{_ 'pause'}}
|
button.js-pause-job.btn.btn-sm.btn-warning(data-job-id="{{_id}}") {{_ 'pause'}}
|
||||||
button.js-delete-job.btn.btn-sm.btn-danger(data-job-id="{{_id}}") {{_ 'delete'}}
|
button.js-delete-job.btn.btn-sm.btn-danger(data-job-id="{{_id}}") {{_ 'delete'}}
|
||||||
|
|
@ -281,7 +274,7 @@ template(name='email')
|
||||||
// li.smtp-form
|
// li.smtp-form
|
||||||
// .title {{_ 'smtp-username'}}
|
// .title {{_ 'smtp-username'}}
|
||||||
// .form-group
|
// .form-group
|
||||||
// input.wekan-form-control#mail-server-username(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}")
|
// input.wekan-form-control#mail-server-u"accounts-allowUserNameChange": "Allow Username Change",sername(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}")
|
||||||
// li.smtp-form
|
// li.smtp-form
|
||||||
// .title {{_ 'smtp-password'}}
|
// .title {{_ 'smtp-password'}}
|
||||||
// .form-group
|
// .form-group
|
||||||
|
|
@ -382,29 +375,6 @@ template(name='layoutSettings')
|
||||||
ul#layout-setting.setting-detail
|
ul#layout-setting.setting-detail
|
||||||
li
|
li
|
||||||
button.js-all-boards-hide-activities.primary {{_ 'hide-activities-of-all-boards'}}
|
button.js-all-boards-hide-activities.primary {{_ 'hide-activities-of-all-boards'}}
|
||||||
li
|
|
||||||
a(href="/support" style="text-decoration: underline; color: blue;") {{_ 'support'}}
|
|
||||||
li
|
|
||||||
a.flex.js-toggle-support
|
|
||||||
.materialCheckBox(class="{{#if currentSetting.supportPageEnabled}}is-checked{{/if}}")
|
|
||||||
|
|
||||||
span {{_ 'support-page-enabled'}}
|
|
||||||
li
|
|
||||||
.support-content(class="{{#if currentSetting.supportPageEnabled}}{{else}}hide{{/if}}")
|
|
||||||
ul
|
|
||||||
li
|
|
||||||
a.flex.js-toggle-support-public
|
|
||||||
.materialCheckBox(class="{{#if currentSetting.supportPagePublic}}is-checked{{/if}}")
|
|
||||||
|
|
||||||
span {{_ 'public'}}
|
|
||||||
li
|
|
||||||
.title {{_ 'support-title'}}
|
|
||||||
textarea#support-title.wekan-form-control= currentSetting.supportTitle
|
|
||||||
li
|
|
||||||
.title {{_ 'support-content'}}
|
|
||||||
textarea#support-page-text.wekan-form-control= currentSetting.supportPageText
|
|
||||||
li
|
|
||||||
button.js-support-save.primary {{_ 'save'}}
|
|
||||||
li.layout-form
|
li.layout-form
|
||||||
.title {{_ 'oidc-button-text'}}
|
.title {{_ 'oidc-button-text'}}
|
||||||
.form-group
|
.form-group
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,6 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
||||||
import { TAPi18n } from '/imports/i18n';
|
import { TAPi18n } from '/imports/i18n';
|
||||||
import { ALLOWED_WAIT_SPINNERS } from '/config/const';
|
import { ALLOWED_WAIT_SPINNERS } from '/config/const';
|
||||||
import LockoutSettings from '/models/lockoutSettings';
|
import LockoutSettings from '/models/lockoutSettings';
|
||||||
import {
|
|
||||||
cronMigrationProgress,
|
|
||||||
cronMigrationStatus,
|
|
||||||
cronMigrationCurrentStep,
|
|
||||||
cronMigrationSteps,
|
|
||||||
cronIsMigrating,
|
|
||||||
cronJobs
|
|
||||||
} from '/imports/cronMigrationClient';
|
|
||||||
|
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
BlazeComponent.extendComponent({
|
||||||
|
|
@ -123,27 +115,15 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
// Cron settings helpers
|
// Cron settings helpers
|
||||||
migrationStatus() {
|
migrationStatus() {
|
||||||
return cronMigrationStatus.get() || TAPi18n.__('idle');
|
return TAPi18n.__('idle'); // Placeholder
|
||||||
},
|
},
|
||||||
|
|
||||||
migrationProgress() {
|
migrationProgress() {
|
||||||
return cronMigrationProgress.get() || 0;
|
return 0; // Placeholder
|
||||||
},
|
|
||||||
|
|
||||||
migrationCurrentStep() {
|
|
||||||
return cronMigrationCurrentStep.get() || '';
|
|
||||||
},
|
|
||||||
|
|
||||||
isMigrating() {
|
|
||||||
return cronIsMigrating.get() || false;
|
|
||||||
},
|
|
||||||
|
|
||||||
migrationSteps() {
|
|
||||||
return cronMigrationSteps.get() || [];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cronJobs() {
|
cronJobs() {
|
||||||
return cronJobs.get() || [];
|
return []; // Placeholder
|
||||||
},
|
},
|
||||||
|
|
||||||
setLoading(w) {
|
setLoading(w) {
|
||||||
|
|
@ -189,9 +169,7 @@ BlazeComponent.extendComponent({
|
||||||
// Event handlers for cron settings
|
// Event handlers for cron settings
|
||||||
'click button.js-start-all-migrations'(event) {
|
'click button.js-start-all-migrations'(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.setLoading(true);
|
Meteor.call('startAllMigrations', (error, result) => {
|
||||||
Meteor.call('cron.startAllMigrations', (error, result) => {
|
|
||||||
this.setLoading(false);
|
|
||||||
if (error) {
|
if (error) {
|
||||||
alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason);
|
alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -202,9 +180,7 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
'click button.js-pause-all-migrations'(event) {
|
'click button.js-pause-all-migrations'(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.setLoading(true);
|
Meteor.call('pauseAllMigrations', (error, result) => {
|
||||||
Meteor.call('cron.pauseAllMigrations', (error, result) => {
|
|
||||||
this.setLoading(false);
|
|
||||||
if (error) {
|
if (error) {
|
||||||
alert(TAPi18n.__('migration-pause-failed') + ': ' + error.reason);
|
alert(TAPi18n.__('migration-pause-failed') + ': ' + error.reason);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -216,9 +192,7 @@ BlazeComponent.extendComponent({
|
||||||
'click button.js-stop-all-migrations'(event) {
|
'click button.js-stop-all-migrations'(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (confirm(TAPi18n.__('migration-stop-confirm'))) {
|
if (confirm(TAPi18n.__('migration-stop-confirm'))) {
|
||||||
this.setLoading(true);
|
Meteor.call('stopAllMigrations', (error, result) => {
|
||||||
Meteor.call('cron.stopAllMigrations', (error, result) => {
|
|
||||||
this.setLoading(false);
|
|
||||||
if (error) {
|
if (error) {
|
||||||
alert(TAPi18n.__('migration-stop-failed') + ': ' + error.reason);
|
alert(TAPi18n.__('migration-stop-failed') + ': ' + error.reason);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -230,28 +204,41 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
'click button.js-schedule-board-cleanup'(event) {
|
'click button.js-schedule-board-cleanup'(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// Placeholder - board cleanup scheduling
|
Meteor.call('scheduleBoardCleanup', (error, result) => {
|
||||||
alert(TAPi18n.__('board-cleanup-scheduled'));
|
if (error) {
|
||||||
|
alert(TAPi18n.__('board-cleanup-failed') + ': ' + error.reason);
|
||||||
|
} else {
|
||||||
|
alert(TAPi18n.__('board-cleanup-scheduled'));
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
'click button.js-schedule-board-archive'(event) {
|
'click button.js-schedule-board-archive'(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// Placeholder - board archive scheduling
|
Meteor.call('scheduleBoardArchive', (error, result) => {
|
||||||
alert(TAPi18n.__('board-archive-scheduled'));
|
if (error) {
|
||||||
|
alert(TAPi18n.__('board-archive-failed') + ': ' + error.reason);
|
||||||
|
} else {
|
||||||
|
alert(TAPi18n.__('board-archive-scheduled'));
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
'click button.js-schedule-board-backup'(event) {
|
'click button.js-schedule-board-backup'(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// Placeholder - board backup scheduling
|
Meteor.call('scheduleBoardBackup', (error, result) => {
|
||||||
alert(TAPi18n.__('board-backup-scheduled'));
|
if (error) {
|
||||||
|
alert(TAPi18n.__('board-backup-failed') + ': ' + error.reason);
|
||||||
|
} else {
|
||||||
|
alert(TAPi18n.__('board-backup-scheduled'));
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
'click button.js-pause-job'(event) {
|
'click button.js-pause-job'(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const jobId = $(event.target).data('job-id');
|
const jobId = $(event.target).data('job-id');
|
||||||
this.setLoading(true);
|
Meteor.call('pauseCronJob', jobId, (error, result) => {
|
||||||
Meteor.call('cron.pauseJob', jobId, (error, result) => {
|
|
||||||
this.setLoading(false);
|
|
||||||
if (error) {
|
if (error) {
|
||||||
alert(TAPi18n.__('cron-job-pause-failed') + ': ' + error.reason);
|
alert(TAPi18n.__('cron-job-pause-failed') + ': ' + error.reason);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -264,9 +251,7 @@ BlazeComponent.extendComponent({
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const jobId = $(event.target).data('job-id');
|
const jobId = $(event.target).data('job-id');
|
||||||
if (confirm(TAPi18n.__('cron-job-delete-confirm'))) {
|
if (confirm(TAPi18n.__('cron-job-delete-confirm'))) {
|
||||||
this.setLoading(true);
|
Meteor.call('deleteCronJob', jobId, (error, result) => {
|
||||||
Meteor.call('cron.removeJob', jobId, (error, result) => {
|
|
||||||
this.setLoading(false);
|
|
||||||
if (error) {
|
if (error) {
|
||||||
alert(TAPi18n.__('cron-job-delete-failed') + ': ' + error.reason);
|
alert(TAPi18n.__('cron-job-delete-failed') + ': ' + error.reason);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -533,45 +518,6 @@ BlazeComponent.extendComponent({
|
||||||
DocHead.setTitle(productName);
|
DocHead.setTitle(productName);
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleSupportPage() {
|
|
||||||
this.setLoading(true);
|
|
||||||
const supportPageEnabled = !$('.js-toggle-support .materialCheckBox').hasClass('is-checked');
|
|
||||||
$('.js-toggle-support .materialCheckBox').toggleClass('is-checked');
|
|
||||||
$('.support-content').toggleClass('hide');
|
|
||||||
Settings.update(Settings.findOne()._id, {
|
|
||||||
$set: { supportPageEnabled },
|
|
||||||
});
|
|
||||||
this.setLoading(false);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleSupportPublic() {
|
|
||||||
this.setLoading(true);
|
|
||||||
const supportPagePublic = !$('.js-toggle-support-public .materialCheckBox').hasClass('is-checked');
|
|
||||||
$('.js-toggle-support-public .materialCheckBox').toggleClass('is-checked');
|
|
||||||
Settings.update(Settings.findOne()._id, {
|
|
||||||
$set: { supportPagePublic },
|
|
||||||
});
|
|
||||||
this.setLoading(false);
|
|
||||||
},
|
|
||||||
|
|
||||||
saveSupportSettings() {
|
|
||||||
this.setLoading(true);
|
|
||||||
const supportTitle = ($('#support-title').val() || '').trim();
|
|
||||||
const supportPageText = ($('#support-page-text').val() || '').trim();
|
|
||||||
try {
|
|
||||||
Settings.update(Settings.findOne()._id, {
|
|
||||||
$set: {
|
|
||||||
supportTitle,
|
|
||||||
supportPageText,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
this.setLoading(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
sendSMTPTestEmail() {
|
sendSMTPTestEmail() {
|
||||||
Meteor.call('sendSMTPTestEmail', (err, ret) => {
|
Meteor.call('sendSMTPTestEmail', (err, ret) => {
|
||||||
if (!err && ret) {
|
if (!err && ret) {
|
||||||
|
|
@ -600,9 +546,6 @@ BlazeComponent.extendComponent({
|
||||||
'click a.js-toggle-hide-card-counter-list': this.toggleHideCardCounterList,
|
'click a.js-toggle-hide-card-counter-list': this.toggleHideCardCounterList,
|
||||||
'click a.js-toggle-hide-board-member-list': this.toggleHideBoardMemberList,
|
'click a.js-toggle-hide-board-member-list': this.toggleHideBoardMemberList,
|
||||||
'click button.js-save-layout': this.saveLayout,
|
'click button.js-save-layout': this.saveLayout,
|
||||||
'click a.js-toggle-support': this.toggleSupportPage,
|
|
||||||
'click a.js-toggle-support-public': this.toggleSupportPublic,
|
|
||||||
'click button.js-support-save': this.saveSupportSettings,
|
|
||||||
'click a.js-toggle-display-authentication-method': this
|
'click a.js-toggle-display-authentication-method': this
|
||||||
.toggleDisplayAuthenticationMethod,
|
.toggleDisplayAuthenticationMethod,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,31 +5,31 @@ template(name="settingHeaderBar")
|
||||||
.setting-header-btns.left
|
.setting-header-btns.left
|
||||||
if currentUser
|
if currentUser
|
||||||
a.setting-header-btn.settings(href="{{pathFor 'setting'}}")
|
a.setting-header-btn.settings(href="{{pathFor 'setting'}}")
|
||||||
span.emoji-icon ⚙️
|
| ⚙️
|
||||||
span {{_ 'settings'}}
|
span {{_ 'settings'}}
|
||||||
|
|
||||||
a.setting-header-btn.people(href="{{pathFor 'people'}}")
|
a.setting-header-btn.people(href="{{pathFor 'people'}}")
|
||||||
span.emoji-icon 👥
|
| 👥
|
||||||
span {{_ 'people'}}
|
span {{_ 'people'}}
|
||||||
|
|
||||||
a.setting-header-btn.informations(href="{{pathFor 'admin-reports'}}")
|
a.setting-header-btn.informations(href="{{pathFor 'admin-reports'}}")
|
||||||
span.emoji-icon 📋
|
| 📋
|
||||||
span {{_ 'reports'}}
|
span {{_ 'reports'}}
|
||||||
|
|
||||||
a.setting-header-btn.informations(href="{{pathFor 'attachments'}}")
|
a.setting-header-btn.informations(href="{{pathFor 'attachments'}}")
|
||||||
span.emoji-icon 📎
|
| 📎
|
||||||
span {{_ 'attachments'}}
|
span {{_ 'attachments'}}
|
||||||
|
|
||||||
a.setting-header-btn.informations(href="{{pathFor 'translation'}}")
|
a.setting-header-btn.informations(href="{{pathFor 'translation'}}")
|
||||||
span.emoji-icon 🔤
|
| 🔤
|
||||||
span {{_ 'translation'}}
|
span {{_ 'translation'}}
|
||||||
|
|
||||||
a.setting-header-btn.informations(href="{{pathFor 'information'}}")
|
a.setting-header-btn.informations(href="{{pathFor 'information'}}")
|
||||||
span.emoji-icon ℹ️
|
| ℹ️
|
||||||
span {{_ 'info'}}
|
span {{_ 'info'}}
|
||||||
|
|
||||||
else
|
else
|
||||||
a.setting-header-btn.js-log-in(
|
a.setting-header-btn.js-log-in(
|
||||||
title="{{_ 'log-in'}}")
|
title="{{_ 'log-in'}}")
|
||||||
span.emoji-icon 🚪
|
| 🚪
|
||||||
span {{_ 'log-in'}}
|
span {{_ 'log-in'}}
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ Template.newTranslationPopup.events({
|
||||||
Template.settingsTranslationPopup.events({
|
Template.settingsTranslationPopup.events({
|
||||||
'click #deleteButton'(event) {
|
'click #deleteButton'(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
Meteor.call('deleteTranslation', this.translationId);
|
Translation.remove(this.translationId);
|
||||||
Popup.back();
|
Popup.back();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -68,14 +68,6 @@
|
||||||
transform-origin: 100% 100% !important;
|
transform-origin: 100% 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Grey checkmarks when grey icons setting is enabled */
|
|
||||||
body.grey-icons-enabled .sidebar .materialCheckBox.is-checked,
|
|
||||||
body.grey-icons-enabled .boardCardSettingsPopup .materialCheckBox.is-checked,
|
|
||||||
body.grey-icons-enabled .boardSubtaskSettingsPopup .materialCheckBox.is-checked {
|
|
||||||
border-bottom: 2px solid #7a7a7a !important;
|
|
||||||
border-right: 2px solid #7a7a7a !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Card Settings 3-column grid layout */
|
/* Card Settings 3-column grid layout */
|
||||||
.card-settings-grid {
|
.card-settings-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
@ -138,11 +130,6 @@ body.grey-icons-enabled .boardSubtaskSettingsPopup .materialCheckBox.is-checked
|
||||||
}
|
}
|
||||||
.sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-check {
|
.sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-check {
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
color: #3cb500;
|
|
||||||
}
|
|
||||||
/* Grey check icons when grey icons setting is enabled */
|
|
||||||
body.grey-icons-enabled .sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-check {
|
|
||||||
color: #7a7a7a;
|
|
||||||
}
|
}
|
||||||
.sidebar .sidebar-content ul.sidebar-list li .minicard {
|
.sidebar .sidebar-content ul.sidebar-list li .minicard {
|
||||||
padding: 6px 8px 4px;
|
padding: 6px 8px 4px;
|
||||||
|
|
@ -162,9 +149,6 @@ body.grey-icons-enabled .sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background: #e6e6e6;
|
background: #e6e6e6;
|
||||||
}
|
}
|
||||||
.sidebar .sidebar-content .sidebar-btn * {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.sidebar .sidebar-content .sidebar-btn:hover * {
|
.sidebar .sidebar-content .sidebar-btn:hover * {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ template(name="boardChangeColorPopup")
|
||||||
span.background-box(class="board-color-{{this}}")
|
span.background-box(class="board-color-{{this}}")
|
||||||
span {{this}}
|
span {{this}}
|
||||||
if isSelected
|
if isSelected
|
||||||
span.checkmark-no-grey ✅
|
| ✅
|
||||||
|
|
||||||
template(name="boardChangeBackgroundImagePopup")
|
template(name="boardChangeBackgroundImagePopup")
|
||||||
form
|
form
|
||||||
|
|
@ -575,8 +575,7 @@ template(name="boardMenuPopup")
|
||||||
if currentUser.isBoardAdmin
|
if currentUser.isBoardAdmin
|
||||||
li
|
li
|
||||||
a.js-open-rules-view(title="{{_ 'rules'}}")
|
a.js-open-rules-view(title="{{_ 'rules'}}")
|
||||||
span.emoji-icon
|
| ✨
|
||||||
| ✨
|
|
||||||
| {{_ 'rules'}}
|
| {{_ 'rules'}}
|
||||||
if currentUser.isBoardAdmin
|
if currentUser.isBoardAdmin
|
||||||
li
|
li
|
||||||
|
|
@ -588,6 +587,10 @@ template(name="boardMenuPopup")
|
||||||
| 📦
|
| 📦
|
||||||
| {{_ 'archived-items'}}
|
| {{_ 'archived-items'}}
|
||||||
if currentUser.isBoardAdmin
|
if currentUser.isBoardAdmin
|
||||||
|
li
|
||||||
|
a.js-open-migrations
|
||||||
|
| 🔧
|
||||||
|
| {{_ 'migrations'}}
|
||||||
li
|
li
|
||||||
a.js-change-board-color
|
a.js-change-board-color
|
||||||
| 🎨
|
| 🎨
|
||||||
|
|
@ -634,8 +637,7 @@ template(name="boardMenuPopup")
|
||||||
// | {{_ 'delete-duplicate-lists'}}
|
// | {{_ 'delete-duplicate-lists'}}
|
||||||
li
|
li
|
||||||
a.js-archive-board
|
a.js-archive-board
|
||||||
span.emoji-icon
|
| ➡️📦
|
||||||
| ➡️📦
|
|
||||||
| {{_ 'archive-board'}}
|
| {{_ 'archive-board'}}
|
||||||
|
|
||||||
template(name="exportBoard")
|
template(name="exportBoard")
|
||||||
|
|
@ -807,12 +809,6 @@ template(name="changePermissionsPopup")
|
||||||
if isNormal
|
if isNormal
|
||||||
| ✅
|
| ✅
|
||||||
span.sub-name {{_ 'normal-desc'}}
|
span.sub-name {{_ 'normal-desc'}}
|
||||||
li
|
|
||||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-normal-assigned-only{{/if}}")
|
|
||||||
| {{_ 'normal-assigned-only'}}
|
|
||||||
if isNormalAssignedOnly
|
|
||||||
| ✅
|
|
||||||
span.sub-name {{_ 'normal-assigned-only-desc'}}
|
|
||||||
li
|
li
|
||||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-no-comments{{/if}}")
|
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-no-comments{{/if}}")
|
||||||
| {{_ 'no-comments'}}
|
| {{_ 'no-comments'}}
|
||||||
|
|
@ -825,24 +821,6 @@ template(name="changePermissionsPopup")
|
||||||
if isCommentOnly
|
if isCommentOnly
|
||||||
| ✅
|
| ✅
|
||||||
span.sub-name {{_ 'comment-only-desc'}}
|
span.sub-name {{_ 'comment-only-desc'}}
|
||||||
li
|
|
||||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-comment-assigned-only{{/if}}")
|
|
||||||
| {{_ 'comment-assigned-only'}}
|
|
||||||
if isCommentAssignedOnly
|
|
||||||
| ✅
|
|
||||||
span.sub-name {{_ 'comment-assigned-only-desc'}}
|
|
||||||
li
|
|
||||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-read-only{{/if}}")
|
|
||||||
| {{_ 'read-only'}}
|
|
||||||
if isReadOnly
|
|
||||||
| ✅
|
|
||||||
span.sub-name {{_ 'read-only-desc'}}
|
|
||||||
li
|
|
||||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-read-assigned-only{{/if}}")
|
|
||||||
| {{_ 'read-assigned-only'}}
|
|
||||||
if isReadAssignedOnly
|
|
||||||
| ✅
|
|
||||||
span.sub-name {{_ 'read-assigned-only-desc'}}
|
|
||||||
li
|
li
|
||||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-worker{{/if}}")
|
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-worker{{/if}}")
|
||||||
| {{_ 'worker'}}
|
| {{_ 'worker'}}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const viewTitles = {
|
||||||
multiselection: 'multi-selection',
|
multiselection: 'multi-selection',
|
||||||
customFields: 'custom-fields',
|
customFields: 'custom-fields',
|
||||||
archives: 'archives',
|
archives: 'archives',
|
||||||
|
migrations: 'migrations',
|
||||||
};
|
};
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
BlazeComponent.extendComponent({
|
||||||
|
|
@ -238,20 +239,8 @@ Template.memberPopup.helpers({
|
||||||
const commentOnly = currentBoard.hasCommentOnly(this.userId);
|
const commentOnly = currentBoard.hasCommentOnly(this.userId);
|
||||||
const noComments = currentBoard.hasNoComments(this.userId);
|
const noComments = currentBoard.hasNoComments(this.userId);
|
||||||
const worker = currentBoard.hasWorker(this.userId);
|
const worker = currentBoard.hasWorker(this.userId);
|
||||||
const normalAssignedOnly = currentBoard.hasNormalAssignedOnly(this.userId);
|
if (commentOnly) {
|
||||||
const commentAssignedOnly = currentBoard.hasCommentAssignedOnly(this.userId);
|
|
||||||
const readOnly = currentBoard.hasReadOnly(this.userId);
|
|
||||||
const readAssignedOnly = currentBoard.hasReadAssignedOnly(this.userId);
|
|
||||||
if (readAssignedOnly) {
|
|
||||||
return TAPi18n.__('read-assigned-only');
|
|
||||||
} else if (readOnly) {
|
|
||||||
return TAPi18n.__('read-only');
|
|
||||||
} else if (commentAssignedOnly) {
|
|
||||||
return TAPi18n.__('comment-assigned-only');
|
|
||||||
} else if (commentOnly) {
|
|
||||||
return TAPi18n.__('comment-only');
|
return TAPi18n.__('comment-only');
|
||||||
} else if (normalAssignedOnly) {
|
|
||||||
return TAPi18n.__('normal-assigned-only');
|
|
||||||
} else if (noComments) {
|
} else if (noComments) {
|
||||||
return TAPi18n.__('no-comments');
|
return TAPi18n.__('no-comments');
|
||||||
} else if (worker) {
|
} else if (worker) {
|
||||||
|
|
@ -283,6 +272,10 @@ Template.boardMenuPopup.events({
|
||||||
Sidebar.setView('archives');
|
Sidebar.setView('archives');
|
||||||
Popup.back();
|
Popup.back();
|
||||||
},
|
},
|
||||||
|
'click .js-open-migrations'() {
|
||||||
|
Sidebar.setView('migrations');
|
||||||
|
Popup.back();
|
||||||
|
},
|
||||||
'click .js-change-board-color': Popup.open('boardChangeColor'),
|
'click .js-change-board-color': Popup.open('boardChangeColor'),
|
||||||
'click .js-change-background-image': Popup.open('boardChangeBackgroundImage'),
|
'click .js-change-background-image': Popup.open('boardChangeBackgroundImage'),
|
||||||
'click .js-board-info-on-my-boards': Popup.open('boardInfoOnMyBoards'),
|
'click .js-board-info-on-my-boards': Popup.open('boardInfoOnMyBoards'),
|
||||||
|
|
@ -401,7 +394,6 @@ Template.memberPopup.events({
|
||||||
FlowRouter.go('home');
|
FlowRouter.go('home');
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.removeMemberPopup.helpers({
|
Template.removeMemberPopup.helpers({
|
||||||
|
|
@ -1933,7 +1925,7 @@ Template.removeBoardTeamPopup.helpers({
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.changePermissionsPopup.events({
|
Template.changePermissionsPopup.events({
|
||||||
'click .js-set-admin, click .js-set-normal, click .js-set-normal-assigned-only, click .js-set-no-comments, click .js-set-comment-only, click .js-set-comment-assigned-only, click .js-set-read-only, click .js-set-read-assigned-only, click .js-set-worker'(
|
'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only, click .js-set-worker'(
|
||||||
event,
|
event,
|
||||||
) {
|
) {
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
|
|
@ -1942,14 +1934,6 @@ Template.changePermissionsPopup.events({
|
||||||
const isCommentOnly = $(event.currentTarget).hasClass(
|
const isCommentOnly = $(event.currentTarget).hasClass(
|
||||||
'js-set-comment-only',
|
'js-set-comment-only',
|
||||||
);
|
);
|
||||||
const isNormalAssignedOnly = $(event.currentTarget).hasClass(
|
|
||||||
'js-set-normal-assigned-only',
|
|
||||||
);
|
|
||||||
const isCommentAssignedOnly = $(event.currentTarget).hasClass(
|
|
||||||
'js-set-comment-assigned-only',
|
|
||||||
);
|
|
||||||
const isReadOnly = $(event.currentTarget).hasClass('js-set-read-only');
|
|
||||||
const isReadAssignedOnly = $(event.currentTarget).hasClass('js-set-read-assigned-only');
|
|
||||||
const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments');
|
const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments');
|
||||||
const isWorker = $(event.currentTarget).hasClass('js-set-worker');
|
const isWorker = $(event.currentTarget).hasClass('js-set-worker');
|
||||||
currentBoard.setMemberPermission(
|
currentBoard.setMemberPermission(
|
||||||
|
|
@ -1958,10 +1942,6 @@ Template.changePermissionsPopup.events({
|
||||||
isNoComments,
|
isNoComments,
|
||||||
isCommentOnly,
|
isCommentOnly,
|
||||||
isWorker,
|
isWorker,
|
||||||
isNormalAssignedOnly,
|
|
||||||
isCommentAssignedOnly,
|
|
||||||
isReadOnly,
|
|
||||||
isReadAssignedOnly,
|
|
||||||
);
|
);
|
||||||
Popup.back(1);
|
Popup.back(1);
|
||||||
},
|
},
|
||||||
|
|
@ -1979,22 +1959,10 @@ Template.changePermissionsPopup.helpers({
|
||||||
!currentBoard.hasAdmin(this.userId) &&
|
!currentBoard.hasAdmin(this.userId) &&
|
||||||
!currentBoard.hasNoComments(this.userId) &&
|
!currentBoard.hasNoComments(this.userId) &&
|
||||||
!currentBoard.hasCommentOnly(this.userId) &&
|
!currentBoard.hasCommentOnly(this.userId) &&
|
||||||
!currentBoard.hasNormalAssignedOnly(this.userId) &&
|
|
||||||
!currentBoard.hasCommentAssignedOnly(this.userId) &&
|
|
||||||
!currentBoard.hasReadOnly(this.userId) &&
|
|
||||||
!currentBoard.hasReadAssignedOnly(this.userId) &&
|
|
||||||
!currentBoard.hasWorker(this.userId)
|
!currentBoard.hasWorker(this.userId)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
isNormalAssignedOnly() {
|
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
|
||||||
return (
|
|
||||||
!currentBoard.hasAdmin(this.userId) &&
|
|
||||||
currentBoard.hasNormalAssignedOnly(this.userId)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
isNoComments() {
|
isNoComments() {
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
return (
|
return (
|
||||||
|
|
@ -2011,30 +1979,6 @@ Template.changePermissionsPopup.helpers({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
isCommentAssignedOnly() {
|
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
|
||||||
return (
|
|
||||||
!currentBoard.hasAdmin(this.userId) &&
|
|
||||||
currentBoard.hasCommentAssignedOnly(this.userId)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
isReadOnly() {
|
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
|
||||||
return (
|
|
||||||
!currentBoard.hasAdmin(this.userId) &&
|
|
||||||
currentBoard.hasReadOnly(this.userId)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
isReadAssignedOnly() {
|
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
|
||||||
return (
|
|
||||||
!currentBoard.hasAdmin(this.userId) &&
|
|
||||||
currentBoard.hasReadAssignedOnly(this.userId)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
isWorker() {
|
isWorker() {
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ template(name="filterSidebar")
|
||||||
else
|
else
|
||||||
span.quiet {{_ "label-default" (_ (concat "color-" color))}}
|
span.quiet {{_ "label-default" (_ (concat "color-" color))}}
|
||||||
if Filter.labelIds.isSelected _id
|
if Filter.labelIds.isSelected _id
|
||||||
| ✅
|
i.fa.fa-check
|
||||||
hr
|
hr
|
||||||
h3
|
h3
|
||||||
| 👥
|
| 👥
|
||||||
|
|
@ -68,7 +68,7 @@ template(name="filterSidebar")
|
||||||
span.sidebar-list-item-description
|
span.sidebar-list-item-description
|
||||||
| {{_ 'filter-no-assignee'}}
|
| {{_ 'filter-no-assignee'}}
|
||||||
if Filter.assignees.isSelected undefined
|
if Filter.assignees.isSelected undefined
|
||||||
| ✅
|
i.fa.fa-check
|
||||||
each currentBoard.activeMembers
|
each currentBoard.activeMembers
|
||||||
with getUser userId
|
with getUser userId
|
||||||
li(class="{{#if Filter.assignees.isSelected _id}}active{{/if}}")
|
li(class="{{#if Filter.assignees.isSelected _id}}active{{/if}}")
|
||||||
|
|
@ -90,37 +90,37 @@ template(name="filterSidebar")
|
||||||
span.sidebar-list-item-description
|
span.sidebar-list-item-description
|
||||||
| {{_ 'filter-no-due-date' }}
|
| {{_ 'filter-no-due-date' }}
|
||||||
if Filter.dueAt.isSelected 'noDate'
|
if Filter.dueAt.isSelected 'noDate'
|
||||||
| ✅
|
i.fa.fa-check
|
||||||
li(class="{{#if Filter.dueAt.isSelected 'past'}}active{{/if}}")
|
li(class="{{#if Filter.dueAt.isSelected 'past'}}active{{/if}}")
|
||||||
a.name.js-toggle-overdue-filter
|
a.name.js-toggle-overdue-filter
|
||||||
span.sidebar-list-item-description
|
span.sidebar-list-item-description
|
||||||
| {{_ 'filter-overdue' }}
|
| {{_ 'filter-overdue' }}
|
||||||
if Filter.dueAt.isSelected 'past'
|
if Filter.dueAt.isSelected 'past'
|
||||||
| ✅
|
i.fa.fa-check
|
||||||
li(class="{{#if Filter.dueAt.isSelected 'today'}}active{{/if}}")
|
li(class="{{#if Filter.dueAt.isSelected 'today'}}active{{/if}}")
|
||||||
a.name.js-toggle-due-today-filter
|
a.name.js-toggle-due-today-filter
|
||||||
span.sidebar-list-item-description
|
span.sidebar-list-item-description
|
||||||
| {{_ 'filter-due-today' }}
|
| {{_ 'filter-due-today' }}
|
||||||
if Filter.dueAt.isSelected 'today'
|
if Filter.dueAt.isSelected 'today'
|
||||||
| ✅
|
i.fa.fa-check
|
||||||
li(class="{{#if Filter.dueAt.isSelected 'tomorrow'}}active{{/if}}")
|
li(class="{{#if Filter.dueAt.isSelected 'tomorrow'}}active{{/if}}")
|
||||||
a.name.js-toggle-due-tomorrow-filter
|
a.name.js-toggle-due-tomorrow-filter
|
||||||
span.sidebar-list-item-description
|
span.sidebar-list-item-description
|
||||||
| {{_ 'filter-due-tomorrow' }}
|
| {{_ 'filter-due-tomorrow' }}
|
||||||
if Filter.dueAt.isSelected 'tomorrow'
|
if Filter.dueAt.isSelected 'tomorrow'
|
||||||
| ✅
|
i.fa.fa-check
|
||||||
li(class="{{#if Filter.dueAt.isSelected 'thisweek'}}active{{/if}}")
|
li(class="{{#if Filter.dueAt.isSelected 'thisweek'}}active{{/if}}")
|
||||||
a.name.js-toggle-due-this-week-filter
|
a.name.js-toggle-due-this-week-filter
|
||||||
span.sidebar-list-item-description
|
span.sidebar-list-item-description
|
||||||
| {{_ 'filter-due-this-week' }}
|
| {{_ 'filter-due-this-week' }}
|
||||||
if Filter.dueAt.isSelected 'thisweek'
|
if Filter.dueAt.isSelected 'thisweek'
|
||||||
| ✅
|
i.fa.fa-check
|
||||||
li(class="{{#if Filter.dueAt.isSelected 'nextweek'}}active{{/if}}")
|
li(class="{{#if Filter.dueAt.isSelected 'nextweek'}}active{{/if}}")
|
||||||
a.name.js-toggle-due-next-week-filter
|
a.name.js-toggle-due-next-week-filter
|
||||||
span.sidebar-list-item-description
|
span.sidebar-list-item-description
|
||||||
| {{_ 'filter-due-next-week' }}
|
| {{_ 'filter-due-next-week' }}
|
||||||
if Filter.dueAt.isSelected 'nextweek'
|
if Filter.dueAt.isSelected 'nextweek'
|
||||||
| ✅
|
i.fa.fa-check
|
||||||
hr
|
hr
|
||||||
h3
|
h3
|
||||||
| 📋
|
| 📋
|
||||||
|
|
@ -138,7 +138,7 @@ template(name="filterSidebar")
|
||||||
span.sidebar-list-item-description
|
span.sidebar-list-item-description
|
||||||
| {{ name }}
|
| {{ name }}
|
||||||
if Filter.customFields.isSelected _id
|
if Filter.customFields.isSelected _id
|
||||||
| ✅
|
i.fa.fa-check
|
||||||
hr
|
hr
|
||||||
h3
|
h3
|
||||||
| {{_ 'other-filters-label'}}
|
| {{_ 'other-filters-label'}}
|
||||||
|
|
@ -148,14 +148,14 @@ template(name="filterSidebar")
|
||||||
span.sidebar-list-item-description
|
span.sidebar-list-item-description
|
||||||
| {{_ 'filter-show-archive'}}
|
| {{_ 'filter-show-archive'}}
|
||||||
if Filter.archive.isSelected _id
|
if Filter.archive.isSelected _id
|
||||||
| ✅
|
i.fa.fa-check
|
||||||
ul.sidebar-list
|
ul.sidebar-list
|
||||||
li(class="{{#if Filter.hideEmpty.isSelected _id}}active{{/if}}")
|
li(class="{{#if Filter.hideEmpty.isSelected _id}}active{{/if}}")
|
||||||
a.name.js-toggle-hideEmpty-filter
|
a.name.js-toggle-hideEmpty-filter
|
||||||
span.sidebar-list-item-description
|
span.sidebar-list-item-description
|
||||||
| {{_ 'filter-hide-empty'}}
|
| {{_ 'filter-hide-empty'}}
|
||||||
if Filter.hideEmpty.isSelected _id
|
if Filter.hideEmpty.isSelected _id
|
||||||
| ✅
|
i.fa.fa-check
|
||||||
hr
|
hr
|
||||||
h3 {{_ 'advanced-filter-label'}}
|
h3 {{_ 'advanced-filter-label'}}
|
||||||
input.js-field-advanced-filter(type="text")
|
input.js-field-advanced-filter(type="text")
|
||||||
|
|
@ -206,12 +206,6 @@ template(name="multiselectionSidebar")
|
||||||
| ⋯
|
| ⋯
|
||||||
if currentUser.isBoardAdmin
|
if currentUser.isBoardAdmin
|
||||||
hr
|
hr
|
||||||
a.sidebar-btn.js-selection-color
|
|
||||||
| 🎨
|
|
||||||
span {{_ 'selection-color'}}
|
|
||||||
a.sidebar-btn.js-copy-selection
|
|
||||||
| 📋
|
|
||||||
span {{_ 'copy-selection'}}
|
|
||||||
a.sidebar-btn.js-move-selection
|
a.sidebar-btn.js-move-selection
|
||||||
| 📤
|
| 📤
|
||||||
span {{_ 'move-selection'}}
|
span {{_ 'move-selection'}}
|
||||||
|
|
@ -230,76 +224,4 @@ template(name="disambiguateMultiMemberPopup")
|
||||||
button.wide.js-assign-member {{_ 'assign-member'}}
|
button.wide.js-assign-member {{_ 'assign-member'}}
|
||||||
|
|
||||||
template(name="moveSelectionPopup")
|
template(name="moveSelectionPopup")
|
||||||
h3 {{_ 'moveSelectionPopup-title'}}
|
+boardLists
|
||||||
label {{_ 'boards'}}:
|
|
||||||
select.js-select-boards
|
|
||||||
each boards
|
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
label {{_ 'swimlanes'}}:
|
|
||||||
select.js-select-swimlanes
|
|
||||||
each swimlanes
|
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
label {{_ 'lists'}}:
|
|
||||||
select.js-select-lists
|
|
||||||
each lists
|
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
label {{_ 'cards'}}:
|
|
||||||
select.js-select-cards
|
|
||||||
each cards
|
|
||||||
option(value="{{_id}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
div
|
|
||||||
input(type="radio" name="position" value="above" checked id="position-above-move" style="display: inline")
|
|
||||||
label(for="position-above-move") {{_ 'above-selected-card'}}
|
|
||||||
div
|
|
||||||
input(type="radio" name="position" value="below" id="position-below-move" style="display: inline")
|
|
||||||
label(for="position-below-move") {{_ 'below-selected-card'}}
|
|
||||||
|
|
||||||
.edit-controls.clearfix
|
|
||||||
button.primary.confirm.js-done {{_ 'done'}}
|
|
||||||
|
|
||||||
template(name="copySelectionPopup")
|
|
||||||
h3 {{_ 'copySelectionPopup-title'}}
|
|
||||||
label {{_ 'boards'}}:
|
|
||||||
select.js-select-boards
|
|
||||||
each boards
|
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
label {{_ 'swimlanes'}}:
|
|
||||||
select.js-select-swimlanes
|
|
||||||
each swimlanes
|
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
label {{_ 'lists'}}:
|
|
||||||
select.js-select-lists
|
|
||||||
each lists
|
|
||||||
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
label {{_ 'cards'}}:
|
|
||||||
select.js-select-cards
|
|
||||||
each cards
|
|
||||||
option(value="{{_id}}") {{add @index 1}}. {{title}}
|
|
||||||
|
|
||||||
div
|
|
||||||
input(type="radio" name="position" value="above" checked id="position-above-copy" style="display: inline")
|
|
||||||
label(for="position-above-copy") {{_ 'above-selected-card'}}
|
|
||||||
div
|
|
||||||
input(type="radio" name="position" value="below" id="position-below-copy" style="display: inline")
|
|
||||||
label(for="position-below-copy") {{_ 'below-selected-card'}}
|
|
||||||
|
|
||||||
.edit-controls.clearfix
|
|
||||||
button.primary.confirm.js-done {{_ 'done'}}
|
|
||||||
|
|
||||||
template(name="setSelectionColorPopup")
|
|
||||||
h3 {{_ 'setSelectionColorPopup-title'}}
|
|
||||||
form.edit-label
|
|
||||||
.palette-colors: each colors
|
|
||||||
unless $eq color 'white'
|
|
||||||
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
|
|
||||||
if(isSelected color)
|
|
||||||
| ✅
|
|
||||||
button.primary.confirm.js-submit {{_ 'save'}}
|
|
||||||
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
|
|
||||||
|
|
|
||||||
|
|
@ -162,8 +162,6 @@ BlazeComponent.extendComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'click .js-move-selection': Popup.open('moveSelection'),
|
'click .js-move-selection': Popup.open('moveSelection'),
|
||||||
'click .js-copy-selection': Popup.open('copySelection'),
|
|
||||||
'click .js-selection-color': Popup.open('setSelectionColor'),
|
|
||||||
'click .js-archive-selection'() {
|
'click .js-archive-selection'() {
|
||||||
mutateSelectedCards('archive');
|
mutateSelectedCards('archive');
|
||||||
EscapeActions.executeUpTo('multiselection');
|
EscapeActions.executeUpTo('multiselection');
|
||||||
|
|
@ -204,267 +202,10 @@ Template.disambiguateMultiMemberPopup.events({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.moveSelectionPopup.onCreated(function() {
|
|
||||||
this.selectedBoardId = new ReactiveVar(Session.get('currentBoard'));
|
|
||||||
this.selectedSwimlaneId = new ReactiveVar('');
|
|
||||||
this.selectedListId = new ReactiveVar('');
|
|
||||||
this.selectedCardId = new ReactiveVar('');
|
|
||||||
this.position = new ReactiveVar('above');
|
|
||||||
|
|
||||||
this.getBoardData = function(boardId) {
|
|
||||||
const self = this;
|
|
||||||
Meteor.subscribe('board', boardId, false, {
|
|
||||||
onReady() {
|
|
||||||
const sameBoardId = self.selectedBoardId.get() === boardId;
|
|
||||||
self.selectedBoardId.set(boardId);
|
|
||||||
|
|
||||||
if (!sameBoardId) {
|
|
||||||
self.setFirstSwimlaneId();
|
|
||||||
self.setFirstListId();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setFirstSwimlaneId = function() {
|
|
||||||
try {
|
|
||||||
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
|
||||||
const swimlaneId = board.swimlanes()[0]._id;
|
|
||||||
this.selectedSwimlaneId.set(swimlaneId);
|
|
||||||
} catch (e) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setFirstListId = function() {
|
|
||||||
try {
|
|
||||||
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
|
||||||
const listId = board.lists()[0]._id;
|
|
||||||
this.selectedListId.set(listId);
|
|
||||||
} catch (e) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getBoardData(Session.get('currentBoard'));
|
|
||||||
this.setFirstSwimlaneId();
|
|
||||||
this.setFirstListId();
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.moveSelectionPopup.helpers({
|
|
||||||
boards() {
|
|
||||||
return ReactiveCache.getBoards(
|
|
||||||
{
|
|
||||||
archived: false,
|
|
||||||
'members.userId': Meteor.userId(),
|
|
||||||
_id: { $ne: ReactiveCache.getCurrentUser().getTemplatesBoardId() },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sort: { sort: 1 },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
swimlanes() {
|
|
||||||
const board = ReactiveCache.getBoard(Template.instance().selectedBoardId.get());
|
|
||||||
return board ? board.swimlanes() : [];
|
|
||||||
},
|
|
||||||
lists() {
|
|
||||||
const board = ReactiveCache.getBoard(Template.instance().selectedBoardId.get());
|
|
||||||
return board ? board.lists() : [];
|
|
||||||
},
|
|
||||||
cards() {
|
|
||||||
const instance = Template.instance();
|
|
||||||
const list = ReactiveCache.getList(instance.selectedListId.get());
|
|
||||||
if (!list) return [];
|
|
||||||
return list.cards(instance.selectedSwimlaneId.get()).sort((a, b) => a.sort - b.sort);
|
|
||||||
},
|
|
||||||
isDialogOptionBoardId(boardId) {
|
|
||||||
return Template.instance().selectedBoardId.get() === boardId;
|
|
||||||
},
|
|
||||||
isDialogOptionSwimlaneId(swimlaneId) {
|
|
||||||
return Template.instance().selectedSwimlaneId.get() === swimlaneId;
|
|
||||||
},
|
|
||||||
isDialogOptionListId(listId) {
|
|
||||||
return Template.instance().selectedListId.get() === listId;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.moveSelectionPopup.events({
|
Template.moveSelectionPopup.events({
|
||||||
'change .js-select-boards'(event) {
|
'click .js-select-list'() {
|
||||||
const boardId = $(event.currentTarget).val();
|
// Move the minicard to the end of the target list
|
||||||
Template.instance().getBoardData(boardId);
|
mutateSelectedCards('moveToEndOfList', { listId: this._id });
|
||||||
},
|
|
||||||
'change .js-select-swimlanes'(event) {
|
|
||||||
Template.instance().selectedSwimlaneId.set($(event.currentTarget).val());
|
|
||||||
},
|
|
||||||
'change .js-select-lists'(event) {
|
|
||||||
Template.instance().selectedListId.set($(event.currentTarget).val());
|
|
||||||
},
|
|
||||||
'change .js-select-cards'(event) {
|
|
||||||
Template.instance().selectedCardId.set($(event.currentTarget).val());
|
|
||||||
},
|
|
||||||
'change input[name="position"]'(event) {
|
|
||||||
Template.instance().position.set($(event.currentTarget).val());
|
|
||||||
},
|
|
||||||
'click .js-done'() {
|
|
||||||
const instance = Template.instance();
|
|
||||||
const boardId = instance.selectedBoardId.get();
|
|
||||||
const swimlaneId = instance.selectedSwimlaneId.get();
|
|
||||||
const listId = instance.selectedListId.get();
|
|
||||||
const cardId = instance.selectedCardId.get();
|
|
||||||
const position = instance.position.get();
|
|
||||||
|
|
||||||
// Calculate sortIndex
|
|
||||||
let sortIndex = 0;
|
|
||||||
if (cardId) {
|
|
||||||
const targetCard = ReactiveCache.getCard(cardId);
|
|
||||||
if (targetCard) {
|
|
||||||
if (position === 'above') {
|
|
||||||
sortIndex = targetCard.sort - 0.5;
|
|
||||||
} else {
|
|
||||||
sortIndex = targetCard.sort + 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If no card selected, move to end
|
|
||||||
const board = ReactiveCache.getBoard(boardId);
|
|
||||||
const cards = board.cards({ swimlaneId, listId }).sort('sort');
|
|
||||||
if (cards.length > 0) {
|
|
||||||
sortIndex = cards[cards.length - 1].sort + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mutateSelectedCards('move', boardId, swimlaneId, listId, sortIndex);
|
|
||||||
EscapeActions.executeUpTo('multiselection');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.copySelectionPopup.onCreated(function() {
|
|
||||||
this.selectedBoardId = new ReactiveVar(Session.get('currentBoard'));
|
|
||||||
this.selectedSwimlaneId = new ReactiveVar('');
|
|
||||||
this.selectedListId = new ReactiveVar('');
|
|
||||||
this.selectedCardId = new ReactiveVar('');
|
|
||||||
this.position = new ReactiveVar('above');
|
|
||||||
|
|
||||||
this.getBoardData = function(boardId) {
|
|
||||||
const self = this;
|
|
||||||
Meteor.subscribe('board', boardId, false, {
|
|
||||||
onReady() {
|
|
||||||
const sameBoardId = self.selectedBoardId.get() === boardId;
|
|
||||||
self.selectedBoardId.set(boardId);
|
|
||||||
|
|
||||||
if (!sameBoardId) {
|
|
||||||
self.setFirstSwimlaneId();
|
|
||||||
self.setFirstListId();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setFirstSwimlaneId = function() {
|
|
||||||
try {
|
|
||||||
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
|
||||||
const swimlaneId = board.swimlanes()[0]._id;
|
|
||||||
this.selectedSwimlaneId.set(swimlaneId);
|
|
||||||
} catch (e) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setFirstListId = function() {
|
|
||||||
try {
|
|
||||||
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
|
||||||
const listId = board.lists()[0]._id;
|
|
||||||
this.selectedListId.set(listId);
|
|
||||||
} catch (e) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getBoardData(Session.get('currentBoard'));
|
|
||||||
this.setFirstSwimlaneId();
|
|
||||||
this.setFirstListId();
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.copySelectionPopup.helpers({
|
|
||||||
boards() {
|
|
||||||
return ReactiveCache.getBoards(
|
|
||||||
{
|
|
||||||
archived: false,
|
|
||||||
'members.userId': Meteor.userId(),
|
|
||||||
_id: { $ne: ReactiveCache.getCurrentUser().getTemplatesBoardId() },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sort: { sort: 1 },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
swimlanes() {
|
|
||||||
const board = ReactiveCache.getBoard(Template.instance().selectedBoardId.get());
|
|
||||||
return board ? board.swimlanes() : [];
|
|
||||||
},
|
|
||||||
lists() {
|
|
||||||
const board = ReactiveCache.getBoard(Template.instance().selectedBoardId.get());
|
|
||||||
return board ? board.lists() : [];
|
|
||||||
},
|
|
||||||
cards() {
|
|
||||||
const instance = Template.instance();
|
|
||||||
const list = ReactiveCache.getList(instance.selectedListId.get());
|
|
||||||
if (!list) return [];
|
|
||||||
return list.cards(instance.selectedSwimlaneId.get()).sort((a, b) => a.sort - b.sort);
|
|
||||||
},
|
|
||||||
isDialogOptionBoardId(boardId) {
|
|
||||||
return Template.instance().selectedBoardId.get() === boardId;
|
|
||||||
},
|
|
||||||
isDialogOptionSwimlaneId(swimlaneId) {
|
|
||||||
return Template.instance().selectedSwimlaneId.get() === swimlaneId;
|
|
||||||
},
|
|
||||||
isDialogOptionListId(listId) {
|
|
||||||
return Template.instance().selectedListId.get() === listId;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.copySelectionPopup.events({
|
|
||||||
'change .js-select-boards'(event) {
|
|
||||||
const boardId = $(event.currentTarget).val();
|
|
||||||
Template.instance().getBoardData(boardId);
|
|
||||||
},
|
|
||||||
'change .js-select-swimlanes'(event) {
|
|
||||||
Template.instance().selectedSwimlaneId.set($(event.currentTarget).val());
|
|
||||||
},
|
|
||||||
'change .js-select-lists'(event) {
|
|
||||||
Template.instance().selectedListId.set($(event.currentTarget).val());
|
|
||||||
},
|
|
||||||
'change .js-select-cards'(event) {
|
|
||||||
Template.instance().selectedCardId.set($(event.currentTarget).val());
|
|
||||||
},
|
|
||||||
'change input[name="position"]'(event) {
|
|
||||||
Template.instance().position.set($(event.currentTarget).val());
|
|
||||||
},
|
|
||||||
'click .js-done'() {
|
|
||||||
const instance = Template.instance();
|
|
||||||
const boardId = instance.selectedBoardId.get();
|
|
||||||
const swimlaneId = instance.selectedSwimlaneId.get();
|
|
||||||
const listId = instance.selectedListId.get();
|
|
||||||
const cardId = instance.selectedCardId.get();
|
|
||||||
const position = instance.position.get();
|
|
||||||
|
|
||||||
mutateSelectedCards((card) => {
|
|
||||||
const newCard = card.copy(boardId, swimlaneId, listId);
|
|
||||||
if (newCard) {
|
|
||||||
let sortIndex = 0;
|
|
||||||
if (cardId) {
|
|
||||||
const targetCard = ReactiveCache.getCard(cardId);
|
|
||||||
if (targetCard) {
|
|
||||||
if (position === 'above') {
|
|
||||||
sortIndex = targetCard.sort - 0.5;
|
|
||||||
} else {
|
|
||||||
sortIndex = targetCard.sort + 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// To end
|
|
||||||
const board = ReactiveCache.getBoard(boardId);
|
|
||||||
const cards = board.cards({ swimlaneId, listId }).sort('sort');
|
|
||||||
if (cards.length > 0) {
|
|
||||||
sortIndex = cards[cards.length - 1].sort + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newCard.setSort(sortIndex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
EscapeActions.executeUpTo('multiselection');
|
EscapeActions.executeUpTo('multiselection');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
109
client/components/sidebar/sidebarMigrations.jade
Normal file
109
client/components/sidebar/sidebarMigrations.jade
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
template(name='migrationsSidebar')
|
||||||
|
if currentUser.isBoardAdmin
|
||||||
|
.sidebar-migrations
|
||||||
|
h3
|
||||||
|
| 🔧
|
||||||
|
| {{_ 'migrations'}}
|
||||||
|
p.quiet {{_ 'migrations-description'}}
|
||||||
|
|
||||||
|
.migrations-list
|
||||||
|
h4 {{_ 'board-migrations'}}
|
||||||
|
.migration-item
|
||||||
|
a.js-run-migration(data-migration="comprehensive")
|
||||||
|
.migration-name
|
||||||
|
| {{_ 'comprehensive-board-migration'}}
|
||||||
|
.migration-status
|
||||||
|
if comprehensiveMigrationNeeded
|
||||||
|
span.badge.badge-warning {{_ 'migration-needed'}}
|
||||||
|
else
|
||||||
|
span.badge.badge-success {{_ 'migration-complete'}}
|
||||||
|
|
||||||
|
.migration-item
|
||||||
|
a.js-run-migration(data-migration="fixMissingLists")
|
||||||
|
.migration-name
|
||||||
|
| {{_ 'fix-missing-lists-migration'}}
|
||||||
|
.migration-status
|
||||||
|
if fixMissingListsNeeded
|
||||||
|
span.badge.badge-warning {{_ 'migration-needed'}}
|
||||||
|
else
|
||||||
|
span.badge.badge-success {{_ 'migration-complete'}}
|
||||||
|
|
||||||
|
.migration-item
|
||||||
|
a.js-run-migration(data-migration="deleteDuplicateEmptyLists")
|
||||||
|
.migration-name
|
||||||
|
| {{_ 'delete-duplicate-empty-lists-migration'}}
|
||||||
|
.migration-status
|
||||||
|
if deleteDuplicateEmptyListsNeeded
|
||||||
|
span.badge.badge-warning {{_ 'migration-needed'}}
|
||||||
|
else
|
||||||
|
span.badge.badge-success {{_ 'migration-complete'}}
|
||||||
|
|
||||||
|
.migration-item
|
||||||
|
a.js-run-migration(data-migration="restoreLostCards")
|
||||||
|
.migration-name
|
||||||
|
| {{_ 'restore-lost-cards-migration'}}
|
||||||
|
.migration-status
|
||||||
|
if restoreLostCardsNeeded
|
||||||
|
span.badge.badge-warning {{_ 'migration-needed'}}
|
||||||
|
else
|
||||||
|
span.badge.badge-success {{_ 'migration-complete'}}
|
||||||
|
|
||||||
|
.migration-item
|
||||||
|
a.js-run-migration(data-migration="restoreAllArchived")
|
||||||
|
.migration-name
|
||||||
|
| {{_ 'restore-all-archived-migration'}}
|
||||||
|
.migration-status
|
||||||
|
if restoreAllArchivedNeeded
|
||||||
|
span.badge.badge-warning {{_ 'migration-needed'}}
|
||||||
|
else
|
||||||
|
span.badge.badge-success {{_ 'migration-complete'}}
|
||||||
|
|
||||||
|
.migration-item
|
||||||
|
a.js-run-migration(data-migration="fixAvatarUrls")
|
||||||
|
.migration-name
|
||||||
|
| {{_ 'fix-avatar-urls-migration'}}
|
||||||
|
.migration-status
|
||||||
|
if fixAvatarUrlsNeeded
|
||||||
|
span.badge.badge-warning {{_ 'migration-needed'}}
|
||||||
|
else
|
||||||
|
span.badge.badge-success {{_ 'migration-complete'}}
|
||||||
|
|
||||||
|
.migration-item
|
||||||
|
a.js-run-migration(data-migration="fixAllFileUrls")
|
||||||
|
.migration-name
|
||||||
|
| {{_ 'fix-all-file-urls-migration'}}
|
||||||
|
.migration-status
|
||||||
|
if fixAllFileUrlsNeeded
|
||||||
|
span.badge.badge-warning {{_ 'migration-needed'}}
|
||||||
|
else
|
||||||
|
span.badge.badge-success {{_ 'migration-complete'}}
|
||||||
|
else
|
||||||
|
p.quiet {{_ 'migrations-admin-only'}}
|
||||||
|
|
||||||
|
template(name='runComprehensiveMigrationPopup')
|
||||||
|
p {{_ 'run-comprehensive-migration-confirm'}}
|
||||||
|
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
|
||||||
|
|
||||||
|
template(name='runFixMissingListsMigrationPopup')
|
||||||
|
p {{_ 'run-fix-missing-lists-migration-confirm'}}
|
||||||
|
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
|
||||||
|
|
||||||
|
template(name='runDeleteDuplicateEmptyListsMigrationPopup')
|
||||||
|
p {{_ 'run-delete-duplicate-empty-lists-migration-confirm'}}
|
||||||
|
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
|
||||||
|
|
||||||
|
template(name='runRestoreLostCardsMigrationPopup')
|
||||||
|
p {{_ 'run-restore-lost-cards-migration-confirm'}}
|
||||||
|
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
|
||||||
|
|
||||||
|
template(name='runRestoreAllArchivedMigrationPopup')
|
||||||
|
p {{_ 'run-restore-all-archived-migration-confirm'}}
|
||||||
|
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
|
||||||
|
|
||||||
|
template(name='runFixAvatarUrlsMigrationPopup')
|
||||||
|
p {{_ 'run-fix-avatar-urls-migration-confirm'}}
|
||||||
|
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
|
||||||
|
|
||||||
|
template(name='runFixAllFileUrlsMigrationPopup')
|
||||||
|
p {{_ 'run-fix-all-file-urls-migration-confirm'}}
|
||||||
|
button.js-confirm.primary.full(type="submit") {{_ 'run-migration'}}
|
||||||
341
client/components/sidebar/sidebarMigrations.js
Normal file
341
client/components/sidebar/sidebarMigrations.js
Normal file
|
|
@ -0,0 +1,341 @@
|
||||||
|
import { ReactiveCache } from '/imports/reactiveCache';
|
||||||
|
import { TAPi18n } from '/imports/i18n';
|
||||||
|
import { migrationProgressManager } from '/client/components/migrationProgress';
|
||||||
|
|
||||||
|
BlazeComponent.extendComponent({
|
||||||
|
onCreated() {
|
||||||
|
this.migrationStatuses = new ReactiveVar({});
|
||||||
|
this.loadMigrationStatuses();
|
||||||
|
},
|
||||||
|
|
||||||
|
loadMigrationStatuses() {
|
||||||
|
const boardId = Session.get('currentBoard');
|
||||||
|
if (!boardId) return;
|
||||||
|
|
||||||
|
// Check comprehensive migration
|
||||||
|
Meteor.call('comprehensiveBoardMigration.needsMigration', boardId, (err, res) => {
|
||||||
|
if (!err) {
|
||||||
|
const statuses = this.migrationStatuses.get();
|
||||||
|
statuses.comprehensive = res;
|
||||||
|
this.migrationStatuses.set(statuses);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check fix missing lists migration
|
||||||
|
Meteor.call('fixMissingListsMigration.needsMigration', boardId, (err, res) => {
|
||||||
|
if (!err) {
|
||||||
|
const statuses = this.migrationStatuses.get();
|
||||||
|
statuses.fixMissingLists = res;
|
||||||
|
this.migrationStatuses.set(statuses);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check delete duplicate empty lists migration
|
||||||
|
Meteor.call('deleteDuplicateEmptyLists.needsMigration', boardId, (err, res) => {
|
||||||
|
if (!err) {
|
||||||
|
const statuses = this.migrationStatuses.get();
|
||||||
|
statuses.deleteDuplicateEmptyLists = res;
|
||||||
|
this.migrationStatuses.set(statuses);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check restore lost cards migration
|
||||||
|
Meteor.call('restoreLostCards.needsMigration', boardId, (err, res) => {
|
||||||
|
if (!err) {
|
||||||
|
const statuses = this.migrationStatuses.get();
|
||||||
|
statuses.restoreLostCards = res;
|
||||||
|
this.migrationStatuses.set(statuses);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check restore all archived migration
|
||||||
|
Meteor.call('restoreAllArchived.needsMigration', boardId, (err, res) => {
|
||||||
|
if (!err) {
|
||||||
|
const statuses = this.migrationStatuses.get();
|
||||||
|
statuses.restoreAllArchived = res;
|
||||||
|
this.migrationStatuses.set(statuses);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check fix avatar URLs migration (board-specific)
|
||||||
|
Meteor.call('fixAvatarUrls.needsMigration', boardId, (err, res) => {
|
||||||
|
if (!err) {
|
||||||
|
const statuses = this.migrationStatuses.get();
|
||||||
|
statuses.fixAvatarUrls = res;
|
||||||
|
this.migrationStatuses.set(statuses);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check fix all file URLs migration (board-specific)
|
||||||
|
Meteor.call('fixAllFileUrls.needsMigration', boardId, (err, res) => {
|
||||||
|
if (!err) {
|
||||||
|
const statuses = this.migrationStatuses.get();
|
||||||
|
statuses.fixAllFileUrls = res;
|
||||||
|
this.migrationStatuses.set(statuses);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
comprehensiveMigrationNeeded() {
|
||||||
|
return this.migrationStatuses.get().comprehensive === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
fixMissingListsNeeded() {
|
||||||
|
return this.migrationStatuses.get().fixMissingLists === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteDuplicateEmptyListsNeeded() {
|
||||||
|
return this.migrationStatuses.get().deleteDuplicateEmptyLists === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
restoreLostCardsNeeded() {
|
||||||
|
return this.migrationStatuses.get().restoreLostCards === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
restoreAllArchivedNeeded() {
|
||||||
|
return this.migrationStatuses.get().restoreAllArchived === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
fixAvatarUrlsNeeded() {
|
||||||
|
return this.migrationStatuses.get().fixAvatarUrls === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
fixAllFileUrlsNeeded() {
|
||||||
|
return this.migrationStatuses.get().fixAllFileUrls === true;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Simulate migration progress updates using the global progress popup
|
||||||
|
async simulateMigrationProgress(progressSteps) {
|
||||||
|
const totalSteps = progressSteps.length;
|
||||||
|
for (let i = 0; i < progressSteps.length; i++) {
|
||||||
|
const step = progressSteps[i];
|
||||||
|
const overall = Math.round(((i + 1) / totalSteps) * 100);
|
||||||
|
|
||||||
|
// Start step
|
||||||
|
migrationProgressManager.updateProgress({
|
||||||
|
overallProgress: overall,
|
||||||
|
currentStep: i + 1,
|
||||||
|
totalSteps,
|
||||||
|
stepName: step.step,
|
||||||
|
stepProgress: 0,
|
||||||
|
stepStatus: `Starting ${step.name}...`,
|
||||||
|
stepDetails: null,
|
||||||
|
boardId: Session.get('currentBoard'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const stepDuration = step.duration;
|
||||||
|
const updateInterval = 100;
|
||||||
|
const totalUpdates = Math.max(1, Math.floor(stepDuration / updateInterval));
|
||||||
|
for (let j = 0; j < totalUpdates; j++) {
|
||||||
|
const per = Math.round(((j + 1) / totalUpdates) * 100);
|
||||||
|
migrationProgressManager.updateProgress({
|
||||||
|
overallProgress: overall,
|
||||||
|
currentStep: i + 1,
|
||||||
|
totalSteps,
|
||||||
|
stepName: step.step,
|
||||||
|
stepProgress: per,
|
||||||
|
stepStatus: `Processing ${step.name}...`,
|
||||||
|
stepDetails: { progress: `${per}%` },
|
||||||
|
boardId: Session.get('currentBoard'),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await new Promise((r) => setTimeout(r, updateInterval));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete step
|
||||||
|
migrationProgressManager.updateProgress({
|
||||||
|
overallProgress: overall,
|
||||||
|
currentStep: i + 1,
|
||||||
|
totalSteps,
|
||||||
|
stepName: step.step,
|
||||||
|
stepProgress: 100,
|
||||||
|
stepStatus: `${step.name} completed`,
|
||||||
|
stepDetails: { status: 'completed' },
|
||||||
|
boardId: Session.get('currentBoard'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
runMigration(migrationType) {
|
||||||
|
const boardId = Session.get('currentBoard');
|
||||||
|
|
||||||
|
let methodName;
|
||||||
|
let methodArgs = [];
|
||||||
|
|
||||||
|
switch (migrationType) {
|
||||||
|
case 'comprehensive':
|
||||||
|
methodName = 'comprehensiveBoardMigration.execute';
|
||||||
|
methodArgs = [boardId];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'fixMissingLists':
|
||||||
|
methodName = 'fixMissingListsMigration.execute';
|
||||||
|
methodArgs = [boardId];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'deleteDuplicateEmptyLists':
|
||||||
|
methodName = 'deleteDuplicateEmptyLists.execute';
|
||||||
|
methodArgs = [boardId];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'restoreLostCards':
|
||||||
|
methodName = 'restoreLostCards.execute';
|
||||||
|
methodArgs = [boardId];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'restoreAllArchived':
|
||||||
|
methodName = 'restoreAllArchived.execute';
|
||||||
|
methodArgs = [boardId];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'fixAvatarUrls':
|
||||||
|
methodName = 'fixAvatarUrls.execute';
|
||||||
|
methodArgs = [boardId];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'fixAllFileUrls':
|
||||||
|
methodName = 'fixAllFileUrls.execute';
|
||||||
|
methodArgs = [boardId];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodName) {
|
||||||
|
// Define simulated steps per migration type
|
||||||
|
const stepsByType = {
|
||||||
|
comprehensive: [
|
||||||
|
{ step: 'analyze_board_structure', name: 'Analyze Board Structure', duration: 800 },
|
||||||
|
{ step: 'fix_orphaned_cards', name: 'Fix Orphaned Cards', duration: 1200 },
|
||||||
|
{ step: 'convert_shared_lists', name: 'Convert Shared Lists', duration: 1000 },
|
||||||
|
{ step: 'ensure_per_swimlane_lists', name: 'Ensure Per-Swimlane Lists', duration: 800 },
|
||||||
|
{ step: 'validate_migration', name: 'Validate Migration', duration: 800 },
|
||||||
|
{ step: 'fix_avatar_urls', name: 'Fix Avatar URLs', duration: 600 },
|
||||||
|
{ step: 'fix_attachment_urls', name: 'Fix Attachment URLs', duration: 600 },
|
||||||
|
],
|
||||||
|
fixMissingLists: [
|
||||||
|
{ step: 'analyze_lists', name: 'Analyze Lists', duration: 600 },
|
||||||
|
{ step: 'create_missing_lists', name: 'Create Missing Lists', duration: 900 },
|
||||||
|
{ step: 'update_cards', name: 'Update Cards', duration: 900 },
|
||||||
|
{ step: 'finalize', name: 'Finalize', duration: 400 },
|
||||||
|
],
|
||||||
|
deleteDuplicateEmptyLists: [
|
||||||
|
{ step: 'convert_shared_lists', name: 'Convert Shared Lists', duration: 700 },
|
||||||
|
{ step: 'delete_duplicate_empty_lists', name: 'Delete Duplicate Empty Lists', duration: 800 },
|
||||||
|
],
|
||||||
|
restoreLostCards: [
|
||||||
|
{ step: 'ensure_lost_cards_swimlane', name: 'Ensure Lost Cards Swimlane', duration: 600 },
|
||||||
|
{ step: 'restore_lists', name: 'Restore Lists', duration: 800 },
|
||||||
|
{ step: 'restore_cards', name: 'Restore Cards', duration: 1000 },
|
||||||
|
],
|
||||||
|
restoreAllArchived: [
|
||||||
|
{ step: 'restore_swimlanes', name: 'Restore Swimlanes', duration: 800 },
|
||||||
|
{ step: 'restore_lists', name: 'Restore Lists', duration: 900 },
|
||||||
|
{ step: 'restore_cards', name: 'Restore Cards', duration: 1000 },
|
||||||
|
{ step: 'fix_missing_ids', name: 'Fix Missing IDs', duration: 600 },
|
||||||
|
],
|
||||||
|
fixAvatarUrls: [
|
||||||
|
{ step: 'scan_users', name: 'Checking board member avatars', duration: 500 },
|
||||||
|
{ step: 'fix_urls', name: 'Fixing avatar URLs', duration: 900 },
|
||||||
|
],
|
||||||
|
fixAllFileUrls: [
|
||||||
|
{ step: 'scan_files', name: 'Checking board file attachments', duration: 600 },
|
||||||
|
{ step: 'fix_urls', name: 'Fixing file URLs', duration: 1000 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const steps = stepsByType[migrationType] || [
|
||||||
|
{ step: 'running', name: 'Running Migration', duration: 1000 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Kick off popup and simulated progress
|
||||||
|
migrationProgressManager.startMigration();
|
||||||
|
const progressPromise = this.simulateMigrationProgress(steps);
|
||||||
|
|
||||||
|
// Start migration call
|
||||||
|
const callPromise = new Promise((resolve, reject) => {
|
||||||
|
Meteor.call(methodName, ...methodArgs, (err, result) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
return resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.allSettled([callPromise, progressPromise]).then(([callRes]) => {
|
||||||
|
if (callRes.status === 'rejected') {
|
||||||
|
migrationProgressManager.failMigration(callRes.reason);
|
||||||
|
} else {
|
||||||
|
const result = callRes.value;
|
||||||
|
// Summarize result details in the popup
|
||||||
|
let summary = {};
|
||||||
|
if (result && result.results) {
|
||||||
|
// Comprehensive returns {success, results}
|
||||||
|
const r = result.results;
|
||||||
|
summary = {
|
||||||
|
totalCardsProcessed: r.totalCardsProcessed,
|
||||||
|
totalListsProcessed: r.totalListsProcessed,
|
||||||
|
totalListsCreated: r.totalListsCreated,
|
||||||
|
};
|
||||||
|
} else if (result && result.changes) {
|
||||||
|
// Many migrations return a changes string array
|
||||||
|
summary = { changes: result.changes.join(' | ') };
|
||||||
|
} else if (result && typeof result === 'object') {
|
||||||
|
summary = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
migrationProgressManager.updateProgress({
|
||||||
|
overallProgress: 100,
|
||||||
|
currentStep: steps.length,
|
||||||
|
totalSteps: steps.length,
|
||||||
|
stepName: 'completed',
|
||||||
|
stepProgress: 100,
|
||||||
|
stepStatus: 'Migration completed',
|
||||||
|
stepDetails: summary,
|
||||||
|
boardId: Session.get('currentBoard'),
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationProgressManager.completeMigration();
|
||||||
|
|
||||||
|
// Refresh status badges slightly after
|
||||||
|
Meteor.setTimeout(() => {
|
||||||
|
this.loadMigrationStatuses();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
events() {
|
||||||
|
const self = this; // Capture component reference
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'click .js-run-migration[data-migration="comprehensive"]': Popup.afterConfirm('runComprehensiveMigration', function() {
|
||||||
|
self.runMigration('comprehensive');
|
||||||
|
Popup.back();
|
||||||
|
}),
|
||||||
|
'click .js-run-migration[data-migration="fixMissingLists"]': Popup.afterConfirm('runFixMissingListsMigration', function() {
|
||||||
|
self.runMigration('fixMissingLists');
|
||||||
|
Popup.back();
|
||||||
|
}),
|
||||||
|
'click .js-run-migration[data-migration="deleteDuplicateEmptyLists"]': Popup.afterConfirm('runDeleteDuplicateEmptyListsMigration', function() {
|
||||||
|
self.runMigration('deleteDuplicateEmptyLists');
|
||||||
|
Popup.back();
|
||||||
|
}),
|
||||||
|
'click .js-run-migration[data-migration="restoreLostCards"]': Popup.afterConfirm('runRestoreLostCardsMigration', function() {
|
||||||
|
self.runMigration('restoreLostCards');
|
||||||
|
Popup.back();
|
||||||
|
}),
|
||||||
|
'click .js-run-migration[data-migration="restoreAllArchived"]': Popup.afterConfirm('runRestoreAllArchivedMigration', function() {
|
||||||
|
self.runMigration('restoreAllArchived');
|
||||||
|
Popup.back();
|
||||||
|
}),
|
||||||
|
'click .js-run-migration[data-migration="fixAvatarUrls"]': Popup.afterConfirm('runFixAvatarUrlsMigration', function() {
|
||||||
|
self.runMigration('fixAvatarUrls');
|
||||||
|
Popup.back();
|
||||||
|
}),
|
||||||
|
'click .js-run-migration[data-migration="fixAllFileUrls"]': Popup.afterConfirm('runFixAllFileUrlsMigration', function() {
|
||||||
|
self.runMigration('fixAllFileUrls');
|
||||||
|
Popup.back();
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
}).register('migrationsSidebar');
|
||||||
|
|
@ -26,21 +26,25 @@ template(name="swimlaneFixedHeader")
|
||||||
if currentUser
|
if currentUser
|
||||||
unless currentUser.isCommentOnly
|
unless currentUser.isCommentOnly
|
||||||
unless currentUser.isWorker
|
unless currentUser.isWorker
|
||||||
a.swimlane-collapse-indicator.js-collapse-swimlane.swimlane-header-collapse(title="{{_ 'collapse'}}")
|
|
||||||
if collapseSwimlane
|
|
||||||
| ▶
|
|
||||||
else
|
|
||||||
| 🔽
|
|
||||||
a.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}")
|
a.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}")
|
||||||
| ➕
|
| ➕
|
||||||
|
a.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
|
||||||
|
| ☰
|
||||||
|
//// TODO: Collapse Swimlane: make button working, etc.
|
||||||
|
//unless collapsed
|
||||||
|
// a.js-collapse-swimlane(title="{{_ 'collapse'}}")
|
||||||
|
// i.fa.fa-arrow-down.swimlane-header-collapse-down
|
||||||
|
// ⬆️.swimlane-header-collapse-up
|
||||||
|
//if collapsed
|
||||||
|
// a.js-collapse-swimlane(title="{{_ 'uncollapse'}}")
|
||||||
|
// ⬆️.swimlane-header-collapse-up
|
||||||
|
// i.fa.fa-arrow-down.swimlane-header-collapse-down
|
||||||
unless isTouchScreen
|
unless isTouchScreen
|
||||||
a.swimlane-header-handle.handle.js-swimlane-header-handle
|
a.swimlane-header-handle.handle.js-swimlane-header-handle
|
||||||
| ↕️
|
| ↕️
|
||||||
if isTouchScreen
|
if isTouchScreen
|
||||||
a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle
|
a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle
|
||||||
| ↕️
|
| ↕️
|
||||||
a.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
|
|
||||||
| ☰
|
|
||||||
|
|
||||||
template(name="editSwimlaneTitleForm")
|
template(name="editSwimlaneTitleForm")
|
||||||
.list-composer
|
.list-composer
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,13 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
collapsed(check = undefined) {
|
collapsed(check = undefined) {
|
||||||
const swimlane = Template.currentData();
|
const swimlane = Template.currentData();
|
||||||
const status = Utils.getSwimlaneCollapseState(swimlane);
|
const status = swimlane.isCollapsed();
|
||||||
if (check === undefined) {
|
if (check === undefined) {
|
||||||
// just check
|
// just check
|
||||||
return status;
|
return status;
|
||||||
} else {
|
} else {
|
||||||
const next = typeof check === 'boolean' ? check : !status;
|
swimlane.collapse(!status);
|
||||||
Utils.setSwimlaneCollapseState(swimlane, next);
|
return !status;
|
||||||
return next;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -50,10 +49,6 @@ Template.swimlaneFixedHeader.helpers({
|
||||||
isBoardAdmin() {
|
isBoardAdmin() {
|
||||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||||
},
|
},
|
||||||
collapseSwimlane() {
|
|
||||||
const swimlane = Template.currentData();
|
|
||||||
return Utils.getSwimlaneCollapseState(swimlane);
|
|
||||||
},
|
|
||||||
isTitleDefault(title) {
|
isTitleDefault(title) {
|
||||||
// https://github.com/wekan/wekan/issues/4763
|
// https://github.com/wekan/wekan/issues/4763
|
||||||
// https://github.com/wekan/wekan/issues/4742
|
// https://github.com/wekan/wekan/issues/4742
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue