mirror of
https://github.com/wekan/wekan.git
synced 2026-11-08 19:35:19 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bc889e274 | ||
|
|
5df924f416 | ||
|
|
5ef94b7079 | ||
|
|
dfcaaf0ae7 | ||
|
|
34e7ff532f | ||
|
|
fbdfcead50 | ||
|
|
b8bbe4431b | ||
|
|
9e31666280 | ||
|
|
5846d9c9b0 | ||
|
|
7707a5b7fc | ||
|
|
18c495940e | ||
|
|
d3155d24c6 | ||
|
|
c0661506c4 | ||
|
|
fa2b34764f | ||
|
|
7a7909b49f | ||
|
|
17d3b3088f | ||
|
|
6a78a178e2 | ||
|
|
749de39e2a | ||
|
|
cd55c0e12e | ||
|
|
d04037739e | ||
|
|
f3a97b9938 | ||
|
|
104fbef37a | ||
|
|
98b3dc71af | ||
|
|
7b28e4d589 | ||
|
|
57291dbe50 | ||
|
|
9f9eefce27 | ||
|
|
af3361b35b | ||
|
|
24d65e1e72 | ||
|
|
29922fca6a | ||
|
|
81319fc48b | ||
|
|
28b7209407 | ||
|
|
0b59f87887 | ||
|
|
b22df9e93d | ||
|
|
1fa72fd34a | ||
|
|
a2f38e7494 | ||
|
|
30a1792c92 | ||
|
|
3d70f24a78 | ||
|
|
b3d14fb5e8 | ||
|
|
59956ca720 | ||
|
|
aaf7690b9a | ||
|
|
9f549dfcd2 | ||
|
|
f191392e3a | ||
|
|
56f26418ea | ||
|
|
118e0e5d5c | ||
|
|
264dcca42b | ||
|
|
73d69f4631 | ||
|
|
485b773338 | ||
|
|
8443e3511d | ||
|
|
b2a349aec5 | ||
|
|
faf8453c6d | ||
|
|
7484c8cce3 | ||
|
|
2b02e7712e | ||
|
|
8a3a7b19aa | ||
|
|
9fa6574c80 | ||
|
|
2bad17010c | ||
|
|
4168385607 | ||
|
|
c0aa1ef375 | ||
|
|
811b79b641 | ||
|
|
dab9755dd4 | ||
|
|
6863479c09 | ||
|
|
e05eebb8b0 | ||
|
|
28ef0e6e89 | ||
|
|
09e65ccac2 | ||
|
|
03e0cfdc15 | ||
|
|
7634cd6c2e | ||
|
|
4594711be4 | ||
|
|
c267933bf7 | ||
|
|
edc6b18b5f | ||
|
|
60657c6e94 | ||
|
|
3c189f9c7f | ||
|
|
bf02c9a453 | ||
|
|
e6b6cd3036 | ||
|
|
6e8589b1f9 | ||
|
|
1354f84a70 | ||
|
|
d07cf92f7c | ||
|
|
e079644ed6 | ||
|
|
6b393178dc | ||
|
|
26e1c9ab69 | ||
|
|
145a17fee5 | ||
|
|
ccc9301ed4 | ||
|
|
f2e46d660f | ||
|
|
8b4676175c | ||
|
|
e9a547c89b | ||
|
|
d07fcceb6a | ||
|
|
6b5002f314 | ||
|
|
5418ffd558 | ||
|
|
ea98afeb68 | ||
|
|
be7d7e26fb | ||
|
|
56377aaf4c | ||
|
|
43601c5e3e | ||
|
|
1db7f3a57d | ||
|
|
e58e99c285 | ||
|
|
b38e25c9f1 | ||
|
|
ce52b8a0ab | ||
|
|
f7800846a5 | ||
|
|
02b75824e5 | ||
|
|
cf52c2f6e4 | ||
|
|
6bc68ba57b | ||
|
|
5ec9cfa537 | ||
|
|
3457cab0b1 | ||
|
|
8349fb6b63 | ||
|
|
33267fa2e4 | ||
|
|
5c4130311a | ||
|
|
ed718f083a | ||
|
|
2aa8ecfc39 | ||
|
|
cdf1d3305b | ||
|
|
fd58bb9e4d | ||
|
|
55ec0b5ff5 | ||
|
|
e5c551f009 | ||
|
|
7666c6c0e1 | ||
|
|
e6ccbfd3b8 | ||
|
|
d8a2e06a95 | ||
|
|
c38fa1c272 | ||
|
|
1a5e23617d | ||
|
|
87916c20bf | ||
|
|
12c223d226 | ||
|
|
9ae2842fa4 | ||
|
|
79853a2fb7 | ||
|
|
ec1e383558 | ||
|
|
df067f01e8 | ||
|
|
4b8cc0683e | ||
|
|
f43b76c323 | ||
|
|
e90b2e384c | ||
|
|
6125822cf7 | ||
|
|
4223c2a79f | ||
|
|
7059cc244a | ||
|
|
5fc608e310 | ||
|
|
29550cbd93 | ||
|
|
35674a205b | ||
|
|
a702596bee | ||
|
|
02fef72e7e | ||
|
|
d5c9e402ce | ||
|
|
b788d398b9 | ||
|
|
5c3b73e3b0 | ||
|
|
82f1de078f | ||
|
|
ddc353023c | ||
|
|
ef75b9ecac | ||
|
|
cf2b3e4fc9 | ||
|
|
cf21991a3f | ||
|
|
9e4f72e2a4 | ||
|
|
f7da243c9e | ||
|
|
a71efd2668 | ||
|
|
6a23290584 | ||
|
|
0e59033ea6 | ||
|
|
186f7a6b82 | ||
|
|
5cf3bba68f | ||
|
|
4647ddc2b7 | ||
|
|
1ac708b0d9 | ||
|
|
07eb062474 | ||
|
|
f660817627 | ||
|
|
0fdf45e847 | ||
|
|
0c3cebd30a | ||
|
|
c589918989 | ||
|
|
256aa0a053 | ||
|
|
67c56e41b0 | ||
|
|
ade9e0f589 | ||
|
|
e14dfb9e7e | ||
|
|
c26cc313fc | ||
|
|
55660e0e69 | ||
|
|
3701544f6a | ||
|
|
acfb081b90 | ||
|
|
a30d7cabe3 | ||
|
|
517d148387 | ||
|
|
f8aec4de73 | ||
|
|
e1b6cf6124 | ||
|
|
1eac00e7fa | ||
|
|
278ed23440 | ||
|
|
b42689a54d | ||
|
|
10bfa30458 | ||
|
|
a0a1cda0a7 | ||
|
|
001af3fabd | ||
|
|
c0316e9446 | ||
|
|
d80a6e7e38 | ||
|
|
8d8d8f9cee | ||
|
|
0630d62ad0 | ||
|
|
c07b282dc9 | ||
|
|
5aa39463b0 | ||
|
|
f1e71247c7 | ||
|
|
419fb2535b | ||
|
|
23697d2a33 | ||
|
|
55edc7ef63 | ||
|
|
c1e3ce64a7 | ||
|
|
d34163402c | ||
|
|
5c5ed102ca | ||
|
|
0bbadfe9a6 | ||
|
|
21ab640686 | ||
|
|
a3f6d200ac | ||
|
|
701c10d618 | ||
|
|
7f8de1f988 | ||
|
|
95ff8ef1f2 | ||
|
|
4eb885f88a | ||
|
|
9a7a0b7271 | ||
|
|
4a8293f881 | ||
|
|
56ed341510 | ||
|
|
7584afda6d | ||
|
|
bbb8f8e51d | ||
|
|
ddd873c7a0 | ||
|
|
63ce45c538 | ||
|
|
8ffc0cb5ff | ||
|
|
ce95907778 | ||
|
|
4ed3250352 | ||
|
|
2dce7a84cd | ||
|
|
b9631659f7 | ||
|
|
8486bb1e87 | ||
|
|
655f44919a | ||
|
|
17ea7978f0 | ||
|
|
b518946c41 | ||
|
|
ca8a4af354 | ||
|
|
1682f8834d | ||
|
|
eac472c54b | ||
|
|
3a873559f8 | ||
|
|
aec6779601 | ||
|
|
973d9eccae | ||
|
|
7468b30001 | ||
|
|
e68cb5f32f | ||
|
|
a157ae5d1c | ||
|
|
620e5ce9dc | ||
|
|
2e3fbf25db | ||
|
|
e8fee653c3 | ||
|
|
0d3f3474d1 | ||
|
|
6ecb6bdaa7 | ||
|
|
073c297490 |
No files matched your search
@@ -251,7 +251,7 @@ gosu wekan:wekan /home/wekan/.meteor/meteor -- help
|
||||
# Build app (Development)
|
||||
cd /home/wekan/app
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor npm install
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor npm install --no-audit
|
||||
|
||||
# Put back the original tar
|
||||
mv $(which tar)~ $(which tar)
|
||||
|
||||
+1
-4
@@ -93,7 +93,6 @@
|
||||
"Template": false,
|
||||
"$": false,
|
||||
"_": false,
|
||||
"autosize": false,
|
||||
"Avatar": true,
|
||||
"Avatars": true,
|
||||
|
||||
@@ -111,8 +110,7 @@
|
||||
"Ps": true,
|
||||
"Restivus": false,
|
||||
"SimpleSchema": false,
|
||||
"SubsManager": false,
|
||||
"T9n": false,
|
||||
"T9n": false,
|
||||
"TAPi18n": false,
|
||||
"AccountsTemplates": true,
|
||||
"Activities": true,
|
||||
@@ -146,7 +144,6 @@
|
||||
"Checklists": true,
|
||||
"Settings": true,
|
||||
"InvitationCodes": true,
|
||||
"JsonRoutes": true,
|
||||
"Authentication": true,
|
||||
"Integrations": true,
|
||||
"HTTP": true,
|
||||
|
||||
@@ -0,0 +1,480 @@
|
||||
name: Release All Platforms
|
||||
|
||||
# Triggered when maintainer pushes a version tag, e.g.: git tag v8.43 && git push --tags
|
||||
# Does not work:
|
||||
# Required GitHub Secrets:
|
||||
# QUAY_USERNAME - Quay.io username
|
||||
# QUAY_PASSWORD - Quay.io password or token
|
||||
# DOCKERHUB_USERNAME - Docker Hub username
|
||||
# DOCKERHUB_TOKEN - Docker Hub access token
|
||||
# SNAP_STORE_LOGIN - Snapcraft store credentials: run 'snapcraft export-login --snaps wekan --channels stable -' and base64-encode the output
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
# ─── 1. Extract version and release notes ───────────────────────────────────
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
changelog: ${{ steps.changelog.outputs.content }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
run: echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Extract release notes from CHANGELOG.md
|
||||
id: changelog
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
# Try to find the version-specific section first, then fall back to Upcoming
|
||||
NOTES=$(python3 - <<PYEOF
|
||||
import re, sys
|
||||
version = "$VERSION"
|
||||
with open("CHANGELOG.md", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
# Pattern: section heading through (but not including) the next heading
|
||||
patterns = [
|
||||
rf"(# v{re.escape(version)} .*?)(?=\n# v[0-9]|\Z)",
|
||||
r"(# Upcoming WeKan.*?)(?=\n# v[0-9]|\Z)",
|
||||
]
|
||||
for pat in patterns:
|
||||
m = re.search(pat, content, re.DOTALL)
|
||||
if m:
|
||||
print(m.group(1).strip())
|
||||
sys.exit(0)
|
||||
print(f"WeKan v{version}")
|
||||
PYEOF
|
||||
)
|
||||
echo "content<<GHEOF" >> $GITHUB_OUTPUT
|
||||
echo "$NOTES" >> $GITHUB_OUTPUT
|
||||
echo "GHEOF" >> $GITHUB_OUTPUT
|
||||
|
||||
# ─── 2. Build amd64 bundle (full Meteor 3 build) ────────────────────────────
|
||||
# Meteor 3.4 installer supports only: Linux x86_64, Linux aarch64, macOS, Windows.
|
||||
# It explicitly rejects armhf, s390x, and ppc64le at install time.
|
||||
# Therefore the full Meteor build always runs here on x86_64.
|
||||
# All other Linux architectures then rebuild only the native npm modules
|
||||
# from this bundle (much faster than a full Meteor build).
|
||||
# Node.js version must match what Meteor 3.4 bundles: 22.22.2 (ABI 127).
|
||||
build-amd64:
|
||||
needs: prepare
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 180
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Cache Meteor installation
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.meteor
|
||||
key: meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.meteor/packages') }}
|
||||
restore-keys: meteor-
|
||||
|
||||
- name: Cache npm packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential g++ make python3 curl wget zip unzip
|
||||
|
||||
# Pin to Node 22.22.2 — the exact version bundled by Meteor 3.4.
|
||||
# Using the same version for build and npm rebuild ensures consistent native module ABI.
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22.22.2'
|
||||
|
||||
- name: Install Meteor
|
||||
run: |
|
||||
if [ ! -f ~/.meteor/meteor ]; then
|
||||
curl https://install.meteor.com/ | sh
|
||||
fi
|
||||
echo "$HOME/.meteor" >> $GITHUB_PATH
|
||||
|
||||
- name: Build WeKan bundle
|
||||
run: |
|
||||
rm -rf .build node_modules .meteor/local
|
||||
meteor npm install
|
||||
meteor build .build --directory --platforms=web.browser
|
||||
|
||||
- name: Install dependencies in bundle
|
||||
run: |
|
||||
cd .build/bundle/programs/server
|
||||
npm install
|
||||
|
||||
- name: Create amd64 zip
|
||||
run: |
|
||||
cd .build
|
||||
zip -r wekan-${VERSION}-amd64.zip bundle
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bundle-amd64
|
||||
path: .build/wekan-${{ needs.prepare.outputs.version }}-amd64.zip
|
||||
retention-days: 1
|
||||
|
||||
# ─── 3. Build arm64 bundle (rebuild native modules natively) ─────────────────
|
||||
# Meteor 3.4 does support Linux aarch64, but running a full Meteor build a
|
||||
# second time would double the build time for the same result. npm rebuild on
|
||||
# the native arm64 GitHub Actions runner is much faster and produces the same
|
||||
# platform-specific native modules.
|
||||
build-arm64:
|
||||
needs: [prepare, build-amd64]
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential g++ make python3 curl unzip zip
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22.22.2'
|
||||
|
||||
- name: Download amd64 bundle
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bundle-amd64
|
||||
|
||||
- name: Rebuild native modules for arm64
|
||||
run: |
|
||||
unzip wekan-${VERSION}-amd64.zip
|
||||
cd bundle/programs/server
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
cd ../..
|
||||
|
||||
- name: Clean up and create arm64 zip
|
||||
run: |
|
||||
zip -r wekan-${VERSION}-arm64.zip bundle
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bundle-arm64
|
||||
path: wekan-${{ needs.prepare.outputs.version }}-arm64.zip
|
||||
retention-days: 1
|
||||
|
||||
# ─── 4. Build s390x bundle (rebuild native modules via QEMU container) ───────
|
||||
# Meteor 3.4 does NOT support s390x (installer exits with error on that arch).
|
||||
# GitHub Actions has no native s390x runners.
|
||||
# Solution: QEMU emulation + node:22.22.2-slim (s390x variant) for npm rebuild.
|
||||
# node:22.22.2-slim is pinned to match Meteor 3.4's bundled Node.js version exactly.
|
||||
build-s390x:
|
||||
needs: [prepare, build-amd64]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
steps:
|
||||
- name: Set up QEMU for s390x
|
||||
uses: docker/setup-qemu-action@v4
|
||||
with:
|
||||
platforms: s390x
|
||||
|
||||
- name: Install unzip/zip
|
||||
run: sudo apt-get update && sudo apt-get install -y unzip zip
|
||||
|
||||
- name: Download amd64 bundle
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bundle-amd64
|
||||
|
||||
- name: Rebuild native modules for s390x in container
|
||||
run: |
|
||||
unzip wekan-${VERSION}-amd64.zip
|
||||
rm -rf bundle/programs/server/node_modules
|
||||
# node:22.22.2-slim s390x — same Node.js version as Meteor 3.4 bundled
|
||||
docker run --rm --platform linux/s390x \
|
||||
-v "$(pwd)/bundle:/bundle" \
|
||||
-w /bundle/programs/server \
|
||||
node:22.22.2-slim \
|
||||
sh -c "apt-get update -qq && apt-get install -y -q build-essential python3 && npm install"
|
||||
|
||||
- name: Clean up and create s390x zip
|
||||
run: |
|
||||
zip -r wekan-${VERSION}-s390x.zip bundle
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bundle-s390x
|
||||
path: wekan-${{ needs.prepare.outputs.version }}-s390x.zip
|
||||
retention-days: 1
|
||||
|
||||
# ─── 4b. Build ppc64le bundle (rebuild native modules via QEMU container) ────
|
||||
# Meteor 3.4 does NOT support ppc64le (installer exits with error on that arch).
|
||||
# GitHub Actions has no native ppc64le runners.
|
||||
# Solution: QEMU emulation + node:22.22.2-slim (ppc64le variant) for npm rebuild.
|
||||
build-ppc64le:
|
||||
needs: [prepare, build-amd64]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
steps:
|
||||
- name: Set up QEMU for ppc64le
|
||||
uses: docker/setup-qemu-action@v4
|
||||
with:
|
||||
platforms: ppc64le
|
||||
|
||||
- name: Install unzip/zip
|
||||
run: sudo apt-get update && sudo apt-get install -y unzip zip
|
||||
|
||||
- name: Download amd64 bundle
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bundle-amd64
|
||||
|
||||
- name: Rebuild native modules for ppc64le in container
|
||||
run: |
|
||||
unzip wekan-${VERSION}-amd64.zip
|
||||
rm -rf bundle/programs/server/node_modules
|
||||
# node:22.22.2-slim ppc64le — same Node.js version as Meteor 3.4 bundled
|
||||
docker run --rm --platform linux/ppc64le \
|
||||
-v "$(pwd)/bundle:/bundle" \
|
||||
-w /bundle/programs/server \
|
||||
node:22.22.2-slim \
|
||||
sh -c "apt-get update -qq && apt-get install -y -q build-essential python3 && npm install"
|
||||
|
||||
- name: Clean up and create ppc64le zip
|
||||
run: |
|
||||
zip -r wekan-${VERSION}-ppc64le.zip bundle
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bundle-ppc64le
|
||||
path: wekan-${{ needs.prepare.outputs.version }}-ppc64le.zip
|
||||
retention-days: 1
|
||||
|
||||
# ─── 4c. Build armhf bundle (rebuild native modules via QEMU container) ──────
|
||||
# Meteor 3.4 does NOT support arm/v7 (armhf). Installer explicitly rejects it.
|
||||
# GitHub Actions has no native arm/v7 runners.
|
||||
# Solution: QEMU emulation + node:22.22.2-slim (arm/v7 variant) for npm rebuild.
|
||||
# arm/v7 under QEMU is slowest — timeout set to 120 min.
|
||||
build-armhf:
|
||||
needs: [prepare, build-amd64]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
steps:
|
||||
- name: Set up QEMU for arm/v7
|
||||
uses: docker/setup-qemu-action@v4
|
||||
with:
|
||||
platforms: arm
|
||||
|
||||
- name: Install unzip/zip
|
||||
run: sudo apt-get update && sudo apt-get install -y unzip zip
|
||||
|
||||
- name: Download amd64 bundle
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bundle-amd64
|
||||
|
||||
- name: Rebuild native modules for armhf in container
|
||||
run: |
|
||||
unzip wekan-${VERSION}-amd64.zip
|
||||
rm -rf bundle/programs/server/node_modules
|
||||
# node:22.22.2-slim arm/v7 — same Node.js version as Meteor 3.4 bundled
|
||||
docker run --rm --platform linux/arm/v7 \
|
||||
-v "$(pwd)/bundle:/bundle" \
|
||||
-w /bundle/programs/server \
|
||||
node:22.22.2-slim \
|
||||
sh -c "apt-get update -qq && apt-get install -y -q build-essential python3 && npm install"
|
||||
|
||||
- name: Clean up and create armhf zip
|
||||
run: |
|
||||
zip -r wekan-${VERSION}-armhf.zip bundle
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bundle-armhf
|
||||
path: wekan-${{ needs.prepare.outputs.version }}-armhf.zip
|
||||
retention-days: 1
|
||||
|
||||
# ─── 5. Build win64 bundle (rebuild native modules on Windows) ───────────────
|
||||
build-win64:
|
||||
needs: [prepare, build-amd64]
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22.22.2'
|
||||
|
||||
- name: Install node-gyp and build tools
|
||||
run: |
|
||||
npm install -g windows-build-tools --vs2019 2>$null; exit 0
|
||||
|
||||
- name: Install 7-zip
|
||||
run: choco install -y 7zip
|
||||
|
||||
- name: Download amd64 bundle
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bundle-amd64
|
||||
|
||||
- name: Rebuild native modules for Windows
|
||||
shell: cmd
|
||||
run: |
|
||||
7z x wekan-%VERSION%-amd64.zip
|
||||
cd bundle\programs\server
|
||||
rmdir /s /q node_modules
|
||||
npm install
|
||||
cd ..\..\..
|
||||
|
||||
- name: Create win64 zip
|
||||
shell: cmd
|
||||
run: 7z a wekan-%VERSION%-win64.zip bundle
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bundle-win64
|
||||
path: wekan-${{ needs.prepare.outputs.version }}-win64.zip
|
||||
retention-days: 1
|
||||
|
||||
# ─── 6. Create GitHub Release and upload all bundles ────────────────────────
|
||||
release:
|
||||
needs: [prepare, build-amd64, build-arm64, build-s390x, build-ppc64le, build-armhf, build-win64]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
VERSION: ${{ needs.prepare.outputs.version }}
|
||||
steps:
|
||||
- name: Download all bundles
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: bundle-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Create GitHub Release with all bundles
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: v${{ needs.prepare.outputs.version }}
|
||||
body: ${{ needs.prepare.outputs.changelog }}
|
||||
files: |
|
||||
wekan-${{ needs.prepare.outputs.version }}-amd64.zip
|
||||
wekan-${{ needs.prepare.outputs.version }}-arm64.zip
|
||||
wekan-${{ needs.prepare.outputs.version }}-s390x.zip
|
||||
wekan-${{ needs.prepare.outputs.version }}-ppc64le.zip
|
||||
wekan-${{ needs.prepare.outputs.version }}-armhf.zip
|
||||
wekan-${{ needs.prepare.outputs.version }}-win64.zip
|
||||
|
||||
# ─── 7. Build and push multi-platform Docker image ──────────────────────────
|
||||
#docker:
|
||||
# needs: [prepare, release]
|
||||
# runs-on: ubuntu-latest
|
||||
# env:
|
||||
# VERSION: ${{ needs.prepare.outputs.version }}
|
||||
# steps:
|
||||
# - uses: actions/checkout@v6
|
||||
#
|
||||
# - name: Set up QEMU
|
||||
# uses: docker/setup-qemu-action@v4
|
||||
#
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v3
|
||||
#
|
||||
# - name: Log in to Docker Hub
|
||||
# uses: docker/login-action@v4
|
||||
# with:
|
||||
# username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
# password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
#
|
||||
# - name: Log in to Quay.io
|
||||
# uses: docker/login-action@v4
|
||||
# with:
|
||||
# registry: quay.io
|
||||
# username: ${{ secrets.QUAY_USERNAME }}
|
||||
# password: ${{ secrets.QUAY_PASSWORD }}
|
||||
#
|
||||
# - name: Log in to GitHub Container Registry
|
||||
# uses: docker/login-action@v4
|
||||
# with:
|
||||
# registry: ghcr.io
|
||||
# username: ${{ github.actor }}
|
||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
||||
#
|
||||
# - name: Build and push Docker image to all registries
|
||||
# uses: docker/build-push-action@v7
|
||||
# with:
|
||||
# context: .
|
||||
# platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/ppc64le,linux/s390x
|
||||
# push: true
|
||||
# build-args: VERSION=${{ needs.prepare.outputs.version }}
|
||||
# tags: |
|
||||
# #wekanteam/wekan:v${{ needs.prepare.outputs.version }}
|
||||
# #wekanteam/wekan:latest
|
||||
# quay.io/wekan/wekan:v${{ needs.prepare.outputs.version }}
|
||||
# quay.io/wekan/wekan:latest
|
||||
# ghcr.io/wekan/wekan:v${{ needs.prepare.outputs.version }}
|
||||
# ghcr.io/wekan/wekan:latest
|
||||
# cache-from: type=gha
|
||||
# cache-to: type=gha,mode=max
|
||||
|
||||
# ─── 8. Build and publish snap packages (all architectures via Launchpad) ────
|
||||
# Uses snapcraft remote-build on Launchpad's native builders (more reliable
|
||||
# than QEMU for snap builds). Each architecture bundles its own database:
|
||||
# amd64, arm64 — MongoDB 7.x prebuilt native binary
|
||||
# armhf, s390x, ppc64el — MongoDB 7.x amd64 binary run via qemu-x86_64-static
|
||||
# (no prebuilt MongoDB binaries exist for these arches)
|
||||
#snap:
|
||||
# needs: [prepare, release]
|
||||
# runs-on: ubuntu-latest
|
||||
# env:
|
||||
# VERSION: ${{ needs.prepare.outputs.version }}
|
||||
# steps:
|
||||
# - uses: actions/checkout@v6
|
||||
#
|
||||
# - name: Install snapcraft
|
||||
# run: sudo snap install snapcraft --classic
|
||||
#
|
||||
# - name: Remote-build snap for all architectures via Launchpad
|
||||
# env:
|
||||
# SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}
|
||||
# run: |
|
||||
# snapcraft remote-build \
|
||||
# --build-for=amd64,arm64,armhf,s390x,ppc64el \
|
||||
# --launchpad-accept-public-upload
|
||||
#
|
||||
# - name: Upload all snap files as artifacts
|
||||
# uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: snaps-all-arches
|
||||
# path: "*.snap"
|
||||
# retention-days: 30
|
||||
#
|
||||
# # Publish to edge — NOT stable.
|
||||
# # Stable channel holds the old WeKan version until database migrations are ready.
|
||||
# # Promote to stable manually via the Snap Store dashboard when migrations exist.
|
||||
# - name: Publish all snaps to Snap Store (candidate, beta, edge)
|
||||
# env:
|
||||
# SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}
|
||||
# run: |
|
||||
# for snap_file in *.snap; do
|
||||
# echo "Publishing $snap_file ..."
|
||||
# snapcraft upload --release=edge "$snap_file"
|
||||
# done
|
||||
#
|
||||
# - name: Upload all snaps to GitHub Release
|
||||
# uses: softprops/action-gh-release@v2
|
||||
# with:
|
||||
# files: "*.snap"
|
||||
@@ -124,13 +124,18 @@ jobs:
|
||||
restore-keys: |
|
||||
v1-meteor_build_cache-
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22.22.2'
|
||||
|
||||
- name: Setup meteor
|
||||
uses: meteorengineer/setup-meteor@v3
|
||||
with:
|
||||
meteor-release: '2.2'
|
||||
meteor-release: '3.4'
|
||||
|
||||
- name: Install NPM Dependencies
|
||||
run: meteor npm ci
|
||||
run: meteor npm install
|
||||
|
||||
- name: Run Tests
|
||||
run: sh ./test-wekan.sh -cv
|
||||
|
||||
+14
@@ -35,7 +35,21 @@ ehthumbs.db
|
||||
.devcontainer/volumes*/
|
||||
.coverage
|
||||
|
||||
# Rspack build artifacts
|
||||
_build/
|
||||
public/build-assets/
|
||||
public/build-chunks/
|
||||
private/build-assets/
|
||||
private/build-chunks/
|
||||
public/client-rspack.js.map
|
||||
|
||||
# Helm chart
|
||||
# Chart dependencies
|
||||
/helm/wekan/**/*.tgz
|
||||
/helm/wekan/charts
|
||||
|
||||
# Meteor Modern-Tools build context directories
|
||||
_build
|
||||
*/build-assets
|
||||
*/build-chunks
|
||||
.rsdoctor
|
||||
+33
-42
@@ -3,32 +3,36 @@
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
meteor-base@1.5.1
|
||||
meteor-base
|
||||
|
||||
# Build system
|
||||
ecmascript
|
||||
standard-minifier-js
|
||||
mquandalle:jade
|
||||
rspack
|
||||
|
||||
# Polyfills
|
||||
es5-shim@4.8.0
|
||||
|
||||
blaze
|
||||
blaze@3.0.2
|
||||
|
||||
# Collections
|
||||
aldeed:collection2
|
||||
aldeed:schema-index@4.0.0-beta.5
|
||||
reywood:publish-composite
|
||||
dburles:collection-helpers
|
||||
mongo@1.16.8
|
||||
mongo
|
||||
|
||||
# The holy trifecta - this is a replacement for subs-manager
|
||||
# for IndexedDB persistence and auto-sync
|
||||
jam:offline
|
||||
# for optimistic methods with offline queuing
|
||||
jam:method
|
||||
# global subscription caching
|
||||
jam:pub-sub
|
||||
|
||||
# Account system
|
||||
accounts-password@2.4.0
|
||||
communitypackages:core@2.0.3
|
||||
communitypackages:unstyled@2.0.1
|
||||
accounts-password
|
||||
communitypackages:core
|
||||
communitypackages:unstyled
|
||||
communitypackages:flow-routing
|
||||
# Routing
|
||||
ostrio:flow-router-extra
|
||||
simple:rest-accounts-password
|
||||
wekan-ldap
|
||||
wekan-accounts-cas
|
||||
wekan-accounts-sandstorm
|
||||
@@ -37,42 +41,29 @@ wekan-oidc
|
||||
wekan-accounts-oidc
|
||||
|
||||
# Utilities
|
||||
check@1.3.2
|
||||
jquery@3.0.0!
|
||||
random@1.2.1
|
||||
reactive-dict@1.3.1
|
||||
session@1.2.1
|
||||
tracker@1.3.3
|
||||
audit-argument-checks@1.0.7
|
||||
http@2.0.0! # force new http package
|
||||
|
||||
# Datepicker (disabled - using native HTML inputs)
|
||||
# wekan-bootstrap-datepicker
|
||||
check
|
||||
random
|
||||
reactive-dict
|
||||
session
|
||||
tracker
|
||||
audit-argument-checks
|
||||
|
||||
# UI components
|
||||
ostrio:i18n
|
||||
reactive-var@1.0.12
|
||||
meteor-autosize
|
||||
shell-server@0.5.0
|
||||
email@2.2.5
|
||||
dynamic-import@0.7.3
|
||||
msavin:usercache
|
||||
# Keep stylus in 1.1.0, because building v2 takes extra 52 minutes.
|
||||
meteorhacks:subs-manager
|
||||
reactive-var
|
||||
shell-server
|
||||
email
|
||||
dynamic-import
|
||||
wekan-markdown
|
||||
quave:synced-cron
|
||||
ostrio:cookies
|
||||
ostrio:files@2.3.0
|
||||
lmieulet:meteor-coverage
|
||||
meteortesting:mocha@2.0.3
|
||||
aldeed:simple-schema
|
||||
ostrio:files
|
||||
# lmieulet:meteor-coverage
|
||||
meteortesting:mocha
|
||||
matb33:collection-hooks
|
||||
simple:json-routes
|
||||
service-configuration@1.3.2
|
||||
communitypackages:picker
|
||||
minifier-css@1.6.4
|
||||
ejson@1.1.3
|
||||
logging@1.3.3
|
||||
service-configuration
|
||||
minifier-css
|
||||
ejson
|
||||
logging
|
||||
wekan-fullcalendar
|
||||
wekan-fontawesome
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
METEOR@2.16
|
||||
METEOR@3.4
|
||||
+102
-114
@@ -1,126 +1,114 @@
|
||||
accounts-base@2.2.11
|
||||
accounts-oauth@1.4.4
|
||||
accounts-password@2.4.0
|
||||
aldeed:collection2@2.10.0
|
||||
aldeed:collection2-core@1.2.0
|
||||
aldeed:schema-deny@1.1.0
|
||||
aldeed:schema-index@1.1.1
|
||||
aldeed:simple-schema@1.5.4
|
||||
allow-deny@1.1.1
|
||||
audit-argument-checks@1.0.7
|
||||
autoupdate@1.8.0
|
||||
babel-compiler@7.10.5
|
||||
babel-runtime@1.5.1
|
||||
base64@1.0.12
|
||||
binary-heap@1.0.11
|
||||
blaze@2.7.1
|
||||
blaze-tools@1.1.3
|
||||
boilerplate-generator@1.7.2
|
||||
caching-compiler@1.2.2
|
||||
caching-html-compiler@1.2.1
|
||||
callback-hook@1.5.1
|
||||
check@1.4.1
|
||||
accounts-base@3.2.0
|
||||
accounts-oauth@1.4.6
|
||||
accounts-password@3.2.2
|
||||
aldeed:collection2@4.1.5
|
||||
aldeed:schema-index@4.0.0
|
||||
aldeed:simple-schema@1.13.1
|
||||
allow-deny@2.1.0
|
||||
audit-argument-checks@1.0.8
|
||||
autoupdate@2.0.1
|
||||
babel-compiler@7.13.0
|
||||
babel-runtime@1.5.2
|
||||
base64@1.0.13
|
||||
binary-heap@1.0.12
|
||||
blaze@3.0.2
|
||||
blaze-tools@2.0.0
|
||||
boilerplate-generator@2.1.0
|
||||
caching-compiler@2.0.1
|
||||
caching-html-compiler@2.0.0
|
||||
callback-hook@1.6.1
|
||||
check@1.5.0
|
||||
communitypackages:core@2.0.3
|
||||
communitypackages:flow-routing@2.1.0
|
||||
communitypackages:picker@1.1.1
|
||||
communitypackages:unstyled@2.0.1
|
||||
dburles:collection-helpers@1.1.0
|
||||
ddp@1.4.1
|
||||
ddp-client@2.6.2
|
||||
ddp-common@1.4.1
|
||||
ddp-rate-limiter@1.2.1
|
||||
ddp-server@2.7.1
|
||||
deps@1.0.12
|
||||
diff-sequence@1.1.2
|
||||
dynamic-import@0.7.3
|
||||
ecmascript@0.16.8
|
||||
ecmascript-runtime@0.8.1
|
||||
ecmascript-runtime-client@0.12.1
|
||||
ecmascript-runtime-server@0.11.0
|
||||
ejson@1.1.3
|
||||
email@2.2.6
|
||||
es5-shim@4.8.0
|
||||
fetch@0.1.4
|
||||
geojson-utils@1.0.11
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.1.3
|
||||
htmljs@1.1.1
|
||||
http@2.0.0
|
||||
id-map@1.1.1
|
||||
inter-process-messaging@0.1.1
|
||||
jquery@3.0.0
|
||||
lmieulet:meteor-coverage@1.1.4
|
||||
localstorage@1.2.0
|
||||
logging@1.3.4
|
||||
matb33:collection-hooks@1.3.0
|
||||
mdg:validation-error@0.5.1
|
||||
meteor@1.11.5
|
||||
meteor-autosize@5.0.1
|
||||
meteor-base@1.5.1
|
||||
meteorhacks:picker@1.0.3
|
||||
meteorhacks:subs-manager@1.6.4
|
||||
core-runtime@1.0.0
|
||||
ddp@1.4.2
|
||||
ddp-client@3.1.1
|
||||
ddp-common@1.4.4
|
||||
ddp-rate-limiter@1.2.2
|
||||
ddp-server@3.1.2
|
||||
diff-sequence@1.1.3
|
||||
dynamic-import@0.7.4
|
||||
ecmascript@0.17.0
|
||||
ecmascript-runtime@0.8.3
|
||||
ecmascript-runtime-client@0.12.3
|
||||
ecmascript-runtime-server@0.11.1
|
||||
ejson@1.1.5
|
||||
email@3.1.2
|
||||
es5-shim@4.8.1
|
||||
facts-base@1.0.2
|
||||
fetch@0.1.6
|
||||
geojson-utils@1.0.12
|
||||
hot-code-push@1.0.5
|
||||
html-tools@2.0.0
|
||||
htmljs@2.0.1
|
||||
http@1.4.4
|
||||
id-map@1.2.0
|
||||
inter-process-messaging@0.1.2
|
||||
jam:method@1.9.1
|
||||
jam:offline@0.4.1
|
||||
jam:pub-sub@0.5.1
|
||||
jquery@3.0.2
|
||||
lai:collection-extensions@1.0.0
|
||||
localstorage@1.2.1
|
||||
logging@1.3.6
|
||||
matb33:collection-hooks@2.1.0
|
||||
meteor@2.2.0
|
||||
meteor-base@1.5.2
|
||||
meteortesting:browser-tests@1.4.2
|
||||
meteortesting:mocha@2.1.0
|
||||
meteortesting:mocha-core@8.0.1
|
||||
minifier-css@1.6.4
|
||||
minifier-js@2.8.0
|
||||
minifiers@1.1.8-faster-rebuild.0
|
||||
minimongo@1.9.4
|
||||
modern-browsers@0.1.10
|
||||
modules@0.20.0
|
||||
modules-runtime@0.13.1
|
||||
mongo@1.16.10
|
||||
mongo-decimal@0.1.3
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.8
|
||||
mquandalle:jade@0.4.9
|
||||
mquandalle:jade-compiler@0.4.5
|
||||
msavin:usercache@1.8.0
|
||||
npm-mongo@4.17.2
|
||||
oauth@2.2.1
|
||||
oauth2@1.3.2
|
||||
observe-sequence@1.0.21
|
||||
ordered-dict@1.1.0
|
||||
ostrio:cookies@2.7.2
|
||||
minifier-css@2.0.1
|
||||
minifier-js@3.1.0
|
||||
minimongo@2.0.5
|
||||
modern-browsers@0.2.3
|
||||
modules@0.20.3
|
||||
modules-runtime@0.13.2
|
||||
mongo@2.2.0
|
||||
mongo-decimal@0.2.0
|
||||
mongo-dev-server@1.1.1
|
||||
mongo-id@1.0.9
|
||||
npm-mongo@6.16.1
|
||||
oauth@3.0.2
|
||||
oauth2@1.3.3
|
||||
observe-sequence@2.0.0
|
||||
ordered-dict@1.2.0
|
||||
ostrio:cookies@2.9.1
|
||||
ostrio:cstorage@4.0.1
|
||||
ostrio:files@2.3.3
|
||||
ostrio:flow-router-extra@3.10.1
|
||||
ostrio:files@3.0.1
|
||||
ostrio:flow-router-extra@3.12.1
|
||||
ostrio:i18n@3.2.1
|
||||
promise@0.12.2
|
||||
quave:synced-cron@2.2.1
|
||||
raix:eventemitter@0.1.3
|
||||
random@1.2.1
|
||||
rate-limit@1.1.1
|
||||
react-fast-refresh@0.2.8
|
||||
reactive-dict@1.3.1
|
||||
reactive-var@1.0.12
|
||||
reload@1.3.1
|
||||
retry@1.1.0
|
||||
promise@1.0.0
|
||||
quave:synced-cron@2.4.0
|
||||
raix:eventemitter@1.0.0
|
||||
random@1.2.2
|
||||
rate-limit@1.1.2
|
||||
react-fast-refresh@0.3.0
|
||||
reactive-dict@1.3.2
|
||||
reactive-var@1.0.13
|
||||
reload@1.3.2
|
||||
retry@1.1.1
|
||||
reywood:publish-composite@1.9.0
|
||||
routepolicy@1.1.1
|
||||
service-configuration@1.3.4
|
||||
session@1.2.1
|
||||
sha@1.0.9
|
||||
shell-server@0.5.0
|
||||
simple:authenticate-user-by-token@1.2.1
|
||||
simple:json-routes@2.3.1
|
||||
simple:rest-accounts-password@1.2.2
|
||||
simple:rest-bearer-token-parser@1.1.1
|
||||
simple:rest-json-error-handler@1.1.1
|
||||
socket-stream-client@0.5.2
|
||||
spacebars@1.4.1
|
||||
spacebars-compiler@1.3.1
|
||||
standard-minifier-js@2.8.1
|
||||
templating@1.4.1
|
||||
templating-compiler@1.4.1
|
||||
templating-runtime@1.5.0
|
||||
templating-tools@1.2.2
|
||||
tracker@1.3.3
|
||||
typescript@4.9.5
|
||||
underscore@1.6.1
|
||||
url@1.3.2
|
||||
webapp@1.13.8
|
||||
webapp-hashing@1.1.1
|
||||
routepolicy@1.1.2
|
||||
rspack@1.0.0
|
||||
service-configuration@1.3.5
|
||||
session@1.2.2
|
||||
sha@1.0.10
|
||||
shell-server@0.7.0
|
||||
socket-stream-client@0.6.1
|
||||
spacebars@2.0.0
|
||||
spacebars-compiler@2.0.0
|
||||
standard-minifier-js@3.2.0
|
||||
templating@1.4.4
|
||||
templating-compiler@2.0.0
|
||||
templating-runtime@2.0.1
|
||||
templating-tools@2.0.0
|
||||
tools-core@1.0.0
|
||||
tracker@1.3.4
|
||||
typescript@5.9.3
|
||||
url@1.3.5
|
||||
webapp@2.1.0
|
||||
webapp-hashing@1.1.2
|
||||
wekan-accounts-cas@0.2.0
|
||||
wekan-accounts-lockout@1.1.0
|
||||
wekan-accounts-oidc@1.0.10
|
||||
|
||||
+412
-2
@@ -14,7 +14,7 @@ Newest WeKan at these platforms:
|
||||
|
||||
Fixing other platforms In Progress.
|
||||
|
||||
- [Node.js 14.21.4](https://github.com/wekan/node-v14-esm/releases/tag/v14.21.4) or [Node.js 14.21.3](https://nodejs.org/dist/latest-v14.x/)
|
||||
- [Node.js 22.x](https://nodejs.org/dist/latest-v22.x/)
|
||||
- MongoDB 6.x and 7.x, or [FerretDB2/PostgreSQL](https://github.com/wekan/wekan/blob/main/docs/Databases/FerretDB2-PostgreSQL.md)
|
||||
|
||||
[Upgrade WeKan](https://wekan.fi/upgrade/)
|
||||
@@ -24,10 +24,420 @@ Those are fixed at WeKan 8.07 where database directory is back to /var/snap/weka
|
||||
|
||||
WeKan 8.00-8.24 used Colorful Unicode Emoji Icons, versions before and after use mostly Font Awesome 4.7 icons.
|
||||
|
||||
Upgrading to Meteor 3 progress:
|
||||
WeKan 8.43 and newer uses Meteor 3.x, Node.js 22.x and MongoDB 7.x, huge thanks to harryadel:
|
||||
|
||||
- https://harryadel.com/dev-diary-24/
|
||||
- https://harryadel.com/dev-diary-25/
|
||||
- https://harryadel.com/dev-diary-26/
|
||||
|
||||
# v8.71 2026-04-06 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fix: Added missing null check to notification](https://github.com/wekan/wekan/commit/5df924f416083a50795443687e1c28f402dc8132).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.70 2026-04-06 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Convert upsert calls to async in authentication.js](https://github.com/wekan/wekan/commit/dfcaaf0ae719e8d8b918ac632100293a6e401371).
|
||||
Thanks to snowsky and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.69 2026-04-06 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Try to fix Snap to start MongoDB OpLog correctly](https://github.com/wekan/wekan/commit/fbdfcead50fb599d4fafecc2f5847c46c73adf72).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.68 2026-04-06 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Try to fix Snap to start MongoDB OpLog correctly](https://github.com/wekan/wekan/commit/9e316662800fc3b5e38b1176e70a50b18d73d2a6).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.67 2026-04-05 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Try to fix GitHub Actions release bundle builds](https://github.com/wekan/wekan/commit/7707a5b7fc21f981f26c512cd3fea6171a57bd46).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.65 2026-04-05 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fix Docker Actions Test suite](https://github.com/wekan/wekan/commit/c0661506c42ac412ae9e8fc5392d160efbc446ea).
|
||||
Thanks to xet7.
|
||||
- [Try to fix GitHub Actions release bundle builds](https://github.com/wekan/wekan/commit/d3155d24c64fe1104afe454b756d2a6324fab5c2).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.64 2026-04-05 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Try to fix GitHub Actions release bundle builds](https://github.com/wekan/wekan/commit/7a7909b49feaaa938b5dbfc2083b0c719187859d).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.63 2026-04-05 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Try to fix GitHub Actions release bundle builds](https://github.com/wekan/wekan/commit/6a78a178e2fcff9ff280ba44c123403e2792bb7b).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.62 2026-04-05 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Try to fix GitHub Actions release bundle builds](https://github.com/wekan/wekan/commit/cd55c0e12ed80ddcb07895f2d787be95c440f991).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.61 2026-04-05 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- [Updated Docker build script to check directories and repos exist](https://github.com/wekan/wekan/commit/81319fc48bbd2a91553090428455bc11aff24fbc).
|
||||
Thanks to xet7.
|
||||
- [Updated release scripts: Delete all GitHub Actions Caches, so that old stuff does not break builds](https://github.com/wekan/wekan/commit/f3a97b993842425e4f13bb78e5939a069d64887e).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix: Server crash during comment deletion](https://github.com/wekan/wekan/pull/6246).
|
||||
Thanks to KhaoulaMaleh.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.60 2026-04-05 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- [Updated Docker build script](https://github.com/wekan/wekan/commit/0b59f87887c215dc34093409ccd12da9acd3ef02).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.59 2026-04-05 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fix docker-compose.yml to use OpLog](https://github.com/wekan/wekan/commit/30a1792c92de9d485c0b9ed3970a1187ca974576).
|
||||
Thanks to xet7.
|
||||
- [Fix Snap to use OpLog](https://github.com/wekan/wekan/commit/1fa72fd34ab4547e97e7e0c201bc3d50d478ef5e).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.58 2026-04-05 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- [Updated build docs](https://github.com/wekan/wekan/commit/b3d14fb5e87545635006958686f0340028711820).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Try to fix .zip Bundle, Docker and Snap](https://github.com/wekan/wekan/commit/59956ca720d2103baaa9ecf2befff2ec76430feb).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.57 2026-04-05 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Try to fix Snap](https://github.com/wekan/wekan/commit/9f549dfcd2efa241a2ad3bdf5b0d2d942064d4e3).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.56 2026-04-05 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Try to fix Snap](https://github.com/wekan/wekan/commit/56f26418ea9d1321fc5109c4b39104ff98d65429).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.55 2026-04-05 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- Docs: IMPORTANT: Do NOT `npm audit fix --force`, it breaks builds.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/2bad17010cd445c004a228b732b17c68e18eb316),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/9fa6574c80589218569dc3c49782a1a8036aa542).
|
||||
Thanks to xet7.
|
||||
- [Updated dependencies](https://github.com/wekan/wekan/commit/73d69f4631216dfe504d9f05230adadc2850c4f3).
|
||||
Thanks to developers of dependencies.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix Cards Reports is not working](https://github.com/wekan/wekan/commit/2b02e7712ee6a8ae8af5d416ce2d288a25386fdf).
|
||||
Thanks to titver968 and xet7.
|
||||
- [Fix Search All Boards](https://github.com/wekan/wekan/commit/faf8453c6d10613edf32a734e549961860fcdd32).
|
||||
Thanks to YosserDerbali and xet7.
|
||||
- [Try to fix Snap](https://github.com/wekan/wekan/commit/8443e3511d61d8c1bb419ed5bca2e28483c5d133).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.54 2026-04-04 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Revert v8.53 Refactor upsert calls to use async method](https://github.com/wekan/wekan/commit/811b79b641c765461f3d696ed00b5459a4747867).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.53 2026-04-04 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Refactor upsert calls to use async method](https://github.com/wekan/wekan/pull/6243).
|
||||
Thanks to snowsky.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.52 2026-04-03 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Try to fix Docker and Snap](https://github.com/wekan/wekan/commit/03e0cfdc15c56ac0219f5e435034a1c0ad5006ae).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.51 2026-04-03 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- [Updated release scripts](https://github.com/wekan/wekan/commit/4594711be41d1e9cf15b1f8bb070fcb36bf736ca).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix Bug: WeKan v8.50 WRITABLE_PATH](https://github.com/wekan/wekan/commit/edc6b18b5f77320e966e56c888575bcb764096dc).
|
||||
Thanks to snowsky and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.50 2026-04-03 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- [Uncommented build steps. Not yet tested are required dependencies included](https://github.com/wekan/wekan/commit/145a17fee5dc9430de7a60100657566a4a69bc7b).
|
||||
Thanks to xet7.
|
||||
- [Updated npm package @wekanteam/exceljs](https://github.com/wekan/wekan/commit/6e8589b1f96b5f0c2ddec0831b238096ce610b1e).
|
||||
Thanks to xet7.
|
||||
- [Updated dependencies](https://github.com/wekan/wekan/commit/bf02c9a453e27edba1c08830eb4783b9dfc7bbc2).
|
||||
Thanks to developers of dependencies.
|
||||
NOTE: Do not `npm audit fix --force`, it downgrades @meteorjs/rspack and breaks builds.
|
||||
`npm audit` shows false info, it does not recognize that elliptic is
|
||||
already updated to 6.6.1 with override at package.json like you can see with
|
||||
`npm list elliptic`.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix indentation for release-all.yml workflow](https://github.com/wekan/wekan/pull/6240).
|
||||
Thanks to snowsky.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.49 2026-03-31 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fix: Missing module @meteorjs/reify/lib/runtime](https://github.com/wekan/wekan/pull/6237).
|
||||
Thanks to mzch and kovacs-andras.
|
||||
- [Fix docker-compose.yml](https://github.com/wekan/wekan/commit/d07fcceb6a884cf1ff0c9a2d36691af0304f36e7).
|
||||
Thanks to xet7.
|
||||
- [Try to fix Snap](https://github.com/wekan/wekan/commit/8b4676175caa4af36bf11567676f3c3f522f9512).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.48 2026-03-31 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- Updated release scripts.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/43601c5e3e95d6026f2e558f261c02a9578dcecd),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/56377aaf4c5e92c1a03dc996b6c649ce17553280).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.47 2026-03-31 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- [Updated attachments and avatar path to be at WRITABLE_PATH/files/attachments etc](https://github.com/wekan/wekan/commit/e58e99c2852dd81a040dcffc0f1f2798c4b8cf15).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.46 2026-03-31 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- Updated release scripts.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/6bc68ba57bf15339b80011d131a7da65124e071d),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/ce52b8a0ab29805c562a2af0b79e30d970594e17).
|
||||
Thanks to xet7.
|
||||
- [Updated docker-compose.yml to change from FerretDB/PostgreSQL to MongoDB](https://github.com/wekan/wekan/commit/02b75824e5dc24c968959d5a64f846efe33dee4f).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.45 2026-03-31 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- Updated release scripts.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/4223c2a79f4c276ab1c9734a94314f1d1d1ce033),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/6125822cf7244242832d1fe02f99150abb7350d0),
|
||||
[Part 3](https://github.com/wekan/wekan/commit/e90b2e384c168fbb4cee389d8af9b54a5bc57734),
|
||||
[Part 4](https://github.com/wekan/wekan/commit/29550cbd9335fc95e4f4ac399fe754ece8573450),
|
||||
[Part 5](https://github.com/wekan/wekan/commit/8349fb6b63097970d06af73af388b030761967bd),
|
||||
[Part 6](https://github.com/wekan/wekan/commit/f43b76c323c1f09cf1885251792b29229ef77d2b),
|
||||
[Part 7](https://github.com/wekan/wekan/commit/e90b2e384c168fbb4cee389d8af9b54a5bc57734).
|
||||
Thanks to xet7.
|
||||
- Updated GitHub Actions.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/4b8cc0683e9dfdffb7ff7896f622afe8f908a7f5),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/df067f01e894f43305ad36cebdf48a1bf55ff33a),
|
||||
[Part 3](https://github.com/wekan/wekan/commit/ec1e3835586bdc09d93d5049794e8657b460acef),
|
||||
[Part 4](https://github.com/wekan/wekan/commit/79853a2fb7efb738f2f10b6c2e9fef69def88c48),
|
||||
[Part 5](https://github.com/wekan/wekan/commit/9ae2842fa4bedbd4c132943703ef462cc353ab1a),
|
||||
[Part 6](https://github.com/wekan/wekan/commit/1a5e23617de98e6b65f78dfb25c233440f818d11),
|
||||
[Part 7](https://github.com/wekan/wekan/commit/c38fa1c272c32323d8a164dfcf34b898127e6b1d),
|
||||
[Part 8](https://github.com/wekan/wekan/commit/d8a2e06a95713e33e5676df38951dfd3f991ecb3),
|
||||
[Part 9](https://github.com/wekan/wekan/commit/e6ccbfd3b88299c52fbaad9f85fc3b438e3cdc89),
|
||||
[Part 10](https://github.com/wekan/wekan/commit/7666c6c0e1660b5aba300f4e465877bcf73d8066).
|
||||
Thanks to dependabot.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- Try to fix Snap.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/e5c551f0092ccb71fafa3433bfd99911f9a2e9c0),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/55ec0b5ff5594c10ebbc8cba74339de2ad0557a6),
|
||||
[Part 3](https://github.com/wekan/wekan/commit/fd58bb9e4dd0f825237a50043a171fc223d44101),
|
||||
[Part 4](https://github.com/wekan/wekan/commit/cdf1d3305bd738fe5357a5f3a9df2f7e6858d1d0),
|
||||
[Part 5](https://github.com/wekan/wekan/commit/2aa8ecfc39a6f971134784817b46ba9867bb402d),
|
||||
[Part 6](https://github.com/wekan/wekan/commit/ed718f083aa0c9f82e368a5badfe73393753a617),
|
||||
[Part 7](https://github.com/wekan/wekan/commit/5c4130311a063340bd89dff571fbf685cda7a3ba),
|
||||
[Part 8](https://github.com/wekan/wekan/commit/33267fa2e462fcc6cd2243ae1b5b39d59b03988f),
|
||||
[Part 9](https://github.com/wekan/wekan/commit/87916c20bf579e16e21fa3bd9ac7687019b277eb),
|
||||
[Part 10](https://github.com/wekan/wekan/commit/12c223d2260c9bd9547ae35a394869ef90e8bc83).
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.44 2026-03-30 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- [From WeKan v8.43 FerretDB/PostgreSQL back to MongoDB 7.x](https://github.com/wekan/wekan/commit/cf2b3e4fc95ce4619c1ba13b63c3ad9d90bf59f6).
|
||||
Thanks to xet7.
|
||||
- [Updated dependencies](https://github.com/wekan/wekan/commit/82f1de078f16e87028ffd101e3f80550a4b18afa).
|
||||
Thanks to developers of dependencies.
|
||||
- Updated release scripts.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/a702596bee6aa812cb69a6673ad34f9b2875c729),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/29550cbd9335fc95e4f4ac399fe754ece8573450).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Always show all data without migrations, like Shared Lists that were before Per-Swimlanes lists, Orphaned Swimlanes/Lists/Cards etc](https://github.com/wekan/wekan/commit/ef75b9ecac8210ff000439aca92765b53a1551a4).
|
||||
Thanks to xet7.
|
||||
- [Fix globalSearch](https://github.com/wekan/wekan/commit/b788d398b9b2cb0caad9c28f79acdceb5b577f25).
|
||||
Thanks to snowsky.
|
||||
- [Fix myCards crash](https://github.com/wekan/wekan/commit/d5c9e402ce4ebe0318bc29d5b83784771cbe8ac3).
|
||||
Thanks to cristomoya.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.43 2026-03-27 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- Updated to Meteor 3.4, Node.js 22.22, MongoDB 7.x.
|
||||
[Part 1](https://github.com/wekan/wekan/pull/6205),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/7584afda6d28b75ae7bfa5388f71e8bcb5ab50a7).
|
||||
Thanks to harryadel and xet7.
|
||||
- [Updated dependencies of npm package @wekanteam/exceljs](https://github.com/wekan/wekan/commit/95ff8ef1f2f119c713ffba72122d51b6569b9d42).
|
||||
Thanks to xet7.
|
||||
- [Updated @meteorjs/rspack](https://github.com/wekan/wekan/commit/21ab640686d868ee46e16e812b36a1c3820952a5).
|
||||
Thanks to Meteor developers.
|
||||
- [Updated rebuild-wekan.sh for macOS 26.4](https://github.com/wekan/wekan/commit/f1e71247c70043634e040bd12f7d065b30ed6fc2).
|
||||
Thanks to xet7.
|
||||
- [Moved Meteor 3 migration docs to docs/Upgrade/](https://github.com/wekan/wekan/commit/517d1483876926cdbf6ce4774c8c52540eaec593).
|
||||
Thanks to xet7.
|
||||
- Updated dependencies.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/acfb081b907703c6846b8ccf71a9e2912b41713f),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/55660e0e69b7a283ab04ad408674d33eb71bb279).
|
||||
Thanks to developers of dependencies.
|
||||
- Updated release scripts.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/0fdf45e8476c0a619cd36891568c67d9092bd65b),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/07eb062474a5593683c1ed32f2637339fab72d48),
|
||||
[Part 3](https://github.com/wekan/wekan/commit/5cf3bba68fdfc57a7a9358bd914c11af7bdd37d4),
|
||||
[Part 4](https://github.com/wekan/wekan/commit/0e59033ea60f504d09d7eddb22a33ed113ea1326).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fixed rebuild-wekan.sh to build WeKan correctly](https://github.com/wekan/wekan/commit/4a8293f8812276f08d8a77a2c8be56441d7e87c1).
|
||||
Thanks to xet7.
|
||||
- [Export Card to Excel. Part 3. Also, fixed uploading attachments](https://github.com/wekan/wekan/commit/5c5ed102cabd07e1be00d3fdb01ea599da0a5517).
|
||||
Thanks to xet7.
|
||||
- [Removed temporary debug logs](https://github.com/wekan/wekan/commit/c1e3ce64a796fc8b921d1a30705b4aaf66c9c259).
|
||||
Thanks to xet7.
|
||||
- [Fixed typo](https://github.com/wekan/wekan/commit/23697d2a33ca527b29bcdadfe432fd9d1a180f9c).
|
||||
Thanks to YosserDerbali and xet7.
|
||||
- [Fix Set Color of Swimlane, List and Card](https://github.com/wekan/wekan/commit/0630d62ad0a02d80ab77436ce9797e4390c5cae2).
|
||||
Thanks to SVANNER, brlin-tw and xet7.
|
||||
- [Fix workspace settings modal overflow](https://github.com/wekan/wekan/pull/6218).
|
||||
Thanks to AymenHassini19 and aminsaidane.
|
||||
- [Fix attachment rename fails for clipboard uploads and keep extension metadata in sync](https://github.com/wekan/wekan/commit/38c670e08700f7b0ff185aadda40985bc71b1ac4).
|
||||
Thanks to snowsky, YosserDerbali and xet7.
|
||||
- [Fix card actions popup overflow](https://github.com/wekan/wekan/pull/6220).
|
||||
Thanks to aminsaidane.
|
||||
- [Fix Search: Guard Async methods in ReactiveCache and search publications](https://github.com/wekan/wekan/pull/6222).
|
||||
Thanks to KhaoulaMaleh.
|
||||
- [Fix/list actions popup overflow scroll](https://github.com/wekan/wekan/pull/6223).
|
||||
Thanks to YosserDerbali.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.42 2026-03-23 WeKan ® release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fix SSO login is broken](https://github.com/wekan/wekan/commit/2dce7a84cd0ce011972d377a05f5b26ff12daba7).
|
||||
Thanks to sbaecker, vladimirdulov and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.41 2026-03-23 WeKan ® release
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ Not paid:
|
||||
- This is because full-featured WeKan FLOSS kanban with MIT license is free to use
|
||||
also for Commercial Use without any payment, with full source code at
|
||||
https://github.com/wekan/wekan for all platforms at https://wekan.fi/install/ .
|
||||
There is nothing propietary. WeKan is NOT Big Tech.
|
||||
There is nothing proprietary. WeKan is NOT Big Tech.
|
||||
WeKan is maintained by https://github.com/xet7 at Finland https://wekan.fi .
|
||||
- Public FLOSS Developer Community Support:
|
||||
- https://github.com/wekan/wekan/issues
|
||||
|
||||
+22
-17
@@ -4,20 +4,21 @@ LABEL org.opencontainers.image.ref.name="ubuntu"
|
||||
LABEL org.opencontainers.image.version="24.04"
|
||||
LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
|
||||
|
||||
# TARGETARCH is automatically provided by Docker Buildx
|
||||
# TARGETARCH and TARGETVARIANT are automatically provided by Docker Buildx
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
ARG VERSION=8.71
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ENV BUILD_DEPS="apt-utils gnupg wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip"
|
||||
|
||||
ENV \
|
||||
DEBUG=false \
|
||||
NODE_VERSION=v14.21.4 \
|
||||
METEOR_RELEASE=METEOR@2.16 \
|
||||
NODE_VERSION=v22.22.2 \
|
||||
METEOR_RELEASE=METEOR@3.4 \
|
||||
USE_EDGE=false \
|
||||
METEOR_EDGE=1.5-beta.17 \
|
||||
NPM_VERSION=6.14.17 \
|
||||
FIBERS_VERSION=4.0.1 \
|
||||
NPM_VERSION=10.9.2 \
|
||||
SRC_PATH=./ \
|
||||
WITH_API=true \
|
||||
MONGO_OPLOG_URL="" \
|
||||
@@ -169,21 +170,24 @@ apt-get update --assume-yes
|
||||
apt-get upgrade --assume-yes
|
||||
apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS}
|
||||
|
||||
# Multi-arch mapping logic
|
||||
# Multi-arch mapping: Docker TARGETARCH -> Node.js arch name + WeKan bundle name
|
||||
# arm/v7 uses TARGETARCH=arm; armhf has no MongoDB Community -> uses FerretDB
|
||||
case "${TARGETARCH}" in
|
||||
"amd64") NODE_ARCH="x64" WEKAN_ARCH="amd64" ;;
|
||||
"arm64") NODE_ARCH="arm64" WEKAN_ARCH="arm64" ;;
|
||||
"s390x") NODE_ARCH="s390x" WEKAN_ARCH="s390x" ;;
|
||||
"amd64") NODE_ARCH="x64" WEKAN_ARCH="amd64" ;;
|
||||
"arm64") NODE_ARCH="arm64" WEKAN_ARCH="arm64" ;;
|
||||
"arm") NODE_ARCH="armv7l" WEKAN_ARCH="armhf" ;;
|
||||
"ppc64le") NODE_ARCH="ppc64le" WEKAN_ARCH="ppc64le" ;;
|
||||
"s390x") NODE_ARCH="s390x" WEKAN_ARCH="s390x" ;;
|
||||
*) echo "Unsupported architecture: ${TARGETARCH}"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Node.js Installation
|
||||
# Node.js Installation (official nodejs.org builds for Node 22)
|
||||
cd /tmp
|
||||
wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz"
|
||||
wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt"
|
||||
grep "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" SHASUMS256.txt | shasum -a 256 -c -
|
||||
wget "https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz"
|
||||
wget "https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc"
|
||||
grep "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" SHASUMS256.txt.asc | shasum -a 256 -c -
|
||||
tar xzf "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
|
||||
rm -f "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" SHASUMS256.txt
|
||||
rm -f "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" SHASUMS256.txt.asc
|
||||
ln -s "/usr/local/bin/node" "/usr/local/bin/nodejs"
|
||||
|
||||
# NPM configuration
|
||||
@@ -197,9 +201,10 @@ ln -sf $(which bsdtar) $(which tar)
|
||||
# WeKan Bundle Installation
|
||||
mkdir -p /home/wekan/app
|
||||
cd /home/wekan/app
|
||||
wget "https://github.com/wekan/wekan/releases/download/v8.41/wekan-8.41-${WEKAN_ARCH}.zip"
|
||||
unzip "wekan-8.41-${WEKAN_ARCH}.zip"
|
||||
rm "wekan-8.41-${WEKAN_ARCH}.zip"
|
||||
wget "https://github.com/wekan/wekan/releases/download/v${VERSION}/wekan-${VERSION}-${WEKAN_ARCH}.zip"
|
||||
unzip "wekan-${VERSION}-${WEKAN_ARCH}.zip"
|
||||
rm "wekan-${VERSION}-${WEKAN_ARCH}.zip"
|
||||
npm install --production --no-audit --prefix ./bundle/programs/server
|
||||
mv /home/wekan/app/bundle /build
|
||||
|
||||
# Restore original tar
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
FROM arm64v8/ubuntu:23.04 AS builder
|
||||
#FROM amd64/alpine:latest AS builder
|
||||
|
||||
# Set the environment variables for builder
|
||||
ENV QEMU_VERSION=v7.2.0-1 \
|
||||
QEMU_ARCHITECTURE=aarch64 \
|
||||
NODE_ARCHITECTURE=linux-arm64 \
|
||||
NODE_VERSION=v14.21.4 \
|
||||
WEKAN_VERSION=latest \
|
||||
WEKAN_ARCHITECTURE=arm64
|
||||
|
||||
# Install dependencies
|
||||
#RUN apk update && apk add ca-certificates outils-sha1 && \
|
||||
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||
RUN apt update && apt install ca-certificates wget unzip -y && \
|
||||
\
|
||||
# Download qemu static for our architecture
|
||||
wget https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VERSION}/qemu-${QEMU_ARCHITECTURE}-static.tar.gz -O - | tar -xz && \
|
||||
\
|
||||
# Download wekan and shasum
|
||||
wget https://releases.wekan.team/raspi3/wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
|
||||
wget https://releases.wekan.team/raspi3/SHA256SUMS.txt && \
|
||||
# Verify wekan
|
||||
grep wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip SHA256SUMS.txt | sha256sum -c - && \
|
||||
\
|
||||
# Unzip wekan
|
||||
unzip wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
|
||||
\
|
||||
# Download node and shasums
|
||||
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
|
||||
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt && \
|
||||
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
|
||||
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
|
||||
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
|
||||
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
|
||||
\
|
||||
# Verify nodejs authenticity
|
||||
grep node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz SHASUMS256.txt | sha256sum -c - && \
|
||||
\
|
||||
# Extract node and remove tar.gz
|
||||
tar xvzf node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz
|
||||
|
||||
# Build wekan dockerfile
|
||||
FROM --platform=linux/arm64 arm64v8/ubuntu:23.04
|
||||
LABEL maintainer="wekan"
|
||||
|
||||
# Set the environment variables (defaults where required)
|
||||
ENV QEMU_ARCHITECTURE=aarch64 \
|
||||
NODE_ARCHITECTURE=linux-arm64 \
|
||||
NODE_VERSION=v14.21.4 \
|
||||
NODE_ENV=production \
|
||||
NPM_VERSION=latest \
|
||||
WITH_API=true \
|
||||
PORT=8080 \
|
||||
ROOT_URL=http://localhost \
|
||||
MONGO_URL=mongodb://127.0.0.1:27017/wekan \
|
||||
LDAP_AUTHENTIFICATION_PASSWORD_FILE="" \
|
||||
OAUTH2_SECRET_FILE="" \
|
||||
MAIL_SERVICE_PASSWORD_FILE="" \
|
||||
MONGO_PASSWORD_FILE="" \
|
||||
S3_SECRET_FILE=""
|
||||
|
||||
# Copy qemu-static to image
|
||||
COPY --from=builder qemu-${QEMU_ARCHITECTURE}-static /usr/bin
|
||||
|
||||
# Copy the app to the image
|
||||
COPY --from=builder bundle /home/wekan/bundle
|
||||
|
||||
# Copy
|
||||
COPY --from=builder node-${NODE_VERSION}-${NODE_ARCHITECTURE} /opt/nodejs
|
||||
|
||||
RUN \
|
||||
set -o xtrace && \
|
||||
# Add non-root user wekan
|
||||
useradd --user-group --system --home-dir /home/wekan wekan && \
|
||||
\
|
||||
# Install Node
|
||||
ln -s /opt/nodejs/bin/node /usr/bin/node && \
|
||||
ln -s /opt/nodejs/bin/npm /usr/bin/npm && \
|
||||
mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/8.16.1 /home/wekan/.config && \
|
||||
chown wekan --recursive /home/wekan/.config
|
||||
|
||||
# \
|
||||
# # Install Node dependencies
|
||||
# #npm install -g npm@${NPM_VERSION} && \
|
||||
# \
|
||||
# # Install Health Check dependencies
|
||||
# #apk add curl
|
||||
#
|
||||
#HEALTHCHECK --start-period=30s --interval=30s --timeout=10s --retries=3 \
|
||||
# CMD curl --fail "http://localhost:$PORT" || exit 1
|
||||
|
||||
EXPOSE $PORT
|
||||
USER wekan
|
||||
|
||||
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 --max-old-space-size=8192 /home/wekan/bundle/main.js"]
|
||||
CMD ["bash", "-c", "ulimit -s 65500; exec node /home/wekan/bundle/main.js"]
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
FROM arm64v8/ubuntu:23.04 AS builder
|
||||
#FROM --platform=linux/amd64 amd64/ubuntu:23.04 AS builder
|
||||
#FROM --platform=linux/amd64 ghcr.io/wekan/wekan:main AS builder
|
||||
#FROM arm64v8/ubuntu:23.04 AS builder
|
||||
#FROM amd64/alpine:latest AS builder
|
||||
|
||||
# Set the environment variables for builder
|
||||
ENV QEMU_VERSION=v7.2.0-1 \
|
||||
QEMU_ARCHITECTURE=s390x \
|
||||
NODE_ARCHITECTURE=linux-s390x \
|
||||
NODE_VERSION=v14.21.4 \
|
||||
WEKAN_VERSION=latest \
|
||||
WEKAN_ARCHITECTURE=s390x
|
||||
|
||||
# Install dependencies
|
||||
#RUN apk update && apk add ca-certificates outils-sha1 && \
|
||||
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||
RUN apt update && apt install ca-certificates wget unzip -y && \
|
||||
\
|
||||
# Download qemu static for our architecture
|
||||
wget https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VERSION}/qemu-${QEMU_ARCHITECTURE}-static.tar.gz -O - | tar -xz && \
|
||||
\
|
||||
# Download wekan and shasum
|
||||
wget https://releases.wekan.team/${WEKAN_ARCHITECTURE}/wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
|
||||
wget https://releases.wekan.team/${WEKAN_ARCHITECTURE}/SHA256SUMS.txt && \
|
||||
# Verify wekan
|
||||
grep wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip SHA256SUMS.txt | sha256sum -c - && \
|
||||
\
|
||||
# Unzip wekan
|
||||
unzip wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
|
||||
\
|
||||
# Download node and shasums
|
||||
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
|
||||
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt && \
|
||||
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
|
||||
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
|
||||
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
|
||||
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
|
||||
\
|
||||
# Verify nodejs authenticity
|
||||
grep node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz SHASUMS256.txt | sha256sum -c - && \
|
||||
\
|
||||
# Extract node and remove tar.gz
|
||||
tar xvzf node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz
|
||||
|
||||
# Build wekan dockerfile
|
||||
FROM --platform=linux/s390x s390x/ubuntu:23.04
|
||||
LABEL maintainer="wekan"
|
||||
|
||||
# Set the environment variables (defaults where required)
|
||||
ENV QEMU_ARCHITECTURE=s390x \
|
||||
NODE_ARCHITECTURE=linux-s390x \
|
||||
NODE_VERSION=v14.21.4 \
|
||||
NODE_ENV=production \
|
||||
NPM_VERSION=latest \
|
||||
WITH_API=true \
|
||||
PORT=8080 \
|
||||
ROOT_URL=http://localhost \
|
||||
MONGO_URL=mongodb://127.0.0.1:27017/wekan \
|
||||
LDAP_AUTHENTIFICATION_PASSWORD_FILE="" \
|
||||
OAUTH2_SECRET_FILE="" \
|
||||
MAIL_SERVICE_PASSWORD_FILE="" \
|
||||
MONGO_PASSWORD_FILE="" \
|
||||
S3_SECRET_FILE=""
|
||||
|
||||
# Copy qemu-static to image
|
||||
COPY --from=builder qemu-${QEMU_ARCHITECTURE}-static /usr/bin
|
||||
|
||||
# Copy the app to the image
|
||||
COPY --from=builder bundle /home/wekan/bundle
|
||||
|
||||
# Copy
|
||||
COPY --from=builder node-${NODE_VERSION}-${NODE_ARCHITECTURE} /opt/nodejs
|
||||
|
||||
RUN \
|
||||
set -o xtrace && \
|
||||
# Add non-root user wekan
|
||||
useradd --user-group --system --home-dir /home/wekan wekan && \
|
||||
\
|
||||
# Install Node
|
||||
ln -s /opt/nodejs/bin/node /usr/bin/node && \
|
||||
ln -s /opt/nodejs/bin/npm /usr/bin/npm && \
|
||||
mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/8.16.1 /home/wekan/.config && \
|
||||
chown wekan --recursive /home/wekan/.config
|
||||
|
||||
# \
|
||||
# # Install Node dependencies
|
||||
# #npm install -g npm@${NPM_VERSION} && \
|
||||
# \
|
||||
# # Install Health Check dependencies
|
||||
# #apk add curl
|
||||
#
|
||||
#HEALTHCHECK --start-period=30s --interval=30s --timeout=10s --retries=3 \
|
||||
# CMD curl --fail "http://localhost:$PORT" || exit 1
|
||||
|
||||
EXPOSE $PORT
|
||||
USER wekan
|
||||
|
||||
CMD ["bash", "-c", "ulimit -s 65500; exec node /home/wekan/bundle/main.js"]
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v8.41.0"
|
||||
appVersion: "v8.71.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
|
||||
@@ -10,7 +10,8 @@ import '/client/config/blazeHelpers';
|
||||
|
||||
// Import board converter for on-demand conversion
|
||||
import '/client/lib/boardConverter';
|
||||
import '/client/components/boardConversionProgress';
|
||||
import '/client/features/boardConversion';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
// Import migration manager and progress UI - COMMENTED OUT
|
||||
// import '/client/lib/attachmentMigrationManager';
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { Template } from 'meteor/templating';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { sanitizeHTML, sanitizeText } from '/imports/lib/secureDOMPurify';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import { getSidebarInstance } from '/client/features/sidebar/service';
|
||||
|
||||
const activitiesPerPage = 500;
|
||||
|
||||
Template.activities.onCreated(function () {
|
||||
// Register with sidebar so it can call loadNextPage on us
|
||||
const Sidebar = getSidebarInstance();
|
||||
if (Sidebar) {
|
||||
Sidebar.activitiesInstance = this;
|
||||
}
|
||||
@@ -22,7 +27,7 @@ Template.activities.onCreated(function () {
|
||||
};
|
||||
|
||||
// TODO is sidebar always available? E.g. on small screens/mobile devices
|
||||
const sidebar = Sidebar;
|
||||
const sidebar = getSidebarInstance();
|
||||
if (sidebar && sidebar.infiniteScrolling) {
|
||||
sidebar.infiniteScrolling.resetNextPeak();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import CardComments from '/models/cardComments';
|
||||
import { UnsavedEdits } from '/client/lib/unsavedEdits';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import autosize from 'autosize';
|
||||
|
||||
const commentFormIsOpen = new ReactiveVar(false);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import Boards from '/models/boards';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Template.archivedBoards.onCreated(function () {
|
||||
this.subscribe('archivedBoards');
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import '../gantt/gantt.js';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import dragscroll from '@wekanteam/dragscroll';
|
||||
@@ -7,13 +6,16 @@ import { boardConverter } from '/client/lib/boardConverter';
|
||||
import { formatDateByUserPreference } from '/imports/lib/dateUtils';
|
||||
import Swimlanes from '/models/swimlanes';
|
||||
import Lists from '/models/lists';
|
||||
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
// SubsManager removed for Meteor 3 migration
|
||||
const { calculateIndex } = Utils;
|
||||
const swimlaneWhileSortingHeight = 150;
|
||||
|
||||
// Global reference so child components (swimlanes, cards) can access boardBody instance
|
||||
BoardBody = null;
|
||||
let BoardBody = null;
|
||||
|
||||
Template.board.onCreated(function () {
|
||||
this.isBoardReady = new ReactiveVar(false);
|
||||
@@ -22,7 +24,7 @@ Template.board.onCreated(function () {
|
||||
this._boardProcessed = false; // Track if board has been processed
|
||||
this._lastProcessedBoardId = null; // Track last processed board ID
|
||||
|
||||
this.ensureDefaultSwimlane = (boardId) => {
|
||||
this.ensureDefaultSwimlane = async (boardId) => {
|
||||
// Only create swimlane once per board
|
||||
if (this._swimlaneCreated.has(boardId)) {
|
||||
return;
|
||||
@@ -33,25 +35,15 @@ Template.board.onCreated(function () {
|
||||
if (!board) return;
|
||||
|
||||
const swimlanes = board.swimlanes();
|
||||
|
||||
if (swimlanes.length === 0) {
|
||||
// Check if any swimlane exists in the database to avoid race conditions
|
||||
const existingSwimlanes = ReactiveCache.getSwimlanes({ boardId });
|
||||
if (existingSwimlanes.length === 0) {
|
||||
const swimlaneId = Swimlanes.insert({
|
||||
title: 'Default',
|
||||
boardId: boardId,
|
||||
});
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log(
|
||||
`Created default swimlane ${swimlaneId} for board ${boardId}`,
|
||||
);
|
||||
}
|
||||
const swimlaneId = await Meteor.callAsync('ensureDefaultSwimlane', boardId);
|
||||
if (process.env.DEBUG === 'true' && swimlaneId) {
|
||||
console.log(
|
||||
`Ensured default swimlane ${swimlaneId} for board ${boardId}`,
|
||||
);
|
||||
}
|
||||
this._swimlaneCreated.add(boardId);
|
||||
} else {
|
||||
this._swimlaneCreated.add(boardId);
|
||||
}
|
||||
this._swimlaneCreated.add(boardId);
|
||||
} catch (error) {
|
||||
console.error('Error creating default swimlane:', error);
|
||||
}
|
||||
@@ -81,7 +73,7 @@ Template.board.onCreated(function () {
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
if (!currentBoardId) return;
|
||||
|
||||
const handle = subManager.subscribe('board', currentBoardId, false);
|
||||
const handle = Meteor.subscribe('board', currentBoardId, false);
|
||||
|
||||
// Use a separate autorun for subscription ready state to avoid reactive loops
|
||||
this.subscriptionReadyAutorun = Tracker.autorun(() => {
|
||||
@@ -1091,11 +1083,33 @@ Template.calendarView.helpers({
|
||||
const createCardButton = modalElement.querySelector(
|
||||
'#create-card-button',
|
||||
);
|
||||
createCardButton.addEventListener('click', function () {
|
||||
createCardButton.addEventListener('click', async function () {
|
||||
const myTitle = modalElement.querySelector('#card-title-input').value;
|
||||
if (myTitle) {
|
||||
const firstList = currentBoard.draggableLists()[0];
|
||||
const firstSwimlane = currentBoard.swimlanes()[0];
|
||||
let firstSwimlane = currentBoard.swimlanes()[0];
|
||||
if (!firstSwimlane) {
|
||||
const swimlaneId = await Swimlanes.insertAsync({
|
||||
title: 'Default',
|
||||
boardId: currentBoard._id,
|
||||
});
|
||||
firstSwimlane = ReactiveCache.getSwimlane(swimlaneId);
|
||||
}
|
||||
|
||||
let firstList = currentBoard.draggableLists()[0];
|
||||
if (!firstList && firstSwimlane) {
|
||||
const defaultTitle = TAPi18n.__('default');
|
||||
const listId = await Lists.insertAsync({
|
||||
title: typeof defaultTitle === 'string' ? defaultTitle : 'Default',
|
||||
boardId: currentBoard._id,
|
||||
swimlaneId: firstSwimlane._id,
|
||||
});
|
||||
firstList = ReactiveCache.getList(listId);
|
||||
}
|
||||
|
||||
if (!firstList || !firstSwimlane) {
|
||||
return;
|
||||
}
|
||||
|
||||
Meteor.call(
|
||||
'createCardWithDueDate',
|
||||
currentBoard._id,
|
||||
|
||||
@@ -2,6 +2,14 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import dragscroll from '@wekanteam/dragscroll';
|
||||
import getSlug from 'limax';
|
||||
import Boards from '/models/boards';
|
||||
import Swimlanes from '/models/swimlanes';
|
||||
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
|
||||
import { Filter } from '/client/lib/filter';
|
||||
import { MultiSelection } from '/client/lib/multiSelection';
|
||||
import { getSidebarInstance } from '/client/features/sidebar/service';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
/*
|
||||
const DOWNCLS = 'fa-sort-down';
|
||||
@@ -11,6 +19,7 @@ const sortCardsBy = new ReactiveVar('');
|
||||
|
||||
Template.boardChangeTitlePopup.events({
|
||||
async submit(event, templateInstance) {
|
||||
event.preventDefault();
|
||||
const newTitle = templateInstance
|
||||
.$('.js-board-name')
|
||||
.val()
|
||||
@@ -27,7 +36,6 @@ Template.boardChangeTitlePopup.events({
|
||||
}
|
||||
Popup.back();
|
||||
}
|
||||
event.preventDefault();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -94,30 +102,32 @@ Template.boardHeaderBar.events({
|
||||
console.log('Hamburger menu clicked');
|
||||
}
|
||||
// Use the same approach as keyboard shortcuts
|
||||
if (typeof Sidebar !== 'undefined' && Sidebar && typeof Sidebar.toggle === 'function') {
|
||||
const sidebar = getSidebarInstance();
|
||||
if (sidebar && typeof sidebar.toggle === 'function') {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Using Sidebar.toggle()');
|
||||
}
|
||||
Sidebar.toggle();
|
||||
sidebar.toggle();
|
||||
} else {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.warn('Sidebar not available, trying alternative approach');
|
||||
}
|
||||
// Try to trigger the sidebar through the global Blaze helper
|
||||
if (typeof Blaze !== 'undefined' && Blaze._globalHelpers && Blaze._globalHelpers.Sidebar) {
|
||||
const sidebar = Blaze._globalHelpers.Sidebar();
|
||||
if (sidebar && typeof sidebar.toggle === 'function') {
|
||||
const blazeSidebar = Blaze._globalHelpers.Sidebar();
|
||||
if (blazeSidebar && typeof blazeSidebar.toggle === 'function') {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Using Blaze helper Sidebar.toggle()');
|
||||
}
|
||||
sidebar.toggle();
|
||||
blazeSidebar.toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'click .js-open-filter-view'() {
|
||||
if (Sidebar) {
|
||||
Sidebar.setView('filter');
|
||||
const sidebar = getSidebarInstance();
|
||||
if (sidebar) {
|
||||
sidebar.setView('filter');
|
||||
} else {
|
||||
console.warn('Sidebar not available for setView');
|
||||
}
|
||||
@@ -137,8 +147,9 @@ Template.boardHeaderBar.events({
|
||||
*/
|
||||
'click .js-filter-reset'(event) {
|
||||
event.stopPropagation();
|
||||
if (Sidebar) {
|
||||
Sidebar.setView();
|
||||
const sidebar = getSidebarInstance();
|
||||
if (sidebar) {
|
||||
sidebar.setView();
|
||||
} else {
|
||||
console.warn('Sidebar not available for setView');
|
||||
}
|
||||
@@ -148,8 +159,9 @@ Template.boardHeaderBar.events({
|
||||
Session.set('sortBy', '');
|
||||
},
|
||||
'click .js-open-search-view'() {
|
||||
if (Sidebar) {
|
||||
Sidebar.setView('search');
|
||||
const sidebar = getSidebarInstance();
|
||||
if (sidebar) {
|
||||
sidebar.setView('search');
|
||||
} else {
|
||||
console.warn('Sidebar not available for setView');
|
||||
}
|
||||
@@ -214,55 +226,26 @@ function createBoardHelpers() {
|
||||
};
|
||||
}
|
||||
|
||||
function createBoardSubmit(tpl, event) {
|
||||
async function createBoardSubmit(tpl, event) {
|
||||
event.preventDefault();
|
||||
const title = tpl.find('.js-new-board-title').value;
|
||||
const slug = getSlug(title) || 'board';
|
||||
|
||||
const addTemplateContainer = $('#add-template-container.is-checked').length > 0;
|
||||
if (addTemplateContainer) {
|
||||
//const templateContainerId = Meteor.call('setCreateTemplateContainer');
|
||||
//Utils.goBoardId(templateContainerId);
|
||||
//alert('niinku template ' + Meteor.call('setCreateTemplateContainer'));
|
||||
|
||||
tpl.boardId.set(
|
||||
Boards.insert({
|
||||
// title: TAPi18n.__('templates'),
|
||||
title: title,
|
||||
permission: 'private',
|
||||
type: 'template-container',
|
||||
migrationVersion: 1, // Latest version - no migration needed
|
||||
}),
|
||||
);
|
||||
|
||||
// Insert the card templates swimlane
|
||||
Swimlanes.insert({
|
||||
// title: TAPi18n.__('card-templates-swimlane'),
|
||||
title: 'Card Templates',
|
||||
boardId: tpl.boardId.get(),
|
||||
sort: 1,
|
||||
await Meteor.callAsync('createBoardWithInitialSwimlanes', {
|
||||
title,
|
||||
slug,
|
||||
permission: 'private',
|
||||
type: 'template-container',
|
||||
migrationVersion: 1,
|
||||
swimlanes: [
|
||||
{ title: 'Card Templates', sort: 1, type: 'template-container' },
|
||||
{ title: 'List Templates', sort: 2, type: 'template-container' },
|
||||
{ title: 'Board Templates', sort: 3, type: 'template-container' },
|
||||
],
|
||||
}),
|
||||
|
||||
// Insert the list templates swimlane
|
||||
Swimlanes.insert(
|
||||
{
|
||||
// title: TAPi18n.__('list-templates-swimlane'),
|
||||
title: 'List Templates',
|
||||
boardId: tpl.boardId.get(),
|
||||
sort: 2,
|
||||
type: 'template-container',
|
||||
},
|
||||
);
|
||||
|
||||
// Insert the board templates swimlane
|
||||
Swimlanes.insert(
|
||||
{
|
||||
//title: TAPi18n.__('board-templates-swimlane'),
|
||||
title: 'Board Templates',
|
||||
boardId: tpl.boardId.get(),
|
||||
sort: 3,
|
||||
type: 'template-container',
|
||||
},
|
||||
);
|
||||
|
||||
// Assign to space if one was selected
|
||||
@@ -274,24 +257,21 @@ function createBoardSubmit(tpl, event) {
|
||||
Session.set('createBoardInWorkspace', null); // Clear after use
|
||||
}
|
||||
|
||||
Utils.goBoardId(tpl.boardId.get());
|
||||
FlowRouter.go('board', { id: tpl.boardId.get(), slug });
|
||||
|
||||
} else {
|
||||
const visibility = tpl.visibility.get();
|
||||
|
||||
tpl.boardId.set(
|
||||
Boards.insert({
|
||||
await Meteor.callAsync('createBoardWithInitialSwimlanes', {
|
||||
title,
|
||||
slug,
|
||||
permission: visibility,
|
||||
migrationVersion: 1, // Latest version - no migration needed
|
||||
migrationVersion: 1,
|
||||
swimlanes: [{ title: 'Default' }],
|
||||
}),
|
||||
);
|
||||
|
||||
Swimlanes.insert({
|
||||
title: 'Default',
|
||||
boardId: tpl.boardId.get(),
|
||||
});
|
||||
|
||||
// Assign to space if one was selected
|
||||
const spaceId = Session.get('createBoardInWorkspace');
|
||||
if (spaceId) {
|
||||
@@ -301,7 +281,7 @@ function createBoardSubmit(tpl, event) {
|
||||
Session.set('createBoardInWorkspace', null); // Clear after use
|
||||
}
|
||||
|
||||
Utils.goBoardId(tpl.boardId.get());
|
||||
FlowRouter.go('board', { id: tpl.boardId.get(), slug });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,8 +295,8 @@ function createBoardEvents() {
|
||||
tpl.visibilityMenuIsOpen.set(!tpl.visibilityMenuIsOpen.get());
|
||||
},
|
||||
'click .js-import': Popup.open('boardImportBoard'),
|
||||
'submit'(event, tpl) {
|
||||
createBoardSubmit(tpl, event);
|
||||
async 'submit'(event, tpl) {
|
||||
await createBoardSubmit(tpl, event);
|
||||
},
|
||||
'click .js-import-board': Popup.open('chooseBoardSource'),
|
||||
'click .js-board-template': Popup.open('searchElement'),
|
||||
@@ -375,7 +355,7 @@ Template.headerBarCreateBoardPopup.events({
|
||||
},
|
||||
'click .js-import': Popup.open('boardImportBoard'),
|
||||
async submit(event, tpl) {
|
||||
createBoardSubmit(tpl, event);
|
||||
await createBoardSubmit(tpl, event);
|
||||
// Immediately star boards created with the headerbar popup.
|
||||
await ReactiveCache.getCurrentUser().toggleBoardStar(tpl.boardId.get());
|
||||
},
|
||||
@@ -403,9 +383,11 @@ Template.boardChangeVisibilityPopup.helpers({
|
||||
Template.boardChangeVisibilityPopup.events({
|
||||
'click .js-select-visibility'() {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
const visibility = String(this);
|
||||
currentBoard.setVisibility(visibility);
|
||||
Popup.back();
|
||||
const visibility = Template.currentData();
|
||||
if (typeof visibility === 'string') {
|
||||
currentBoard.setVisibility(visibility);
|
||||
Popup.back();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -423,16 +405,18 @@ Template.boardChangeWatchPopup.helpers({
|
||||
|
||||
Template.boardChangeWatchPopup.events({
|
||||
'click .js-select-watch'() {
|
||||
const level = String(this);
|
||||
Meteor.call(
|
||||
'watch',
|
||||
'board',
|
||||
Session.get('currentBoard'),
|
||||
level,
|
||||
(err, ret) => {
|
||||
if (!err && ret) Popup.back();
|
||||
},
|
||||
);
|
||||
const level = Template.currentData();
|
||||
if (typeof level === 'string') {
|
||||
Meteor.call(
|
||||
'watch',
|
||||
'board',
|
||||
Session.get('currentBoard'),
|
||||
level,
|
||||
(err, ret) => {
|
||||
if (!err && ret) Popup.back();
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import url("../../../css/reset.css") print, screen;
|
||||
@import url("../../../public/css/reset.css") print, screen;
|
||||
|
||||
/* Board List Header with Zoom Controls */
|
||||
.board-list-header {
|
||||
|
||||
@@ -2,8 +2,12 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import getSlug from 'limax';
|
||||
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
|
||||
import { BoardMultiSelection } from '/client/lib/boardMultiSelection';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
// SubsManager removed for Meteor 3 migration
|
||||
|
||||
const DEFAULT_WORKSPACE_ICON = '📁';
|
||||
|
||||
@@ -657,7 +661,7 @@ Template.boardList.events({
|
||||
console.error(err);
|
||||
} else {
|
||||
Session.set('fromBoard', null);
|
||||
subManager.subscribe('board', res, false);
|
||||
Meteor.subscribe('board', res, false);
|
||||
FlowRouter.go('board', {
|
||||
id: res,
|
||||
slug: title,
|
||||
|
||||
@@ -86,6 +86,10 @@ template(name="attachmentGallery")
|
||||
b
|
||||
= name
|
||||
span.file-size ({{fileSize size}})
|
||||
if uploaderName
|
||||
div.attachment-uploader {{uploaderName}}
|
||||
if uploadedAt
|
||||
div.attachment-uploaded-at {{uploadedAt}}
|
||||
.attachment-actions
|
||||
a.js-download(href="{{link}}?download=true", download="{{name}}", title="{{_ 'download'}}")
|
||||
i.fa.fa-arrow-down
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { ObjectID } from 'bson';
|
||||
import { ObjectId } from 'bson';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { sanitizeHTML, sanitizeText } from '/imports/lib/secureDOMPurify';
|
||||
import uploadProgressManager from '../../lib/uploadProgressManager';
|
||||
import { attachmentMigrationManager } from '/client/lib/attachmentMigrationManager';
|
||||
import Attachments from '/models/attachments';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import { formatDateTime } from '/imports/lib/dateUtils';
|
||||
|
||||
const filesize = require('filesize');
|
||||
const prettyMilliseconds = require('pretty-ms');
|
||||
const { filesize } = require('filesize');
|
||||
import prettyMilliseconds from 'pretty-ms';
|
||||
|
||||
// We store current card ID and the ID of currently opened attachment in a
|
||||
// global var. This is used so that we know what's the next attachment to open
|
||||
@@ -19,7 +22,7 @@ let touchStartCoords = null;
|
||||
let touchEndCoords = null;
|
||||
|
||||
// Stores link to the attachment for which attachment actions popup was opened
|
||||
attachmentActionsLink = null;
|
||||
let attachmentActionsLink = null;
|
||||
|
||||
Template.attachmentGallery.events({
|
||||
'click .open-preview'(event) {
|
||||
@@ -40,8 +43,8 @@ Template.attachmentGallery.events({
|
||||
attachmentActionsLink = event.currentTarget.getAttribute("data-attachment-link");
|
||||
},
|
||||
'click .js-rename': Popup.open('attachmentRename'),
|
||||
'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', function() {
|
||||
Attachments.remove(this._id);
|
||||
'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', async function() {
|
||||
await Attachments.removeAsync(this._id);
|
||||
Popup.back();
|
||||
}),
|
||||
});
|
||||
@@ -196,8 +199,6 @@ function processTouch(){
|
||||
xDist = touchEndCoords.x - touchStartCoords.x;
|
||||
yDist = touchEndCoords.y - touchStartCoords.y;
|
||||
|
||||
console.log("xDist: " + xDist);
|
||||
|
||||
// Left swipe
|
||||
if (Math.abs(xDist) > Math.abs(yDist) && xDist < 0) {
|
||||
openNextAttachment();
|
||||
@@ -217,14 +218,12 @@ function processTouch(){
|
||||
|
||||
Template.attachmentViewer.events({
|
||||
'touchstart #viewer-container'(event) {
|
||||
console.log("touchstart")
|
||||
touchStartCoords = {
|
||||
x: event.changedTouches[0].screenX,
|
||||
y: event.changedTouches[0].screenY
|
||||
}
|
||||
},
|
||||
'touchend #viewer-container'(event) {
|
||||
console.log("touchend")
|
||||
touchEndCoords = {
|
||||
x: event.changedTouches[0].screenX,
|
||||
y: event.changedTouches[0].screenY
|
||||
@@ -273,6 +272,26 @@ Template.attachmentGallery.helpers({
|
||||
sanitize(value) {
|
||||
return sanitizeHTML(value);
|
||||
},
|
||||
uploaderName() {
|
||||
const uploaderId = this.userId;
|
||||
if (!uploaderId) return '';
|
||||
const uploader = ReactiveCache.getUser(uploaderId);
|
||||
if (!uploader) return '';
|
||||
return uploader.profile && uploader.profile.fullname
|
||||
? uploader.profile.fullname
|
||||
: uploader.username || '';
|
||||
},
|
||||
uploadedAt() {
|
||||
if (this.uploadedAtOstrio) {
|
||||
return formatDateTime(this.uploadedAtOstrio);
|
||||
}
|
||||
// Fall back to ObjectId timestamp (first 4 bytes = Unix seconds)
|
||||
try {
|
||||
const ts = parseInt(this._id.substring(0, 8), 16) * 1000;
|
||||
if (!isNaN(ts)) return formatDateTime(new Date(ts));
|
||||
} catch (_) {}
|
||||
return '';
|
||||
},
|
||||
});
|
||||
|
||||
Template.cardAttachmentsPopup.onCreated(function() {
|
||||
@@ -294,12 +313,12 @@ Template.cardAttachmentsPopup.helpers({
|
||||
});
|
||||
|
||||
Template.cardAttachmentsPopup.events({
|
||||
'change .js-attach-file'(event, templateInstance) {
|
||||
async 'change .js-attach-file'(event, templateInstance) {
|
||||
const card = this;
|
||||
const files = event.currentTarget.files;
|
||||
if (files) {
|
||||
let uploads = [];
|
||||
const uploaders = handleFileUpload(card, files);
|
||||
const uploaders = await handleFileUpload(card, files);
|
||||
|
||||
uploaders.forEach(uploader => {
|
||||
uploader.on('start', function() {
|
||||
@@ -328,7 +347,7 @@ const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
|
||||
let pastedResults = null;
|
||||
|
||||
// Shared upload logic for drag-and-drop functionality
|
||||
export function handleFileUpload(card, files) {
|
||||
export async function handleFileUpload(card, files) {
|
||||
if (!files || files.length === 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -361,7 +380,7 @@ export function handleFileUpload(card, files) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fileId = new ObjectID().toString();
|
||||
const fileId = new ObjectId().toString();
|
||||
let fileName = sanitizeText(file.name);
|
||||
|
||||
// If sanitized filename is not same as original filename,
|
||||
@@ -385,7 +404,7 @@ export function handleFileUpload(card, files) {
|
||||
config.meta.fileId = fileId;
|
||||
|
||||
try {
|
||||
const uploader = Attachments.insert(
|
||||
const uploader = await Attachments.insertAsync(
|
||||
config,
|
||||
false,
|
||||
);
|
||||
@@ -460,12 +479,12 @@ Template.previewClipboardImagePopup.onRendered(() => {
|
||||
});
|
||||
|
||||
Template.previewClipboardImagePopup.events({
|
||||
'click .js-upload-pasted-image'() {
|
||||
async 'click .js-upload-pasted-image'() {
|
||||
const card = this;
|
||||
if (pastedResults && pastedResults.file) {
|
||||
const file = pastedResults.file;
|
||||
window.oPasted = pastedResults;
|
||||
const fileId = new ObjectID().toString();
|
||||
const fileId = new ObjectId().toString();
|
||||
const config = {
|
||||
file,
|
||||
fileId: fileId,
|
||||
@@ -474,7 +493,7 @@ Template.previewClipboardImagePopup.events({
|
||||
chunkSize: 'dynamic',
|
||||
};
|
||||
config.meta.fileId = fileId;
|
||||
const uploader = Attachments.insert(
|
||||
const uploader = await Attachments.insertAsync(
|
||||
config,
|
||||
false,
|
||||
);
|
||||
|
||||
@@ -29,6 +29,8 @@ import {
|
||||
} from '/imports/lib/dateUtils';
|
||||
import { CustomFieldStringTemplate } from '/client/lib/customFields'
|
||||
import { getCurrentCardFromContext } from '/client/lib/currentCard';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { getSidebarInstance } from '/client/features/sidebar/service';
|
||||
|
||||
Template.cardCustomFieldsPopup.helpers({
|
||||
hasCustomField() {
|
||||
@@ -49,7 +51,10 @@ Template.cardCustomFieldsPopup.events({
|
||||
},
|
||||
'click .js-settings'(event) {
|
||||
EscapeActions.executeUpTo('detailsPane');
|
||||
Sidebar.setView('customFields');
|
||||
const sidebar = getSidebarInstance();
|
||||
if (sidebar) {
|
||||
sidebar.setView('customFields');
|
||||
}
|
||||
event.preventDefault();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@ import CardComments from '/models/cardComments';
|
||||
import { ALLOWED_COLORS } from '/config/const';
|
||||
import { uniqBy } from '/imports/lib/collectionHelpers';
|
||||
import { UserAvatar } from '../users/userAvatar';
|
||||
import { Filter } from '/client/lib/filter';
|
||||
import { BoardSwimlaneListCardDialog } from '/client/lib/dialogWithBoardSwimlaneListCard';
|
||||
import { handleFileUpload } from './attachments';
|
||||
import { InfiniteScrolling } from '/client/lib/infiniteScrolling';
|
||||
@@ -45,8 +46,14 @@ import {
|
||||
getCurrentCardFromContext,
|
||||
} from '/client/lib/currentCard';
|
||||
import uploadProgressManager from '../../lib/uploadProgressManager';
|
||||
import { CSSEvents } from '/client/lib/cssEvents';
|
||||
import { UnsavedEdits } from '/client/lib/unsavedEdits';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { MultiSelection } from '/client/lib/multiSelection';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import autosize from 'autosize';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
// SubsManager removed for Meteor 3 migration
|
||||
const { calculateIndexData } = Utils;
|
||||
|
||||
function getCardId() {
|
||||
@@ -1427,7 +1434,8 @@ Template.setCardColorPopup.helpers({
|
||||
|
||||
Template.setCardColorPopup.events({
|
||||
'click .js-palette-color'(event, tpl) {
|
||||
tpl.currentColor.set(Template.currentData().color);
|
||||
const paletteData = Blaze.getData(event.currentTarget);
|
||||
tpl.currentColor.set(paletteData?.color);
|
||||
},
|
||||
async 'click .js-submit'(event, tpl) {
|
||||
event.preventDefault();
|
||||
@@ -1446,7 +1454,9 @@ Template.setCardColorPopup.events({
|
||||
});
|
||||
|
||||
Template.setSelectionColorPopup.onCreated(function () {
|
||||
this.currentColor = new ReactiveVar(null);
|
||||
const selectedCards = ReactiveCache.getCards(MultiSelection.getMongoSelector());
|
||||
const uniqueColors = [...new Set(selectedCards.map(card => card.color || null))];
|
||||
this.currentColor = new ReactiveVar(uniqueColors.length === 1 ? uniqueColors[0] : null);
|
||||
});
|
||||
|
||||
Template.setSelectionColorPopup.helpers({
|
||||
@@ -1467,22 +1477,32 @@ Template.setSelectionColorPopup.events({
|
||||
const color = colorClass ? colorClass.replace('card-details-', '') : null;
|
||||
tpl.currentColor.set(color);
|
||||
},
|
||||
async 'click .js-submit'(event, tpl) {
|
||||
async 'submit form.edit-label'(event, tpl) {
|
||||
event.preventDefault();
|
||||
const color = tpl.currentColor.get();
|
||||
// Use MultiSelection to get selected cards and set color on each
|
||||
for (const card of ReactiveCache.getCards(MultiSelection.getMongoSelector())) {
|
||||
await card.setColor(color);
|
||||
try {
|
||||
for (const card of ReactiveCache.getCards(MultiSelection.getMongoSelector())) {
|
||||
await card.setColor(color);
|
||||
}
|
||||
Popup.back();
|
||||
} catch (error) {
|
||||
alert(error?.reason || error?.message || 'Failed to save selection color');
|
||||
}
|
||||
Popup.back();
|
||||
},
|
||||
async 'click .js-submit'(event, tpl) {
|
||||
event.preventDefault();
|
||||
await tpl.$('form.edit-label').trigger('submit');
|
||||
},
|
||||
async 'click .js-remove-color'(event, tpl) {
|
||||
event.preventDefault();
|
||||
// Use MultiSelection to get selected cards and remove color from each
|
||||
for (const card of ReactiveCache.getCards(MultiSelection.getMongoSelector())) {
|
||||
await card.setColor(null);
|
||||
try {
|
||||
for (const card of ReactiveCache.getCards(MultiSelection.getMongoSelector())) {
|
||||
await card.setColor(null);
|
||||
}
|
||||
Popup.back();
|
||||
} catch (error) {
|
||||
alert(error?.reason || error?.message || 'Failed to unset selection color');
|
||||
}
|
||||
Popup.back();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1593,7 +1613,7 @@ Template.cardMorePopup.events({
|
||||
if (selection === 'none') {
|
||||
tpl.parentBoard.set(null);
|
||||
} else {
|
||||
subManager.subscribe('board', $(event.currentTarget).val(), false);
|
||||
Meteor.subscribe('board', $(event.currentTarget).val(), false);
|
||||
tpl.parentBoard.set(selection);
|
||||
list.prop('disabled', false);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,14 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import Cards from '/models/cards';
|
||||
import Boards from '/models/boards';
|
||||
import ChecklistItems from '/models/checklistItems';
|
||||
import Checklists from '/models/checklists';
|
||||
import { BoardSwimlaneListCardDialog } from '/client/lib/dialogWithBoardSwimlaneListCard';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import autosize from 'autosize';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
// SubsManager removed for Meteor 3 migration
|
||||
const { calculateIndexData } = Utils;
|
||||
|
||||
function initSorting(items) {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { LABEL_COLORS } from '/models/metadata/colors';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
let labelColors;
|
||||
Meteor.startup(() => {
|
||||
labelColors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues;
|
||||
labelColors = LABEL_COLORS;
|
||||
});
|
||||
|
||||
const getFallbackLabelColor = () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { TAPi18n } from '/imports/i18n';
|
||||
import { CustomFieldStringTemplate } from '/client/lib/customFields';
|
||||
import { handleFileUpload } from './attachments';
|
||||
import uploadProgressManager from '../../lib/uploadProgressManager';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
// Template.cards.events({
|
||||
// 'click .member': Popup.open('cardMember')
|
||||
|
||||
@@ -1,7 +1,52 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
|
||||
Template.resultCard.helpers({
|
||||
userId() {
|
||||
return Meteor.userId();
|
||||
},
|
||||
getBoard() {
|
||||
const card = Template.currentData();
|
||||
try {
|
||||
if (card && card.boardId) {
|
||||
const board = ReactiveCache.getBoard(card.boardId);
|
||||
return board || null;
|
||||
}
|
||||
} catch (err) {
|
||||
// Silently handle errors
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getSwimlane() {
|
||||
const card = Template.currentData();
|
||||
try {
|
||||
if (card && card.swimlaneId) {
|
||||
const swimlane = ReactiveCache.getSwimlane(card.swimlaneId);
|
||||
return swimlane || null;
|
||||
}
|
||||
} catch (err) {
|
||||
// Silently handle errors
|
||||
}
|
||||
return null;
|
||||
},
|
||||
getList() {
|
||||
const card = Template.currentData();
|
||||
try {
|
||||
if (card && card.listId) {
|
||||
const list = ReactiveCache.getList(card.listId);
|
||||
return list || null;
|
||||
}
|
||||
} catch (err) {
|
||||
// Silently handle errors
|
||||
}
|
||||
return null;
|
||||
},
|
||||
originRelativeUrl() {
|
||||
const card = Template.currentData();
|
||||
if (card && card.boardId && card._id) {
|
||||
return `/board/${card.boardId}?cardId=${card._id}`;
|
||||
}
|
||||
return '#';
|
||||
},
|
||||
});
|
||||
|
||||
Template.resultCard.events({
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import Cards from '/models/cards';
|
||||
import { Filter } from '/client/lib/filter';
|
||||
|
||||
Template.subtasks.events({
|
||||
'click .js-open-subtask-details-menu': Popup.open('subtaskActions'),
|
||||
@@ -11,8 +13,16 @@ Template.subtasks.events({
|
||||
const card = ReactiveCache.getCard(cardId);
|
||||
const sortIndex = -1;
|
||||
const crtBoard = ReactiveCache.getBoard(card.boardId);
|
||||
const targetBoard = crtBoard.getDefaultSubtasksBoard();
|
||||
const listId = targetBoard.getDefaultSubtasksListId();
|
||||
const targetBoard = await crtBoard.getDefaultSubtasksBoardAsync();
|
||||
if (!targetBoard) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetList = await targetBoard.getDefaultSubtasksListAsync();
|
||||
if (!targetList) {
|
||||
return;
|
||||
}
|
||||
const listId = targetList._id;
|
||||
|
||||
//Get the full swimlane data for the parent task.
|
||||
const parentSwimlane = ReactiveCache.getSwimlane({
|
||||
@@ -33,7 +43,7 @@ Template.subtasks.events({
|
||||
const nextCardNumber = await targetBoard.getNextCardNumber();
|
||||
|
||||
if (title) {
|
||||
const _id = Cards.insert({
|
||||
const _id = await Cards.insertAsync({
|
||||
title,
|
||||
parentId: cardId,
|
||||
members: [],
|
||||
@@ -44,7 +54,7 @@ Template.subtasks.events({
|
||||
sort: sortIndex,
|
||||
swimlaneId,
|
||||
type: 'cardType-card',
|
||||
cardNumber: nextCardNumber
|
||||
cardNumber: nextCardNumber,
|
||||
});
|
||||
|
||||
// In case the filter is active we need to add the newly inserted card in
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import Cards from '/models/cards';
|
||||
|
||||
// Add click handler to ganttView for card titles
|
||||
Template.ganttView.events({
|
||||
@@ -14,6 +15,7 @@ Template.ganttView.events({
|
||||
},
|
||||
});
|
||||
import { Template } from 'meteor/templating';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
// Blaze template helpers for ganttView
|
||||
function getISOWeekInfo(d) {
|
||||
|
||||
@@ -4,6 +4,8 @@ import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import { wekanGetMembersToMap } from './wekanMembersMapper';
|
||||
import { csvGetMembersToMap } from './csvMembersMapper';
|
||||
import getSlug from 'limax';
|
||||
import { UserSearchIndex } from '/models/users';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
const Papa = require('papaparse');
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { ReactiveVar } = require('meteor/reactive-var');
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
|
||||
// Creation-time stack tracking which basicTabs is currently being rendered.
|
||||
// Blaze creates parent views before children, so when tabContent.onCreated fires,
|
||||
|
||||
@@ -1226,52 +1226,52 @@ form.inlined-form .list-composer {
|
||||
margin-top: unset !important;
|
||||
}
|
||||
.list-header-white {
|
||||
border-bottom: 6px solid #fff;
|
||||
border-bottom: 6px solid #fff !important;
|
||||
}
|
||||
.list-header-green {
|
||||
border-bottom: 6px solid #3cb500;
|
||||
border-bottom: 6px solid #3cb500 !important;
|
||||
}
|
||||
.list-header-yellow {
|
||||
border-bottom: 6px solid #fad900;
|
||||
border-bottom: 6px solid #fad900 !important;
|
||||
}
|
||||
.list-header-orange {
|
||||
border-bottom: 6px solid #ff9f19;
|
||||
border-bottom: 6px solid #ff9f19 !important;
|
||||
}
|
||||
.list-header-red {
|
||||
border-bottom: 6px solid #eb4646;
|
||||
border-bottom: 6px solid #eb4646 !important;
|
||||
}
|
||||
.list-header-purple {
|
||||
border-bottom: 6px solid #a632db;
|
||||
border-bottom: 6px solid #a632db !important;
|
||||
}
|
||||
.list-header-blue {
|
||||
border-bottom: 6px solid #0079bf;
|
||||
border-bottom: 6px solid #0079bf !important;
|
||||
}
|
||||
.list-header-pink {
|
||||
border-bottom: 6px solid #ff78cb;
|
||||
border-bottom: 6px solid #ff78cb !important;
|
||||
}
|
||||
.list-header-sky {
|
||||
border-bottom: 6px solid #00c2e0;
|
||||
border-bottom: 6px solid #00c2e0 !important;
|
||||
}
|
||||
.list-header-black {
|
||||
border-bottom: 6px solid #4d4d4d;
|
||||
border-bottom: 6px solid #4d4d4d !important;
|
||||
}
|
||||
.list-header-lime {
|
||||
border-bottom: 6px solid #51e898;
|
||||
border-bottom: 6px solid #51e898 !important;
|
||||
}
|
||||
.list-header-silver {
|
||||
border-bottom: 6px solid #e4e4e4;
|
||||
border-bottom: 6px solid #e4e4e4 !important;
|
||||
}
|
||||
.list-header-peachpuff {
|
||||
border-bottom: 6px solid #ffdab9;
|
||||
border-bottom: 6px solid #ffdab9 !important;
|
||||
}
|
||||
.list-header-crimson {
|
||||
border-bottom: 6px solid #dc143c;
|
||||
border-bottom: 6px solid #dc143c !important;
|
||||
}
|
||||
.list-header-plum {
|
||||
border-bottom: 6px solid #dda0dd;
|
||||
border-bottom: 6px solid #dda0dd !important;
|
||||
}
|
||||
.list-header-darkgreen {
|
||||
border-bottom: 6px solid #006400;
|
||||
border-bottom: 6px solid #006400 !important;
|
||||
}
|
||||
|
||||
/* Final collapsed desktop overrides:
|
||||
@@ -1379,31 +1379,31 @@ form.inlined-form .list-composer {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.list-header-slateblue {
|
||||
border-bottom: 6px solid #6a5acd;
|
||||
border-bottom: 6px solid #6a5acd !important;
|
||||
}
|
||||
.list-header-magenta {
|
||||
border-bottom: 6px solid #f0f;
|
||||
border-bottom: 6px solid #f0f !important;
|
||||
}
|
||||
.list-header-gold {
|
||||
border-bottom: 6px solid #ffd700;
|
||||
border-bottom: 6px solid #ffd700 !important;
|
||||
}
|
||||
.list-header-navy {
|
||||
border-bottom: 6px solid #000080;
|
||||
border-bottom: 6px solid #000080 !important;
|
||||
}
|
||||
.list-header-gray {
|
||||
border-bottom: 6px solid #808080;
|
||||
border-bottom: 6px solid #808080 !important;
|
||||
}
|
||||
.list-header-saddlebrown {
|
||||
border-bottom: 6px solid #8b4513;
|
||||
border-bottom: 6px solid #8b4513 !important;
|
||||
}
|
||||
.list-header-paleturquoise {
|
||||
border-bottom: 6px solid #afeeee;
|
||||
border-bottom: 6px solid #afeeee !important;
|
||||
}
|
||||
.list-header-mistyrose {
|
||||
border-bottom: 6px solid #ffe4e1;
|
||||
border-bottom: 6px solid #ffe4e1 !important;
|
||||
}
|
||||
.list-header-indigo {
|
||||
border-bottom: 6px solid #4b0082;
|
||||
border-bottom: 6px solid #4b0082 !important;
|
||||
}
|
||||
|
||||
.list.list-collapsed .collapsed-list-drag-area {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { MultiSelection } from '/client/lib/multiSelection';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
require('/client/lib/jquery-ui.js')
|
||||
|
||||
const { calculateIndex } = Utils;
|
||||
|
||||
@@ -3,8 +3,15 @@ import { TAPi18n } from '/imports/i18n';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import { getSpinnerName, getSpinnerTemplate } from '/client/lib/spinner';
|
||||
import getSlug from 'limax';
|
||||
import Boards from '/models/boards';
|
||||
import Cards from '/models/cards';
|
||||
import Swimlanes from '/models/swimlanes';
|
||||
import { Filter } from '/client/lib/filter';
|
||||
import { MultiSelection } from '/client/lib/multiSelection';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import autosize from 'autosize';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
// SubsManager removed for Meteor 3 migration
|
||||
const InfiniteScrollIter = 10;
|
||||
|
||||
Template.listBody.onCreated(function () {
|
||||
@@ -59,7 +66,10 @@ Template.listBody.onCreated(function () {
|
||||
};
|
||||
|
||||
this.idOrNull = (swimlaneId) => {
|
||||
const data = Template.currentData();
|
||||
const data = this.data;
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
Utils.boardView() === 'board-view-swimlanes' ||
|
||||
data.board().isTemplatesBoard()
|
||||
@@ -88,7 +98,10 @@ Template.listBody.onCreated(function () {
|
||||
const labelIds = formComponent.labels.get();
|
||||
const customFields = formComponent.customFields.get();
|
||||
|
||||
const data = Template.currentData();
|
||||
const data = this.data;
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
const board = data.board();
|
||||
let linkedId = '';
|
||||
let swimlaneId = '';
|
||||
@@ -107,13 +120,15 @@ Template.listBody.onCreated(function () {
|
||||
if (swimlane.isCardTemplatesSwimlane()) cardType = 'template-card';
|
||||
// If this is the board templates swimlane, insert a board template and a linked card
|
||||
else if (swimlane.isBoardTemplatesSwimlane()) {
|
||||
linkedId = Boards.insert({
|
||||
linkedId = await Boards.insertAsync({
|
||||
title,
|
||||
slug: getSlug(title) || 'board',
|
||||
permission: 'private',
|
||||
type: 'template-board',
|
||||
});
|
||||
Swimlanes.insert({
|
||||
title: TAPi18n.__('default'),
|
||||
const defaultTitle = TAPi18n.__('default');
|
||||
await Swimlanes.insertAsync({
|
||||
title: typeof defaultTitle === 'string' ? defaultTitle : 'Default',
|
||||
boardId: linkedId,
|
||||
});
|
||||
cardType = 'cardType-linkedBoard';
|
||||
@@ -294,7 +309,15 @@ Template.listBody.helpers({
|
||||
listId: Template.currentData()._id,
|
||||
archived: false,
|
||||
};
|
||||
if (swimlaneId) selector.swimlaneId = swimlaneId;
|
||||
if (swimlaneId) {
|
||||
// Fallback: also show cards with no swimlaneId (null/empty) so that
|
||||
// pre-migration / orphaned cards are always visible without a DB migration.
|
||||
selector.$or = [
|
||||
{ swimlaneId },
|
||||
{ swimlaneId: null }, // null covers null AND missing field
|
||||
{ swimlaneId: '' }, // empty string from shared-lists era
|
||||
];
|
||||
}
|
||||
const ret = ReactiveCache.getCards(Filter.mongoSelector(selector), {
|
||||
// sort: ['sort'],
|
||||
sort: sortBy,
|
||||
@@ -554,7 +577,7 @@ Template.linkCardPopup.onCreated(function () {
|
||||
|
||||
this.boardId = Session.get('currentBoard');
|
||||
// In order to get current board info
|
||||
subManager.subscribe('board', this.boardId, false);
|
||||
Meteor.subscribe('board', this.boardId, false);
|
||||
this.board = ReactiveCache.getBoard(this.boardId);
|
||||
// List where to insert card
|
||||
this.list = $(Popup._getTopStack().openerElement).closest('.js-list');
|
||||
@@ -685,7 +708,7 @@ Template.linkCardPopup.helpers({
|
||||
Template.linkCardPopup.events({
|
||||
'change .js-select-boards'(evt, tpl) {
|
||||
const val = $(evt.currentTarget).val();
|
||||
subManager.subscribe('board', val, false);
|
||||
Meteor.subscribe('board', val, false);
|
||||
// Clear selections to allow linking only board or re-choose swimlane/list
|
||||
tpl.selectedSwimlaneId.set('');
|
||||
tpl.selectedListId.set('');
|
||||
@@ -773,7 +796,7 @@ Template.searchElementPopup.onCreated(function () {
|
||||
if (this.isTemplateSearch) {
|
||||
const boardId = (ReactiveCache.getCurrentUser().profile || {}).templatesBoardId;
|
||||
if (boardId) {
|
||||
subManager.subscribe('board', boardId, false);
|
||||
Meteor.subscribe('board', boardId, false);
|
||||
this.board = ReactiveCache.getBoard(boardId);
|
||||
}
|
||||
} else {
|
||||
@@ -785,7 +808,7 @@ Template.searchElementPopup.onCreated(function () {
|
||||
}
|
||||
this.boardId = this.board._id;
|
||||
// Subscribe to this board
|
||||
subManager.subscribe('board', this.boardId, false);
|
||||
Meteor.subscribe('board', this.boardId, false);
|
||||
this.selectedBoardId = new ReactiveVar(this.boardId);
|
||||
this.list = $(Popup._getTopStack().openerElement).closest('.js-list');
|
||||
|
||||
@@ -849,7 +872,7 @@ Template.searchElementPopup.helpers({
|
||||
} else if (tpl.isBoardTemplateSearch) {
|
||||
const boards = board.searchBoards(tpl.term.get());
|
||||
boards.forEach(board => {
|
||||
subManager.subscribe('board', board.linkedId, false);
|
||||
Meteor.subscribe('board', board.linkedId, false);
|
||||
});
|
||||
return boards;
|
||||
} else {
|
||||
@@ -860,7 +883,7 @@ Template.searchElementPopup.helpers({
|
||||
|
||||
Template.searchElementPopup.events({
|
||||
'change .js-select-boards'(evt, tpl) {
|
||||
subManager.subscribe('board', $(evt.currentTarget).val(), false);
|
||||
Meteor.subscribe('board', $(evt.currentTarget).val(), false);
|
||||
tpl.selectedBoardId.set($(evt.currentTarget).val());
|
||||
},
|
||||
'submit .js-search-term-form'(evt, tpl) {
|
||||
@@ -915,7 +938,7 @@ Template.searchElementPopup.events({
|
||||
},
|
||||
(err, data) => {
|
||||
_id = data;
|
||||
subManager.subscribe('board', _id, false);
|
||||
Meteor.subscribe('board', _id, false);
|
||||
FlowRouter.go('board', {
|
||||
id: _id,
|
||||
slug: getSlug(element.title),
|
||||
|
||||
@@ -3,10 +3,14 @@ import Lists from '../../../models/lists';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import dragscroll from '@wekanteam/dragscroll';
|
||||
import { BoardSwimlaneListDialog } from '/client/lib/dialogWithBoardSwimlaneList';
|
||||
import Cards from '/models/cards';
|
||||
import { LIST_COLORS } from '/models/metadata/colors';
|
||||
import { MultiSelection } from '/client/lib/multiSelection';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
let listsColors;
|
||||
Meteor.startup(() => {
|
||||
listsColors = Lists.simpleSchema()._schema.color.allowedValues;
|
||||
listsColors = LIST_COLORS;
|
||||
});
|
||||
|
||||
Template.listHeader.helpers({
|
||||
@@ -439,14 +443,39 @@ Template.setListColorPopup.helpers({
|
||||
|
||||
Template.setListColorPopup.events({
|
||||
'click .js-palette-color'(event, tpl) {
|
||||
tpl.currentColor.set(Template.currentData().color);
|
||||
const paletteData = Blaze.getData(event.currentTarget);
|
||||
tpl.currentColor.set(paletteData?.color);
|
||||
},
|
||||
async 'submit form'(event, tpl) {
|
||||
event.preventDefault();
|
||||
console.log('[ListColor] submit form, color:', tpl.currentColor.get(), 'listId:', tpl.currentList?._id, 'setColor type:', typeof tpl.currentList?.setColor);
|
||||
try {
|
||||
const result = await tpl.currentList.setColor(tpl.currentColor.get());
|
||||
console.log('[ListColor] submit form setColor result:', result);
|
||||
} catch (err) {
|
||||
console.error('[ListColor] submit form setColor error:', err);
|
||||
}
|
||||
Popup.close();
|
||||
},
|
||||
async 'click .js-submit'(event, tpl) {
|
||||
await tpl.currentList.setColor(tpl.currentColor.get());
|
||||
event.preventDefault();
|
||||
console.log('[ListColor] click submit, color:', tpl.currentColor.get(), 'listId:', tpl.currentList?._id, 'setColor type:', typeof tpl.currentList?.setColor);
|
||||
try {
|
||||
const result = await tpl.currentList.setColor(tpl.currentColor.get());
|
||||
console.log('[ListColor] click submit setColor result:', result);
|
||||
} catch (err) {
|
||||
console.error('[ListColor] click submit setColor error:', err);
|
||||
}
|
||||
Popup.close();
|
||||
},
|
||||
async 'click .js-remove-color'(event, tpl) {
|
||||
await tpl.currentList.setColor(null);
|
||||
event.preventDefault();
|
||||
console.log('[ListColor] remove color, listId:', tpl.currentList?._id);
|
||||
try {
|
||||
await tpl.currentList.setColor(null);
|
||||
} catch (err) {
|
||||
console.error('[ListColor] remove color error:', err);
|
||||
}
|
||||
Popup.close();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import AccessibilitySettings from '/models/accessibilitySettings';
|
||||
|
||||
// Shared helpers for both accessibility templates
|
||||
const accessibilityHelpers = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import Boards from '/models/boards';
|
||||
|
||||
Template.bookmarks.helpers({
|
||||
hasStarredBoards() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
// const subManager = new SubsManager();
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { findWhere } from '/imports/lib/collectionHelpers';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import Attachments from '/models/attachments';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import autosize from 'autosize';
|
||||
var converter = require('@wekanteam/html-to-markdown');
|
||||
|
||||
const specialHandles = [
|
||||
@@ -207,11 +210,11 @@ Template.editor.onRendered(function () {
|
||||
const currentCard = Utils.getCurrentCard();
|
||||
const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
|
||||
const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
|
||||
const processUpload = function(file) {
|
||||
const uploader = Attachments.insert(
|
||||
const processUpload = async function(file) {
|
||||
const uploader = await Attachments.insertAsync(
|
||||
{
|
||||
file,
|
||||
meta: Utils.getCommonAttachmentMetaFrom(card),
|
||||
meta: Utils.getCommonAttachmentMetaFrom(currentCard),
|
||||
chunkSize: 'dynamic',
|
||||
},
|
||||
false,
|
||||
|
||||
@@ -84,6 +84,21 @@
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
.js-new-search {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.board-color-belize.js-new-search {
|
||||
background-color: #216694 !important;
|
||||
color: #fff !important;
|
||||
border: none !important;
|
||||
}
|
||||
.board-color-belize.js-new-search:hover {
|
||||
background-color: #1a4d6a !important;
|
||||
}
|
||||
.board-color-belize.js-new-search:active {
|
||||
background-color: #0d334d !important;
|
||||
}
|
||||
.global-search-operator {
|
||||
font-family: Courier;
|
||||
}
|
||||
|
||||
@@ -11,68 +11,47 @@ template(name="globalSearchModalTitle")
|
||||
| {{_ 'globalSearch-title'}}
|
||||
|
||||
template(name="resultsPaged")
|
||||
if resultsHeading.get
|
||||
h1
|
||||
= resultsHeading.get
|
||||
a(title="{{_ 'link-to-search' }}" href="{{ getSearchHref }}")
|
||||
| 🔗
|
||||
each card in results.get
|
||||
if resultsHeading
|
||||
h1= resultsHeading
|
||||
each card in results
|
||||
+resultCard(card)
|
||||
table.global-search-footer
|
||||
tr
|
||||
td.global-search-previous-page
|
||||
if hasPreviousPage.get
|
||||
if hasPreviousPage
|
||||
button.js-previous-page
|
||||
| {{_ 'previous-page' }}
|
||||
td.global-search-next-page(align="right")
|
||||
if hasNextPage.get
|
||||
if hasNextPage
|
||||
button.js-next-page
|
||||
| {{_ 'next-page' }}
|
||||
|
||||
template(name="globalSearch")
|
||||
if currentUser
|
||||
.wrapper
|
||||
form.global-search-page.js-search-query-form
|
||||
.wrapper(class=boardColorClass)
|
||||
form.global-search-page.js-search-query-form(onsubmit="return false;")
|
||||
input.global-search-query-input(
|
||||
style="{# if hasResults.get #}display: inline-block;{#/if#}"
|
||||
style="display: inline-block;"
|
||||
id="global-search-input"
|
||||
type="text"
|
||||
name="searchQuery"
|
||||
placeholder="{{_ 'search-example'}}"
|
||||
value="{{ query.get }}"
|
||||
value="{{ queryValue }}"
|
||||
autofocus dir="auto"
|
||||
)
|
||||
a.js-new-search
|
||||
button.js-new-search.board-color-belize(type="button" style="background: #216694 !important; background-color: #216694 !important; color: #fff !important; border: none !important;")
|
||||
| 🧹
|
||||
if debug.get.show
|
||||
h1 Debug
|
||||
if debug.get.showSelector
|
||||
h2 Selector
|
||||
button.js-copy-debug-selector
|
||||
= 'Copy'
|
||||
pre(
|
||||
id="debug-selector"
|
||||
)
|
||||
= sessionData.selector
|
||||
if debug.get.showProjection
|
||||
h2 Projection
|
||||
button.js-copy-debug-projection
|
||||
= 'Copy'
|
||||
pre(
|
||||
id="debug-projection"
|
||||
)
|
||||
= sessionData.projection
|
||||
if searching.get
|
||||
if searching
|
||||
+spinner
|
||||
else if hasResults.get
|
||||
else if hasResults
|
||||
.global-search-results-list-wrapper
|
||||
if hasQueryErrors.get
|
||||
if hasQueryErrors
|
||||
ul
|
||||
each msg in errorMessages
|
||||
li.global-search-error-messages
|
||||
= msg
|
||||
+resultsPaged(this)
|
||||
else if serverError.get
|
||||
+resultsPaged getSearchData
|
||||
else if serverError
|
||||
.global-search-page
|
||||
.global-search-help
|
||||
h1 {{_ 'server-error' }}
|
||||
@@ -83,22 +62,22 @@ template(name="globalSearch")
|
||||
.global-search-help
|
||||
h2 {{_ 'boards' }}
|
||||
.lists-wrapper
|
||||
each title in myBoardNames.get
|
||||
each title in myBoardNames
|
||||
span.card-label.list-title.js-board-title
|
||||
= title
|
||||
h2 {{_ 'lists' }}
|
||||
.lists-wrapper
|
||||
each title in myLists.get
|
||||
each title in myLists
|
||||
span.card-label.list-title.js-list-title
|
||||
= title
|
||||
h2 {{_ 'label-colors' }}
|
||||
.palette-colors: each label in labelColors
|
||||
span.card-label.palette-color.js-label-color(class="card-label-{{label.color}}")
|
||||
= label.name
|
||||
if myLabelNames.get.length
|
||||
if myLabelNames
|
||||
h2 {{_ 'label-names' }}
|
||||
.lists-wrapper
|
||||
each name in myLabelNames.get
|
||||
each name in myLabelNames
|
||||
span.card-label.list-title.js-label-name
|
||||
= name
|
||||
.global-search-instructions
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { CardSearchPaged } from '../../lib/cardSearch';
|
||||
import Boards from '../../../models/boards';
|
||||
import { LABEL_COLORS } from '/models/metadata/colors';
|
||||
import { Query, QueryErrors } from '../../../config/query-classes';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
|
||||
// const subManager = new SubsManager();
|
||||
|
||||
@@ -98,36 +99,45 @@ Template.globalSearch.helpers({
|
||||
return Meteor.userId();
|
||||
},
|
||||
|
||||
// Return ReactiveVars so jade can use .get pattern
|
||||
// Return ReactiveVar values so Blaze can use them
|
||||
searching() {
|
||||
return Template.instance().search.searching;
|
||||
const val = Template.instance().search.searching.get();
|
||||
return val;
|
||||
},
|
||||
hasResults() {
|
||||
return Template.instance().search.hasResults;
|
||||
const val = Template.instance().search.hasResults.get();
|
||||
return val;
|
||||
},
|
||||
hasQueryErrors() {
|
||||
return Template.instance().search.hasQueryErrors;
|
||||
const val = Template.instance().search.hasQueryErrors.get();
|
||||
return val;
|
||||
},
|
||||
serverError() {
|
||||
return Template.instance().search.serverError;
|
||||
const val = Template.instance().search.serverError.get();
|
||||
return val;
|
||||
},
|
||||
query() {
|
||||
return Template.instance().search.query;
|
||||
},
|
||||
debug() {
|
||||
return Template.instance().search.debug;
|
||||
const val = Template.instance().search.debug.get();
|
||||
return val;
|
||||
},
|
||||
resultsHeading() {
|
||||
return Template.instance().search.resultsHeading;
|
||||
const rv = Template.instance().search.resultsHeading;
|
||||
return rv ? rv.get() : '';
|
||||
},
|
||||
results() {
|
||||
return Template.instance().search.results;
|
||||
const rv = Template.instance().search.results;
|
||||
return rv ? rv.get() : [];
|
||||
},
|
||||
hasNextPage() {
|
||||
return Template.instance().search.hasNextPage;
|
||||
const rv = Template.instance().search.hasNextPage;
|
||||
return rv ? rv.get() : false;
|
||||
},
|
||||
hasPreviousPage() {
|
||||
return Template.instance().search.hasPreviousPage;
|
||||
const rv = Template.instance().search.hasPreviousPage;
|
||||
return rv ? rv.get() : false;
|
||||
},
|
||||
sessionData() {
|
||||
return Template.instance().search.sessionData;
|
||||
@@ -137,124 +147,170 @@ Template.globalSearch.helpers({
|
||||
},
|
||||
|
||||
myLists() {
|
||||
return Template.instance().myLists;
|
||||
const rv = Template.instance().myLists;
|
||||
return rv ? rv.get() : [];
|
||||
},
|
||||
myLabelNames() {
|
||||
return Template.instance().myLabelNames;
|
||||
const rv = Template.instance().myLabelNames;
|
||||
return rv ? rv.get() : [];
|
||||
},
|
||||
myBoardNames() {
|
||||
return Template.instance().myBoardNames;
|
||||
const rv = Template.instance().myBoardNames;
|
||||
return rv ? rv.get() : [];
|
||||
},
|
||||
|
||||
errorMessages() {
|
||||
return errorMessages(Template.instance());
|
||||
},
|
||||
|
||||
searchInstructions() {
|
||||
const tags = {
|
||||
operator_board: TAPi18n.__('operator-board'),
|
||||
operator_list: TAPi18n.__('operator-list'),
|
||||
operator_swimlane: TAPi18n.__('operator-swimlane'),
|
||||
operator_comment: TAPi18n.__('operator-comment'),
|
||||
operator_label: TAPi18n.__('operator-label'),
|
||||
operator_label_abbrev: TAPi18n.__('operator-label-abbrev'),
|
||||
operator_user: TAPi18n.__('operator-user'),
|
||||
operator_user_abbrev: TAPi18n.__('operator-user-abbrev'),
|
||||
operator_member: TAPi18n.__('operator-member'),
|
||||
operator_member_abbrev: TAPi18n.__('operator-member-abbrev'),
|
||||
operator_assignee: TAPi18n.__('operator-assignee'),
|
||||
operator_assignee_abbrev: TAPi18n.__('operator-assignee-abbrev'),
|
||||
operator_creator: TAPi18n.__('operator-creator'),
|
||||
operator_due: TAPi18n.__('operator-due'),
|
||||
operator_created: TAPi18n.__('operator-created'),
|
||||
operator_modified: TAPi18n.__('operator-modified'),
|
||||
operator_status: TAPi18n.__('operator-status'),
|
||||
operator_has: TAPi18n.__('operator-has'),
|
||||
operator_sort: TAPi18n.__('operator-sort'),
|
||||
operator_limit: TAPi18n.__('operator-limit'),
|
||||
operator_debug: TAPi18n.__('operator-debug'),
|
||||
operator_org: TAPi18n.__('operator-org'),
|
||||
operator_team: TAPi18n.__('operator-team'),
|
||||
predicate_overdue: TAPi18n.__('predicate-overdue'),
|
||||
predicate_archived: TAPi18n.__('predicate-archived'),
|
||||
predicate_all: TAPi18n.__('predicate-all'),
|
||||
predicate_ended: TAPi18n.__('predicate-ended'),
|
||||
predicate_week: TAPi18n.__('predicate-week'),
|
||||
predicate_month: TAPi18n.__('predicate-month'),
|
||||
predicate_quarter: TAPi18n.__('predicate-quarter'),
|
||||
predicate_year: TAPi18n.__('predicate-year'),
|
||||
predicate_attachment: TAPi18n.__('predicate-attachment'),
|
||||
predicate_description: TAPi18n.__('predicate-description'),
|
||||
predicate_checklist: TAPi18n.__('predicate-checklist'),
|
||||
predicate_public: TAPi18n.__('predicate-public'),
|
||||
predicate_private: TAPi18n.__('predicate-private'),
|
||||
predicate_due: TAPi18n.__('predicate-due'),
|
||||
predicate_created: TAPi18n.__('predicate-created'),
|
||||
predicate_modified: TAPi18n.__('predicate-modified'),
|
||||
predicate_start: TAPi18n.__('predicate-start'),
|
||||
predicate_end: TAPi18n.__('predicate-end'),
|
||||
predicate_assignee: TAPi18n.__('predicate-assignee'),
|
||||
predicate_member: TAPi18n.__('predicate-member'),
|
||||
predicate_selector: TAPi18n.__('predicate-selector'),
|
||||
predicate_projection: TAPi18n.__('predicate-projection'),
|
||||
};
|
||||
getSearchData() {
|
||||
return Template.instance().search;
|
||||
},
|
||||
|
||||
let text = '';
|
||||
[
|
||||
['# ', 'globalSearch-instructions-heading'],
|
||||
['\n', 'globalSearch-instructions-description'],
|
||||
['\n\n', 'globalSearch-instructions-operators'],
|
||||
['\n- ', 'globalSearch-instructions-operator-board'],
|
||||
['\n- ', 'globalSearch-instructions-operator-list'],
|
||||
['\n- ', 'globalSearch-instructions-operator-swimlane'],
|
||||
['\n- ', 'globalSearch-instructions-operator-comment'],
|
||||
['\n- ', 'globalSearch-instructions-operator-label'],
|
||||
['\n- ', 'globalSearch-instructions-operator-hash'],
|
||||
['\n- ', 'globalSearch-instructions-operator-user'],
|
||||
['\n- ', 'globalSearch-instructions-operator-at'],
|
||||
['\n- ', 'globalSearch-instructions-operator-member'],
|
||||
['\n- ', 'globalSearch-instructions-operator-assignee'],
|
||||
['\n- ', 'globalSearch-instructions-operator-creator'],
|
||||
['\n- ', 'globalSearch-instructions-operator-org'],
|
||||
['\n- ', 'globalSearch-instructions-operator-team'],
|
||||
['\n- ', 'globalSearch-instructions-operator-due'],
|
||||
['\n- ', 'globalSearch-instructions-operator-created'],
|
||||
['\n- ', 'globalSearch-instructions-operator-modified'],
|
||||
['\n- ', 'globalSearch-instructions-operator-status'],
|
||||
['\n - ', 'globalSearch-instructions-status-archived'],
|
||||
['\n - ', 'globalSearch-instructions-status-public'],
|
||||
['\n - ', 'globalSearch-instructions-status-private'],
|
||||
['\n - ', 'globalSearch-instructions-status-all'],
|
||||
['\n - ', 'globalSearch-instructions-status-ended'],
|
||||
['\n- ', 'globalSearch-instructions-operator-has'],
|
||||
['\n- ', 'globalSearch-instructions-operator-sort'],
|
||||
['\n- ', 'globalSearch-instructions-operator-limit'],
|
||||
['\n## ', 'heading-notes'],
|
||||
['\n- ', 'globalSearch-instructions-notes-1'],
|
||||
['\n- ', 'globalSearch-instructions-notes-2'],
|
||||
['\n- ', 'globalSearch-instructions-notes-3'],
|
||||
['\n- ', 'globalSearch-instructions-notes-3-2'],
|
||||
['\n- ', 'globalSearch-instructions-notes-4'],
|
||||
['\n- ', 'globalSearch-instructions-notes-5'],
|
||||
].forEach(([prefix, instruction]) => {
|
||||
text += `${prefix}${TAPi18n.__(instruction, tags)}`
|
||||
// Replace *<text>* with `<text>` so markdown shows correctly
|
||||
.replace(/\*\</, '`<')
|
||||
.replace(/\>\*/, '\>\`')
|
||||
});
|
||||
return text;
|
||||
boardColorClass() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const board = boardId ? ReactiveCache.getBoard(boardId) : null;
|
||||
return board ? board.colorClass() : 'board-color-belize';
|
||||
},
|
||||
|
||||
searchInstructions() {
|
||||
try {
|
||||
const tags = {
|
||||
operator_board: TAPi18n.__('operator-board'),
|
||||
operator_list: TAPi18n.__('operator-list'),
|
||||
operator_swimlane: TAPi18n.__('operator-swimlane'),
|
||||
operator_comment: TAPi18n.__('operator-comment'),
|
||||
operator_label: TAPi18n.__('operator-label'),
|
||||
operator_label_abbrev: TAPi18n.__('operator-label-abbrev'),
|
||||
operator_user: TAPi18n.__('operator-user'),
|
||||
operator_user_abbrev: TAPi18n.__('operator-user-abbrev'),
|
||||
operator_member: TAPi18n.__('operator-member'),
|
||||
operator_member_abbrev: TAPi18n.__('operator-member-abbrev'),
|
||||
operator_assignee: TAPi18n.__('operator-assignee'),
|
||||
operator_assignee_abbrev: TAPi18n.__('operator-assignee-abbrev'),
|
||||
operator_creator: TAPi18n.__('operator-creator'),
|
||||
operator_due: TAPi18n.__('operator-due'),
|
||||
operator_created: TAPi18n.__('operator-created'),
|
||||
operator_modified: TAPi18n.__('operator-modified'),
|
||||
operator_status: TAPi18n.__('operator-status'),
|
||||
operator_has: TAPi18n.__('operator-has'),
|
||||
operator_sort: TAPi18n.__('operator-sort'),
|
||||
operator_limit: TAPi18n.__('operator-limit'),
|
||||
operator_debug: TAPi18n.__('operator-debug'),
|
||||
operator_org: TAPi18n.__('operator-org'),
|
||||
operator_team: TAPi18n.__('operator-team'),
|
||||
predicate_overdue: TAPi18n.__('predicate-overdue'),
|
||||
predicate_archived: TAPi18n.__('predicate-archived'),
|
||||
predicate_all: TAPi18n.__('predicate-all'),
|
||||
predicate_ended: TAPi18n.__('predicate-ended'),
|
||||
predicate_week: TAPi18n.__('predicate-week'),
|
||||
predicate_month: TAPi18n.__('predicate-month'),
|
||||
predicate_quarter: TAPi18n.__('predicate-quarter'),
|
||||
predicate_year: TAPi18n.__('predicate-year'),
|
||||
predicate_attachment: TAPi18n.__('predicate-attachment'),
|
||||
predicate_description: TAPi18n.__('predicate-description'),
|
||||
predicate_checklist: TAPi18n.__('predicate-checklist'),
|
||||
predicate_public: TAPi18n.__('predicate-public'),
|
||||
predicate_private: TAPi18n.__('predicate-private'),
|
||||
predicate_due: TAPi18n.__('predicate-due'),
|
||||
predicate_created: TAPi18n.__('predicate-created'),
|
||||
predicate_modified: TAPi18n.__('predicate-modified'),
|
||||
predicate_start: TAPi18n.__('predicate-start'),
|
||||
predicate_end: TAPi18n.__('predicate-end'),
|
||||
predicate_assignee: TAPi18n.__('predicate-assignee'),
|
||||
predicate_member: TAPi18n.__('predicate-member'),
|
||||
predicate_selector: TAPi18n.__('predicate-selector'),
|
||||
predicate_projection: TAPi18n.__('predicate-projection'),
|
||||
};
|
||||
|
||||
let text = '';
|
||||
[
|
||||
['# ', 'globalSearch-instructions-heading'],
|
||||
['\n', 'globalSearch-instructions-description'],
|
||||
['\n\n', 'globalSearch-instructions-operators'],
|
||||
['\n- ', 'globalSearch-instructions-operator-board'],
|
||||
['\n- ', 'globalSearch-instructions-operator-list'],
|
||||
['\n- ', 'globalSearch-instructions-operator-swimlane'],
|
||||
['\n- ', 'globalSearch-instructions-operator-comment'],
|
||||
['\n- ', 'globalSearch-instructions-operator-label'],
|
||||
['\n- ', 'globalSearch-instructions-operator-hash'],
|
||||
['\n- ', 'globalSearch-instructions-operator-user'],
|
||||
['\n- ', 'globalSearch-instructions-operator-at'],
|
||||
['\n- ', 'globalSearch-instructions-operator-member'],
|
||||
['\n- ', 'globalSearch-instructions-operator-assignee'],
|
||||
['\n- ', 'globalSearch-instructions-operator-creator'],
|
||||
['\n- ', 'globalSearch-instructions-operator-org'],
|
||||
['\n- ', 'globalSearch-instructions-operator-team'],
|
||||
['\n- ', 'globalSearch-instructions-operator-due'],
|
||||
['\n- ', 'globalSearch-instructions-operator-created'],
|
||||
['\n- ', 'globalSearch-instructions-operator-modified'],
|
||||
['\n- ', 'globalSearch-instructions-operator-status'],
|
||||
['\n - ', 'globalSearch-instructions-status-archived'],
|
||||
['\n - ', 'globalSearch-instructions-status-public'],
|
||||
['\n - ', 'globalSearch-instructions-status-private'],
|
||||
['\n - ', 'globalSearch-instructions-status-all'],
|
||||
['\n - ', 'globalSearch-instructions-status-ended'],
|
||||
['\n- ', 'globalSearch-instructions-operator-has'],
|
||||
['\n- ', 'globalSearch-instructions-operator-sort'],
|
||||
['\n- ', 'globalSearch-instructions-operator-limit'],
|
||||
['\n## ', 'heading-notes'],
|
||||
['\n- ', 'globalSearch-instructions-notes-1'],
|
||||
['\n- ', 'globalSearch-instructions-notes-2'],
|
||||
['\n- ', 'globalSearch-instructions-notes-3'],
|
||||
['\n- ', 'globalSearch-instructions-notes-3-2'],
|
||||
['\n- ', 'globalSearch-instructions-notes-4'],
|
||||
['\n- ', 'globalSearch-instructions-notes-5'],
|
||||
].forEach(([prefix, instruction]) => {
|
||||
text += `${prefix}${TAPi18n.__(instruction, tags)}`
|
||||
// Replace *<text>* with `<text>` so markdown shows correctly
|
||||
.replace(/\*\</, '`<')
|
||||
.replace(/\>\*/, '\>\`');
|
||||
});
|
||||
return text;
|
||||
} catch (e) {
|
||||
console.error('Error building searchInstructions:', e);
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
labelColors() {
|
||||
return Boards.simpleSchema()._schema['labels.$.color'].allowedValues.map(
|
||||
color => {
|
||||
return { color, name: TAPi18n.__(`color-${color}`) };
|
||||
},
|
||||
);
|
||||
try {
|
||||
if (!LABEL_COLORS) return [];
|
||||
return LABEL_COLORS.map(
|
||||
color => {
|
||||
return { color, name: TAPi18n.__(`color-${color}`) };
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Error building labelColors:', e);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Template.globalSearch.events({
|
||||
'click input.global-search-query-input'(evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
},
|
||||
'focus input.global-search-query-input'(evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
},
|
||||
'keyup input.global-search-query-input'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
// Store the query value
|
||||
tpl.search.query.set(evt.target.value);
|
||||
},
|
||||
'keydown input.global-search-query-input'(evt, tpl) {
|
||||
// Allow normal key behavior but capture Enter key
|
||||
if (evt.key === 'Enter') {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
searchAllBoards(tpl, evt.target.value);
|
||||
}
|
||||
},
|
||||
'submit .js-search-query-form'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
searchAllBoards(tpl, evt.target.searchQuery.value);
|
||||
@@ -269,30 +325,6 @@ Template.globalSearch.events({
|
||||
);
|
||||
document.getElementById('global-search-input').focus();
|
||||
},
|
||||
'click .js-copy-debug-selector'(evt) {
|
||||
/* Get the text field */
|
||||
const selector = document.getElementById("debug-selector");
|
||||
|
||||
try {
|
||||
navigator.clipboard.writeText(selector.textContent);
|
||||
alert("Selector copied to clipboard");
|
||||
} catch(err) {
|
||||
alert("Error copying text: " + err);
|
||||
}
|
||||
|
||||
},
|
||||
'click .js-copy-debug-projection'(evt) {
|
||||
/* Get the text field */
|
||||
const projection = document.getElementById("debug-projection");
|
||||
|
||||
try {
|
||||
navigator.clipboard.writeText(projection.textContent);
|
||||
alert("Projection copied to clipboard");
|
||||
} catch(err) {
|
||||
alert("Error copying text: " + err);
|
||||
}
|
||||
|
||||
},
|
||||
'click .js-board-title'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
const input = document.getElementById('global-search-input');
|
||||
@@ -340,47 +372,46 @@ Template.globalSearch.events({
|
||||
},
|
||||
});
|
||||
|
||||
// resultsPaged template helpers - this template is used by globalSearch, brokenCards, and brokenCardsReport.
|
||||
// It receives the parent's data context (which includes reactive vars) via +resultsPaged(this).
|
||||
// The jade accesses resultsHeading.get, results.get, getSearchHref, hasPreviousPage.get, hasNextPage.get
|
||||
// directly on the data context, so we don't need helpers for those when the parent passes
|
||||
// the right data context. However, we need to ensure the helpers exist for the template.
|
||||
// resultsPaged template helpers - receives search data context from parent template.
|
||||
Template.resultsPaged.helpers({
|
||||
resultsHeading() {
|
||||
const data = Template.currentData();
|
||||
if (data && data.resultsHeading) return data.resultsHeading;
|
||||
// fallback: check parent template
|
||||
const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
|
||||
if (parentTpl && parentTpl.search) return parentTpl.search.resultsHeading;
|
||||
return new ReactiveVar('');
|
||||
if (data && data.resultsHeading) {
|
||||
const rv = data.resultsHeading;
|
||||
return rv && typeof rv.get === 'function' ? rv.get() : rv;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
results() {
|
||||
const data = Template.currentData();
|
||||
if (data && data.results) return data.results;
|
||||
const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
|
||||
if (parentTpl && parentTpl.search) return parentTpl.search.results;
|
||||
return new ReactiveVar([]);
|
||||
if (data && data.results) {
|
||||
const rv = data.results;
|
||||
return rv && typeof rv.get === 'function' ? rv.get() : rv;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
getSearchHref() {
|
||||
const data = Template.currentData();
|
||||
if (data && data.getSearchHref) return data.getSearchHref();
|
||||
const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
|
||||
if (parentTpl && parentTpl.search) return parentTpl.search.getSearchHref();
|
||||
if (data && typeof data.getSearchHref === 'function') {
|
||||
return data.getSearchHref();
|
||||
}
|
||||
return '';
|
||||
},
|
||||
hasPreviousPage() {
|
||||
const data = Template.currentData();
|
||||
if (data && data.hasPreviousPage) return data.hasPreviousPage;
|
||||
const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
|
||||
if (parentTpl && parentTpl.search) return parentTpl.search.hasPreviousPage;
|
||||
return new ReactiveVar(false);
|
||||
if (data && data.hasPreviousPage) {
|
||||
const rv = data.hasPreviousPage;
|
||||
return rv && typeof rv.get === 'function' ? rv.get() : rv;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
hasNextPage() {
|
||||
const data = Template.currentData();
|
||||
if (data && data.hasNextPage) return data.hasNextPage;
|
||||
const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
|
||||
if (parentTpl && parentTpl.search) return parentTpl.search.hasNextPage;
|
||||
return new ReactiveVar(false);
|
||||
if (data && data.hasNextPage) {
|
||||
const rv = data.hasNextPage;
|
||||
return rv && typeof rv.get === 'function' ? rv.get() : rv;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -126,5 +126,9 @@ template(name="offlineWarning")
|
||||
p
|
||||
i.fa.fa-warning
|
||||
| {{_ 'app-is-offline'}}
|
||||
|
||||
a.app-try-reconnect {{_ 'app-try-reconnect'}}
|
||||
if appOfflineMessage
|
||||
br
|
||||
span= appOfflineMessage
|
||||
if canReconnectNow
|
||||
br
|
||||
a.app-try-reconnect {{_ 'app-try-reconnect'}}
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Session } from 'meteor/session';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import Announcements from '/models/announcements';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Meteor.subscribe('user-admin');
|
||||
Meteor.subscribe('boards');
|
||||
@@ -43,7 +47,27 @@ Template.header.helpers({
|
||||
},
|
||||
|
||||
appIsOffline() {
|
||||
return !Meteor.status().connected;
|
||||
const status = Meteor.status();
|
||||
return ['waiting', 'failed', 'offline'].includes(status.status);
|
||||
},
|
||||
|
||||
appOfflineMessage() {
|
||||
const status = Meteor.status();
|
||||
|
||||
if (status.status === 'failed' && status.reason) {
|
||||
return status.reason;
|
||||
}
|
||||
|
||||
if (status.status === 'offline') {
|
||||
return 'Connection is paused.';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
canReconnectNow() {
|
||||
const status = Meteor.status();
|
||||
return ['waiting', 'failed', 'offline'].includes(status.status);
|
||||
},
|
||||
|
||||
hasAnnouncement() {
|
||||
@@ -132,7 +156,7 @@ Template.header.events({
|
||||
Session.set('currentCard', null);
|
||||
},
|
||||
'click .js-toggle-desktop-drag-handles'() {
|
||||
currentUser = Meteor.user();
|
||||
const currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
Meteor.call('toggleDesktopDragHandles');
|
||||
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import Users from '/models/users';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
|
||||
let alreadyCheck = 1;
|
||||
let isCheckDone = false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CardSearchPaged } from '../../lib/cardSearch';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Template.myCardsHeaderBar.helpers({
|
||||
myCardsSort() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
border-radius: 0.4vw;
|
||||
border: 1px solid #dbdbdb;
|
||||
border-bottom-color: #c2c2c2;
|
||||
box-shadow: 0 0.2vh 0.8vh rgba(0,0,0,0.3);
|
||||
box-shadow: 0 0.2vh 0.8vh rgba(0, 0, 0, 0.3);
|
||||
position: absolute;
|
||||
/* Wider default to fit full color palette */
|
||||
width: min(380px, 55vw);
|
||||
@@ -15,10 +15,10 @@
|
||||
}
|
||||
.pop-over p,
|
||||
.pop-over textarea,
|
||||
.pop-over input[type="text"],
|
||||
.pop-over input[type="email"],
|
||||
.pop-over input[type="password"],
|
||||
.pop-over input[type="file"] {
|
||||
.pop-over input[type='text'],
|
||||
.pop-over input[type='email'],
|
||||
.pop-over input[type='password'],
|
||||
.pop-over input[type='file'] {
|
||||
width: 100%;
|
||||
}
|
||||
.pop-over select {
|
||||
@@ -79,8 +79,22 @@
|
||||
}
|
||||
|
||||
/* Allow dynamic max-height to override default constraint */
|
||||
.pop-over[style*="max-height"] .content-wrapper {
|
||||
max-height: inherit;
|
||||
.pop-over[style*='max-height'] {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pop-over[style*='max-height'] .header {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.pop-over[style*='max-height'] .content-wrapper {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
max-height: none;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.pop-over .content-container {
|
||||
width: 100%;
|
||||
@@ -89,17 +103,17 @@
|
||||
}
|
||||
|
||||
/* Allow dynamic max-height to override default constraint for content-container */
|
||||
.pop-over[style*="max-height"] .content-container {
|
||||
max-height: inherit;
|
||||
.pop-over[style*='max-height'] .content-container {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
/* Fix overflow in the Member Settings (member menu) popup:
|
||||
the popup itself gets a max-height inline style, but the header consumes space.
|
||||
Make the header overlay the scrollable area so the list can't spill out. */
|
||||
.pop-over[data-popup="memberMenuPopup"] {
|
||||
.pop-over[data-popup='memberMenuPopup'] {
|
||||
overflow: hidden;
|
||||
}
|
||||
.pop-over[data-popup="memberMenuPopup"] > .header {
|
||||
.pop-over[data-popup='memberMenuPopup'] > .header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -107,30 +121,30 @@
|
||||
margin-bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.pop-over[data-popup="memberMenuPopup"] > .content-wrapper {
|
||||
.pop-over[data-popup='memberMenuPopup'] > .content-wrapper {
|
||||
padding-top: calc(4.5vh + 1vh);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Admin edit popups: use full height */
|
||||
.pop-over[data-popup="editUserPopup"],
|
||||
.pop-over[data-popup="editOrgPopup"],
|
||||
.pop-over[data-popup="editTeamPopup"] {
|
||||
.pop-over[data-popup='editUserPopup'],
|
||||
.pop-over[data-popup='editOrgPopup'],
|
||||
.pop-over[data-popup='editTeamPopup'] {
|
||||
height: calc(100vh - 20px) !important;
|
||||
max-height: calc(100vh - 20px) !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editUserPopup"] .content-wrapper,
|
||||
.pop-over[data-popup="editOrgPopup"] .content-wrapper,
|
||||
.pop-over[data-popup="editTeamPopup"] .content-wrapper {
|
||||
.pop-over[data-popup='editUserPopup'] .content-wrapper,
|
||||
.pop-over[data-popup='editOrgPopup'] .content-wrapper,
|
||||
.pop-over[data-popup='editTeamPopup'] .content-wrapper {
|
||||
max-height: calc(100vh - 80px) !important; /* Subtract header height */
|
||||
height: calc(100vh - 80px) !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editUserPopup"] .content-container,
|
||||
.pop-over[data-popup="editOrgPopup"] .content-container,
|
||||
.pop-over[data-popup="editTeamPopup"] .content-container {
|
||||
.pop-over[data-popup='editUserPopup'] .content-container,
|
||||
.pop-over[data-popup='editOrgPopup'] .content-container,
|
||||
.pop-over[data-popup='editTeamPopup'] .content-container {
|
||||
max-height: calc(100vh - 80px) !important; /* Subtract header height */
|
||||
height: calc(100vh - 80px) !important;
|
||||
}
|
||||
@@ -142,7 +156,7 @@
|
||||
}
|
||||
|
||||
/* Specific styling for language popup list */
|
||||
.pop-over[data-popup="changeLanguagePopup"] .pop-over-list {
|
||||
.pop-over[data-popup='changeLanguagePopup'] .pop-over-list {
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
height: auto;
|
||||
@@ -150,7 +164,7 @@
|
||||
}
|
||||
|
||||
/* Ensure content div in language popup contains all items */
|
||||
.pop-over[data-popup="changeLanguagePopup"] .content {
|
||||
.pop-over[data-popup='changeLanguagePopup'] .content {
|
||||
height: auto;
|
||||
/* Remove forced min-height to avoid top gap */
|
||||
display: flex;
|
||||
@@ -158,7 +172,7 @@
|
||||
}
|
||||
|
||||
/* Ensure hidden stack pages truly take no space */
|
||||
.pop-over[data-popup="changeLanguagePopup"] .content.no-height {
|
||||
.pop-over[data-popup='changeLanguagePopup'] .content.no-height {
|
||||
min-height: 0 !important;
|
||||
height: 0 !important;
|
||||
padding: 0 !important;
|
||||
@@ -167,7 +181,7 @@
|
||||
}
|
||||
|
||||
/* Make language popup extend to bottom of browser window */
|
||||
.pop-over[data-popup="changeLanguagePopup"] {
|
||||
.pop-over[data-popup='changeLanguagePopup'] {
|
||||
position: fixed !important;
|
||||
bottom: 0 !important;
|
||||
top: auto !important;
|
||||
@@ -184,12 +198,12 @@
|
||||
}
|
||||
|
||||
/* Allow dynamic height for Change Language popup */
|
||||
.pop-over[data-popup="changeLanguagePopup"] .header {
|
||||
.pop-over[data-popup='changeLanguagePopup'] .header {
|
||||
flex-shrink: 0 !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="changeLanguagePopup"] .content-wrapper {
|
||||
.pop-over[data-popup='changeLanguagePopup'] .content-wrapper {
|
||||
flex: 1 !important;
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
@@ -199,7 +213,7 @@
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="changeLanguagePopup"] .content-container {
|
||||
.pop-over[data-popup='changeLanguagePopup'] .content-container {
|
||||
height: auto !important;
|
||||
max-height: none !important;
|
||||
flex: 1 !important;
|
||||
@@ -208,7 +222,7 @@
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="changeLanguagePopup"] .content {
|
||||
.pop-over[data-popup='changeLanguagePopup'] .content {
|
||||
height: auto !important;
|
||||
max-height: none !important;
|
||||
padding-bottom: 50px !important;
|
||||
@@ -216,59 +230,59 @@
|
||||
}
|
||||
|
||||
/* Date popup sizing for native HTML inputs */
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"],
|
||||
.pop-over[data-popup="editCardStartDatePopup"],
|
||||
.pop-over[data-popup="editCardDueDatePopup"],
|
||||
.pop-over[data-popup="editCardEndDatePopup"],
|
||||
.pop-over[data-popup*="Date"] {
|
||||
.pop-over[data-popup='editCardReceivedDatePopup'],
|
||||
.pop-over[data-popup='editCardStartDatePopup'],
|
||||
.pop-over[data-popup='editCardDueDatePopup'],
|
||||
.pop-over[data-popup='editCardEndDatePopup'],
|
||||
.pop-over[data-popup*='Date'] {
|
||||
width: min(400px, 90vw) !important; /* Smaller width for native inputs */
|
||||
min-width: 350px !important;
|
||||
max-height: 80vh !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .content-wrapper,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .content-wrapper,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .content-wrapper,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .content-wrapper,
|
||||
.pop-over[data-popup*="Date"] .content-wrapper {
|
||||
.pop-over[data-popup='editCardReceivedDatePopup'] .content-wrapper,
|
||||
.pop-over[data-popup='editCardStartDatePopup'] .content-wrapper,
|
||||
.pop-over[data-popup='editCardDueDatePopup'] .content-wrapper,
|
||||
.pop-over[data-popup='editCardEndDatePopup'] .content-wrapper,
|
||||
.pop-over[data-popup*='Date'] .content-wrapper {
|
||||
max-height: 60vh !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .content-container,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .content-container,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .content-container,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .content-container,
|
||||
.pop-over[data-popup*="Date"] .content-container {
|
||||
.pop-over[data-popup='editCardReceivedDatePopup'] .content-container,
|
||||
.pop-over[data-popup='editCardStartDatePopup'] .content-container,
|
||||
.pop-over[data-popup='editCardDueDatePopup'] .content-container,
|
||||
.pop-over[data-popup='editCardEndDatePopup'] .content-container,
|
||||
.pop-over[data-popup*='Date'] .content-container {
|
||||
max-height: 60vh !important;
|
||||
}
|
||||
|
||||
/* Native HTML input styling */
|
||||
.pop-over[data-popup*="Date"] .datepicker-container {
|
||||
.pop-over[data-popup*='Date'] .datepicker-container {
|
||||
width: 100% !important;
|
||||
padding: 15px !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup*="Date"] .datepicker-container .fields {
|
||||
.pop-over[data-popup*='Date'] .datepicker-container .fields {
|
||||
display: flex !important;
|
||||
gap: 15px !important;
|
||||
margin-bottom: 15px !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup*="Date"] .datepicker-container .fields .left,
|
||||
.pop-over[data-popup*="Date"] .datepicker-container .fields .right {
|
||||
.pop-over[data-popup*='Date'] .datepicker-container .fields .left,
|
||||
.pop-over[data-popup*='Date'] .datepicker-container .fields .right {
|
||||
flex: 1 !important;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup*="Date"] .datepicker-container label {
|
||||
.pop-over[data-popup*='Date'] .datepicker-container label {
|
||||
display: block !important;
|
||||
margin-bottom: 5px !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup*="Date"] .datepicker-container input[type="date"],
|
||||
.pop-over[data-popup*="Date"] .datepicker-container input[type="time"] {
|
||||
.pop-over[data-popup*='Date'] .datepicker-container input[type='date'],
|
||||
.pop-over[data-popup*='Date'] .datepicker-container input[type='time'] {
|
||||
width: 100% !important;
|
||||
padding: 8px !important;
|
||||
border: 1px solid #ccc !important;
|
||||
@@ -277,71 +291,109 @@
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup*="Date"] .datepicker-container input[type="date"]:focus,
|
||||
.pop-over[data-popup*="Date"] .datepicker-container input[type="time"]:focus {
|
||||
.pop-over[data-popup*='Date'] .datepicker-container input[type='date']:focus,
|
||||
.pop-over[data-popup*='Date'] .datepicker-container input[type='time']:focus {
|
||||
outline: none !important;
|
||||
border-color: #007cba !important;
|
||||
box-shadow: 0 0 0 2px rgba(0, 124, 186, 0.2) !important;
|
||||
}
|
||||
|
||||
/* Ensure date popup buttons stay within popup boundaries */
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .content,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .content,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .content,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .content,
|
||||
.pop-over[data-popup*="Date"] .content {
|
||||
.pop-over[data-popup='editCardReceivedDatePopup'] .content,
|
||||
.pop-over[data-popup='editCardStartDatePopup'] .content,
|
||||
.pop-over[data-popup='editCardDueDatePopup'] .content,
|
||||
.pop-over[data-popup='editCardEndDatePopup'] .content,
|
||||
.pop-over[data-popup*='Date'] .content {
|
||||
max-height: 60vh !important; /* Leave space for buttons */
|
||||
overflow-y: auto !important;
|
||||
padding-bottom: 100px !important; /* More space for buttons */
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .datepicker-container,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .datepicker-container,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .datepicker-container,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .datepicker-container,
|
||||
.pop-over[data-popup*="Date"] .datepicker-container {
|
||||
/* Workspace Settings popup: ensure modal doesn't overflow viewport */
|
||||
.pop-over[data-popup='workspaceActionsPopup'] {
|
||||
max-height: 80vh !important;
|
||||
width: min(420px, 85vw) !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup='workspaceActionsPopup'] .content-wrapper {
|
||||
max-height: calc(80vh - 70px) !important; /* Subtract header height */
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup='workspaceActionsPopup'] .content-container {
|
||||
max-height: calc(80vh - 70px) !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup='workspaceActionsPopup'] .content {
|
||||
max-height: none !important; /* Allow content to scroll within container */
|
||||
overflow: visible !important; /* Overflow handled by parent */
|
||||
}
|
||||
|
||||
/* Card Details Actions popup: ensure menu doesn't overflow viewport */
|
||||
.pop-over[data-popup="cardDetailsActionsPopup"] {
|
||||
max-height: 80vh !important;
|
||||
width: min(380px, 85vw) !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="cardDetailsActionsPopup"] .content-wrapper {
|
||||
max-height: calc(80vh - 70px) !important; /* Subtract header height */
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="cardDetailsActionsPopup"] .content-container {
|
||||
max-height: calc(80vh - 70px) !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="cardDetailsActionsPopup"] .content {
|
||||
max-height: none !important; /* Allow content to scroll within container */
|
||||
overflow: visible !important; /* Overflow handled by parent */
|
||||
}
|
||||
|
||||
.pop-over[data-popup='editCardReceivedDatePopup'] .datepicker-container,
|
||||
.pop-over[data-popup='editCardStartDatePopup'] .datepicker-container,
|
||||
.pop-over[data-popup='editCardDueDatePopup'] .datepicker-container,
|
||||
.pop-over[data-popup='editCardEndDatePopup'] .datepicker-container,
|
||||
.pop-over[data-popup*='Date'] .datepicker-container {
|
||||
max-height: 50vh !important; /* Limit calendar height */
|
||||
overflow-y: auto !important;
|
||||
margin-bottom: 20px !important; /* Space before buttons */
|
||||
}
|
||||
|
||||
/* Ensure buttons are properly positioned */
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .edit-date,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .edit-date,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .edit-date,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .edit-date,
|
||||
.pop-over[data-popup*="Date"] .edit-date {
|
||||
.pop-over[data-popup='editCardReceivedDatePopup'] .edit-date,
|
||||
.pop-over[data-popup='editCardStartDatePopup'] .edit-date,
|
||||
.pop-over[data-popup='editCardDueDatePopup'] .edit-date,
|
||||
.pop-over[data-popup='editCardEndDatePopup'] .edit-date,
|
||||
.pop-over[data-popup*='Date'] .edit-date {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .edit-date .fields,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .edit-date .fields,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .edit-date .fields,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .edit-date .fields,
|
||||
.pop-over[data-popup*="Date"] .edit-date .fields {
|
||||
.pop-over[data-popup='editCardReceivedDatePopup'] .edit-date .fields,
|
||||
.pop-over[data-popup='editCardStartDatePopup'] .edit-date .fields,
|
||||
.pop-over[data-popup='editCardDueDatePopup'] .edit-date .fields,
|
||||
.pop-over[data-popup='editCardEndDatePopup'] .edit-date .fields,
|
||||
.pop-over[data-popup*='Date'] .edit-date .fields {
|
||||
flex-shrink: 0 !important;
|
||||
margin-bottom: 15px !important;
|
||||
}
|
||||
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .edit-date .js-datepicker,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .edit-date .js-datepicker,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .edit-date .js-datepicker,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .edit-date .js-datepicker,
|
||||
.pop-over[data-popup*="Date"] .edit-date .js-datepicker {
|
||||
.pop-over[data-popup='editCardReceivedDatePopup'] .edit-date .js-datepicker,
|
||||
.pop-over[data-popup='editCardStartDatePopup'] .edit-date .js-datepicker,
|
||||
.pop-over[data-popup='editCardDueDatePopup'] .edit-date .js-datepicker,
|
||||
.pop-over[data-popup='editCardEndDatePopup'] .edit-date .js-datepicker,
|
||||
.pop-over[data-popup*='Date'] .edit-date .js-datepicker {
|
||||
flex: 1 !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.pop-over[data-popup="editCardReceivedDatePopup"] .edit-date button,
|
||||
.pop-over[data-popup="editCardStartDatePopup"] .edit-date button,
|
||||
.pop-over[data-popup="editCardDueDatePopup"] .edit-date button,
|
||||
.pop-over[data-popup="editCardEndDatePopup"] .edit-date button,
|
||||
.pop-over[data-popup*="Date"] .edit-date button {
|
||||
.pop-over[data-popup='editCardReceivedDatePopup'] .edit-date button,
|
||||
.pop-over[data-popup='editCardStartDatePopup'] .edit-date button,
|
||||
.pop-over[data-popup='editCardDueDatePopup'] .edit-date button,
|
||||
.pop-over[data-popup='editCardEndDatePopup'] .edit-date button,
|
||||
.pop-over[data-popup*='Date'] .edit-date button {
|
||||
flex-shrink: 0 !important;
|
||||
margin-top: 15px !important;
|
||||
position: relative !important;
|
||||
@@ -350,7 +402,7 @@
|
||||
.pop-over .content-container .content {
|
||||
/* Match wider popover, leave padding */
|
||||
width: 100%;
|
||||
padding: 0 1.3vw 1.3vh;
|
||||
padding: 1.3vh 1.3vw 1.3vh;
|
||||
box-sizing: border-box;
|
||||
/* Ensure content is not shifted left */
|
||||
margin-left: 0 !important;
|
||||
@@ -387,8 +439,6 @@
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Fix popup depth containers - don't block transforms which are needed for popup navigation */
|
||||
.pop-over .popup-container-depth-1,
|
||||
.pop-over .popup-container-depth-2,
|
||||
@@ -477,15 +527,15 @@
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
line-height: 33px;
|
||||
display:flex;
|
||||
/* flex-wrap:wrap;*/
|
||||
gap:5px;
|
||||
display: flex;
|
||||
/* flex-wrap:wrap;*/
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
color: #000 !important;
|
||||
}
|
||||
.pop-over-list li > a > .member{
|
||||
.pop-over-list li > a > .member {
|
||||
align-self: flex-start;
|
||||
flex:0 0 auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.pop-over-list li > a .item-name {
|
||||
display: block;
|
||||
@@ -657,10 +707,10 @@ body.grey-icons-enabled .pop-over-list .pop-over-list.checkable .fa-check {
|
||||
}
|
||||
.pop-over .content-container .content p,
|
||||
.pop-over .content-container .content textarea,
|
||||
.pop-over .content-container .content input[type="text"],
|
||||
.pop-over .content-container .content input[type="email"],
|
||||
.pop-over .content-container .content input[type="password"],
|
||||
.pop-over .content-container .content input[type="file"] {
|
||||
.pop-over .content-container .content input[type='text'],
|
||||
.pop-over .content-container .content input[type='email'],
|
||||
.pop-over .content-container .content input[type='password'],
|
||||
.pop-over .content-container .content input[type='file'] {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CSSEvents } from '/client/lib/cssEvents';
|
||||
|
||||
Popup.template.events({
|
||||
'click .js-back-view'() {
|
||||
Popup.back();
|
||||
@@ -26,8 +28,11 @@ Popup.template.events({
|
||||
// When a popup content is removed (ie, when the user press the "back" button),
|
||||
// we need to wait for the container translation to end before removing the
|
||||
// actual DOM element. For that purpose we use the undocumented `_uihooks` API.
|
||||
Popup.template.onRendered(() => {
|
||||
Popup.template.onRendered(function () {
|
||||
const container = this.find('.content-container');
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
container._uihooks = {
|
||||
removeElement(node) {
|
||||
$(node).addClass('no-height');
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { formatDateByUserPreference } from '/imports/lib/dateUtils';
|
||||
import Users from '/models/users';
|
||||
|
||||
Template.notification.events({
|
||||
'click .read-status .materialCheckBox'() {
|
||||
async 'click .read-status .materialCheckBox'() {
|
||||
const update = {};
|
||||
const newReadValue = this.read ? null : Date.now();
|
||||
const newReadValue = this.read ? null : new Date();
|
||||
update[`profile.notifications.${this.index}.read`] = newReadValue;
|
||||
|
||||
Users.update(Meteor.userId(), { $set: update }, (error, result) => {
|
||||
await Users.updateAsync(Meteor.userId(), { $set: update }).catch((error) => {
|
||||
if (error) {
|
||||
console.error('Error updating notification:', error);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { toggleNotificationsDrawer } from './notifications.js';
|
||||
import Users from '/models/users';
|
||||
|
||||
Template.notificationsDrawer.onCreated(function() {
|
||||
Meteor.subscribe('notificationActivities');
|
||||
@@ -34,7 +36,7 @@ Template.notificationsDrawer.events({
|
||||
event.stopPropagation();
|
||||
Session.set('showNotificationMenu', !Session.get('showNotificationMenu'));
|
||||
},
|
||||
'click .notification-menu .menu-item'(event) {
|
||||
async 'click .notification-menu .menu-item'(event) {
|
||||
const target = event.currentTarget;
|
||||
|
||||
if (target.classList.contains('mark-all-read')) {
|
||||
@@ -42,8 +44,10 @@ Template.notificationsDrawer.events({
|
||||
for (const index in notifications) {
|
||||
if (notifications.hasOwnProperty(index) && !notifications[index].read) {
|
||||
const update = {};
|
||||
update[`profile.notifications.${index}.read`] = Date.now();
|
||||
Users.update(Meteor.userId(), { $set: update });
|
||||
update[`profile.notifications.${index}.read`] = new Date();
|
||||
await Users.updateAsync(Meteor.userId(), { $set: update }).catch((error) => {
|
||||
console.error('Error marking notification as read:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
Session.set('showNotificationMenu', false);
|
||||
@@ -53,7 +57,9 @@ Template.notificationsDrawer.events({
|
||||
if (notifications.hasOwnProperty(index) && notifications[index].read) {
|
||||
const update = {};
|
||||
update[`profile.notifications.${index}.read`] = null;
|
||||
Users.update(Meteor.userId(), { $set: update });
|
||||
await Users.updateAsync(Meteor.userId(), { $set: update }).catch((error) => {
|
||||
console.error('Error marking notification as unread:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
Session.set('showNotificationMenu', false);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import Actions from '/models/actions';
|
||||
import Rules from '/models/rules';
|
||||
import Triggers from '/models/triggers';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Template.boardActions.onCreated(function () {
|
||||
this.subscribe('boards');
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import Actions from '/models/actions';
|
||||
import { CARD_COLORS } from '/models/metadata/colors';
|
||||
import Rules from '/models/rules';
|
||||
import Triggers from '/models/triggers';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
let cardColors;
|
||||
Meteor.startup(() => {
|
||||
cardColors = Cards.simpleSchema()._schema.color.allowedValues;
|
||||
cardColors = CARD_COLORS;
|
||||
});
|
||||
|
||||
// Module-level shared state so the color popup can read/write the
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import Actions from '/models/actions';
|
||||
import Rules from '/models/rules';
|
||||
import Triggers from '/models/triggers';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Template.checklistActions.onCreated(function () {
|
||||
this.subscribe('allRules');
|
||||
});
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import Actions from '/models/actions';
|
||||
import Rules from '/models/rules';
|
||||
import Triggers from '/models/triggers';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Template.mailActions.events({
|
||||
'click .js-mail-action'(event, tpl) {
|
||||
const emailTo = tpl.find('#email-to').value;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import Actions from '/models/actions';
|
||||
import Rules from '/models/rules';
|
||||
import Triggers from '/models/triggers';
|
||||
|
||||
Template.rulesMain.onCreated(function () {
|
||||
this.rulesCurrentTab = new ReactiveVar('rulesList');
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Template.boardTriggers.onCreated(function () {
|
||||
this.provaVar = new ReactiveVar('');
|
||||
this.currentPopupTriggerId = 'def';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Template.cardTriggers.onCreated(function () {
|
||||
this.subscribe('allRules');
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Template.checklistTriggers.onCreated(function () {
|
||||
this.subscribe('allRules');
|
||||
});
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { AttachmentStorage } from '/models/attachments';
|
||||
import Attachments, { AttachmentStorage } from '/models/attachments';
|
||||
import { CardSearchPaged } from '/client/lib/cardSearch';
|
||||
import SessionData from '/models/usersessiondata';
|
||||
import { QueryParams } from '/config/query-classes';
|
||||
import { OPERATOR_LIMIT } from '/config/search-const';
|
||||
const filesize = require('filesize');
|
||||
import Boards from '/models/boards';
|
||||
import Cards from '/models/cards';
|
||||
import Rules from '/models/rules';
|
||||
const { filesize } = require('filesize');
|
||||
|
||||
// --- Shared helper functions (formerly AdminReport base class methods) ---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { groupBy } from '/imports/lib/collectionHelpers';
|
||||
import Attachments, { fileStoreStrategyFactory } from '/models/attachments';
|
||||
const filesize = require('filesize');
|
||||
import Attachments from '/models/attachments';
|
||||
const { filesize } = require('filesize');
|
||||
|
||||
Template.attachments.onCreated(function () {
|
||||
this.subscription = null;
|
||||
@@ -62,7 +62,10 @@ Template.moveAttachments.helpers({
|
||||
_attachment.flatVersion = Object.keys(_attachment.versions)
|
||||
.map(_versionName => {
|
||||
const _version = Object.assign(_attachment.versions[_versionName], {"versionName": _versionName});
|
||||
_version.storageName = fileStoreStrategyFactory.getFileStrategy(_attachment, _versionName).getStorageName();
|
||||
// Read storage directly from the document (set by onAfterUpload on server)
|
||||
_version.storageName = _version.storage || (
|
||||
(_attachment.meta?.source === 'import' || _version.meta?.gridFsFileId) ? 'gridfs' : 'fs'
|
||||
);
|
||||
return _version;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
const filesize = require('filesize');
|
||||
const { filesize } = require('filesize');
|
||||
|
||||
Template.statistics.onCreated(function () {
|
||||
this.info = new ReactiveVar({});
|
||||
|
||||
@@ -2,6 +2,9 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { InfiniteScrolling } from '/client/lib/infiniteScrolling';
|
||||
import LockoutSettings from '/models/lockoutSettings';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import Org from '/models/org';
|
||||
import Team from '/models/team';
|
||||
import Users from '/models/users';
|
||||
|
||||
const orgsPerPage = 25;
|
||||
const teamsPerPage = 25;
|
||||
@@ -1180,7 +1183,7 @@ Template.editUserPopup.events({
|
||||
},
|
||||
});
|
||||
|
||||
UpdateUserOrgsOrTeamsElement = function(isNewUser = false){
|
||||
const UpdateUserOrgsOrTeamsElement = function(isNewUser = false){
|
||||
let selectedElt;
|
||||
let selectedEltValue;
|
||||
let selectedEltValueId;
|
||||
|
||||
@@ -2,6 +2,11 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { ALLOWED_WAIT_SPINNERS } from '/config/const';
|
||||
import LockoutSettings from '/models/lockoutSettings';
|
||||
import AccessibilitySettings from '/models/accessibilitySettings';
|
||||
import AccountSettings from '/models/accountSettings';
|
||||
import Announcements from '/models/announcements';
|
||||
import Settings from '/models/settings';
|
||||
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
|
||||
// import {
|
||||
// cronMigrationProgress,
|
||||
// cronMigrationStatus,
|
||||
@@ -492,7 +497,7 @@ Template.setting.events({
|
||||
});
|
||||
const validEmails = [];
|
||||
emails.forEach((email) => {
|
||||
if (email && SimpleSchema.RegEx.Email.test(email.trim())) {
|
||||
if (email && /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(email.trim())) {
|
||||
validEmails.push(email.trim());
|
||||
}
|
||||
});
|
||||
@@ -1083,25 +1088,44 @@ Template.announcementSettings.helpers({
|
||||
});
|
||||
|
||||
Template.announcementSettings.events({
|
||||
'click a.js-toggle-activemessage'(event, tpl) {
|
||||
async 'click a.js-toggle-activemessage'(event, tpl) {
|
||||
event.preventDefault();
|
||||
tpl.loading.set(true);
|
||||
const announcements = Announcements.findOne();
|
||||
if (!announcements) {
|
||||
tpl.loading.set(false);
|
||||
return;
|
||||
}
|
||||
const isActive = announcements.enabled;
|
||||
Announcements.update(announcements._id, {
|
||||
$set: { enabled: !isActive },
|
||||
});
|
||||
tpl.loading.set(false);
|
||||
if (isActive) {
|
||||
$('.admin-announcement').slideUp();
|
||||
} else {
|
||||
$('.admin-announcement').slideDown();
|
||||
try {
|
||||
await Announcements.updateAsync(announcements._id, {
|
||||
$set: { enabled: !isActive },
|
||||
});
|
||||
if (isActive) {
|
||||
$('.admin-announcement').slideUp();
|
||||
} else {
|
||||
$('.admin-announcement').slideDown();
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error?.reason || error?.message || 'Failed to update announcement setting');
|
||||
} finally {
|
||||
tpl.loading.set(false);
|
||||
}
|
||||
},
|
||||
'click button.js-announcement-save'() {
|
||||
async 'click button.js-announcement-save'(event) {
|
||||
event.preventDefault();
|
||||
const message = $('#admin-announcement').val().trim();
|
||||
Announcements.update(Announcements.findOne()._id, {
|
||||
$set: { body: message },
|
||||
});
|
||||
const announcement = Announcements.findOne();
|
||||
if (!announcement) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await Announcements.updateAsync(announcement._id, {
|
||||
$set: { body: message },
|
||||
});
|
||||
} catch (error) {
|
||||
alert(error?.reason || error?.message || 'Failed to save announcement');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -528,8 +528,8 @@ template(name="deleteDuplicateListsPopup")
|
||||
template(name="outgoingWebhooksPopup")
|
||||
each integrations
|
||||
form.integration-form
|
||||
a.flex
|
||||
i.fa(class="{{#unless enabled}}fa-check{{else}}fa-square-o{{/unless}}")
|
||||
a.flex.js-toggle-webhook-enabled
|
||||
.materialCheckBox(class="{{#unless enabled}}is-checked{{/unless}}")
|
||||
span {{_ 'disable-webhook'}}
|
||||
input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" value=title)
|
||||
input.js-outgoing-webhooks-url(type="text" name="url" value=url)
|
||||
@@ -640,11 +640,11 @@ template(name="exportBoardPopup")
|
||||
li
|
||||
a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}")
|
||||
i.fa.fa-upload
|
||||
| {{_ 'export-board-csv'}} ,
|
||||
| {{_ 'export-board-csv-comma'}}
|
||||
li
|
||||
a(href="{{exportScsvUrl}}", download="{{exportCsvFilename}}")
|
||||
i.fa.fa-upload
|
||||
| {{_ 'export-board-csv'}} ;
|
||||
| {{_ 'export-board-csv-semicolon'}}
|
||||
li
|
||||
a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}")
|
||||
i.fa.fa-upload
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Template } from 'meteor/templating';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import { InfiniteScrolling } from '/client/lib/infiniteScrolling';
|
||||
import AccessibilitySettings from '/models/accessibilitySettings';
|
||||
import Boards from '/models/boards';
|
||||
import Integrations from '/models/integrations';
|
||||
import Lists from '/models/lists';
|
||||
import { BOARD_COLORS } from '/models/metadata/colors';
|
||||
import { Filter } from '/client/lib/filter';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import {
|
||||
clearSidebarInstance,
|
||||
setSidebarInstance,
|
||||
} from '/client/features/sidebar/service';
|
||||
|
||||
Sidebar = null;
|
||||
export let Sidebar = null;
|
||||
|
||||
const defaultView = 'home';
|
||||
const MCB = '.materialCheckBox';
|
||||
@@ -25,6 +40,7 @@ Template.sidebar.onCreated(function() {
|
||||
this.infiniteScrolling = new InfiniteScrolling();
|
||||
this.activitiesInstance = null;
|
||||
Sidebar = this;
|
||||
setSidebarInstance(this);
|
||||
|
||||
// Subscribe to accessibility settings
|
||||
Meteor.subscribe('accessibilitySettings');
|
||||
@@ -106,6 +122,7 @@ Template.sidebar.onCreated(function() {
|
||||
});
|
||||
|
||||
Template.sidebar.onDestroyed(function() {
|
||||
clearSidebarInstance(this);
|
||||
Sidebar = null;
|
||||
});
|
||||
|
||||
@@ -153,7 +170,7 @@ Template.sidebar.events({
|
||||
tpl.setView();
|
||||
},
|
||||
'click .js-toggle-minicard-label-text'() {
|
||||
currentUser = ReactiveCache.getCurrentUser();
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) {
|
||||
Meteor.call('toggleMinicardLabelText');
|
||||
} else if (window.localStorage.getItem('hiddenMinicardLabelText')) {
|
||||
@@ -176,12 +193,12 @@ Template.sidebar.events({
|
||||
'click .js-show-week-of-year-toggle'() {
|
||||
ReactiveCache.getCurrentUser().toggleShowWeekOfYear();
|
||||
},
|
||||
'click .sidebar-accessibility'() {
|
||||
'click .sidebar-accessibility'(event, tpl) {
|
||||
FlowRouter.go('accessibility');
|
||||
Sidebar.toggle();
|
||||
tpl.toggle();
|
||||
},
|
||||
'click .js-close-sidebar'() {
|
||||
Sidebar.toggle()
|
||||
'click .js-close-sidebar'(event, tpl) {
|
||||
tpl.toggle();
|
||||
},
|
||||
'scroll .js-board-sidebar-content'(event, tpl) {
|
||||
tpl.infiniteScrolling.checkScrollPosition(event.currentTarget, () => {
|
||||
@@ -194,7 +211,7 @@ Blaze.registerHelper('Sidebar', () => Sidebar);
|
||||
|
||||
Template.homeSidebar.helpers({
|
||||
hiddenMinicardLabelText() {
|
||||
currentUser = ReactiveCache.getCurrentUser();
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).hiddenMinicardLabelText;
|
||||
} else if (window.localStorage.getItem('hiddenMinicardLabelText')) {
|
||||
@@ -237,7 +254,9 @@ Template.boardInfoOnMyBoardsPopup.helpers({
|
||||
EscapeActions.register(
|
||||
'sidebarView',
|
||||
() => {
|
||||
Sidebar.setView(defaultView);
|
||||
if (Sidebar) {
|
||||
Sidebar.setView(defaultView);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
return Sidebar && Sidebar.getView() !== defaultView;
|
||||
@@ -296,11 +315,15 @@ Template.boardMenuPopup.events({
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-custom-fields'() {
|
||||
Sidebar.setView('customFields');
|
||||
if (Sidebar) {
|
||||
Sidebar.setView('customFields');
|
||||
}
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-open-archives'() {
|
||||
Sidebar.setView('archives');
|
||||
if (Sidebar) {
|
||||
Sidebar.setView('archives');
|
||||
}
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-change-board-color': Popup.open('boardChangeColor'),
|
||||
@@ -544,10 +567,6 @@ Template.membersWidget.events({
|
||||
},
|
||||
});
|
||||
|
||||
Template.outgoingWebhooksPopup.onCreated(function() {
|
||||
this.disabled = new ReactiveVar(false);
|
||||
});
|
||||
|
||||
Template.outgoingWebhooksPopup.helpers({
|
||||
boardId() {
|
||||
return Session.get('currentBoard') || Integrations.Const.GLOBAL_WEBHOOK_ID;
|
||||
@@ -571,20 +590,23 @@ Template.outgoingWebhooksPopup.helpers({
|
||||
});
|
||||
|
||||
Template.outgoingWebhooksPopup.events({
|
||||
'click a.flex'(evt, tpl) {
|
||||
tpl.disabled.set(!tpl.disabled.get());
|
||||
$(evt.target).toggleClass(CKCLS, tpl.disabled.get());
|
||||
},
|
||||
submit(evt, tpl) {
|
||||
'click .js-toggle-webhook-enabled'(evt) {
|
||||
evt.preventDefault();
|
||||
const url = evt.target.url.value;
|
||||
$(evt.currentTarget).find(MCB).toggleClass(CKCLS);
|
||||
},
|
||||
async submit(evt) {
|
||||
evt.preventDefault();
|
||||
const url = evt.target.url.value.trim();
|
||||
const boardId = Session.get('currentBoard') || Integrations.Const.GLOBAL_WEBHOOK_ID;
|
||||
let id = null;
|
||||
let integration = null;
|
||||
const title = evt.target.title.value;
|
||||
const token = evt.target.token.value;
|
||||
const type = evt.target.type.value;
|
||||
const enabled = !tpl.disabled.get();
|
||||
const title = evt.target.title.value.trim();
|
||||
const token = evt.target.token.value.trim();
|
||||
const type = evt.target.type.value.trim();
|
||||
const enabled = !$(evt.target)
|
||||
.find('.js-toggle-webhook-enabled')
|
||||
.find(MCB)
|
||||
.hasClass(CKCLS);
|
||||
let remove = false;
|
||||
const values = {
|
||||
url,
|
||||
@@ -609,22 +631,27 @@ Template.outgoingWebhooksPopup.events({
|
||||
} else if (url) {
|
||||
integration = findIntegration({ url, token });
|
||||
}
|
||||
if (remove) {
|
||||
Integrations.remove(integration._id);
|
||||
} else if (integration && integration._id) {
|
||||
Integrations.update(integration._id, {
|
||||
$set: values,
|
||||
});
|
||||
} else if (url) {
|
||||
Integrations.insert({
|
||||
...values,
|
||||
userId: Meteor.userId(),
|
||||
enabled: true,
|
||||
boardId,
|
||||
activities: ['all'],
|
||||
});
|
||||
|
||||
try {
|
||||
if (remove && integration && integration._id) {
|
||||
await Integrations.removeAsync(integration._id);
|
||||
} else if (integration && integration._id) {
|
||||
await Integrations.updateAsync(integration._id, {
|
||||
$set: values,
|
||||
});
|
||||
} else if (url) {
|
||||
await Integrations.insertAsync({
|
||||
...values,
|
||||
userId: Meteor.userId(),
|
||||
enabled,
|
||||
boardId,
|
||||
activities: ['all'],
|
||||
});
|
||||
}
|
||||
Popup.back();
|
||||
} catch (error) {
|
||||
alert(error?.reason || error?.message || 'Failed to save webhook');
|
||||
}
|
||||
Popup.back();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -778,7 +805,7 @@ Template.labelsWidget.onRendered(draggableMembersLabelsWidgets);
|
||||
|
||||
Template.boardChangeColorPopup.helpers({
|
||||
backgroundColors() {
|
||||
return Boards.simpleSchema()._schema.color.allowedValues;
|
||||
return BOARD_COLORS;
|
||||
},
|
||||
isSelected() {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
@@ -1472,7 +1499,7 @@ Template.addMemberPopup.onCreated(function() {
|
||||
};
|
||||
|
||||
this.isValidEmail = function(email) {
|
||||
return SimpleSchema.RegEx.Email.test(email);
|
||||
return /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(email);
|
||||
};
|
||||
|
||||
this.performSearch = function(query) {
|
||||
|
||||
@@ -2,10 +2,13 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import Lists from '/models/lists';
|
||||
import Swimlanes from '/models/swimlanes';
|
||||
import Cards from '/models/cards';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
const ARCHIVE_PAGE_SIZE = 30;
|
||||
const ARCHIVE_SCROLL_THRESHOLD_PX = 120;
|
||||
|
||||
|
||||
function throttle(func, wait) {
|
||||
let timeoutId = null;
|
||||
let lastRan = 0;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import CustomFields from '/models/customFields';
|
||||
|
||||
Template.customFieldsSidebar.helpers({
|
||||
customFields() {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { Filter } from '/client/lib/filter';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { MultiSelection } from '/client/lib/multiSelection';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
// SubsManager removed for Meteor 3 migration
|
||||
|
||||
Template.filterSidebar.events({
|
||||
'submit .js-list-filter'(evt, tpl) {
|
||||
@@ -64,7 +68,7 @@ Template.filterSidebar.events({
|
||||
Filter.resetExceptions();
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
if (!currentBoardId) return;
|
||||
subManager.subscribe(
|
||||
Meteor.subscribe(
|
||||
'board',
|
||||
currentBoardId,
|
||||
Filter.archive.isSelected(),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Template.searchSidebar.onCreated(function () {
|
||||
this.term = new ReactiveVar('');
|
||||
});
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { SWIMLANE_COLORS } from '/models/metadata/colors';
|
||||
import Swimlanes from '/models/swimlanes';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
const { calculateIndexData } = Utils;
|
||||
|
||||
let swimlaneColors;
|
||||
Meteor.startup(() => {
|
||||
swimlaneColors = Swimlanes.simpleSchema()._schema.color.allowedValues;
|
||||
swimlaneColors = SWIMLANE_COLORS;
|
||||
});
|
||||
|
||||
function swimlaneHeaderCollapsed(check = undefined) {
|
||||
@@ -117,7 +120,7 @@ Template.swimlaneAddPopup.onCreated(function () {
|
||||
});
|
||||
|
||||
Template.swimlaneAddPopup.events({
|
||||
submit(event, tpl) {
|
||||
async submit(event, tpl) {
|
||||
event.preventDefault();
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
const nextSwimlane = currentBoard.nextSwimlane(tpl.currentSwimlane);
|
||||
@@ -133,7 +136,7 @@ Template.swimlaneAddPopup.events({
|
||||
: 'swimlane';
|
||||
|
||||
if (title) {
|
||||
Swimlanes.insert({
|
||||
await Swimlanes.insertAsync({
|
||||
title,
|
||||
boardId: Session.get('currentBoard'),
|
||||
sort: sortValue.base || 0,
|
||||
@@ -172,13 +175,16 @@ Template.setSwimlaneColorPopup.events({
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-palette-color'(event, tpl) {
|
||||
tpl.currentColor.set(Template.currentData().color);
|
||||
const paletteData = Blaze.getData(event.currentTarget);
|
||||
tpl.currentColor.set(paletteData?.color);
|
||||
},
|
||||
async 'click .js-submit'(event, tpl) {
|
||||
event.preventDefault();
|
||||
await tpl.currentSwimlane.setColor(tpl.currentColor.get());
|
||||
Popup.back();
|
||||
},
|
||||
async 'click .js-remove-color'(event, tpl) {
|
||||
event.preventDefault();
|
||||
await tpl.currentSwimlane.setColor(null);
|
||||
Popup.back();
|
||||
},
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { once } from '/imports/lib/collectionHelpers';
|
||||
import dragscroll from '@wekanteam/dragscroll';
|
||||
import Lists from '/models/lists';
|
||||
import { CSSEvents } from '/client/lib/cssEvents';
|
||||
import { Filter } from '/client/lib/filter';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
const { calculateIndex } = Utils;
|
||||
|
||||
function saveSorting(ui) {
|
||||
@@ -137,10 +142,23 @@ function saveSorting(ui) {
|
||||
|
||||
function currentListIsInThisSwimlane(swimlaneId) {
|
||||
const currentList = Utils.getCurrentList();
|
||||
return (
|
||||
currentList &&
|
||||
(currentList.swimlaneId === swimlaneId || currentList.swimlaneId === '')
|
||||
if (!currentList) return false;
|
||||
// Match the list's own swimlane, or shared/orphaned lists (empty/null
|
||||
// swimlaneId are visible in every swimlane as a fallback).
|
||||
if (currentList.swimlaneId === swimlaneId || !currentList.swimlaneId) {
|
||||
return true;
|
||||
}
|
||||
// Also match when the list has an orphaned swimlaneId (references a deleted
|
||||
// swimlane) and THIS is the first swimlane — orphaned lists are shown there.
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
if (!currentBoard) return false;
|
||||
const allSwimlanes = ReactiveCache.getSwimlanes(
|
||||
{ boardId: currentBoard._id, archived: false },
|
||||
{ sort: ['sort'] },
|
||||
);
|
||||
if (!allSwimlanes.length || allSwimlanes[0]._id !== swimlaneId) return false;
|
||||
const validIds = new Set(allSwimlanes.map(s => s._id));
|
||||
return !validIds.has(currentList.swimlaneId);
|
||||
}
|
||||
|
||||
function currentCardIsInThisList(listId, swimlaneId) {
|
||||
@@ -154,7 +172,10 @@ function currentCardIsInThisList(listId, swimlaneId) {
|
||||
return (
|
||||
currentCard &&
|
||||
currentCard.listId === listId &&
|
||||
currentCard.swimlaneId === swimlaneId
|
||||
// Match cards for this swimlane, AND orphaned/shared cards that have no
|
||||
// swimlaneId assigned (null/empty) — they are shown in every swimlane as
|
||||
// a fallback so no content is ever invisible.
|
||||
(currentCard.swimlaneId === swimlaneId || !currentCard.swimlaneId)
|
||||
);
|
||||
else if (
|
||||
//currentUser &&
|
||||
@@ -577,8 +598,38 @@ Template.swimlane.helpers({
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
},
|
||||
lists() {
|
||||
// Return per-swimlane lists for this swimlane
|
||||
return this.myLists();
|
||||
const swimlane = this;
|
||||
// myLists() already covers:
|
||||
// • lists owned by this swimlane (swimlaneId === this._id)
|
||||
// • shared / pre-migration lists (swimlaneId empty or null)
|
||||
const regularLists = swimlane.myLists();
|
||||
|
||||
// Additionally, detect lists whose swimlaneId references a swimlane that
|
||||
// no longer exists ("orphaned-swimlane" lists). These would be invisible
|
||||
// in every swimlane without this fallback. Show them in the FIRST swimlane
|
||||
// on the board so they are always accessible without a DB migration.
|
||||
const allSwimlanes = ReactiveCache.getSwimlanes(
|
||||
{ boardId: swimlane.boardId, archived: false },
|
||||
{ sort: ['sort'] },
|
||||
);
|
||||
// Only the first swimlane picks up orphaned-swimlane lists.
|
||||
if (!allSwimlanes.length || allSwimlanes[0]._id !== swimlane._id) {
|
||||
return regularLists;
|
||||
}
|
||||
const validIds = allSwimlanes.map(s => s._id);
|
||||
const orphaned = swimlane.orphanedSwimlaneLists(validIds);
|
||||
if (!orphaned.length) return regularLists;
|
||||
|
||||
// Merge, deduplicating by _id (regularLists may already contain some).
|
||||
const seen = new Set(regularLists.map(l => l._id));
|
||||
const combined = [...regularLists];
|
||||
for (const l of orphaned) {
|
||||
if (!seen.has(l._id)) {
|
||||
seen.add(l._id);
|
||||
combined.push(l);
|
||||
}
|
||||
}
|
||||
return combined;
|
||||
},
|
||||
collapseSwimlane() {
|
||||
return Utils.getSwimlaneCollapseState(this);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import Cards from '/models/cards';
|
||||
import Avatars from '/models/avatars';
|
||||
import Users from '/models/users';
|
||||
import Org from '/models/org';
|
||||
import Team from '/models/team';
|
||||
import Presences from '/models/presences';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Template.userAvatar.helpers({
|
||||
userData() {
|
||||
@@ -202,9 +200,9 @@ Template.changeAvatarPopup.events({
|
||||
'click .js-upload-avatar'(event, tpl) {
|
||||
tpl.$('.js-upload-avatar-input').click();
|
||||
},
|
||||
'change .js-upload-avatar-input'(event, tpl) {
|
||||
async 'change .js-upload-avatar-input'(event, tpl) {
|
||||
if (event.currentTarget.files && event.currentTarget.files[0]) {
|
||||
const uploader = Avatars.insert(
|
||||
const uploader = await Avatars.insertAsync(
|
||||
{
|
||||
file: event.currentTarget.files[0],
|
||||
chunkSize: 'dynamic',
|
||||
@@ -231,8 +229,8 @@ Template.changeAvatarPopup.events({
|
||||
event.stopPropagation();
|
||||
changeAvatarSetAvatar(tpl, '');
|
||||
},
|
||||
'click .js-delete-avatar': Popup.afterConfirm('deleteAvatar', function (event) {
|
||||
Avatars.remove(this._id);
|
||||
'click .js-delete-avatar': Popup.afterConfirm('deleteAvatar', async function (event) {
|
||||
await Avatars.removeAsync(this._id);
|
||||
Popup.back();
|
||||
event.stopPropagation();
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import AccountSettings from '/models/accountSettings';
|
||||
import Users from '/models/users';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Template.headerUserBar.events({
|
||||
'click .js-open-header-member-menu': Popup.open('memberMenu'),
|
||||
@@ -37,7 +40,7 @@ Template.memberMenuPopup.helpers({
|
||||
isSameDomainNameSettingValue(){
|
||||
const currSett = ReactiveCache.getCurrentSetting();
|
||||
if(currSett && currSett != undefined && currSett.disableRegistration && currSett.mailDomainName !== undefined && currSett.mailDomainName != ""){
|
||||
currentUser = ReactiveCache.getCurrentUser();
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) {
|
||||
let found = false;
|
||||
for(let i = 0; i < currentUser.emails.length; i++) {
|
||||
@@ -57,7 +60,8 @@ Template.memberMenuPopup.helpers({
|
||||
isNotOAuth2AuthenticationMethod(){
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) {
|
||||
return currentUser.authenticationMethod.toLowerCase() != 'oauth2';
|
||||
const method = (currentUser.authenticationMethod || '').toLowerCase();
|
||||
return method !== 'oauth2';
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
@@ -136,7 +140,7 @@ Template.invitePeoplePopup.events({
|
||||
});
|
||||
const validEmails = [];
|
||||
emails.forEach(email => {
|
||||
if (email && SimpleSchema.RegEx.Email.test(email.trim())) {
|
||||
if (email && /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(email.trim())) {
|
||||
validEmails.push(email.trim());
|
||||
}
|
||||
});
|
||||
@@ -361,7 +365,7 @@ Template.changeSettingsPopup.helpers({
|
||||
});
|
||||
},
|
||||
startDayOfWeek() {
|
||||
currentUser = ReactiveCache.getCurrentUser();
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) {
|
||||
return currentUser.getStartDayOfWeek();
|
||||
} else {
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
fromNow,
|
||||
calendar,
|
||||
} from '/imports/lib/dateUtils';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
|
||||
Blaze.registerHelper('currentBoard', () => {
|
||||
const ret = Utils.getCurrentBoard();
|
||||
@@ -139,3 +140,21 @@ Blaze.registerHelper('$nin', (...args) => {
|
||||
return args.every((arg) => value !== arg);
|
||||
});
|
||||
Blaze.registerHelper('$', () => ({ Session, Meteor }));
|
||||
|
||||
// Expose module-scoped objects to Blaze templates (jade references)
|
||||
import { Filter } from '/client/lib/filter';
|
||||
import { MultiSelection } from '/client/lib/multiSelection';
|
||||
import { BoardMultiSelection } from '/client/lib/boardMultiSelection';
|
||||
|
||||
let _Sidebar;
|
||||
function getSidebar() {
|
||||
if (!_Sidebar) {
|
||||
_Sidebar = require('/client/features/sidebar/service').getSidebarInstance;
|
||||
}
|
||||
return _Sidebar();
|
||||
}
|
||||
|
||||
Blaze.registerHelper('Filter', () => Filter);
|
||||
Blaze.registerHelper('MultiSelection', () => MultiSelection);
|
||||
Blaze.registerHelper('Sidebar', () => getSidebar());
|
||||
Blaze.registerHelper('BoardMultiSelection', () => BoardMultiSelection);
|
||||
@@ -0,0 +1,9 @@
|
||||
import '/client/components/activities/activities.jade';
|
||||
import '/client/components/activities/comments.jade';
|
||||
|
||||
import '/client/components/activities/activities.js';
|
||||
import '/client/components/activities/comments.js';
|
||||
|
||||
import '/client/components/activities/activities.css';
|
||||
import '/client/components/activities/comments.css';
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import '/client/components/boardConversionProgress.jade';
|
||||
import '/client/components/boardConversionProgress.js';
|
||||
import '/client/components/boardConversionProgress.css';
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import '/client/components/boards/boardArchive.jade';
|
||||
import '/client/components/boards/boardBody.jade';
|
||||
import '/client/components/boards/boardHeader.jade';
|
||||
import '/client/components/boards/boardsList.jade';
|
||||
import '/client/components/boards/miniboard.jade';
|
||||
import '/client/components/boards/originalPositionsView.jade';
|
||||
|
||||
import '/client/components/boards/boardArchive.js';
|
||||
import '/client/components/boards/boardBody.js';
|
||||
import '/client/components/boards/boardHeader.js';
|
||||
import '/client/components/boards/boardsList.js';
|
||||
import '/client/components/boards/originalPositionsView.js';
|
||||
|
||||
import '/client/components/boards/boardBody.css';
|
||||
import '/client/components/boards/boardColors.css';
|
||||
import '/client/components/boards/boardHeader.css';
|
||||
import '/client/components/boards/boardsList.css';
|
||||
import '/client/components/boards/calendarView.css';
|
||||
import '/client/components/boards/originalPositionsView.css';
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import '/client/components/cards/attachments.jade';
|
||||
import '/client/components/cards/cardCustomFields.jade';
|
||||
import '/client/components/cards/cardDate.jade';
|
||||
import '/client/components/cards/cardDescription.jade';
|
||||
import '/client/components/cards/cardDetails.jade';
|
||||
import '/client/components/cards/cardTime.jade';
|
||||
import '/client/components/cards/checklists.jade';
|
||||
import '/client/components/cards/inlinedCardDescription.jade';
|
||||
import '/client/components/cards/labels.jade';
|
||||
import '/client/components/cards/minicard.jade';
|
||||
import '/client/components/cards/resultCard.jade';
|
||||
import '/client/components/cards/subtasks.jade';
|
||||
|
||||
import '/client/components/cards/attachments.js';
|
||||
import '/client/components/cards/cardCustomFields.js';
|
||||
import '/client/components/cards/cardDate.js';
|
||||
import '/client/components/cards/cardDescription.js';
|
||||
import '/client/components/cards/cardDetails.js';
|
||||
import '/client/components/cards/cardTime.js';
|
||||
import '/client/components/cards/checklists.js';
|
||||
import '/client/components/cards/labels.js';
|
||||
import '/client/components/cards/minicard.js';
|
||||
import '/client/components/cards/resultCard.js';
|
||||
import '/client/components/cards/subtasks.js';
|
||||
|
||||
import '/client/components/cards/attachments.css';
|
||||
import '/client/components/cards/cardDate.css';
|
||||
import '/client/components/cards/cardDescription.css';
|
||||
import '/client/components/cards/cardDetails.css';
|
||||
import '/client/components/cards/cardTime.css';
|
||||
import '/client/components/cards/checklists.css';
|
||||
import '/client/components/cards/labels.css';
|
||||
import '/client/components/cards/minicard.css';
|
||||
import '/client/components/cards/resultCard.css';
|
||||
import '/client/components/cards/subtasks.css';
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import '/client/components/common/originalPosition.jade';
|
||||
import '/client/components/common/originalPosition.js';
|
||||
import '/client/components/common/originalPosition.css';
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import '/client/components/forms/datepicker.jade';
|
||||
import '/client/components/forms/inlinedform.jade';
|
||||
|
||||
import '/client/lib/inlinedform';
|
||||
import '/client/components/forms/datepicker.js';
|
||||
|
||||
import '/client/components/forms/datepicker.css';
|
||||
import '/client/components/forms/forms.css';
|
||||
@@ -0,0 +1,9 @@
|
||||
import '/client/components/gantt/gantt.jade';
|
||||
import '/client/components/gantt/ganttCard.jade';
|
||||
|
||||
import '/client/components/gantt/gantt.js';
|
||||
import '/client/components/gantt/ganttCard.js';
|
||||
|
||||
import '/client/components/gantt/gantt.css';
|
||||
import '/client/components/gantt/ganttCard.css';
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import '/client/components/import/import.jade';
|
||||
|
||||
import '/client/components/import/import.js';
|
||||
import '/client/components/import/csvMembersMapper.js';
|
||||
import '/client/components/import/trelloMembersMapper.js';
|
||||
import '/client/components/import/wekanMembersMapper.js';
|
||||
|
||||
import '/client/components/import/import.css';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import '/client/components/lib/basicTabs.jade';
|
||||
import '/client/components/lib/basicTabs.js';
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import '/client/components/lists/list.jade';
|
||||
import '/client/components/lists/listBody.jade';
|
||||
import '/client/components/lists/listHeader.jade';
|
||||
import '/client/components/lists/minilist.jade';
|
||||
|
||||
import '/client/components/lists/list.js';
|
||||
import '/client/components/lists/listBody.js';
|
||||
import '/client/components/lists/listHeader.js';
|
||||
|
||||
import '/client/components/lists/list.css';
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import '/client/components/main/accessibility.jade';
|
||||
import '/client/components/main/bookmarks.jade';
|
||||
import '/client/components/main/brokenCards.jade';
|
||||
import '/client/components/main/dueCards.jade';
|
||||
import '/client/components/main/editor.jade';
|
||||
import '/client/components/main/globalSearch.jade';
|
||||
import '/client/components/main/header.jade';
|
||||
import '/client/components/main/keyboardShortcuts.jade';
|
||||
import '/client/components/main/layouts.jade';
|
||||
import '/client/components/main/myCards.jade';
|
||||
import '/client/components/main/popup.tpl.jade';
|
||||
import '/client/components/main/spinner.jade';
|
||||
import '/client/components/main/spinner_bounce.jade';
|
||||
import '/client/components/main/spinner_cube.jade';
|
||||
import '/client/components/main/spinner_cube_grid.jade';
|
||||
import '/client/components/main/spinner_dot.jade';
|
||||
import '/client/components/main/spinner_double_bounce.jade';
|
||||
import '/client/components/main/spinner_rotateplane.jade';
|
||||
import '/client/components/main/spinner_scaleout.jade';
|
||||
import '/client/components/main/spinner_wave.jade';
|
||||
import '/client/components/main/support.jade';
|
||||
|
||||
import '/client/lib/keyboard';
|
||||
import '/client/lib/popup';
|
||||
import '/client/components/main/accessibility.js';
|
||||
import '/client/components/main/bookmarks.js';
|
||||
import '/client/components/main/brokenCards.js';
|
||||
import '/client/components/main/dueCards.js';
|
||||
import '/client/components/main/editor.js';
|
||||
import '/client/components/main/globalSearch.js';
|
||||
import '/client/components/main/header.js';
|
||||
import '/client/components/main/layouts.js';
|
||||
import '/client/components/main/myCards.js';
|
||||
import '/client/components/main/popup.js';
|
||||
import '/client/components/main/spinner.js';
|
||||
import '/client/components/main/support.js';
|
||||
|
||||
import '/client/components/main/accessibility.css';
|
||||
import '/client/components/main/brokenCards.css';
|
||||
import '/client/components/main/dueCards.css';
|
||||
import '/client/components/main/editor.css';
|
||||
import '/client/components/main/fonts.css';
|
||||
import '/client/components/main/globalSearch.css';
|
||||
import '/client/components/main/header.css';
|
||||
import '/client/components/main/keyboardShortcuts.css';
|
||||
import '/client/components/main/layouts.css';
|
||||
import '/client/components/main/myCards.css';
|
||||
import '/client/components/main/popup.css';
|
||||
import '/client/components/main/spinner_bounce.css';
|
||||
import '/client/components/main/spinner_cube.css';
|
||||
import '/client/components/main/spinner_cube_grid.css';
|
||||
import '/client/components/main/spinner_dot.css';
|
||||
import '/client/components/main/spinner_double_bounce.css';
|
||||
import '/client/components/main/spinner_rotateplane.css';
|
||||
import '/client/components/main/spinner_scaleout.css';
|
||||
import '/client/components/main/spinner_wave.css';
|
||||
@@ -0,0 +1,13 @@
|
||||
import '/client/components/notifications/notification.jade';
|
||||
import '/client/components/notifications/notificationIcon.jade';
|
||||
import '/client/components/notifications/notifications.jade';
|
||||
import '/client/components/notifications/notificationsDrawer.jade';
|
||||
|
||||
import '/client/components/notifications/notification.js';
|
||||
import '/client/components/notifications/notifications.js';
|
||||
import '/client/components/notifications/notificationsDrawer.js';
|
||||
|
||||
import '/client/components/notifications/notification.css';
|
||||
import '/client/components/notifications/notifications.css';
|
||||
import '/client/components/notifications/notificationsDrawer.css';
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import '/client/components/rules/ruleDetails.jade';
|
||||
import '/client/components/rules/rulesActions.jade';
|
||||
import '/client/components/rules/rulesList.jade';
|
||||
import '/client/components/rules/rulesMain.jade';
|
||||
import '/client/components/rules/rulesTriggers.jade';
|
||||
import '/client/components/rules/actions/boardActions.jade';
|
||||
import '/client/components/rules/actions/cardActions.jade';
|
||||
import '/client/components/rules/actions/checklistActions.jade';
|
||||
import '/client/components/rules/actions/mailActions.jade';
|
||||
import '/client/components/rules/triggers/boardTriggers.jade';
|
||||
import '/client/components/rules/triggers/cardTriggers.jade';
|
||||
import '/client/components/rules/triggers/checklistTriggers.jade';
|
||||
|
||||
import '/client/components/rules/ruleDetails.js';
|
||||
import '/client/components/rules/rulesActions.js';
|
||||
import '/client/components/rules/rulesList.js';
|
||||
import '/client/components/rules/rulesMain.js';
|
||||
import '/client/components/rules/rulesTriggers.js';
|
||||
import '/client/components/rules/actions/boardActions.js';
|
||||
import '/client/components/rules/actions/cardActions.js';
|
||||
import '/client/components/rules/actions/checklistActions.js';
|
||||
import '/client/components/rules/actions/mailActions.js';
|
||||
import '/client/components/rules/triggers/boardTriggers.js';
|
||||
import '/client/components/rules/triggers/cardTriggers.js';
|
||||
import '/client/components/rules/triggers/checklistTriggers.js';
|
||||
|
||||
import '/client/components/rules/rules.css';
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import '/client/components/settings/adminReports.jade';
|
||||
import '/client/components/settings/attachments.jade';
|
||||
import '/client/components/settings/attachmentSettings.jade';
|
||||
import '/client/components/settings/connectionMethod.jade';
|
||||
import '/client/components/settings/cronSettings.jade';
|
||||
import '/client/components/settings/informationBody.jade';
|
||||
import '/client/components/settings/invitationCode.jade';
|
||||
import '/client/components/settings/migrationProgress.jade';
|
||||
import '/client/components/settings/peopleBody.jade';
|
||||
import '/client/components/settings/settingBody.jade';
|
||||
import '/client/components/settings/settingHeader.jade';
|
||||
import '/client/components/settings/translationBody.jade';
|
||||
|
||||
import '/client/components/settings/adminReports.js';
|
||||
import '/client/components/settings/attachments.js';
|
||||
import '/client/components/settings/connectionMethod.js';
|
||||
import '/client/components/settings/informationBody.js';
|
||||
import '/client/components/settings/invitationCode.js';
|
||||
import '/client/components/settings/lockedUsersBody.js';
|
||||
import '/client/components/settings/migrationProgress.js';
|
||||
import '/client/components/settings/peopleBody.js';
|
||||
import '/client/components/settings/settingBody.js';
|
||||
import '/client/components/settings/settingHeader.js';
|
||||
import '/client/components/settings/translationBody.js';
|
||||
|
||||
import '/client/components/settings/attachments.css';
|
||||
import '/client/components/settings/cronSettings.css';
|
||||
import '/client/components/settings/lockedUsersBody.css';
|
||||
import '/client/components/settings/migrationProgress.css';
|
||||
import '/client/components/settings/peopleBody.css';
|
||||
import '/client/components/settings/settingBody.css';
|
||||
import '/client/components/settings/settingHeader.css';
|
||||
import '/client/components/settings/translationBody.css';
|
||||
@@ -0,0 +1,15 @@
|
||||
import '/client/components/sidebar/sidebar.jade';
|
||||
import '/client/components/sidebar/sidebarArchives.jade';
|
||||
import '/client/components/sidebar/sidebarCustomFields.jade';
|
||||
import '/client/components/sidebar/sidebarFilters.jade';
|
||||
import '/client/components/sidebar/sidebarSearches.jade';
|
||||
|
||||
import '/client/components/sidebar/sidebar.js';
|
||||
import '/client/components/sidebar/sidebarArchives.js';
|
||||
import '/client/components/sidebar/sidebarCustomFields.js';
|
||||
import '/client/components/sidebar/sidebarFilters.js';
|
||||
import '/client/components/sidebar/sidebarSearches.js';
|
||||
|
||||
import '/client/components/sidebar/sidebar.css';
|
||||
import '/client/components/sidebar/sidebarSearches.css';
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
let sidebarInstance = null;
|
||||
|
||||
export function setSidebarInstance(instance) {
|
||||
sidebarInstance = instance || null;
|
||||
}
|
||||
|
||||
export function getSidebarInstance() {
|
||||
return sidebarInstance;
|
||||
}
|
||||
|
||||
export function clearSidebarInstance(instance) {
|
||||
if (!instance || sidebarInstance === instance) {
|
||||
sidebarInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import '/client/components/swimlanes/swimlaneHeader.jade';
|
||||
import '/client/components/swimlanes/swimlanes.jade';
|
||||
import '/client/components/swimlanes/miniswimlane.jade';
|
||||
|
||||
import '/client/components/swimlanes/swimlaneHeader.js';
|
||||
import '/client/components/swimlanes/swimlanes.js';
|
||||
|
||||
import '/client/components/swimlanes/swimlanes.css';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import '/client/components/unicode-icons.js';
|
||||
import '/client/components/unicode-icons.css';
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import '/client/components/users/passwordInput.jade';
|
||||
import '/client/components/users/userAvatar.jade';
|
||||
import '/client/components/users/userHeader.jade';
|
||||
|
||||
import '/client/components/users/passwordInput.js';
|
||||
import '/client/components/users/userAvatar.js';
|
||||
import '/client/components/users/userHeader.js';
|
||||
|
||||
import '/client/components/users/userAvatar.css';
|
||||
import '/client/components/users/userForm.css';
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// ============================================================================
|
||||
// WeKan Client — All Imports
|
||||
// Loaded by client/main.js after bootstrap (collectionHelpers) completes.
|
||||
// ============================================================================
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 1. Shared imports (i18n, utilities, reactive cache)
|
||||
// ----------------------------------------------------------------------------
|
||||
import '/imports/i18n/index';
|
||||
import '/imports/i18n/accounts';
|
||||
import '/imports/i18n/blaze';
|
||||
import '/imports/i18n/languages';
|
||||
import '/imports/i18n/moment';
|
||||
import '/imports/i18n/tap';
|
||||
import '/imports/lib/customHeadDefaults';
|
||||
import '/imports/lib/dateUtils';
|
||||
import '/imports/lib/secureDOMPurify';
|
||||
import '/models/attachmentMigrationStatus';
|
||||
import '/imports/cronMigrationClient';
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 3. Shared models/collections
|
||||
// ----------------------------------------------------------------------------
|
||||
import '/imports/startup/shared-models';
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 4. Shared runtime services
|
||||
// ----------------------------------------------------------------------------
|
||||
import '/imports/reactiveCache';
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 5. Config (shared)
|
||||
// ----------------------------------------------------------------------------
|
||||
import '/config/accounts';
|
||||
import '/config/const';
|
||||
import '/config/models';
|
||||
import '/config/query-classes';
|
||||
import '/config/router';
|
||||
import '/config/search-const';
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 6. Client library files
|
||||
// ----------------------------------------------------------------------------
|
||||
import '/client/lib/accessibility';
|
||||
import '/client/lib/attachmentMigrationManager';
|
||||
import '/client/lib/autofocus';
|
||||
import '/client/lib/boardConverter';
|
||||
import '/client/lib/boardMultiSelection';
|
||||
import '/client/lib/cardSearch';
|
||||
import '/client/lib/cssEvents';
|
||||
import '/client/lib/currentCard';
|
||||
import '/client/lib/customFields';
|
||||
import '/client/lib/datepicker';
|
||||
import '/client/lib/dialogWithBoardSwimlaneList';
|
||||
import '/client/lib/dialogWithBoardSwimlaneListCard';
|
||||
import '/client/lib/dropImage';
|
||||
import '/client/lib/escapeActions';
|
||||
import '/client/lib/exportHTML';
|
||||
import '/client/lib/filter';
|
||||
import '/client/lib/fixDuplicateLists';
|
||||
import '/client/lib/i18n';
|
||||
import '/client/lib/infiniteScrolling';
|
||||
import '/client/lib/jquery-ui';
|
||||
import '/client/lib/localStorageValidator';
|
||||
import '/client/lib/modal';
|
||||
import '/client/lib/multiSelection';
|
||||
import '/client/lib/originalPositionHelpers';
|
||||
import '/client/lib/pasteImage';
|
||||
import '/client/lib/secureDOMPurify';
|
||||
import '/client/lib/spinner';
|
||||
import '/client/lib/textComplete';
|
||||
import '/client/lib/unsavedEdits';
|
||||
import '/client/lib/uploadProgressManager';
|
||||
import '/client/lib/utils';
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 7. Client config
|
||||
// ----------------------------------------------------------------------------
|
||||
import '/client/config/blazeHelpers';
|
||||
import '/client/config/gecko-fix';
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 8. Client features
|
||||
// Each feature entry owns its template/logic/style ordering.
|
||||
// ----------------------------------------------------------------------------
|
||||
import '/client/features/forms';
|
||||
import '/client/features/main';
|
||||
import '/client/features/settings';
|
||||
import '/client/features/sidebar';
|
||||
import '/client/features/users';
|
||||
import '/client/features/activities';
|
||||
import '/client/features/boardConversion';
|
||||
import '/client/features/gantt';
|
||||
import '/client/features/boards';
|
||||
import '/client/features/cards';
|
||||
import '/client/features/common';
|
||||
import '/client/features/importing';
|
||||
import '/client/features/libComponents';
|
||||
import '/client/features/lists';
|
||||
import '/client/features/notifications';
|
||||
import '/client/features/rules';
|
||||
import '/client/features/swimlanes';
|
||||
import '/client/features/unicodeIcons';
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// 8. Startup (must load last — registers service worker, sets up subscriptions)
|
||||
// ----------------------------------------------------------------------------
|
||||
import '/client/00-startup';
|
||||
@@ -7,7 +7,7 @@
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { AttachmentMigrationStatus } from '/imports/attachmentMigrationClient';
|
||||
import AttachmentMigrationStatus from '/models/attachmentMigrationStatus';
|
||||
|
||||
// Reactive variables for attachment migration progress
|
||||
export const attachmentMigrationProgress = new ReactiveVar(0);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
|
||||
BoardMultiSelection = {
|
||||
export const BoardMultiSelection = {
|
||||
_selectedBoards: new ReactiveVar([]),
|
||||
|
||||
_isActive: new ReactiveVar(false),
|
||||
|
||||
+40
-98
@@ -32,10 +32,7 @@ export class CardSearchPaged {
|
||||
const that = this;
|
||||
this.subscriptionCallbacks = {
|
||||
onReady() {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Subscription ready, getting results...');
|
||||
console.log('Subscription ready - sessionId:', that.sessionId);
|
||||
}
|
||||
const cardsInCollection = Cards.find().count();
|
||||
|
||||
// Wait for session data to be available (with timeout)
|
||||
let waitCount = 0;
|
||||
@@ -44,21 +41,12 @@ export class CardSearchPaged {
|
||||
const waitForSessionData = () => {
|
||||
waitCount++;
|
||||
const sessionData = that.getSessionData();
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('waitForSessionData - attempt', waitCount, 'session data:', sessionData);
|
||||
}
|
||||
|
||||
if (sessionData) {
|
||||
const results = that.getResults();
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Search results count:', results ? results.length : 0);
|
||||
}
|
||||
|
||||
// If no results and this is a due cards search, try to retry
|
||||
if ((!results || results.length === 0) && that.searchRetryCount !== undefined && that.searchRetryCount < that.maxRetries) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('No results found, retrying search...');
|
||||
}
|
||||
that.searchRetryCount++;
|
||||
Meteor.setTimeout(() => {
|
||||
if (that.performSearch) {
|
||||
@@ -68,47 +56,37 @@ export class CardSearchPaged {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the results in the ReactiveVar so they display in the template
|
||||
that.results.set(results);
|
||||
that.resultsHeading.set(that.getResultsHeading());
|
||||
that.searching.set(false);
|
||||
that.hasResults.set(true);
|
||||
that.serverError.set(false);
|
||||
} else if (waitCount < maxWaitCount) {
|
||||
// Session data not available yet, wait a bit more
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Session data not available yet, waiting... (attempt', waitCount, 'of', maxWaitCount, ')');
|
||||
}
|
||||
Meteor.setTimeout(waitForSessionData, 200);
|
||||
} else {
|
||||
// Timeout reached, try fallback search
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Timeout reached waiting for session data, trying fallback search');
|
||||
}
|
||||
const results = that.getResults();
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Fallback search results count:', results ? results.length : 0);
|
||||
}
|
||||
} else if (waitCount < maxWaitCount) {
|
||||
// Session data not available yet, wait a bit more
|
||||
Meteor.setTimeout(waitForSessionData, 200);
|
||||
} else {
|
||||
// Timeout reached, try fallback search
|
||||
const results = that.getResults();
|
||||
|
||||
if (results && results.length > 0) {
|
||||
that.searching.set(false);
|
||||
that.hasResults.set(true);
|
||||
that.serverError.set(false);
|
||||
} else {
|
||||
that.searching.set(false);
|
||||
that.hasResults.set(false);
|
||||
that.serverError.set(true);
|
||||
}
|
||||
}
|
||||
if (results && results.length > 0) {
|
||||
that.results.set(results);
|
||||
that.resultsHeading.set(that.getResultsHeading());
|
||||
that.searching.set(false);
|
||||
that.hasResults.set(true);
|
||||
that.serverError.set(false);
|
||||
} else {
|
||||
that.searching.set(false);
|
||||
that.hasResults.set(false);
|
||||
that.serverError.set(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Start waiting for session data
|
||||
Meteor.setTimeout(waitForSessionData, 100);
|
||||
},
|
||||
onError(error) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Subscription error:', error);
|
||||
console.log('Error.reason:', error.reason);
|
||||
console.log('Error.message:', error.message);
|
||||
console.log('Error.stack:', error.stack);
|
||||
}
|
||||
that.searching.set(false);
|
||||
that.hasResults.set(false);
|
||||
that.serverError.set(true);
|
||||
@@ -131,56 +109,41 @@ export class CardSearchPaged {
|
||||
|
||||
getSessionData(sessionId) {
|
||||
const sessionIdToUse = sessionId || SessionData.getSessionId();
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('getSessionData - looking for sessionId:', sessionIdToUse);
|
||||
}
|
||||
|
||||
// Try using the raw SessionData collection instead of ReactiveCache
|
||||
// Use SessionData.findOne() directly - it's synchronous on the client
|
||||
const sessionData = SessionData.findOne({
|
||||
userId: Meteor.userId(),
|
||||
sessionId: sessionIdToUse,
|
||||
});
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('getSessionData - found session data (raw):', sessionData);
|
||||
}
|
||||
|
||||
// Also try ReactiveCache for comparison
|
||||
const reactiveSessionData = ReactiveCache.getSessionData({
|
||||
sessionId: sessionIdToUse,
|
||||
});
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('getSessionData - found session data (reactive):', reactiveSessionData);
|
||||
}
|
||||
|
||||
return sessionData || reactiveSessionData;
|
||||
return sessionData;
|
||||
}
|
||||
|
||||
getResults() {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('getting results');
|
||||
this.sessionData = this.getSessionData();
|
||||
// eslint-disable-next-line no-console
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('getResults - sessionId:', this.sessionId);
|
||||
console.log('getResults - session data:', this.sessionData);
|
||||
}
|
||||
const cards = [];
|
||||
|
||||
if (this.sessionData && this.sessionData.cards) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('getResults - cards array length:', this.sessionData.cards.length);
|
||||
}
|
||||
this.sessionData.cards.forEach(cardId => {
|
||||
const card = ReactiveCache.getCard(cardId);
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('getResults - card:', cardId, card);
|
||||
if (card && card._id) {
|
||||
cards.push(card);
|
||||
}
|
||||
cards.push(card);
|
||||
});
|
||||
|
||||
// Fallback: if no cards found, try fetching directly from Cards collection
|
||||
if (cards.length === 0 && this.sessionData.cards && this.sessionData.cards.length > 0) {
|
||||
if (directCards && directCards.length > 0) {
|
||||
directCards.forEach(card => {
|
||||
if (card && card._id) {
|
||||
cards.push(card);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.queryErrors = this.sessionData.errors || [];
|
||||
} else {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('getResults - no sessionData or no cards array, trying direct card search');
|
||||
}
|
||||
// Fallback: try to get cards directly from the client-side collection
|
||||
// Use a more efficient query with limit and sort
|
||||
const selector = {
|
||||
@@ -192,9 +155,6 @@ export class CardSearchPaged {
|
||||
limit: 100 // Limit to 100 cards for performance
|
||||
};
|
||||
const allCards = Cards.find(selector, options).fetch();
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('getResults - direct card search found:', allCards ? allCards.length : 0, 'cards');
|
||||
}
|
||||
|
||||
if (allCards && allCards.length > 0) {
|
||||
allCards.forEach(card => {
|
||||
@@ -212,11 +172,6 @@ export class CardSearchPaged {
|
||||
// return null;
|
||||
}
|
||||
this.debug.set(new QueryDebug(this.sessionData ? this.sessionData.debug : null));
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('debug:', this.debug.get().get());
|
||||
console.log('debug.show():', this.debug.get().show());
|
||||
console.log('debug.showSelector():', this.debug.get().showSelector());
|
||||
}
|
||||
|
||||
if (cards) {
|
||||
if (this.sessionData) {
|
||||
@@ -254,15 +209,7 @@ export class CardSearchPaged {
|
||||
}
|
||||
|
||||
getSubscription(queryParams) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Subscribing to globalSearch with:', {
|
||||
sessionId: this.sessionId,
|
||||
params: queryParams.params,
|
||||
text: queryParams.text
|
||||
});
|
||||
}
|
||||
|
||||
// Subscribe to both globalSearch and sessionData
|
||||
// Subscribe to globalSearch which includes sessionData as the 11th cursor
|
||||
const globalSearchHandle = Meteor.subscribe(
|
||||
'globalSearch',
|
||||
this.sessionId,
|
||||
@@ -271,11 +218,6 @@ export class CardSearchPaged {
|
||||
this.subscriptionCallbacks,
|
||||
);
|
||||
|
||||
const sessionDataHandle = Meteor.subscribe('sessionData', this.sessionId);
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.log('Subscribed to sessionData with sessionId:', this.sessionId);
|
||||
}
|
||||
|
||||
return globalSearchHandle;
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 896 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user