mirror of
https://github.com/wekan/wekan.git
synced 2026-11-05 01:20:07 -05:00
Compare commits
No files matched your search
@@ -24,7 +24,7 @@ updates:
|
||||
# * The only upgrades Dependabot/npm offer are build-breaking: a
|
||||
# @meteorjs/rspack downgrade, or @rspack/cli 2.0 / Rspack 2.0, which
|
||||
# needs Meteor 3.6 (see https://github.com/meteor/meteor/pull/14360).
|
||||
# WeKan currently runs METEOR@3.5-rc.1.
|
||||
# WeKan currently runs METEOR@3.5-rc.2.
|
||||
#
|
||||
# So pin them here until Meteor 3.6 + Rspack 2.0 land, then revisit.
|
||||
- dependency-name: "@meteorjs/rspack"
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -48,14 +48,14 @@ jobs:
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
# Build and push Docker image with Buildx (don't push on PR)
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
@@ -27,14 +27,14 @@ jobs:
|
||||
cache: 'npm'
|
||||
|
||||
- name: Cache Meteor installation
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.meteor
|
||||
key: meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.meteor/packages') }}
|
||||
restore-keys: meteor-
|
||||
|
||||
- name: Cache Meteor build (.meteor/local)
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: .meteor/local
|
||||
key: meteor-local-unit-${{ hashFiles('.meteor/versions') }}
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
run: meteor test --once --driver-package meteortesting:mocha
|
||||
|
||||
e2e-tests:
|
||||
name: Playwright E2E (${{ matrix.project }})
|
||||
name: Playwright E2E (${{ matrix.project }} ${{ matrix.shard }}/2)
|
||||
runs-on: ubuntu-24.04
|
||||
needs: unit-tests
|
||||
strategy:
|
||||
@@ -71,6 +71,12 @@ jobs:
|
||||
# Playwright bundles Chromium, Firefox and WebKit builds that all run
|
||||
# headless on the Linux runner (installed per-job below with --with-deps).
|
||||
project: [chromium, firefox, webkit]
|
||||
# Split each browser's ~245 specs across 2 parallel shards (6 jobs total)
|
||||
# to cut wall-clock time. Each shard job starts its own WeKan + Mongo, so
|
||||
# there is no shared-server contention between shards. Raise the shard
|
||||
# count (here and in the --shard flag below) once the baseline is green
|
||||
# and/or once E2E runs against a prebuilt bundle (cheaper per-job startup).
|
||||
shard: [1, 2]
|
||||
|
||||
services:
|
||||
mongodb:
|
||||
@@ -92,14 +98,14 @@ jobs:
|
||||
cache: 'npm'
|
||||
|
||||
- name: Cache Meteor installation
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.meteor
|
||||
key: meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.meteor/packages') }}
|
||||
restore-keys: meteor-
|
||||
|
||||
- name: Cache Meteor build (.meteor/local)
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: .meteor/local
|
||||
key: meteor-local-e2e-${{ hashFiles('.meteor/versions') }}
|
||||
@@ -123,34 +129,54 @@ jobs:
|
||||
run: npx playwright install --with-deps ${{ matrix.project }}
|
||||
working-directory: tests/playwright
|
||||
|
||||
- name: Start WeKan in background
|
||||
- name: Build and start WeKan (production bundle)
|
||||
run: |
|
||||
# Run E2E against a PRODUCTION build instead of `meteor run` (dev mode).
|
||||
# In dev the client JS is served by the rspack dev server, which did not
|
||||
# serve the bundle reliably in headless CI: the browser received HTML for
|
||||
# the bundle ("Unexpected token '<'"), Meteor never initialised, and every
|
||||
# spec timed out. A production build bakes the client JS into the bundle,
|
||||
# served statically by the Meteor server at :3000.
|
||||
meteor build ./output --directory
|
||||
( cd output/bundle/programs/server && npm install )
|
||||
# WeKan's server/00checkStartup.js refuses to start unless WRITABLE_PATH
|
||||
# (used for attachment/file storage) is set and writable.
|
||||
mkdir -p "$PWD/wekan-writable"
|
||||
# Match the env the dev runner (rebuild-wekan.sh) and the specs expect:
|
||||
# WITH_API enables the REST API (specs 17/23/24/28), comment-editor
|
||||
# flag matches dev.
|
||||
MONGO_URL=mongodb://localhost:27017/wekan_test \
|
||||
ROOT_URL=http://localhost:3000 \
|
||||
PORT=3000 \
|
||||
meteor run --settings settings.json > wekan-run.log 2>&1 &
|
||||
# The first Meteor build is slow, so wait up to 20 minutes for the
|
||||
# server to start responding before giving up.
|
||||
for i in $(seq 1 120); do
|
||||
WRITABLE_PATH="$PWD/wekan-writable" \
|
||||
WITH_API=true \
|
||||
RICHER_CARD_COMMENT_EDITOR=false \
|
||||
node output/bundle/main.js > wekan-run.log 2>&1 &
|
||||
# The production server serves the client JS statically, so once :3000
|
||||
# responds the bundle is being served. Wait up to 10 minutes.
|
||||
for i in $(seq 1 60); do
|
||||
if curl -sf http://localhost:3000 > /dev/null; then
|
||||
echo "WeKan is up after $((i * 10))s"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for WeKan... attempt $i/120"
|
||||
echo "Waiting for WeKan... attempt $i/60"
|
||||
sleep 10
|
||||
done
|
||||
if ! curl -sf http://localhost:3000 > /dev/null; then
|
||||
echo "WeKan failed to start. Last 200 log lines:"
|
||||
tail -n 200 wekan-run.log
|
||||
echo "WeKan failed to start. Last 250 log lines:"
|
||||
tail -n 250 wekan-run.log
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
DISABLE_CAPTCHA: 'true'
|
||||
TOOL_NODE_FLAGS: --max-old-space-size=8192
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
DISABLE_CAPTCHA: 'true'
|
||||
|
||||
- name: Run Playwright tests (${{ matrix.project }})
|
||||
- name: Run Playwright tests (${{ matrix.project }} shard ${{ matrix.shard }}/2)
|
||||
run: |
|
||||
npx playwright test \
|
||||
--project=${{ matrix.project }} \
|
||||
--shard=${{ matrix.shard }}/2 \
|
||||
--reporter=github
|
||||
working-directory: tests/playwright
|
||||
env:
|
||||
@@ -164,7 +190,7 @@ jobs:
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: playwright-report-${{ matrix.project }}
|
||||
name: playwright-report-${{ matrix.project }}-shard${{ matrix.shard }}
|
||||
path: tests/playwright/playwright-report/
|
||||
retention-days: 14
|
||||
|
||||
@@ -172,7 +198,7 @@ jobs:
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: test-results-${{ matrix.project }}
|
||||
name: test-results-${{ matrix.project }}-shard${{ matrix.shard }}
|
||||
path: tests/playwright/test-results/
|
||||
retention-days: 7
|
||||
|
||||
@@ -201,14 +227,14 @@ jobs:
|
||||
cache: 'npm'
|
||||
|
||||
- name: Cache Meteor installation
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.meteor
|
||||
key: meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.meteor/packages') }}
|
||||
restore-keys: meteor-
|
||||
|
||||
- name: Cache Meteor build (.meteor/local)
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: .meteor/local
|
||||
key: meteor-local-regression-${{ hashFiles('.meteor/versions') }}
|
||||
@@ -227,27 +253,40 @@ jobs:
|
||||
- name: Install Chromium
|
||||
run: sudo apt-get install -y chromium-browser
|
||||
|
||||
- name: Start WeKan in background
|
||||
- name: Build and start WeKan (production bundle)
|
||||
run: |
|
||||
# Run a PRODUCTION build, not `meteor run` (dev): exactly like the
|
||||
# Playwright e2e job, because the rspack dev server does not serve the
|
||||
# client JS bundle in headless CI, so Meteor never initialises and the
|
||||
# puppeteer regression test times out in waitForMeteorGlobals(). The
|
||||
# production bundle serves the client statically. WRITABLE_PATH is
|
||||
# required by server/00checkStartup.js; WITH_API mirrors the dev runner.
|
||||
meteor build ./output --directory
|
||||
( cd output/bundle/programs/server && npm install )
|
||||
mkdir -p "$PWD/wekan-writable"
|
||||
MONGO_URL=mongodb://localhost:27017/wekan_regression \
|
||||
ROOT_URL=http://localhost:3000 \
|
||||
PORT=3000 \
|
||||
meteor run > wekan-run.log 2>&1 &
|
||||
# The first Meteor build is slow, so wait up to 20 minutes.
|
||||
for i in $(seq 1 120); do
|
||||
WRITABLE_PATH="$PWD/wekan-writable" \
|
||||
WITH_API=true \
|
||||
RICHER_CARD_COMMENT_EDITOR=false \
|
||||
node output/bundle/main.js > wekan-run.log 2>&1 &
|
||||
for i in $(seq 1 60); do
|
||||
if curl -sf http://localhost:3000 > /dev/null; then
|
||||
echo "WeKan is up after $((i * 10))s"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for WeKan... attempt $i/120"
|
||||
echo "Waiting for WeKan... attempt $i/60"
|
||||
sleep 10
|
||||
done
|
||||
if ! curl -sf http://localhost:3000 > /dev/null; then
|
||||
echo "WeKan failed to start. Last 200 log lines:"
|
||||
tail -n 200 wekan-run.log
|
||||
echo "WeKan failed to start. Last 250 log lines:"
|
||||
tail -n 250 wekan-run.log
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
TOOL_NODE_FLAGS: --max-old-space-size=8192
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
DISABLE_CAPTCHA: 'true'
|
||||
|
||||
- name: Run list-regressions e2e
|
||||
|
||||
@@ -22,7 +22,9 @@ name: Release All Platforms
|
||||
# commits and pushes the bump to wekan/wekan main
|
||||
# - prepare: tags the bumped main as v<new_version> and extracts release notes
|
||||
# - website: updates the wekan/wekan.fi repo (install page + API docs) and pushes
|
||||
# - charts: updates and publishes the wekan/charts Helm chart and pushes
|
||||
# - charts: updates and publishes the wekan/charts Helm chart and pushes,
|
||||
# only AFTER the `docker` job has published the image (skipped if
|
||||
# docker fails)
|
||||
# - build-*: builds the amd64 bundle (full Meteor 3 build), then rebuilds native
|
||||
# modules for arm64 from that amd64 base
|
||||
# - release: creates the GitHub Release with the newest CHANGELOG.md notes and
|
||||
@@ -64,7 +66,7 @@ permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
# Node.js major version that Meteor 3.5-beta bundles. Used for native module
|
||||
# Node.js major version that Meteor 3.5 bundles. Used for native module
|
||||
# rebuilds on every platform — it must match the amd64 build's Node ABI.
|
||||
env:
|
||||
NODE_VERSION: '24'
|
||||
@@ -210,12 +212,14 @@ jobs:
|
||||
cd ..
|
||||
./wekan/releases/release-website.sh "${{ inputs.old_version }}" "${{ inputs.new_version }}"
|
||||
|
||||
# ─── 1c. Update and publish the Helm chart repo (parallel) ──────────────────
|
||||
# ─── 1c. Update and publish the Helm chart repo (after docker) ──────────────
|
||||
# Bumps the WeKan version in wekan/charts, packages the chart and publishes it
|
||||
# to the gh-pages Helm index, then pushes both branches. Runs in parallel with
|
||||
# the platform builds; packaging the chart does not need the built artifacts.
|
||||
# to the gh-pages Helm index, then pushes both branches. Runs only AFTER the
|
||||
# `docker` job succeeds: the chart points users at the freshly published
|
||||
# multi-arch image, so we don't advertise a new chart version until that image
|
||||
# is actually on the registries. If `docker` fails, this job is skipped.
|
||||
charts:
|
||||
needs: bump
|
||||
needs: docker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout wekan repo (for releases/release-charts.sh + version refs)
|
||||
@@ -250,7 +254,7 @@ jobs:
|
||||
./wekan/releases/release-charts.sh "${{ inputs.new_version }}"
|
||||
|
||||
# ─── 2. Build amd64 bundle (full Meteor 3 build) ────────────────────────────
|
||||
# Meteor 3.5-beta installer supports only Linux x86_64/aarch64, macOS, Windows.
|
||||
# Meteor 3.5 installer supports only Linux x86_64/aarch64, macOS, Windows.
|
||||
# The full Meteor build always runs here on x86_64; every other platform then
|
||||
# only rebuilds the native npm modules from this bundle.
|
||||
build-amd64:
|
||||
@@ -265,14 +269,14 @@ jobs:
|
||||
ref: refs/tags/v${{ needs.prepare.outputs.version }}
|
||||
|
||||
- name: Cache Meteor installation
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.meteor
|
||||
key: meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.meteor/packages') }}
|
||||
restore-keys: meteor-
|
||||
|
||||
- name: Cache npm packages
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
@@ -796,7 +800,20 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install snapcraft
|
||||
run: sudo snap install snapcraft --classic
|
||||
# The Snap Store intermittently rejects the install with
|
||||
# 'error: cannot install "snapcraft": too many requests' (a 429-style
|
||||
# rate limit) that instantly fails the whole snap job before any build.
|
||||
# Retry a few times so one transient store throttle does not fail the
|
||||
# release (same spirit as the remote-build retry below).
|
||||
run: |
|
||||
set -uo pipefail
|
||||
for i in 1 2 3 4 5; do
|
||||
sudo snap install snapcraft --classic && exit 0
|
||||
echo "snapcraft install attempt $i/5 failed (Snap Store throttle?); retrying in 30s..."
|
||||
sleep 30
|
||||
done
|
||||
echo "::error::snapcraft install failed after 5 attempts (Snap Store rate limit)."
|
||||
exit 1
|
||||
|
||||
- name: Provide Launchpad credentials for remote-build
|
||||
env:
|
||||
|
||||
+8
-8
@@ -6,7 +6,7 @@
|
||||
meteor-base@1.5.2
|
||||
|
||||
# Build system
|
||||
ecmascript@0.18.0
|
||||
ecmascript@0.18.1
|
||||
standard-minifier-js@3.2.0
|
||||
rspack@1.1.0
|
||||
|
||||
@@ -16,11 +16,11 @@ blaze@3.0.2
|
||||
aldeed:collection2
|
||||
aldeed:schema-index@4.0.0-beta.5
|
||||
reywood:publish-composite
|
||||
mongo@2.4.0-rc350.1
|
||||
mongo@2.4.0
|
||||
|
||||
# Account system
|
||||
accounts-password@3.3.0-rc350.1
|
||||
accounts-express@1.0.0-rc350.1
|
||||
accounts-password@3.3.0
|
||||
accounts-express@1.0.0
|
||||
communitypackages:core
|
||||
communitypackages:unstyled
|
||||
communitypackages:flow-routing
|
||||
@@ -37,7 +37,7 @@ wekan-accounts-oidc
|
||||
check@1.5.0
|
||||
random@1.2.2
|
||||
reactive-dict@1.3.2
|
||||
session@1.2.3-rc350.1
|
||||
session@1.2.3
|
||||
tracker@1.3.4
|
||||
audit-argument-checks@1.0.8
|
||||
|
||||
@@ -45,7 +45,7 @@ audit-argument-checks@1.0.8
|
||||
ostrio:i18n
|
||||
reactive-var@1.0.13
|
||||
shell-server@0.7.0
|
||||
email@3.2.0-rc350.1
|
||||
email@3.2.0
|
||||
dynamic-import@0.7.4
|
||||
wekan-markdown
|
||||
quave:synced-cron
|
||||
@@ -55,8 +55,8 @@ ostrio:files
|
||||
meteortesting:mocha@3.0.0
|
||||
matb33:collection-hooks
|
||||
service-configuration@1.3.5
|
||||
minifier-css@2.0.2-rc350.1
|
||||
ejson@1.2.0-rc350.1
|
||||
minifier-css@2.0.2
|
||||
ejson@1.2.0
|
||||
logging@1.3.6
|
||||
wekan-fullcalendar
|
||||
wekan-fontawesome
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
METEOR@3.5-rc.1
|
||||
METEOR@3.5
|
||||
+28
-28
@@ -1,44 +1,44 @@
|
||||
accounts-base@3.3.0-rc350.1
|
||||
accounts-express@1.0.0-rc350.1
|
||||
accounts-oauth@1.4.7-rc350.1
|
||||
accounts-password@3.3.0-rc350.1
|
||||
accounts-base@3.3.0
|
||||
accounts-express@1.0.0
|
||||
accounts-oauth@1.4.7
|
||||
accounts-password@3.3.0
|
||||
aldeed:collection2@4.2.0
|
||||
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.14.0
|
||||
babel-compiler@7.14.1
|
||||
babel-runtime@1.5.2
|
||||
base64@1.0.14-rc350.1
|
||||
binary-heap@1.0.13-rc350.1
|
||||
base64@1.0.14
|
||||
binary-heap@1.0.13
|
||||
blaze@3.0.3
|
||||
blaze-tools@2.0.1
|
||||
boilerplate-generator@2.1.0
|
||||
caching-compiler@2.0.1
|
||||
caching-html-compiler@2.0.1
|
||||
callback-hook@1.8.0-rc350.1
|
||||
callback-hook@1.8.0
|
||||
check@1.5.0
|
||||
communitypackages:core@2.0.3
|
||||
communitypackages:flow-routing@2.1.0
|
||||
communitypackages:unstyled@2.0.1
|
||||
core-runtime@1.0.0
|
||||
ddp@1.4.2
|
||||
ddp-client@3.3.0-rc350.1
|
||||
ddp-common@1.4.5-rc350.1
|
||||
ddp-rate-limiter@1.3.0-rc350.1
|
||||
ddp-server@3.3.0-rc350.1
|
||||
ddp-client@3.3.0
|
||||
ddp-common@1.4.5
|
||||
ddp-rate-limiter@1.3.0
|
||||
ddp-server@3.3.0
|
||||
diff-sequence@1.1.3
|
||||
dynamic-import@0.7.4
|
||||
ecmascript@0.18.0
|
||||
ecmascript@0.18.1
|
||||
ecmascript-runtime@0.8.3
|
||||
ecmascript-runtime-client@0.13.0
|
||||
ecmascript-runtime-server@0.11.1
|
||||
ejson@1.2.0-rc350.1
|
||||
email@3.2.0-rc350.1
|
||||
ejson@1.2.0
|
||||
email@3.2.0
|
||||
es5-shim@4.8.1
|
||||
facts-base@1.0.2
|
||||
fetch@0.2.0-rc350.1
|
||||
fetch@0.2.0
|
||||
geojson-utils@1.0.12
|
||||
hot-code-push@1.0.5
|
||||
html-tools@2.0.1
|
||||
@@ -53,23 +53,23 @@ lai:collection-extensions@1.0.0
|
||||
localstorage@1.2.1
|
||||
logging@1.3.6
|
||||
matb33:collection-hooks@2.1.0
|
||||
meteor@2.3.1-rc350.1
|
||||
meteor@2.3.1
|
||||
meteor-base@1.5.2
|
||||
meteortesting:browser-tests@1.8.0
|
||||
meteortesting:mocha@3.3.0
|
||||
meteortesting:mocha-core@8.2.0
|
||||
minifier-css@2.0.2-rc350.1
|
||||
minifier-js@3.2.0
|
||||
minimongo@2.2.0-rc350.1
|
||||
minifier-css@2.0.2
|
||||
minifier-js@3.2.1
|
||||
minimongo@2.2.0
|
||||
modern-browsers@0.2.3
|
||||
modules@0.20.3
|
||||
modules-runtime@0.13.2
|
||||
mongo@2.4.0-rc350.1
|
||||
mongo@2.4.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.4-rc350.1
|
||||
oauth@3.0.4
|
||||
oauth2@1.3.3
|
||||
observe-sequence@2.0.1
|
||||
ordered-dict@1.2.0
|
||||
@@ -82,7 +82,7 @@ promise@1.0.0
|
||||
quave:synced-cron@2.4.0
|
||||
raix:eventemitter@1.0.0
|
||||
random@1.2.2
|
||||
rate-limit@1.2.0-rc350.1
|
||||
rate-limit@1.2.0
|
||||
react-fast-refresh@0.3.0
|
||||
reactive-dict@1.3.2
|
||||
reactive-var@1.0.13
|
||||
@@ -92,10 +92,10 @@ reywood:publish-composite@1.9.0
|
||||
routepolicy@1.1.2
|
||||
rspack@1.1.0
|
||||
service-configuration@1.3.5
|
||||
session@1.2.3-rc350.1
|
||||
session@1.2.3
|
||||
sha@1.0.10
|
||||
shell-server@0.7.0
|
||||
socket-stream-client@0.7.0-rc350.1
|
||||
socket-stream-client@0.7.0
|
||||
spacebars@2.0.1
|
||||
spacebars-compiler@2.0.1
|
||||
standard-minifier-js@3.2.0
|
||||
@@ -105,9 +105,9 @@ templating-runtime@2.0.2
|
||||
templating-tools@2.0.1
|
||||
tools-core@1.1.0
|
||||
tracker@1.3.4
|
||||
typescript@5.10.0
|
||||
url@1.3.6-rc350.1
|
||||
webapp@2.2.0-rc350.1
|
||||
typescript@5.10.1
|
||||
url@1.3.6
|
||||
webapp@2.2.0
|
||||
webapp-hashing@1.1.2
|
||||
wekan-accounts-cas@0.2.0
|
||||
wekan-accounts-lockout@1.1.0
|
||||
|
||||
+8
-1
@@ -1,2 +1,9 @@
|
||||
npm-packages/
|
||||
tests/
|
||||
# Root tests/ holds Playwright specs, Node E2E and node-based unit tests - none
|
||||
# of it is Meteor code, and Meteor's file watcher otherwise churns on the
|
||||
# tests/playwright/test-results/ tree (inotify errors). The leading slash anchors
|
||||
# this to the repo root only, so it does NOT match server/lib/tests/ or
|
||||
# client/lib/tests/ (the Mocha suites, loaded via meteor.testModule in
|
||||
# package.json). A naked "tests/" would match a dir named tests at any depth and
|
||||
# would hide those suites, making `meteor test` report "0 passing".
|
||||
/tests/
|
||||
+1092
-145
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -2,10 +2,10 @@
|
||||
|
||||
For all code at WeKan GitHub Organization https://github.com/wekan
|
||||
|
||||
- For pull requests, mention only those participants that are **human**.
|
||||
- All code in pull requests need to have permission already to add it to WeKan with MIT license, and will become MIT license.
|
||||
- For any dependencies, permissive licenses like https://copyfree.org are preferred
|
||||
- For anything currently that is non-permissive (like GPL, AGPL, SSPL), those will be replaced with permissive-licensed alternatives
|
||||
- Any intelligence allowed: Human, AI or any other. No discrimination, please have world peace. Mentioning participants optional.
|
||||
- All code is checked and validated by xet7 and many different security AI. If code is real working fix or feature, it usually will be merged.
|
||||
- All known vulnerabilities have been fixed at newest WeKan, please upgrade https://wekan.fi/upgrade/
|
||||
- There is no known backdoors. If WeKan is installed by someone else, first registered user is Admin and has Admin Panel access.
|
||||
|
||||
+4
-4
@@ -7,17 +7,17 @@ LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
|
||||
# TARGETARCH and TARGETVARIANT are automatically provided by Docker Buildx
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
ARG VERSION=9.67
|
||||
ARG VERSION=9.78
|
||||
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=v24.17.0 \
|
||||
METEOR_RELEASE=METEOR@3.5-beta.12 \
|
||||
NODE_VERSION=v24.18.0 \
|
||||
METEOR_RELEASE=METEOR@3.5-rc.2 \
|
||||
USE_EDGE=false \
|
||||
NPM_VERSION=11.11.0 \
|
||||
NPM_VERSION=11.12.1 \
|
||||
SRC_PATH=./ \
|
||||
WITH_API=true \
|
||||
MONGO_OPLOG_URL="" \
|
||||
|
||||
@@ -103,7 +103,7 @@ By working directly with WeKan ® you get the benefit of active maintenance and
|
||||
|
||||
## Getting Started with Development
|
||||
|
||||
The default branch uses Meteor 3.5-beta.7 with Node.js 24.x.
|
||||
The default branch uses Meteor 3.5-rc.2 with Node.js 24.x.
|
||||
See [CHANGELOG.md](https://github.com/wekan/wekan/blob/main/CHANGELOG.md) for the latest runtime updates.
|
||||
|
||||
To contribute, [create a fork](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#2-create-fork-of-httpsgithubcomwekanwekan-at-github-web-page) and run `./rebuild-wekan.sh` (or `./rebuild-wekan.bat` on Windows) as detailed [here](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#3-select-option-1-to-install-dependencies-and-then-enter). Once you're ready, please test your code and [submit a pull request (PR)](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#7-test).
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v9.67.0"
|
||||
appVersion: "v9.78.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
|
||||
@@ -265,10 +265,9 @@ if arguments == 10:
|
||||
boardid = sys.argv[3]
|
||||
name = sys.argv[4]
|
||||
type1 = sys.argv[5]
|
||||
settings = str(json.loads(sys.argv[6]))
|
||||
# There is error: Settings must be object. So this does not work yet.
|
||||
#settings = {'currencyCode': 'EUR'}
|
||||
print(type(settings))
|
||||
settings_arg = sys.argv[6]
|
||||
# settings is optional; an empty arg -> {} (avoids json.loads('') crashing).
|
||||
settings = json.loads(settings_arg) if settings_arg.strip() else {}
|
||||
showoncard = sys.argv[7]
|
||||
automaticallyoncard = sys.argv[8]
|
||||
showlabelonminicard = sys.argv[9]
|
||||
@@ -276,7 +275,7 @@ if arguments == 10:
|
||||
customfieldtoboard = wekanurl + apiboards + boardid + s + cf
|
||||
# Add Custom Field to Board
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
post_data = {'authorId': '{}'.format(authorid), 'name': '{}'.format(name), 'type': '{}'.format(type1), 'settings': '{}'.format(settings), 'showoncard': '{}'.format(showoncard), 'automaticallyoncard': '{}'.format(automaticallyoncard), 'showlabelonminicard': '{}'.format(showlabelonminicard), 'showsumattopoflist': '{}'.format(showsumattopoflist)}
|
||||
post_data = {'authorId': '{}'.format(authorid), 'name': '{}'.format(name), 'type': '{}'.format(type1), 'settings': json.dumps(settings), 'showoncard': '{}'.format(showoncard), 'automaticallyoncard': '{}'.format(automaticallyoncard), 'showlabelonminicard': '{}'.format(showlabelonminicard), 'showsumattopoflist': '{}'.format(showsumattopoflist)}
|
||||
body = requests.post(customfieldtoboard, data=post_data, headers=headers)
|
||||
print(body.text)
|
||||
# ------- ADD CUSTOM FIELD TO BOARD END -----------
|
||||
|
||||
@@ -108,7 +108,13 @@ Template.comment.events({
|
||||
},
|
||||
'click .js-delete-comment': Popup.afterConfirm('deleteComment', function () {
|
||||
const commentId = this._id;
|
||||
CardComments.remove(commentId);
|
||||
// #3252: only remove if the doc is still in the local cache. Under heavy
|
||||
// archive/delete churn the comment can already be evicted from Minimongo,
|
||||
// and CardComments.remove() of a missing _id throws "Removed nonexistent
|
||||
// document" on the client.
|
||||
if (commentId && CardComments.findOne(commentId)) {
|
||||
CardComments.remove(commentId);
|
||||
}
|
||||
Popup.back();
|
||||
}),
|
||||
'submit .js-edit-comment'(evt, tpl) {
|
||||
|
||||
@@ -9,7 +9,7 @@ Template.archivedBoards.onCreated(function () {
|
||||
|
||||
Template.archivedBoards.helpers({
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
|
||||
archivedBoards() {
|
||||
|
||||
@@ -107,17 +107,17 @@
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
/* Mobile - make all text 2x bigger inside #content by default (icons stay same size) */
|
||||
/* Mobile - keep #content text at normal size (reverted #6426 oversizing) */
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 800px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
|
||||
screen and (max-width: 800px) and (orientation: portrait),
|
||||
screen and (max-width: 800px) and (orientation: landscape) {
|
||||
#content {
|
||||
font-size: 2em !important; /* 2x bigger base font size for content area */
|
||||
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
|
||||
}
|
||||
|
||||
/* Make all text elements 2x bigger */
|
||||
/* Keep all text elements at normal size */
|
||||
#content h1, #content h2, #content h3, #content h4, #content h5, #content h6,
|
||||
#content p, #content span, #content div, #content a, #content button,
|
||||
#content .minicard, #content .list-header-name, #content .board-header-btn,
|
||||
@@ -186,7 +186,7 @@
|
||||
|
||||
/* Fallback for iPhone devices using JavaScript detection */
|
||||
.iphone-device #content {
|
||||
font-size: 2em !important; /* 2x bigger base font size for content area */
|
||||
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
|
||||
}
|
||||
|
||||
.iphone-device #content h1, .iphone-device #content h2, .iphone-device #content h3, .iphone-device #content h4, .iphone-device #content h5, .iphone-device #content h6,
|
||||
@@ -231,9 +231,9 @@
|
||||
font-size: 1em !important; /* Keep original icon size */
|
||||
}
|
||||
|
||||
/* Mobile iPhone: scale card details text and icons to 2x */
|
||||
/* Mobile iPhone: keep card details text/icons at normal size (reverted #6426 oversizing) */
|
||||
body.mobile-mode.iphone-device .card-details {
|
||||
font-size: 2em !important;
|
||||
font-size: 1em !important;
|
||||
}
|
||||
body.mobile-mode.iphone-device .card-details .fa,
|
||||
body.mobile-mode.iphone-device .card-details .icon,
|
||||
|
||||
@@ -937,3 +937,20 @@
|
||||
.desktop-mode .board-header .board-header-btn i {
|
||||
font-size: clamp(12px, 2.5vw, 16px) !important;
|
||||
}
|
||||
|
||||
/* #6419: make the board-header action buttons touch-friendly by VIEWPORT WIDTH,
|
||||
not only when the mobile-mode class is toggled. The measured default at a phone
|
||||
width was 28px tall / 13px font (too small to tap reliably). Enlarge vertically
|
||||
+ bump the icon/font; width is left to flex so the existing horizontal layout
|
||||
is unchanged. Applies on real phones whether or not mobile-mode auto-applied. */
|
||||
@media screen and (max-width: 800px) {
|
||||
.board-header-btns .board-header-btn {
|
||||
min-height: 40px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.board-header-btns .board-header-btn i.fa {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ template(name="boardHeaderBar")
|
||||
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
|
||||
if showStarCounter
|
||||
span.board-star-counter {{currentBoard.stars}}
|
||||
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
|
||||
a.board-header-btn(title="{{_ 'sort-cards'}}" class="js-sort-cards {{#if isSortActive }}emphasis{{/if}}")
|
||||
i.fa.fa-sort
|
||||
span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
|
||||
if isSortActive
|
||||
@@ -78,7 +78,7 @@ template(name="boardHeaderBar")
|
||||
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
|
||||
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
|
||||
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
|
||||
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
|
||||
a.board-header-btn(title="{{_ 'sort-cards'}}" class="js-sort-cards {{#if isSortActive }}emphasis{{/if}}")
|
||||
i.fa.fa-sort
|
||||
span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
|
||||
if isSortActive
|
||||
|
||||
@@ -17,6 +17,21 @@ const UPCLS = 'fa-sort-up';
|
||||
*/
|
||||
const sortCardsBy = new ReactiveVar('');
|
||||
|
||||
// Persist the card sort so it survives a page reload. Session is in-memory and
|
||||
// is reset on reload, which is why "sort by due date" kept reverting to the
|
||||
// default. See https://github.com/wekan/wekan/issues/5886
|
||||
const CARDS_SORT_BY_STORAGE_KEY = 'wekan-cards-sortBy';
|
||||
function setCardsSortBy(sortBy) {
|
||||
Session.set('sortBy', sortBy);
|
||||
try {
|
||||
if (sortBy) {
|
||||
window.localStorage.setItem(CARDS_SORT_BY_STORAGE_KEY, JSON.stringify(sortBy));
|
||||
} else {
|
||||
window.localStorage.removeItem(CARDS_SORT_BY_STORAGE_KEY);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
Template.boardChangeTitlePopup.events({
|
||||
async submit(event, templateInstance) {
|
||||
event.preventDefault();
|
||||
@@ -41,6 +56,16 @@ Template.boardChangeTitlePopup.events({
|
||||
|
||||
Template.boardHeaderBar.onCreated(function () {
|
||||
Meteor.subscribe('tableVisibilityModeSettings');
|
||||
// Restore the persisted card sort after a reload (Session is in-memory), so
|
||||
// both the sorting and the sort icon are remembered. See #5886.
|
||||
if (!Session.get('sortBy')) {
|
||||
try {
|
||||
const stored = window.localStorage.getItem(CARDS_SORT_BY_STORAGE_KEY);
|
||||
if (stored) {
|
||||
Session.set('sortBy', JSON.parse(stored));
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
|
||||
Template.boardHeaderBar.helpers({
|
||||
@@ -171,7 +196,7 @@ Template.boardHeaderBar.events({
|
||||
Filter.reset();
|
||||
},
|
||||
'click .js-sort-reset'() {
|
||||
Session.set('sortBy', '');
|
||||
setCardsSortBy('');
|
||||
},
|
||||
'click .js-open-search-view'() {
|
||||
const sidebar = getSidebarInstance();
|
||||
@@ -529,7 +554,7 @@ Template.cardsSortPopup.events({
|
||||
const sortBy = {
|
||||
dueAt: 1,
|
||||
};
|
||||
Session.set('sortBy', sortBy);
|
||||
setCardsSortBy(sortBy);
|
||||
sortCardsBy.set(TAPi18n.__('due-date'));
|
||||
Popup.back();
|
||||
},
|
||||
@@ -537,7 +562,7 @@ Template.cardsSortPopup.events({
|
||||
const sortBy = {
|
||||
title: 1,
|
||||
};
|
||||
Session.set('sortBy', sortBy);
|
||||
setCardsSortBy(sortBy);
|
||||
sortCardsBy.set(TAPi18n.__('title'));
|
||||
Popup.back();
|
||||
},
|
||||
@@ -545,7 +570,7 @@ Template.cardsSortPopup.events({
|
||||
const sortBy = {
|
||||
createdAt: 1,
|
||||
};
|
||||
Session.set('sortBy', sortBy);
|
||||
setCardsSortBy(sortBy);
|
||||
sortCardsBy.set(TAPi18n.__('date-created-newest-first'));
|
||||
Popup.back();
|
||||
},
|
||||
@@ -553,7 +578,7 @@ Template.cardsSortPopup.events({
|
||||
const sortBy = {
|
||||
createdAt: -1,
|
||||
};
|
||||
Session.set('sortBy', sortBy);
|
||||
setCardsSortBy(sortBy);
|
||||
sortCardsBy.set(TAPi18n.__('date-created-oldest-first'));
|
||||
Popup.back();
|
||||
},
|
||||
|
||||
@@ -297,6 +297,62 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* #5799: board-name search box in the All Boards header */
|
||||
.boards-path-header .board-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.boards-path-header .board-search input {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
font-size: 14px;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.boards-path-header .board-search .board-search-clear {
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.boards-path-header .board-search .board-search-clear:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* #5799: board grid pagination controls, mirroring Admin Panel > People */
|
||||
.boards-pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.boards-pagination button {
|
||||
min-width: 32px;
|
||||
height: 30px;
|
||||
border: 1px solid #bbb;
|
||||
background: #fff;
|
||||
border-radius: 0.4vw;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.boards-pagination button.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.boards-page-info {
|
||||
min-width: 72px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.boards-path-header .path-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
@@ -946,21 +1002,13 @@ body.grey-icons-enabled .checkmark-no-grey {
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile - make all text and icons 2x bigger above #content on All Boards page */
|
||||
/* Mobile All Boards layout fixes */
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 800px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
|
||||
screen and (max-width: 800px) and (orientation: portrait),
|
||||
screen and (max-width: 800px) and (orientation: landscape),
|
||||
screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) {
|
||||
.wrapper {
|
||||
font-size: 2em !important; /* 2x bigger base font size for All Boards page */
|
||||
}
|
||||
|
||||
.wrapper * {
|
||||
font-size: inherit !important; /* Inherit the 2x scaling */
|
||||
}
|
||||
|
||||
/* Stack the two-column All Boards layout on small screens. The fixed
|
||||
"260px 1fr" grid had no mobile collapse, so on narrow non-iPhone browsers
|
||||
(Fairphone Firefox ESR / Ubuntu Touch Morph, narrow desktop) it overflowed
|
||||
@@ -976,243 +1024,6 @@ body.grey-icons-enabled .checkmark-no-grey {
|
||||
padding-inline-end: 0 !important;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.wrapper .fa, .wrapper .icon {
|
||||
font-size: 2em !important; /* 2x bigger icons */
|
||||
}
|
||||
|
||||
.board-list-header {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list-header h1 {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.AllBoardTeamsOrgs {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.AllBoardTeamsOrgs select {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.AllBoardTeamsOrgs input {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.AllBoardTeamsOrgs .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .board-list-item {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .board-list-item-name {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .board-list-item-desc {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .minicard-members {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .minicard-lists {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.board-list .board-handle {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
}
|
||||
|
||||
/* Fallback for iPhone devices using JavaScript detection - All Boards page */
|
||||
.iphone-device .wrapper {
|
||||
font-size: 2em !important; /* 2x bigger base font size for All Boards page */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper * {
|
||||
font-size: inherit !important; /* Inherit the 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper .fa, .iphone-device .wrapper .icon {
|
||||
font-size: 2em !important; /* 2x bigger icons */
|
||||
}
|
||||
|
||||
.iphone-device .board-list-header {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list-header h1 {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .AllBoardTeamsOrgs {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .AllBoardTeamsOrgs select {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .AllBoardTeamsOrgs input {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .AllBoardTeamsOrgs .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .board-list-item {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .board-list-item-name {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .board-list-item-desc {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .minicard-members {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .minicard-lists {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .fa {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
.iphone-device .board-list .board-handle {
|
||||
font-size: 1em !important; /* Use inherited 2x scaling */
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini and very small screens - make everything much larger */
|
||||
@media screen and (max-width: 400px) and (max-height: 900px) {
|
||||
.board-list {
|
||||
height: calc(100vh - 120px) !important;
|
||||
overflow-y: scroll !important;
|
||||
overflow-x: hidden !important;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.board-list li {
|
||||
width: 100% !important;
|
||||
float: none !important;
|
||||
display: block !important;
|
||||
margin-bottom: 1.5rem !important;
|
||||
}
|
||||
|
||||
.board-list .board-list-item {
|
||||
height: 12rem !important; /* Much taller */
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 1rem !important; /* More padding */
|
||||
font-size: 18px !important; /* Much larger text */
|
||||
line-height: 1.4 !important;
|
||||
}
|
||||
|
||||
.board-list .board-list-item .board-list-item-name {
|
||||
font-size: 20px !important; /* Larger board names */
|
||||
font-weight: bold !important;
|
||||
margin-bottom: 0.5rem !important;
|
||||
}
|
||||
|
||||
.board-list .board-list-item .board-list-item-desc {
|
||||
font-size: 16px !important; /* Larger descriptions */
|
||||
line-height: 1.3 !important;
|
||||
}
|
||||
|
||||
.board-list .board-list-item .minicard-members {
|
||||
font-size: 14px !important; /* Larger member avatars */
|
||||
}
|
||||
|
||||
.board-list .board-list-item .minicard-lists {
|
||||
font-size: 14px !important; /* Larger list counters */
|
||||
}
|
||||
|
||||
.board-list .board-handle {
|
||||
position: absolute;
|
||||
padding: 10px !important;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
inset-inline-end: 15px !important;
|
||||
font-size: 28px !important; /* Much larger drag handle */
|
||||
color: #fff;
|
||||
background: rgba(0,0,0,0.4) !important;
|
||||
border-radius: 50%;
|
||||
width: 50px !important; /* Larger handle */
|
||||
height: 50px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.board-list .board-handle:hover {
|
||||
background: rgba(255, 255, 0, 0.8) !important; /* Yellow hover */
|
||||
}
|
||||
|
||||
/* Force scrollbar to be visible and larger */
|
||||
.board-list::-webkit-scrollbar {
|
||||
width: 16px !important; /* Much wider scrollbar */
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.board-list::-webkit-scrollbar-track {
|
||||
background: #f1f1f1 !important;
|
||||
border-radius: 8px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.board-list::-webkit-scrollbar-thumb {
|
||||
background: #666 !important; /* Darker for better visibility */
|
||||
border-radius: 8px !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
min-height: 50px !important; /* Minimum thumb size */
|
||||
}
|
||||
|
||||
.board-list::-webkit-scrollbar-thumb:hover {
|
||||
background: #333 !important;
|
||||
}
|
||||
|
||||
/* Ensure scrollbar is always visible */
|
||||
.board-list {
|
||||
scrollbar-gutter: stable;
|
||||
scrollbar-width: auto !important;
|
||||
min-height: 100vh !important;
|
||||
}
|
||||
|
||||
.board-list::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 200px !important; /* More space to ensure scrolling */
|
||||
}
|
||||
}
|
||||
.AllBoardTeamsOrgs {
|
||||
list-style-type: none;
|
||||
|
||||
@@ -85,6 +85,18 @@ template(name="boardList")
|
||||
i.fa.fa-thumb-tack
|
||||
| {{_ 'multi-selection-active'}}
|
||||
.path-right
|
||||
.board-search
|
||||
span.emoji-icon
|
||||
i.fa.fa-search
|
||||
input.js-board-search-input(type="text" placeholder="{{_ 'search-boards'}}" value="{{boardSearch}}" autocomplete="off")
|
||||
if boardSearch
|
||||
a.board-search-clear.js-board-search-clear(title="{{_ 'filter-clear'}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-times
|
||||
a.board-header-btn.js-open-boards-sort(title="{{_ 'sort-boards'}}" class="{{#unless isBoardsSort 'custom'}}emphasis{{/unless}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-sort-alpha-asc
|
||||
span {{_ 'sort'}}
|
||||
if canModifyBoards
|
||||
if hasBoardsSelected
|
||||
button.js-archive-selected-boards.board-header-btn
|
||||
@@ -104,6 +116,13 @@ template(name="boardList")
|
||||
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-times
|
||||
if boardsPaginationActive
|
||||
.boards-pagination
|
||||
button.js-boards-prev-page(class="{{#if hasBoardsPrevPage}}{{else}}disabled{{/if}}")
|
||||
| <
|
||||
span.boards-page-info {{boardsCurrentPage}} / {{boardsTotalPages}}
|
||||
button.js-boards-next-page(class="{{#if hasBoardsNextPage}}{{else}}disabled{{/if}}")
|
||||
| >
|
||||
ul.board-list.clearfix.js-boards.dragscroll(class="{{#if isMiniScreen}}mobile-view{{/if}} {{#if BoardMultiSelection.isActive}}is-multiselection-active{{/if}}")
|
||||
li.js-add-board
|
||||
if isSelectedMenu 'templates'
|
||||
@@ -205,6 +224,24 @@ template(name="boardList")
|
||||
span.emoji-icon
|
||||
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
|
||||
|
||||
template(name="boardsSortPopup")
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.js-boards-sort(data-sort="custom" class="{{#if isBoardsSort 'custom'}}active{{/if}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-arrows
|
||||
| {{_ 'sort-boards-custom'}}
|
||||
li
|
||||
a.js-boards-sort(data-sort="title-asc" class="{{#if isBoardsSort 'title-asc'}}active{{/if}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-sort-alpha-asc
|
||||
| {{_ 'sort-boards-title-asc'}}
|
||||
li
|
||||
a.js-boards-sort(data-sort="title-desc" class="{{#if isBoardsSort 'title-desc'}}active{{/if}}")
|
||||
span.emoji-icon
|
||||
i.fa.fa-sort-alpha-desc
|
||||
| {{_ 'sort-boards-title-desc'}}
|
||||
|
||||
template(name="boardListHeaderBar")
|
||||
h1 {{_ title }}
|
||||
//.board-header-btns.right
|
||||
|
||||
@@ -2,8 +2,6 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import getSlug from 'limax';
|
||||
import '/client/lib/dragscrollTouch';
|
||||
import { enablePageDragscroll, disablePageDragscroll } from '/client/lib/pageDragscroll';
|
||||
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
|
||||
import { BoardMultiSelection } from '/client/lib/boardMultiSelection';
|
||||
import { EscapeActions } from '/client/lib/escapeActions';
|
||||
@@ -57,6 +55,10 @@ function shareBoardWith(boardId, shareType, name, id) {
|
||||
|
||||
const DEFAULT_WORKSPACE_ICON = '📁';
|
||||
|
||||
// #5799: how many board icons to show per page in the sorted (non-custom) modes.
|
||||
// Matches the Admin Panel > People page size.
|
||||
const BOARDS_PER_PAGE = 25;
|
||||
|
||||
function getCurrentWorkspacesTree() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
const tree =
|
||||
@@ -164,6 +166,13 @@ Template.boardList.onCreated(function () {
|
||||
this.selectedMenu = new ReactiveVar(Session.get('boardListMenu') || 'starred');
|
||||
this.selectedWorkspaceIdVar = new ReactiveVar(null);
|
||||
this.workspacesTreeVar = new ReactiveVar([]);
|
||||
// #5799: free-text search by board name. When non-empty it searches across
|
||||
// ALL the user's boards (Starred, Templates, Remaining and every workspace),
|
||||
// ignoring the selected-menu filter.
|
||||
this.boardSearchVar = new ReactiveVar('');
|
||||
// #5799: server-side pagination state for the sorted (non-custom) modes.
|
||||
this.boardsPageVar = new ReactiveVar(1);
|
||||
this.pagedBoardsVar = new ReactiveVar({ ids: [], total: 0 });
|
||||
// #5850: the user's orgs/teams that have the per-org/team Shared Templates
|
||||
// flag set (plus email domains), fetched via a non-admin server method since
|
||||
// the org/team publications are admin-only. Gates the drag-to-share targets.
|
||||
@@ -262,6 +271,50 @@ Template.boardList.onCreated(function () {
|
||||
}
|
||||
});
|
||||
|
||||
// #5799: reset to the first page whenever the active filter changes (search
|
||||
// text, selected menu/workspace or sort mode). Depends only on those signals,
|
||||
// not on boardsPageVar, so paging next/prev does not reset itself.
|
||||
this.autorun(() => {
|
||||
this.boardSearchVar.get();
|
||||
this.selectedMenu.get();
|
||||
const cu = ReactiveCache.getCurrentUser();
|
||||
if (cu && typeof cu.getAllBoardsSortBy === 'function') {
|
||||
cu.getAllBoardsSortBy();
|
||||
}
|
||||
this.boardsPageVar.set(1);
|
||||
});
|
||||
|
||||
// #5799: fetch the current page of boards from the server for the sorted
|
||||
// (non-custom) modes, so only that page of icons is rendered. Custom (manual
|
||||
// drag order) stays unpaginated client-side so drag-reordering keeps working.
|
||||
// Uses the effective current user server-side, so it also works under
|
||||
// GlobalAdmin impersonation.
|
||||
this.autorun(() => {
|
||||
const cu = ReactiveCache.getCurrentUser();
|
||||
const sortBy =
|
||||
cu && typeof cu.getAllBoardsSortBy === 'function'
|
||||
? cu.getAllBoardsSortBy()
|
||||
: 'custom';
|
||||
if (sortBy === 'custom') {
|
||||
this.pagedBoardsVar.set({ ids: [], total: 0 });
|
||||
return;
|
||||
}
|
||||
const search = (this.boardSearchVar.get() || '').trim();
|
||||
const menu = this.selectedMenu.get();
|
||||
const page = this.boardsPageVar.get();
|
||||
Meteor.call(
|
||||
'getAllBoardsPage',
|
||||
{ search, sortBy, menu, page, perPage: BOARDS_PER_PAGE },
|
||||
(err, res) => {
|
||||
if (err) {
|
||||
console.error('getAllBoardsPage failed:', err);
|
||||
return;
|
||||
}
|
||||
if (res) this.pagedBoardsVar.set(res);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// The templates-container board is no longer auto-created at signup (#2339,
|
||||
// #5850), and it is no longer auto-created when the user merely opens the
|
||||
// Templates sub-view either: opening the view must not leave behind an empty
|
||||
@@ -274,12 +327,9 @@ Template.boardList.onCreated(function () {
|
||||
});
|
||||
|
||||
Template.boardList.onRendered(function () {
|
||||
// Enable drag-to-scroll on the All Boards page the same way the board
|
||||
// canvas/swimlanes do. On desktop the page scroll container is <body>, so we
|
||||
// tag it via enablePageDragscroll(); the .board-list.dragscroll element only
|
||||
// becomes a scroll container in mobile view, where its own class handles it.
|
||||
// Finger drag-scrolling is handled by dragscrollTouch.js.
|
||||
enablePageDragscroll();
|
||||
// Drag-to-scroll on the All Boards page is handled centrally for all
|
||||
// non-board pages by defaultLayout's route-aware autorun
|
||||
// (see client/components/main/layouts.js).
|
||||
|
||||
// jQuery sortable is disabled in favor of HTML5 drag-and-drop for space management
|
||||
// The old sortable code has been removed to prevent conflicts
|
||||
@@ -325,13 +375,6 @@ Template.boardList.onRendered(function () {
|
||||
*/
|
||||
});
|
||||
|
||||
Template.boardList.onDestroyed(function () {
|
||||
// Stop tagging <body> as drag-scrollable when leaving the All Boards page so
|
||||
// other layouts (e.g. a board view, whose canvas does its own dragscroll) are
|
||||
// not affected.
|
||||
disablePageDragscroll();
|
||||
});
|
||||
|
||||
Template.boardList.helpers({
|
||||
userHasTeams() {
|
||||
if (ReactiveCache.getCurrentUser()?.teams?.length > 0) return true;
|
||||
@@ -486,27 +529,53 @@ Template.boardList.helpers({
|
||||
|
||||
const boards = ReactiveCache.getBoards(query, {});
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
|
||||
// #5799: in a sorted (non-custom) mode the server already computed the
|
||||
// current page (filtered by menu/search and sorted), so render exactly that
|
||||
// ordered page of board icons. Custom (manual drag order) falls through to
|
||||
// the unpaginated client-side path below so drag-reordering keeps working.
|
||||
const sortMode =
|
||||
currentUser && typeof currentUser.getAllBoardsSortBy === 'function'
|
||||
? currentUser.getAllBoardsSortBy()
|
||||
: 'custom';
|
||||
if (sortMode !== 'custom') {
|
||||
const paged = tpl.pagedBoardsVar.get();
|
||||
return (paged.ids || [])
|
||||
.map((id) => ReactiveCache.getBoard(id))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
let list = boards;
|
||||
// Apply left menu filtering
|
||||
const sel = tpl.selectedMenu.get();
|
||||
const assignments =
|
||||
(currentUser &&
|
||||
currentUser.profile &&
|
||||
currentUser.profile.boardWorkspaceAssignments) ||
|
||||
{};
|
||||
if (sel === 'starred') {
|
||||
// Starred boards are always visible in Starred.
|
||||
list = list.filter((b) => currentUser && currentUser.hasStarred(b._id));
|
||||
} else if (sel === 'templates') {
|
||||
list = list.filter((b) => b.type === 'template-container');
|
||||
} else if (sel === 'remaining') {
|
||||
// Remaining only shows boards not assigned to any workspace.
|
||||
list = list.filter(
|
||||
(b) => !assignments[b._id] && b.type !== 'template-container',
|
||||
);
|
||||
|
||||
// #5799: when a board-name search is active, search across ALL the user's
|
||||
// boards (every menu/workspace) by title and skip the menu filter, so a
|
||||
// board in any category — Starred, Templates, Remaining or a (sub)workspace
|
||||
// — is found from a single search box.
|
||||
const search = (tpl.boardSearchVar.get() || '').trim().toLowerCase();
|
||||
if (search) {
|
||||
list = list.filter((b) => (b.title || '').toLowerCase().includes(search));
|
||||
} else {
|
||||
// Workspace view includes all boards in that workspace, including starred.
|
||||
list = list.filter((b) => assignments[b._id] === sel);
|
||||
// Apply left menu filtering
|
||||
const sel = tpl.selectedMenu.get();
|
||||
if (sel === 'starred') {
|
||||
// Starred boards are always visible in Starred.
|
||||
list = list.filter((b) => currentUser && currentUser.hasStarred(b._id));
|
||||
} else if (sel === 'templates') {
|
||||
list = list.filter((b) => b.type === 'template-container');
|
||||
} else if (sel === 'remaining') {
|
||||
// Remaining only shows boards not assigned to any workspace.
|
||||
list = list.filter(
|
||||
(b) => !assignments[b._id] && b.type !== 'template-container',
|
||||
);
|
||||
} else {
|
||||
// Workspace view includes all boards in that workspace, including starred.
|
||||
list = list.filter((b) => assignments[b._id] === sel);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentUser && typeof currentUser.sortBoardsForUser === 'function') {
|
||||
@@ -650,6 +719,46 @@ Template.boardList.helpers({
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
return currentUser && !currentUser.isCommentOnly();
|
||||
},
|
||||
// #5799: current board-name search text (for the input value and the clear button).
|
||||
boardSearch() {
|
||||
return Template.instance().boardSearchVar.get();
|
||||
},
|
||||
// #5799: pagination controls (only shown in the sorted, non-custom modes).
|
||||
boardsPaginationActive() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
const sortMode =
|
||||
currentUser && typeof currentUser.getAllBoardsSortBy === 'function'
|
||||
? currentUser.getAllBoardsSortBy()
|
||||
: 'custom';
|
||||
if (sortMode === 'custom') return false;
|
||||
const total = Template.instance().pagedBoardsVar.get().total || 0;
|
||||
return total > BOARDS_PER_PAGE;
|
||||
},
|
||||
boardsCurrentPage() {
|
||||
return Template.instance().boardsPageVar.get();
|
||||
},
|
||||
boardsTotalPages() {
|
||||
const total = Template.instance().pagedBoardsVar.get().total || 0;
|
||||
return Math.max(1, Math.ceil(total / BOARDS_PER_PAGE));
|
||||
},
|
||||
hasBoardsPrevPage() {
|
||||
return Template.instance().boardsPageVar.get() > 1;
|
||||
},
|
||||
hasBoardsNextPage() {
|
||||
const tpl = Template.instance();
|
||||
const total = tpl.pagedBoardsVar.get().total || 0;
|
||||
const totalPages = Math.max(1, Math.ceil(total / BOARDS_PER_PAGE));
|
||||
return tpl.boardsPageVar.get() < totalPages;
|
||||
},
|
||||
// #5799: current All Boards sort mode ('custom' | 'title-asc' | 'title-desc').
|
||||
isBoardsSort(mode) {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
const current =
|
||||
currentUser && typeof currentUser.getAllBoardsSortBy === 'function'
|
||||
? currentUser.getAllBoardsSortBy()
|
||||
: 'custom';
|
||||
return current === mode;
|
||||
},
|
||||
hasBoardsSelected() {
|
||||
return BoardMultiSelection.count() > 0;
|
||||
},
|
||||
@@ -745,6 +854,41 @@ Template.boardList.events({
|
||||
Popup.open('createBoard')(evt);
|
||||
}
|
||||
},
|
||||
// #5799: choose how the All Boards page is sorted.
|
||||
'click .js-open-boards-sort': Popup.open('boardsSort'),
|
||||
// #5799: search boards by name across all categories.
|
||||
'input .js-board-search-input'(evt, tpl) {
|
||||
tpl.boardSearchVar.set(evt.currentTarget.value);
|
||||
},
|
||||
'keydown .js-board-search-input'(evt, tpl) {
|
||||
// Esc clears the search.
|
||||
if (evt.keyCode === 27) {
|
||||
tpl.boardSearchVar.set('');
|
||||
evt.currentTarget.value = '';
|
||||
}
|
||||
},
|
||||
'click .js-board-search-clear'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
tpl.boardSearchVar.set('');
|
||||
const input = tpl.find('.js-board-search-input');
|
||||
if (input) {
|
||||
input.value = '';
|
||||
input.focus();
|
||||
}
|
||||
},
|
||||
// #5799: board grid pagination (sorted modes only).
|
||||
'click .js-boards-prev-page'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
const page = tpl.boardsPageVar.get();
|
||||
if (page > 1) tpl.boardsPageVar.set(page - 1);
|
||||
},
|
||||
'click .js-boards-next-page'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
const total = tpl.pagedBoardsVar.get().total || 0;
|
||||
const totalPages = Math.max(1, Math.ceil(total / BOARDS_PER_PAGE));
|
||||
const page = tpl.boardsPageVar.get();
|
||||
if (page < totalPages) tpl.boardsPageVar.set(page + 1);
|
||||
},
|
||||
'click .js-star-board'(evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
@@ -1157,6 +1301,30 @@ Template.boardList.events({
|
||||
},
|
||||
});
|
||||
|
||||
// #5799: All Boards sort popup — pick custom (manual drag order) or
|
||||
// alphabetical A→Z / Z→A. The choice is stored per user.
|
||||
Template.boardsSortPopup.helpers({
|
||||
isBoardsSort(mode) {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
const current =
|
||||
currentUser && typeof currentUser.getAllBoardsSortBy === 'function'
|
||||
? currentUser.getAllBoardsSortBy()
|
||||
: 'custom';
|
||||
return current === mode;
|
||||
},
|
||||
});
|
||||
|
||||
Template.boardsSortPopup.events({
|
||||
'click .js-boards-sort'(evt) {
|
||||
evt.preventDefault();
|
||||
const mode = evt.currentTarget.getAttribute('data-sort');
|
||||
if (mode) {
|
||||
Meteor.call('setAllBoardsSortBy', mode);
|
||||
}
|
||||
Popup.back();
|
||||
},
|
||||
});
|
||||
|
||||
Template.workspaceActionsPopup.helpers({
|
||||
workspaceName() {
|
||||
return this.name || '';
|
||||
|
||||
@@ -290,7 +290,7 @@ Template.attachmentGallery.helpers({
|
||||
return filesCursor.each();
|
||||
},
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
fileSize(size) {
|
||||
const ret = filesize(size);
|
||||
|
||||
@@ -504,16 +504,21 @@ input[type="submit"].attachment-add-link-submit {
|
||||
inset-inline-start: 0 !important;
|
||||
inset-inline-end: 0 !important;
|
||||
bottom: 0 !important;
|
||||
z-index: 100 !important;
|
||||
/* #6419: the open card is a full-screen takeover on mobile, so it must sit
|
||||
ABOVE the app header bar (#header-main-bar is z-index 1000) — otherwise the
|
||||
header overlaps the top of the card and hides the close (X) button, making
|
||||
it impossible to close the card. Kept below popups (z-index 2000+) so card
|
||||
menus / date pickers still open over the card. */
|
||||
z-index: 1100 !important;
|
||||
height: 100vh !important;
|
||||
max-height: 100vh !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Ensure card details are above everything on mobile */
|
||||
/* Ensure card details are above the app header on mobile (#6419) */
|
||||
body.mobile-mode .card-details {
|
||||
z-index: 100 !important;
|
||||
z-index: 1100 !important;
|
||||
width: 100vw !important;
|
||||
inset-inline-start: 0 !important;
|
||||
inset-inline-end: 0 !important;
|
||||
@@ -601,7 +606,8 @@ body.mobile-mode .card-details {
|
||||
inset-inline-start: 0 !important;
|
||||
inset-inline-end: 0 !important;
|
||||
bottom: 0 !important;
|
||||
z-index: 100 !important;
|
||||
/* #6419: above the app header (#header-main-bar z-index 1000), below popups. */
|
||||
z-index: 1100 !important;
|
||||
height: 100vh !important;
|
||||
max-height: 100vh !important;
|
||||
border-radius: 0 !important;
|
||||
@@ -1081,3 +1087,14 @@ body.mobile-mode .card-details .card-details-header .close-card-details-mobile-w
|
||||
line-height: 1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* #6431 follow-up: cover image on the opened card (Card Settings > Cover image
|
||||
on card, opt-in). Mirrors the minicard cover but taller. */
|
||||
.card-details-cover {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
height: 200px;
|
||||
user-select: none;
|
||||
margin: 8px 0;
|
||||
}
|
||||
@@ -112,6 +112,11 @@ template(name="cardDetails")
|
||||
i.fa.fa-check
|
||||
span {{_ 'upload-completed'}}
|
||||
|
||||
//- #6431 follow-up: cover image on the opened card (opt-in, OFF by default).
|
||||
if currentBoard.allowsCoverAttachmentOnCard
|
||||
if cover
|
||||
.card-details-cover(style="background-image: url('{{cover.link 'original'}}');")
|
||||
|
||||
.card-details-left
|
||||
|
||||
.card-details-items
|
||||
@@ -684,6 +689,14 @@ template(name="cardDetails")
|
||||
.card-checklist-attachmentGallery.card-checklists
|
||||
if currentBoard.allowsChecklists
|
||||
hr
|
||||
//- Total checklist item count (finished/total) shown above all
|
||||
checklists. The board flag is a cheap boolean, so when it is off
|
||||
the checklist counting below is never evaluated.
|
||||
if currentBoard.allowsChecklistCountBadgeOnCard
|
||||
if checklists.length
|
||||
h3.card-details-item-title
|
||||
i.fa.fa-check-square-o
|
||||
| {{checklistFinishedCount}}/{{checklistItemCount}}
|
||||
+checklists(cardId = _id card = this)
|
||||
if currentBoard.allowsSubtasks
|
||||
hr
|
||||
@@ -693,6 +706,8 @@ template(name="cardDetails")
|
||||
h3.card-details-item-title
|
||||
i.fa.fa-paperclip
|
||||
| {{_ 'attachments'}}
|
||||
if currentBoard.allowsAttachmentCountOnCard
|
||||
| ({{attachments.length}})
|
||||
if Meteor.settings.public.attachmentsUploadMaxSize
|
||||
| {{_ 'max-upload-filesize'}} {{Meteor.settings.public.attachmentsUploadMaxSize}}
|
||||
br
|
||||
|
||||
@@ -51,6 +51,7 @@ import Lists from '/models/lists';
|
||||
import CardComments from '/models/cardComments';
|
||||
import { ALLOWED_COLORS } from '/config/const';
|
||||
import { uniqBy } from '/imports/lib/collectionHelpers';
|
||||
import { memberTargetBoardId } from '/models/lib/linkedCardMembers';
|
||||
import { UserAvatar } from '../users/userAvatar';
|
||||
import { Filter } from '/client/lib/filter';
|
||||
import { BoardSwimlaneListCardDialog } from '/client/lib/dialogWithBoardSwimlaneListCard';
|
||||
@@ -627,18 +628,24 @@ Template.cardDetails.helpers({
|
||||
|
||||
showVotingButtons() {
|
||||
const card = Template.currentData();
|
||||
// #6420: currentUser was referenced but never defined here, so the helper
|
||||
// threw "ReferenceError: currentUser is not defined" on every card render and
|
||||
// the voting buttons disappeared. Define it and guard the board-member call.
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
return (
|
||||
(currentUser.isBoardMember() ||
|
||||
(currentUser && card.voteAllowNonBoardMembers())) &&
|
||||
currentUser &&
|
||||
(currentUser.isBoardMember() || card.voteAllowNonBoardMembers()) &&
|
||||
!card.expiredVote()
|
||||
);
|
||||
},
|
||||
|
||||
showPlanningPokerButtons() {
|
||||
const card = Template.currentData();
|
||||
// #6420: same as showVotingButtons — currentUser was undefined here.
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
return (
|
||||
(currentUser.isBoardMember() ||
|
||||
(currentUser && card.pokerAllowNonBoardMembers())) &&
|
||||
currentUser &&
|
||||
(currentUser.isBoardMember() || card.pokerAllowNonBoardMembers()) &&
|
||||
!card.expiredPoker()
|
||||
);
|
||||
},
|
||||
@@ -853,17 +860,25 @@ Template.cardDetails.events({
|
||||
event.preventDefault();
|
||||
const description = tpl.find('.js-new-description-input').value;
|
||||
const card = Template.currentData();
|
||||
await card.setDescription(description);
|
||||
// #5809: surface a visible error instead of failing silently — e.g. editing
|
||||
// a linked card whose target is on a board the user cannot write to gets a
|
||||
// permission denial that otherwise vanished with no feedback.
|
||||
try {
|
||||
await card.setDescription(description);
|
||||
} catch (error) {
|
||||
alert(error?.reason || error?.message || 'Failed to save description');
|
||||
}
|
||||
},
|
||||
async 'submit .js-card-details-title'(event, tpl) {
|
||||
event.preventDefault();
|
||||
const titleInput = tpl.find('.js-edit-card-title');
|
||||
const title = titleInput ? titleInput.value.trim() : '';
|
||||
const card = Template.currentData();
|
||||
if (title) {
|
||||
await card.setTitle(title);
|
||||
} else {
|
||||
await card.setTitle('');
|
||||
// #5809: surface a visible error instead of failing silently (see above).
|
||||
try {
|
||||
await card.setTitle(title || '');
|
||||
} catch (error) {
|
||||
alert(error?.reason || error?.message || 'Failed to save title');
|
||||
}
|
||||
},
|
||||
'submit .js-card-details-assigner'(event, tpl) {
|
||||
@@ -1402,7 +1417,7 @@ Template.cardDetailsActionsPopup.helpers({
|
||||
},
|
||||
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
|
||||
showListOnMinicard() {
|
||||
@@ -1492,9 +1507,13 @@ Template.editCardTitleForm.events({
|
||||
});
|
||||
|
||||
Template.cardMembersPopup.onCreated(function () {
|
||||
let currBoard = Utils.getCurrentBoard();
|
||||
let members = currBoard.activeMembers();
|
||||
this.members = new ReactiveVar(members);
|
||||
// #4965: only the filter term is stored reactively; the candidate list is
|
||||
// derived reactively in the members() helper (via filterMembers, which reads
|
||||
// board.activeMembers() on each call). Previously the list was snapshotted
|
||||
// once here, so a member added to the board after the popup opened — or whose
|
||||
// user document finished loading after open — was missing until the popup was
|
||||
// reopened.
|
||||
this.filterTerm = new ReactiveVar('');
|
||||
});
|
||||
|
||||
Template.cardMembersPopup.events({
|
||||
@@ -1506,8 +1525,7 @@ Template.cardMembersPopup.events({
|
||||
event.preventDefault();
|
||||
},
|
||||
'keyup .card-members-filter'(event) {
|
||||
const members = filterMembers(event.target.value);
|
||||
Template.instance().members.set(members);
|
||||
Template.instance().filterTerm.set(event.target.value);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1521,7 +1539,7 @@ Template.cardMembersPopup.helpers({
|
||||
},
|
||||
|
||||
members() {
|
||||
const members = Template.instance().members.get();
|
||||
const members = filterMembers(Template.instance().filterTerm.get());
|
||||
const uniqueMembers = uniqBy(members, 'userId');
|
||||
return [...uniqueMembers].sort((a, b) => {
|
||||
const userA = ReactiveCache.getUser(a.userId);
|
||||
@@ -1640,7 +1658,25 @@ Template.cardLocationsPopup.events({
|
||||
});
|
||||
|
||||
const filterMembers = (filterTerm) => {
|
||||
let currBoard = Utils.getCurrentBoard();
|
||||
// #5676: for a linked card the members live on the REAL card it points at,
|
||||
// which may be on another board (that is what getMembers()/toggleMember() act
|
||||
// on). Offer that board's members instead of the viewing board's, so the
|
||||
// picker is consistent with where membership is actually stored. Falls back to
|
||||
// the current board for normal cards or when the real card isn't loaded yet.
|
||||
const card = getCurrentCardFromContext();
|
||||
let currBoard = null;
|
||||
if (card) {
|
||||
const boardId = memberTargetBoardId(card, id => ReactiveCache.getCard(id));
|
||||
if (boardId) {
|
||||
currBoard = ReactiveCache.getBoard(boardId);
|
||||
}
|
||||
}
|
||||
if (!currBoard) {
|
||||
currBoard = Utils.getCurrentBoard();
|
||||
}
|
||||
if (!currBoard) {
|
||||
return [];
|
||||
}
|
||||
let members = currBoard.activeMembers();
|
||||
|
||||
if (filterTerm) {
|
||||
@@ -2076,6 +2112,11 @@ Template.cardMorePopup.onCreated(function () {
|
||||
const cardId = getCardId();
|
||||
this.currentCard = Cards.findOne(cardId);
|
||||
this.parentBoard = new ReactiveVar(null);
|
||||
// #3745: tracks whether the selected parent board's cards have finished
|
||||
// loading. The card list stays empty until the subscription is ready, so it
|
||||
// is no longer blank the first time another board is picked (the cards()
|
||||
// helper queries minimongo, which was empty before the subscription arrived).
|
||||
this.parentBoardReady = new ReactiveVar(true);
|
||||
this.parentCard = this.currentCard?.parentCard();
|
||||
if (this.parentCard) {
|
||||
const list = $('.js-field-parent-card');
|
||||
@@ -2114,7 +2155,10 @@ Template.cardMorePopup.helpers({
|
||||
cards() {
|
||||
const tpl = Template.instance();
|
||||
const currentId = getCardId();
|
||||
if (tpl.parentBoard.get()) {
|
||||
// #3745: don't list cards until the selected board's subscription is ready,
|
||||
// otherwise the first open shows an empty list. Depending on parentBoardReady
|
||||
// also re-runs this helper once the data has arrived.
|
||||
if (tpl.parentBoard.get() && tpl.parentBoardReady.get()) {
|
||||
const ret = ReactiveCache.getCards({
|
||||
boardId: tpl.parentBoard.get(),
|
||||
_id: { $ne: currentId },
|
||||
@@ -2179,7 +2223,14 @@ Template.cardMorePopup.events({
|
||||
if (selection === 'none') {
|
||||
tpl.parentBoard.set(null);
|
||||
} else {
|
||||
Meteor.subscribe('board', $(event.currentTarget).val(), false);
|
||||
// #3745: wait for the board subscription to be ready before showing its
|
||||
// cards, so the parent-card list is populated on the first selection.
|
||||
tpl.parentBoardReady.set(false);
|
||||
Meteor.subscribe('board', selection, false, {
|
||||
onReady() {
|
||||
tpl.parentBoardReady.set(true);
|
||||
},
|
||||
});
|
||||
tpl.parentBoard.set(selection);
|
||||
list.prop('disabled', false);
|
||||
}
|
||||
|
||||
@@ -214,7 +214,9 @@ Template.checklists.events({
|
||||
'click .js-delete-checklist-item': Popup.afterConfirm('checklistItemDelete', function () {
|
||||
Popup.back();
|
||||
const item = this?.item || this;
|
||||
if (item && item._id) {
|
||||
// #3252: guard against removing a doc already evicted from Minimongo (heavy
|
||||
// archive/delete churn), which throws "Removed nonexistent document".
|
||||
if (item && item._id && ChecklistItems.findOne(item._id)) {
|
||||
ChecklistItems.remove(item._id);
|
||||
}
|
||||
}),
|
||||
@@ -258,7 +260,8 @@ Template.checklistActionsPopup.events({
|
||||
'click .js-delete-checklist': Popup.afterConfirm('checklistDelete', function () {
|
||||
Popup.back(2);
|
||||
const checklist = this.checklist;
|
||||
if (checklist && checklist._id) {
|
||||
// #3252: see js-delete-checklist-item — avoid "Removed nonexistent document".
|
||||
if (checklist && checklist._id && Checklists.findOne(checklist._id)) {
|
||||
Checklists.remove(checklist._id);
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -119,6 +119,22 @@
|
||||
margin-inline-end: 0.4vw;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
/* Keep text labels on a single compact line. The label name is rendered
|
||||
inside a `.viewer`, whose global `min-height: 2.5vh` otherwise forces every
|
||||
label to roughly double height on the minicard. */
|
||||
.minicard .minicard-labels .card-label {
|
||||
min-height: 0;
|
||||
padding: 1px 6px;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.minicard .minicard-labels .card-label .viewer {
|
||||
min-height: 0;
|
||||
display: inline;
|
||||
}
|
||||
.minicard .minicard-labels .card-label .viewer p {
|
||||
display: inline;
|
||||
}
|
||||
.minicard .minicard-labels-no-text {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -215,6 +215,15 @@ template(name="minicard")
|
||||
i.fa.fa-globe
|
||||
span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}}
|
||||
//{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down
|
||||
//- #6431: compact checklist item-count badge (finished/total). The outer
|
||||
guard is a cheap boolean, so when the setting is off the checklist
|
||||
counting below never runs.
|
||||
if showChecklistCountBadge
|
||||
if checklists.length
|
||||
.badge(class="{{#if checklistFinished}}is-finished{{/if}}" title="{{_ 'checklists'}}")
|
||||
span.badge-icon
|
||||
i.fa.fa-check-square-o
|
||||
span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}}
|
||||
if currentBoard.allowsCardSortingByNumber
|
||||
if currentBoard.allowsCardSortingByNumberOnMinicard
|
||||
.badge
|
||||
|
||||
@@ -144,6 +144,14 @@ Template.minicard.helpers({
|
||||
const board = this.board();
|
||||
return getMinicardFlag(board, 'allowsSubtasksOnMinicard', 'allowsSubtasks', true);
|
||||
},
|
||||
// #6431: compact checklist item-count badge (finished/total) on the minicard.
|
||||
// Opt-in, OFF by default. Only a cheap boolean read here; the actual checklist
|
||||
// counting (checklistFinishedCount/checklistItemCount) is gated behind this in
|
||||
// the template, so a board with the badge off spends no resources counting.
|
||||
showChecklistCountBadge() {
|
||||
const board = this.board();
|
||||
return !!(board && board.allowsChecklistCountBadgeOnMinicard);
|
||||
},
|
||||
|
||||
hiddenMinicardLabelText() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
@@ -209,7 +217,11 @@ Template.minicard.helpers({
|
||||
// Show checklist if either:
|
||||
// 1. Board-wide setting is enabled, OR
|
||||
// 2. This specific checklist has the setting enabled
|
||||
if (currentBoard.allowsChecklistAtMinicard || checklist.showChecklistAtMinicard) {
|
||||
// #5565: use the same board field the sidebar toggle writes
|
||||
// (`allowsChecklistsOnMinicard`); this previously checked a different,
|
||||
// UI-less field (`allowsChecklistAtMinicard`), so the board toggle to show
|
||||
// checklists on minicards had no effect.
|
||||
if (currentBoard.allowsChecklistsOnMinicard || checklist.showChecklistAtMinicard) {
|
||||
visibleChecklists.push(checklist);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -78,7 +78,7 @@ Template.subtasks.onCreated(function () {
|
||||
|
||||
Template.subtasks.helpers({
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
toggleDeleteDialog() {
|
||||
return Template.instance().toggleDeleteDialog;
|
||||
@@ -115,7 +115,7 @@ Template.subtaskItemDetail.events({
|
||||
|
||||
Template.subtaskActionsPopup.helpers({
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -155,6 +155,6 @@ Template.editSubtaskItemForm.helpers({
|
||||
return ReactiveCache.getUser(this.userId);
|
||||
},
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
});
|
||||
@@ -135,10 +135,14 @@ body.list-resizing-active * {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
/* Ensure the width persists after resize is complete */
|
||||
.js-swimlane .list[style*="--list-width"],
|
||||
.dragscroll .list[style*="--list-width"],
|
||||
[id^="swimlane-"] .list[style*="--list-width"] {
|
||||
/* Ensure the width persists after resize is complete.
|
||||
Exclude collapsed lists: a collapsed list keeps its inline --list-width custom
|
||||
property, and without :not() this !important rule overrode the 30px collapsed
|
||||
width, so collapsing a list that had a custom width left it full width.
|
||||
See https://github.com/wekan/wekan/issues/5892 */
|
||||
.js-swimlane .list[style*="--list-width"]:not(.list-collapsed),
|
||||
.dragscroll .list[style*="--list-width"]:not(.list-collapsed),
|
||||
[id^="swimlane-"] .list[style*="--list-width"]:not(.list-collapsed) {
|
||||
/* Maintain the width after resize */
|
||||
width: var(--list-width, auto) !important;
|
||||
min-width: var(--list-width, auto) !important;
|
||||
@@ -253,10 +257,12 @@ body.list-resizing-active * {
|
||||
height: 15vh;
|
||||
}
|
||||
.list.list-collapsed {
|
||||
flex: none;
|
||||
min-width: 30px;
|
||||
max-width: 30px;
|
||||
width: 30px;
|
||||
flex: none !important;
|
||||
/* !important so a custom per-list --list-width never keeps a collapsed list
|
||||
full width (see https://github.com/wekan/wekan/issues/5892) */
|
||||
min-width: 30px !important;
|
||||
max-width: 30px !important;
|
||||
width: 30px !important;
|
||||
min-height: 60vh;
|
||||
height: 60vh;
|
||||
overflow: visible;
|
||||
|
||||
@@ -54,8 +54,78 @@ function readAnonListWidth(boardId, listId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// #5729 Fixed (same) width for all lists.
|
||||
//
|
||||
// A per-viewer, per-board toggle that makes EVERY list render at a single
|
||||
// shared value, and makes dragging any list update that one value (so all lists
|
||||
// change together). Stored exactly like the personal width:
|
||||
// * logged-in -> profile.fixedListWidthBoards / profile.fixedListWidths
|
||||
// * anonymous -> localStorage (mirrors readAnonListWidth)
|
||||
// ---------------------------------------------------------------------------
|
||||
function readAnonFixedListWidthEnabled(boardId) {
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-fixed-list-width-enabled');
|
||||
if (stored) {
|
||||
const flags = JSON.parse(stored);
|
||||
return flags[boardId] === true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error reading fixed list width flag from localStorage:', e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function readAnonFixedListWidth(boardId) {
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-fixed-list-width');
|
||||
if (stored) {
|
||||
const widths = JSON.parse(stored);
|
||||
const w = widths[boardId];
|
||||
if (typeof w === 'number' && w >= MIN_LIST_WIDTH) return w;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error reading fixed list width from localStorage:', e);
|
||||
}
|
||||
return DEFAULT_LIST_WIDTH;
|
||||
}
|
||||
|
||||
// Whether the current viewer has fixed width mode enabled for this board.
|
||||
function isFixedListWidth(boardId) {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (user) {
|
||||
return !!user.isFixedListWidth(boardId);
|
||||
}
|
||||
return readAnonFixedListWidthEnabled(boardId);
|
||||
}
|
||||
|
||||
// The single width every list should use when fixed width mode is enabled.
|
||||
function fixedListWidthValue(boardId) {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (user) {
|
||||
return user.getFixedListWidth(boardId);
|
||||
}
|
||||
return readAnonFixedListWidth(boardId);
|
||||
}
|
||||
|
||||
// Persist the single fixed width for the whole board (anon localStorage).
|
||||
function saveAnonFixedListWidth(boardId, width) {
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-fixed-list-width');
|
||||
const widths = stored ? JSON.parse(stored) : {};
|
||||
widths[boardId] = width;
|
||||
localStorage.setItem('wekan-fixed-list-width', JSON.stringify(widths));
|
||||
} catch (e) {
|
||||
console.warn('Error saving fixed list width to localStorage:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function effectiveListWidth(list) {
|
||||
if (!list) return DEFAULT_LIST_WIDTH;
|
||||
// #5729 In fixed width mode every list renders at the single shared value.
|
||||
if (isFixedListWidth(list.boardId)) {
|
||||
return fixedListWidthValue(list.boardId);
|
||||
}
|
||||
const shared =
|
||||
typeof list.width === 'number' && list.width >= MIN_LIST_WIDTH
|
||||
? list.width
|
||||
@@ -80,6 +150,9 @@ function effectiveListWidth(list) {
|
||||
function canResizeList(list) {
|
||||
if (!list) return false;
|
||||
if (effectiveAutoWidth(list.boardId)) return false;
|
||||
// #5729 Fixed width is the viewer's own per-board setting, so any viewer
|
||||
// (logged-in or anonymous) may drag-resize; the change applies to all lists.
|
||||
if (isFixedListWidth(list.boardId)) return true;
|
||||
if (isPersonalListWidth(list.boardId)) return true;
|
||||
return Utils.canModifyBoard();
|
||||
}
|
||||
@@ -90,6 +163,18 @@ function saveListWidth(list, width) {
|
||||
const boardId = list.boardId;
|
||||
const listId = list._id;
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
// #5729 In fixed width mode, persist to the single per-board value instead of
|
||||
// the per-list width, so EVERY list re-renders at the new width.
|
||||
if (isFixedListWidth(boardId)) {
|
||||
if (user) {
|
||||
Meteor.call('setFixedListWidth', boardId, width, error => {
|
||||
if (error) console.error('Error saving fixed list width:', error);
|
||||
});
|
||||
} else {
|
||||
saveAnonFixedListWidth(boardId, width);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isPersonalListWidth(boardId)) {
|
||||
if (user) {
|
||||
Meteor.call('applyListWidthToStorage', boardId, listId, width, width);
|
||||
|
||||
@@ -9,6 +9,7 @@ import Swimlanes from '/models/swimlanes';
|
||||
import { Filter } from '/client/lib/filter';
|
||||
import { MultiSelection } from '/client/lib/multiSelection';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import { isLinkableCardTarget } from '/models/lib/linkedCardTarget';
|
||||
import autosize from 'autosize';
|
||||
|
||||
// SubsManager removed for Meteor 3 migration
|
||||
@@ -324,7 +325,20 @@ Template.listBody.helpers({
|
||||
const tpl = Template.instance();
|
||||
const limit = tpl.cardlimit.get();
|
||||
const defaultSort = { sort: 1 };
|
||||
const sortBy = Session.get('sortBy') ? Session.get('sortBy') : defaultSort;
|
||||
// Session is reset on page reload; fall back to the persisted card sort so a
|
||||
// chosen sort (e.g. by due date) is remembered across reloads (#5886).
|
||||
let sortBy = Session.get('sortBy');
|
||||
if (!sortBy) {
|
||||
try {
|
||||
const stored = window.localStorage.getItem('wekan-cards-sortBy');
|
||||
if (stored) {
|
||||
sortBy = JSON.parse(stored);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (!sortBy) {
|
||||
sortBy = defaultSort;
|
||||
}
|
||||
const selector = {
|
||||
listId: Template.currentData()._id,
|
||||
archived: false,
|
||||
@@ -693,7 +707,10 @@ Template.linkCardPopup.helpers({
|
||||
archived: false,
|
||||
linkedId: { $nin: ownCardsIds },
|
||||
_id: { $nin: ownCardsIds },
|
||||
type: { $nin: ['template-card'] },
|
||||
// #5808: never offer an existing linked card/board as a link target —
|
||||
// linking to one builds a chain of linkedId pointers that renders the
|
||||
// card inaccessible. Only real cards may be linked.
|
||||
type: { $nin: ['template-card', 'cardType-linkedCard', 'cardType-linkedBoard'] },
|
||||
};
|
||||
if (tpl.selectedBoardId.get()) selector.boardId = tpl.selectedBoardId.get();
|
||||
if (tpl.selectedSwimlaneId.get()) selector.swimlaneId = tpl.selectedSwimlaneId.get();
|
||||
@@ -743,11 +760,48 @@ Template.linkCardPopup.events({
|
||||
tpl.selectedListId.set($(evt.currentTarget).val());
|
||||
},
|
||||
async 'click .js-done'(evt, tpl) {
|
||||
// LINK CARD
|
||||
// LINK CARD (or, when no card is chosen, fall back to a board-level link
|
||||
// so a whole board can be linked even when that board already has cards).
|
||||
// https://github.com/wekan/wekan/issues/5715
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
const linkedId = $('.js-select-cards option:selected').val();
|
||||
if (!linkedId) {
|
||||
const boardId = $('.js-select-boards option:selected').val();
|
||||
// No board and no card selected: nothing to link.
|
||||
if (!boardId) {
|
||||
Popup.back();
|
||||
return;
|
||||
}
|
||||
// A linkedBoard card for this board already exists; avoid duplicates.
|
||||
if (ReactiveCache.getCard({ linkedId: boardId, archived: false })) {
|
||||
Popup.back();
|
||||
return;
|
||||
}
|
||||
const boardCardNumber = await tpl.board.getNextCardNumber();
|
||||
const boardSortIndex = tpl.getSortIndex();
|
||||
const boardCardId = Cards.insert({
|
||||
title: $('.js-select-boards option:selected').text(), //dummy
|
||||
listId: tpl.listId,
|
||||
swimlaneId: tpl.swimlaneId,
|
||||
boardId: tpl.boardId,
|
||||
sort: boardSortIndex,
|
||||
type: 'cardType-linkedBoard',
|
||||
linkedId: boardId,
|
||||
cardNumber: boardCardNumber,
|
||||
});
|
||||
Filter.addException(boardCardId);
|
||||
Popup.back();
|
||||
return;
|
||||
}
|
||||
// #5808: refuse to link to a linked card/board or to a card that links back
|
||||
// to this board — those build a chain/cycle of linkedId pointers that renders
|
||||
// the cards inaccessible. The <select> already filters these out, but its
|
||||
// options can be stale, so re-check the resolved target here.
|
||||
const targetCard = ReactiveCache.getCard(linkedId);
|
||||
const ownCardsIds = tpl.board.cards().map(card => card.getRealId());
|
||||
if (!isLinkableCardTarget(targetCard, ownCardsIds)) {
|
||||
alert(TAPi18n.__('error-linked-card-not-allowed'));
|
||||
Popup.back();
|
||||
return;
|
||||
}
|
||||
@@ -924,13 +978,37 @@ Template.searchElementPopup.events({
|
||||
if (!tpl.isTemplateSearch || tpl.isCardTemplateSearch) {
|
||||
// Card insertion
|
||||
// 1. Common
|
||||
// Compute the sort index BEFORE the first await: getSortIndex() reads
|
||||
// Template.currentData(), and Blaze's current-view context is lost across
|
||||
// an await, so calling it afterwards threw "There is no current view" and
|
||||
// no card was created from the template. Capture it while still in the
|
||||
// synchronous event/view context.
|
||||
const sortIndex = tpl.getSortIndex();
|
||||
element.cardNumber = await tpl.board.getNextCardNumber();
|
||||
element.sort = tpl.getSortIndex();
|
||||
element.sort = sortIndex;
|
||||
// 1.A From template
|
||||
if (tpl.isTemplateSearch) {
|
||||
element.type = 'cardType-card';
|
||||
element.linkedId = '';
|
||||
_id = await element.copy(tpl.boardId, tpl.swimlaneId, tpl.listId);
|
||||
// #5798: instantiate the template into the CURRENT board, not the
|
||||
// templates board. For a template search tpl.boardId is the templates
|
||||
// board (it is the search source), so copying with it created the card
|
||||
// with boardId = templates board. The card still showed in the target
|
||||
// list (the list query is by listId and the templates board is
|
||||
// subscribed), but clicking it navigated to the templates board instead
|
||||
// of opening the card. The target list/swimlane were already resolved on
|
||||
// the current board.
|
||||
// Instantiate into the board that OWNS the target list (where the user
|
||||
// clicked "add card"). Deriving it from tpl.listId is reliable even when
|
||||
// Utils.getCurrentBoard() (route/session state) has not settled yet — as
|
||||
// on the slower CI production bundle, where falling back to tpl.boardId
|
||||
// wrongly created the card on the templates board.
|
||||
const targetList = ReactiveCache.getList(tpl.listId);
|
||||
const targetBoardId =
|
||||
(targetList || {}).boardId ||
|
||||
(Utils.getCurrentBoard() || {})._id ||
|
||||
tpl.boardId;
|
||||
_id = await element.copy(targetBoardId, tpl.swimlaneId, tpl.listId);
|
||||
// 1.B Linked card
|
||||
} else {
|
||||
_id = element.link(tpl.boardId, tpl.swimlaneId, tpl.listId);
|
||||
|
||||
@@ -262,6 +262,9 @@ template(name="setListWidthPopup")
|
||||
a.js-toggle-auto-width(title="{{#if isAutoWidth}}{{_ 'click-to-disable-auto-width'}}{{else}}{{_ 'click-to-enable-auto-width'}}{{/if}}")
|
||||
| {{#if isAutoWidth}}🗜️{{else}}📏{{/if}}
|
||||
span {{_ 'auto-list-width'}}
|
||||
a.js-toggle-fixed-list-width(title="{{#if isFixedListWidth}}{{_ 'click-to-disable-fixed-list-width'}}{{else}}{{_ 'click-to-enable-fixed-list-width'}}{{/if}}")
|
||||
| {{#if isFixedListWidth}}✅{{else}}⬜{{/if}}
|
||||
span {{_ 'fixed-list-width'}}
|
||||
p.list-width-scope-note {{ listWidthScopeNote }}
|
||||
|
||||
template(name="listWidthErrorPopup")
|
||||
|
||||
@@ -20,12 +20,12 @@ Template.listHeader.helpers({
|
||||
(!list.getWipLimit('enabled') ||
|
||||
list.getWipLimit('soft') ||
|
||||
!Template.instance().reachedWipLimit()) &&
|
||||
!ReactiveCache.getCurrentUser().isWorker()
|
||||
!ReactiveCache.getCurrentUser()?.isWorker()
|
||||
);
|
||||
},
|
||||
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
|
||||
starred() {
|
||||
@@ -218,7 +218,7 @@ Template.listHeader.events({
|
||||
|
||||
Template.listActionPopup.helpers({
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
|
||||
isWipLimitEnabled() {
|
||||
@@ -520,9 +520,77 @@ function isPersonalListWidth(boardId) {
|
||||
return !!(board && board.allowsPersonalListWidth);
|
||||
}
|
||||
|
||||
// #5729 Fixed (same) width for all lists is a per-viewer/per-board setting.
|
||||
// Logged-in users store it in their profile; anonymous (public board) users in
|
||||
// localStorage (mirrors the per-list anon storage in list.js).
|
||||
function readAnonFixedListWidthEnabled(boardId) {
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-fixed-list-width-enabled');
|
||||
if (stored) {
|
||||
const flags = JSON.parse(stored);
|
||||
return flags[boardId] === true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error reading fixed list width flag from localStorage:', e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function readAnonFixedListWidth(boardId) {
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-fixed-list-width');
|
||||
if (stored) {
|
||||
const widths = JSON.parse(stored);
|
||||
const w = widths[boardId];
|
||||
if (typeof w === 'number' && w >= 270) return w;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error reading fixed list width from localStorage:', e);
|
||||
}
|
||||
return 272;
|
||||
}
|
||||
|
||||
function isFixedListWidth(boardId) {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (user) return !!user.isFixedListWidth(boardId);
|
||||
return readAnonFixedListWidthEnabled(boardId);
|
||||
}
|
||||
|
||||
function fixedListWidthValue(boardId) {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (user) return user.getFixedListWidth(boardId);
|
||||
return readAnonFixedListWidth(boardId);
|
||||
}
|
||||
|
||||
function setAnonFixedListWidthEnabled(boardId, enabled) {
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-fixed-list-width-enabled');
|
||||
const flags = stored ? JSON.parse(stored) : {};
|
||||
flags[boardId] = !!enabled;
|
||||
localStorage.setItem('wekan-fixed-list-width-enabled', JSON.stringify(flags));
|
||||
} catch (e) {
|
||||
console.warn('Error saving fixed list width flag to localStorage:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function setAnonFixedListWidth(boardId, width) {
|
||||
try {
|
||||
const stored = localStorage.getItem('wekan-fixed-list-width');
|
||||
const widths = stored ? JSON.parse(stored) : {};
|
||||
widths[boardId] = width;
|
||||
localStorage.setItem('wekan-fixed-list-width', JSON.stringify(widths));
|
||||
} catch (e) {
|
||||
console.warn('Error saving fixed list width to localStorage:', e);
|
||||
}
|
||||
}
|
||||
|
||||
Template.setListWidthPopup.helpers({
|
||||
listWidthValue() {
|
||||
const list = Template.currentData();
|
||||
// #5729 In fixed width mode the input edits the single per-board value.
|
||||
if (isFixedListWidth(list.boardId)) {
|
||||
return fixedListWidthValue(list.boardId);
|
||||
}
|
||||
const shared =
|
||||
typeof list.width === 'number' && list.width >= 270 ? list.width : 272;
|
||||
if (!isPersonalListWidth(list.boardId)) {
|
||||
@@ -539,6 +607,10 @@ Template.setListWidthPopup.helpers({
|
||||
|
||||
listWidthScopeNote() {
|
||||
const list = Template.currentData();
|
||||
// #5729 In fixed width mode the note explains the all-lists behaviour.
|
||||
if (isFixedListWidth(list.boardId)) {
|
||||
return TAPi18n.__('fixed-list-width-note');
|
||||
}
|
||||
return isPersonalListWidth(list.boardId)
|
||||
? TAPi18n.__('list-width-personal-note')
|
||||
: TAPi18n.__('list-width-shared-note');
|
||||
@@ -564,6 +636,12 @@ Template.setListWidthPopup.helpers({
|
||||
}
|
||||
return Utils.canModifyBoard();
|
||||
},
|
||||
|
||||
// #5729 Whether "same width for all lists" mode is on for the current viewer.
|
||||
isFixedListWidth() {
|
||||
const list = Template.currentData();
|
||||
return isFixedListWidth(list.boardId);
|
||||
},
|
||||
});
|
||||
|
||||
Template.setListWidthPopup.events({
|
||||
@@ -581,6 +659,24 @@ Template.setListWidthPopup.events({
|
||||
}
|
||||
Popup.back();
|
||||
},
|
||||
// #5729 Toggle "same width for all lists" (fixed width) for the current
|
||||
// viewer. Enabling it turns off auto-width (the two modes are exclusive).
|
||||
'click .js-toggle-fixed-list-width'(event) {
|
||||
event.preventDefault();
|
||||
const list = Template.currentData();
|
||||
const boardId = list.boardId;
|
||||
const enabled = !isFixedListWidth(boardId);
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (user) {
|
||||
if (enabled && isPersonalListWidth(boardId) && user.isAutoWidth(boardId)) {
|
||||
user.toggleAutoWidth(boardId);
|
||||
}
|
||||
Meteor.call('setFixedListWidthEnabled', boardId, enabled);
|
||||
} else {
|
||||
setAnonFixedListWidthEnabled(boardId, enabled);
|
||||
}
|
||||
Popup.back();
|
||||
},
|
||||
'click .list-width-apply'(event, tpl) {
|
||||
const list = Template.currentData();
|
||||
const boardId = list.boardId;
|
||||
@@ -591,6 +687,16 @@ Template.setListWidthPopup.events({
|
||||
return;
|
||||
}
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
// #5729 In fixed width mode the input applies to the single per-board value.
|
||||
if (isFixedListWidth(boardId)) {
|
||||
if (user) {
|
||||
Meteor.call('setFixedListWidth', boardId, width);
|
||||
} else {
|
||||
setAnonFixedListWidth(boardId, width);
|
||||
}
|
||||
Popup.back();
|
||||
return;
|
||||
}
|
||||
if (isPersonalListWidth(boardId)) {
|
||||
if (user) {
|
||||
Meteor.call('applyListWidthToStorage', boardId, list._id, width, width);
|
||||
|
||||
@@ -496,15 +496,19 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) {
|
||||
#header #header-main-bar {
|
||||
height: 40px;
|
||||
/* #6419: taller bar so the action icons can use ~44px touch targets. */
|
||||
height: 48px;
|
||||
}
|
||||
#header #header-main-bar .board-header-btns {
|
||||
margin-top: 0px;
|
||||
}
|
||||
#header #header-main-bar .board-header-btn {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
font-size: 15px;
|
||||
/* #6419: 32px tall / 15px was below the comfortable mobile touch-target and
|
||||
readable-font minimum (~44px / 16px). Enlarged vertically + bigger font;
|
||||
width is unchanged so this does not worsen the horizontal icon crowding. */
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
font-size: 16px;
|
||||
}
|
||||
#header #header-main-bar .board-header-btn i.fa {
|
||||
line-height: 32px;
|
||||
@@ -554,7 +558,7 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile - make all text and icons 2x bigger above #content by default */
|
||||
/* Mobile - keep header text/icons at normal size (reverted #6426 oversizing) */
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 800px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
|
||||
@@ -569,7 +573,7 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
overflow: hidden !important; /* Prevent content overflow */
|
||||
}
|
||||
#header-quick-access {
|
||||
font-size: 2em !important; /* 2x bigger base font size */
|
||||
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
|
||||
}
|
||||
|
||||
#header-quick-access * {
|
||||
@@ -578,7 +582,7 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
|
||||
#header-quick-access .fa,
|
||||
#header-quick-access .icon {
|
||||
font-size: 2em !important; /* 2x bigger icons */
|
||||
font-size: 1em !important; /* Normal icon size (reverted #6426 oversizing) */
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon a {
|
||||
@@ -648,7 +652,7 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile All Boards page - make logo row elements 2x bigger */
|
||||
/* Mobile All Boards page - keep logo row at normal size (reverted #6426 oversizing) */
|
||||
@media screen and (max-width: 800px),
|
||||
screen and (max-device-width: 800px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
|
||||
@@ -656,16 +660,16 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
screen and (max-width: 800px) and (orientation: landscape) {
|
||||
.wrapper ~ #header-quick-access,
|
||||
body:not(.board-view) #header-quick-access {
|
||||
font-size: 2em !important; /* 2x bigger base font size for logo row */
|
||||
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini specific - 3x bigger for All Boards page */
|
||||
/* iPhone 12 Mini specific - keep All Boards logo row at normal size (reverted #6426) */
|
||||
@media screen and (device-width: 375px) and (device-height: 812px), /* iPhone 12 Mini exact */
|
||||
screen and (max-width: 375px) and (max-height: 812px), /* iPhone 12 Mini viewport */
|
||||
screen and (-webkit-min-device-pixel-ratio: 3) and (max-width: 375px) /* iPhone 12 Mini Retina */ {
|
||||
.wrapper ~ #header-quick-access,
|
||||
body:not(.board-view) #header-quick-access {
|
||||
font-size: 3em !important; /* 3x bigger base font size for iPhone 12 Mini All Boards page */
|
||||
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,7 +682,7 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
.wrapper ~ #header-quick-access .icon,
|
||||
body:not(.board-view) #header-quick-access .fa,
|
||||
body:not(.board-view) #header-quick-access .icon {
|
||||
font-size: 2em !important; /* 2x bigger icons in logo row */
|
||||
font-size: 1em !important; /* Normal icon size (reverted #6426 oversizing) */
|
||||
}
|
||||
|
||||
.wrapper ~ #header-quick-access .home-icon a,
|
||||
@@ -737,19 +741,19 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
}
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini specific - make header elements 3x bigger */
|
||||
/* iPhone 12 Mini specific - keep the header a normal compact single row */
|
||||
@media screen and (device-width: 375px) and (device-height: 812px), /* iPhone 12 Mini exact */
|
||||
screen and (max-width: 375px) and (max-height: 812px), /* iPhone 12 Mini viewport */
|
||||
screen and (-webkit-min-device-pixel-ratio: 3) and (max-width: 375px), /* iPhone 12 Mini Retina */
|
||||
screen and (max-width: 375px) and (orientation: portrait), /* iPhone 12 Mini portrait */
|
||||
screen and (max-width: 375px) and (orientation: landscape) /* iPhone 12 Mini landscape */ {
|
||||
#header-quick-access {
|
||||
font-size: 3em !important; /* 3x bigger base font size for iPhone 12 Mini */
|
||||
height: auto !important; /* Allow height to grow */
|
||||
min-height: 84px !important; /* Much taller minimum height for iPhone 12 Mini */
|
||||
flex-wrap: wrap !important; /* Force wrapping */
|
||||
align-items: flex-start !important; /* Align to top when wrapping */
|
||||
padding: 18px 0px !important; /* More padding for iPhone 12 Mini */
|
||||
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
|
||||
height: 48px !important;
|
||||
min-height: 48px !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: center !important;
|
||||
padding: 8px 0px !important;
|
||||
}
|
||||
|
||||
#header-quick-access * {
|
||||
@@ -758,7 +762,7 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
|
||||
#header-quick-access .fa,
|
||||
#header-quick-access .icon {
|
||||
font-size: 3em !important; /* 3x bigger icons for iPhone 12 Mini */
|
||||
font-size: 1em !important; /* Normal icon size (reverted #6426 oversizing) */
|
||||
}
|
||||
|
||||
#header-quick-access .home-icon a {
|
||||
@@ -844,7 +848,7 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
}
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini and very small screens - make header elements much larger */
|
||||
/* iPhone 12 Mini and very small screens - header element sizing */
|
||||
@media screen and (max-width: 400px) and (max-height: 900px),
|
||||
screen and (max-device-width: 400px) and (max-device-height: 900px),
|
||||
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 400px),
|
||||
@@ -908,12 +912,12 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
|
||||
/* Fallback for iPhone devices using JavaScript detection */
|
||||
.iphone-device #header-quick-access {
|
||||
font-size: 2em !important; /* 2x bigger base font size */
|
||||
height: auto !important; /* Allow height to grow */
|
||||
min-height: 48px !important; /* Minimum height for mobile */
|
||||
flex-wrap: wrap !important; /* Force wrapping */
|
||||
align-items: flex-start !important; /* Align to top when wrapping */
|
||||
padding: 8px 0px !important; /* Adjust padding for mobile */
|
||||
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
|
||||
height: 48px !important;
|
||||
min-height: 48px !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: center !important;
|
||||
padding: 8px 0px !important;
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access * {
|
||||
@@ -922,7 +926,7 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
|
||||
.iphone-device #header-quick-access .fa,
|
||||
.iphone-device #header-quick-access .icon {
|
||||
font-size: 2em !important; /* 2x bigger icons */
|
||||
font-size: 1em !important; /* Normal icon size (reverted #6426 oversizing) */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .home-icon a {
|
||||
@@ -991,14 +995,14 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini specific - JavaScript detection fallback */
|
||||
/* iPhone 12 Mini specific - JavaScript detection fallback (normal compact row) */
|
||||
.iphone-device #header-quick-access {
|
||||
font-size: 3em !important; /* 3x bigger base font size for iPhone 12 Mini */
|
||||
height: auto !important; /* Allow height to grow */
|
||||
min-height: 84px !important; /* Much taller minimum height for iPhone 12 Mini */
|
||||
flex-wrap: wrap !important; /* Force wrapping */
|
||||
align-items: flex-start !important; /* Align to top when wrapping */
|
||||
padding: 18px 0px !important; /* More padding for iPhone 12 Mini */
|
||||
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
|
||||
height: 48px !important;
|
||||
min-height: 48px !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: center !important;
|
||||
padding: 8px 0px !important;
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access * {
|
||||
@@ -1007,7 +1011,7 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
|
||||
.iphone-device #header-quick-access .fa,
|
||||
.iphone-device #header-quick-access .icon {
|
||||
font-size: 3em !important; /* 3x bigger icons for iPhone 12 Mini */
|
||||
font-size: 1em !important; /* Normal icon size (reverted #6426 oversizing) */
|
||||
}
|
||||
|
||||
.iphone-device #header-quick-access .home-icon a {
|
||||
@@ -1092,10 +1096,10 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
/* iPhone 12 Mini All Boards page - make logo row elements 3x bigger */
|
||||
/* iPhone 12 Mini All Boards page - keep logo row at normal size (reverted #6426) */
|
||||
.iphone-device .wrapper ~ #header-quick-access,
|
||||
.iphone-device body:not(.board-view) #header-quick-access {
|
||||
font-size: 3em !important; /* 3x bigger base font size for logo row */
|
||||
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access *,
|
||||
@@ -1107,7 +1111,7 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
.iphone-device .wrapper ~ #header-quick-access .icon,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .fa,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .icon {
|
||||
font-size: 2em !important; /* 2x bigger icons in logo row */
|
||||
font-size: 1em !important; /* Normal icon size (reverted #6426 oversizing) */
|
||||
}
|
||||
|
||||
.iphone-device .wrapper ~ #header-quick-access .home-icon a,
|
||||
@@ -1205,7 +1209,7 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
.iphone-device #header-quick-access,
|
||||
.iphone-device .wrapper ~ #header-quick-access,
|
||||
.iphone-device body:not(.board-view) #header-quick-access {
|
||||
font-size: 1.2em !important;
|
||||
font-size: 1em !important;
|
||||
}
|
||||
body:not(.board-view) #header-quick-access .fa,
|
||||
body:not(.board-view) #header-quick-access .icon,
|
||||
@@ -1217,7 +1221,7 @@ body:not(.board-view) #header #header-main-bar h1 {
|
||||
.iphone-device .wrapper ~ #header-quick-access .icon,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .fa,
|
||||
.iphone-device body:not(.board-view) #header-quick-access .icon {
|
||||
font-size: 1.5em !important;
|
||||
font-size: 1.2em !important;
|
||||
}
|
||||
/* Zoom (100%), drag-handle toggle and mobile/desktop toggle 30% smaller than
|
||||
the other quick-access controls at small width — for all browsers. */
|
||||
|
||||
@@ -52,6 +52,13 @@ Template.userFormsLayout.onRendered(() => {
|
||||
// gesture works the same as on the board swimlanes view.
|
||||
enablePageDragscroll();
|
||||
|
||||
// Mark the auth pages so CSS can let <body> scroll as a normal document.
|
||||
// body.mobile-mode locks <body> to position:fixed/100vh (an iOS board-view
|
||||
// anti-bounce fix); without an inner #content scroller that traps these pages
|
||||
// at viewport height, hiding the sign-up link / language selector below the
|
||||
// form (and they sit behind the on-screen keyboard). See userForm.css.
|
||||
document.body.classList.add('userform-layout');
|
||||
|
||||
Meteor.call('getAuthenticationsEnabled', (_, result) => {
|
||||
let enabledAuthenticationMethods = ['password']; // we show/hide this based on isPasswordLoginEnabled
|
||||
|
||||
@@ -200,6 +207,7 @@ Template.userFormsLayout.onDestroyed(() => {
|
||||
// Stop drag-scrolling <body> after leaving login / register (e.g. once logged
|
||||
// in) so other layouts keep their normal behaviour.
|
||||
disablePageDragscroll();
|
||||
document.body.classList.remove('userform-layout');
|
||||
});
|
||||
|
||||
Template.userFormsLayout.helpers({
|
||||
@@ -267,7 +275,12 @@ Template.main.helpers({
|
||||
Template.userFormsLayout.events({
|
||||
'change .js-userform-set-language'(event) {
|
||||
const tag = $(event.currentTarget).val();
|
||||
TAPi18n.setLanguage(tag);
|
||||
// setLanguage is async; surface a failed load instead of silently leaving
|
||||
// the UI in English (#5756).
|
||||
Promise.resolve(TAPi18n.setLanguage(tag)).catch(error => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Failed to switch language to ${tag}:`, error);
|
||||
});
|
||||
event.preventDefault();
|
||||
},
|
||||
'click #at-btn'(event, templateInstance) {
|
||||
@@ -309,6 +322,31 @@ Template.defaultLayout.onRendered(function () {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Drag-to-scroll for every whole-page layout rendered inside defaultLayout
|
||||
// (All Boards, My Cards, Due Cards, Global Search, Public, Bookmarks, Broken
|
||||
// Cards, Settings, People, Admin, Import, board Rules, …). These pages scroll
|
||||
// <body> on desktop and #content in mobile mode (where <body> is
|
||||
// position:fixed), so enablePageDragscroll() tags whichever element actually
|
||||
// scrolls. The board-canvas pages are the exception: their own
|
||||
// `.board-canvas.dragscroll` handles dragging, and the canvas does its own
|
||||
// vertical/horizontal scroll, so page-level tagging is turned off there to
|
||||
// avoid scrolling #content and the canvas at the same time. defaultLayout is
|
||||
// persistent (only its `content` swaps), so this single autorun covers every
|
||||
// route without per-template wiring. Login / Register use userFormsLayout and
|
||||
// keep their own enable/disable. Works the same in mobile and desktop mode.
|
||||
const boardCanvasRoutes = ['board', 'board-short', 'card'];
|
||||
this.autorun(() => {
|
||||
FlowRouter.watchPathChange();
|
||||
const onBoardCanvas = boardCanvasRoutes.includes(FlowRouter.getRouteName());
|
||||
Tracker.afterFlush(() => {
|
||||
if (onBoardCanvas) {
|
||||
disablePageDragscroll();
|
||||
} else {
|
||||
enablePageDragscroll();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function authentication(event, templateInstance) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CardSearchPaged } from '../../lib/cardSearch';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import { enablePageDragscroll, disablePageDragscroll } from '/client/lib/pageDragscroll';
|
||||
|
||||
Template.myCardsHeaderBar.helpers({
|
||||
myCardsSort() {
|
||||
@@ -41,15 +40,8 @@ Template.myCards.onCreated(function () {
|
||||
Meteor.subscribe('setting');
|
||||
});
|
||||
|
||||
Template.myCards.onRendered(function () {
|
||||
// My Cards renders into <body>'s page scroll area (.wrapper inside #content),
|
||||
// so enable drag-to-scroll on <body> like the All Boards page does.
|
||||
enablePageDragscroll();
|
||||
});
|
||||
|
||||
Template.myCards.onDestroyed(function () {
|
||||
disablePageDragscroll();
|
||||
});
|
||||
// Drag-to-scroll is handled centrally for all non-board pages by
|
||||
// defaultLayout's route-aware autorun (see client/components/main/layouts.js).
|
||||
|
||||
Template.myCards.helpers({
|
||||
userId() {
|
||||
|
||||
@@ -4,6 +4,7 @@ import Actions from '/models/actions';
|
||||
import Rules from '/models/rules';
|
||||
import Triggers from '/models/triggers';
|
||||
import { Utils } from '/client/lib/utils';
|
||||
import { canSelectBoardInRules } from '/models/lib/rulesBoardSelector';
|
||||
|
||||
Template.boardActions.onCreated(function () {
|
||||
this.subscribe('boards');
|
||||
@@ -11,19 +12,30 @@ Template.boardActions.onCreated(function () {
|
||||
|
||||
Template.boardActions.helpers({
|
||||
boards() {
|
||||
const ret = ReactiveCache.getBoards(
|
||||
{
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
_id: {
|
||||
$ne: ReactiveCache.getCurrentUser().getTemplatesBoardId(),
|
||||
},
|
||||
},
|
||||
// #5698: don't restrict to boards where the user is a DIRECT member — that
|
||||
// hid every board reached through an Organization / Team / email-domain
|
||||
// share (the reporter's whole board dropdown was empty, current board
|
||||
// included). Filter the already access-scoped client cache with the same
|
||||
// visibility rule as Boards.userBoards() so org/team/domain boards show too.
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (!user) return [];
|
||||
const ctx = {
|
||||
userId: user._id,
|
||||
orgIds: typeof user.orgIds === 'function' ? user.orgIds() : [],
|
||||
teamIds: typeof user.teamIds === 'function' ? user.teamIds() : [],
|
||||
emailDomains:
|
||||
typeof user.emailDomains === 'function' ? user.emailDomains() : [],
|
||||
};
|
||||
const templatesBoardId = user.getTemplatesBoardId();
|
||||
const boards = ReactiveCache.getBoards(
|
||||
{ archived: false },
|
||||
{
|
||||
sort: { sort: 1 /* boards default sorting */ },
|
||||
},
|
||||
);
|
||||
return ret;
|
||||
return boards.filter(board =>
|
||||
canSelectBoardInRules(board, ctx, templatesBoardId),
|
||||
);
|
||||
},
|
||||
|
||||
loadingBoardsLabel() {
|
||||
|
||||
@@ -364,6 +364,10 @@ Template.boardsReport.helpers({
|
||||
},
|
||||
userNames(members) {
|
||||
const ret = (members || [])
|
||||
// #5122: removing a member from a board marks it `isActive: false` (the
|
||||
// entry stays in board.members for role history / re-activation). The
|
||||
// boards report must not list those removed members as current members.
|
||||
.filter(_member => _member.isActive !== false)
|
||||
.map(_member => {
|
||||
const _ret = ReactiveCache.getUser(_member.userId)?.username || _member.userId;
|
||||
return _ret;
|
||||
|
||||
@@ -278,3 +278,50 @@ li.has-error .form-group .wekan-form-control {
|
||||
width: 120px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
/* #6419: on phones the admin settings forced a side-by-side menu+content layout
|
||||
(flex-direction: row "even on narrow windows"), so the ~127px side menu and the
|
||||
content were crammed together and the long (e.g. Finnish) menu labels overflowed
|
||||
the menu box rightward, visually overlapping the content. Stack them on small
|
||||
screens: a full-width compact menu on top, full-width content below. */
|
||||
@media screen and (max-width: 800px) {
|
||||
.setting-content {
|
||||
position: relative;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
.setting-content .content-body {
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
height: auto;
|
||||
padding-top: 1vh;
|
||||
}
|
||||
.setting-content .content-body .side-menu {
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 6px;
|
||||
}
|
||||
/* Menu items as a wrapping horizontal row so the menu stays compact on top. */
|
||||
.setting-content .content-body .side-menu ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
}
|
||||
.setting-content .content-body .side-menu ul li {
|
||||
flex: 0 0 auto;
|
||||
margin: 0;
|
||||
}
|
||||
.setting-content .content-body .side-menu ul li a {
|
||||
width: auto;
|
||||
white-space: nowrap;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
.setting-content .content-body .main-body {
|
||||
width: 100%;
|
||||
flex: 1 1 auto;
|
||||
min-height: 60vh;
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import AccessibilitySettings from '/models/accessibilitySettings';
|
||||
import AccountSettings from '/models/accountSettings';
|
||||
import Announcements from '/models/announcements';
|
||||
import Settings from '/models/settings';
|
||||
import { resolveDefaultAuthenticationMethod } from '/models/lib/authenticationMethod';
|
||||
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
|
||||
// import {
|
||||
// cronMigrationProgress,
|
||||
@@ -803,7 +804,17 @@ Template.setting.events({
|
||||
$('input[name=hideBoardMemberList]:checked').val() === 'true';
|
||||
const displayAuthenticationMethod =
|
||||
$('input[name=displayAuthenticationMethod]:checked').val() === 'true';
|
||||
const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
|
||||
// #5879: the <select> options are populated by an async Meteor.call, so its
|
||||
// value can still be '' / null when Save is clicked. Saving that empty value
|
||||
// over the required `defaultAuthenticationMethod` string silently failed
|
||||
// validation, so the Layout save looked like it hung / did nothing. Fall back
|
||||
// to the currently stored method so a real value is never overwritten by ''.
|
||||
const currentDefaultAuthenticationMethod =
|
||||
ReactiveCache.getCurrentSetting()?.defaultAuthenticationMethod;
|
||||
const defaultAuthenticationMethod = resolveDefaultAuthenticationMethod(
|
||||
$('#defaultAuthenticationMethod').val(),
|
||||
currentDefaultAuthenticationMethod,
|
||||
);
|
||||
const spinnerName = ($('#spinnerName').val() || '').trim();
|
||||
|
||||
try {
|
||||
|
||||
@@ -49,3 +49,30 @@
|
||||
float: none;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
/* #6419: the admin panel's 2nd top bar (settingHeaderBar) is rendered inside
|
||||
#header-quick-access, whose mobile rules scale ALL text/icons 2x
|
||||
(`#header-quick-access * { font-size: inherit }` + `.fa { font-size: 2em }`).
|
||||
That made the admin tab buttons 28px text / 56px icons — huge, overlapping the
|
||||
"Version" label and the content below. Cap them at a normal touch size on small
|
||||
screens. Selectors are more specific than the 2em rules (and repeated for the
|
||||
.iphone-device JS-detection fallback) so they win. */
|
||||
@media screen and (max-width: 800px) {
|
||||
#header-quick-access .setting-header-btns .setting-header-btn,
|
||||
#header #header-main-bar .setting-header-btns .setting-header-btn,
|
||||
#header-quick-access .setting-header-btns .setting-header-btn span,
|
||||
#header #header-main-bar .setting-header-btns .setting-header-btn span {
|
||||
font-size: 15px !important;
|
||||
}
|
||||
#header-quick-access .setting-header-btns .setting-header-btn i.fa,
|
||||
#header #header-main-bar .setting-header-btns .setting-header-btn i.fa {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
}
|
||||
.iphone-device #header-quick-access .setting-header-btns .setting-header-btn,
|
||||
.iphone-device #header-quick-access .setting-header-btns .setting-header-btn span {
|
||||
font-size: 15px !important;
|
||||
}
|
||||
.iphone-device #header-quick-access .setting-header-btns .setting-header-btn i.fa {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
@@ -82,6 +82,14 @@ body.grey-icons-enabled .boardSubtaskSettingsPopup .materialCheckBox.is-checked
|
||||
grid-template-columns: 1fr 1fr 2fr;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
/* Keep the "Show on Card / Show on Minicard / Description" titles visible
|
||||
while the rows below scroll (the popup scrolls in .pop-over .content-wrapper). */
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
background: #fff;
|
||||
padding: 5px 0;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
.card-settings-row {
|
||||
@@ -261,9 +269,40 @@ body.grey-icons-enabled .sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-
|
||||
padding: 0 10px 0 10px;
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
.board-sidebar {
|
||||
width: 100%;
|
||||
/* On phones (iPhone 12 mini, etc.) the right sidebar should be a full-width
|
||||
panel at the top of the board, in BOTH mobile mode and desktop mode. The
|
||||
sidebar lives inside .board-wrapper, which is wider than the viewport on a
|
||||
phone, so an absolutely-positioned sidebar (inset-inline-end: 0) pins to the
|
||||
far right of the wide board and shows as a narrow strip. Pin it to the
|
||||
VIEWPORT instead (position: fixed + 100vw) so it spans the full screen
|
||||
width. At 100% zoom .board-wrapper has no transform, so fixed resolves to
|
||||
the viewport in desktop mode too. */
|
||||
.board-sidebar.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: 0;
|
||||
width: 100vw;
|
||||
max-width: 100vw;
|
||||
z-index: 1000;
|
||||
}
|
||||
/* Normalise sidebar text on phones so the headings are not oversized and the
|
||||
small text is not tiny — keep them close to the board's text size so board
|
||||
and sidebar read at one consistent size. */
|
||||
.board-sidebar.sidebar .sidebar-content {
|
||||
font-size: 14px;
|
||||
}
|
||||
.board-sidebar.sidebar .sidebar-content h2,
|
||||
.board-sidebar.sidebar .sidebar-content h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
.board-sidebar.sidebar .sidebar-content ul.sidebar-list li > a,
|
||||
.board-sidebar.sidebar .sidebar-content .card-label,
|
||||
.board-sidebar.sidebar .sidebar-content .card-label .card-label-name,
|
||||
.board-sidebar.sidebar .sidebar-content p,
|
||||
.board-sidebar.sidebar .sidebar-content span {
|
||||
font-size: 14px;
|
||||
}
|
||||
.board-sidebar .sidebar-content .hide-btn {
|
||||
width: 40px;
|
||||
|
||||
@@ -315,21 +315,12 @@ template(name="boardCardSettingsPopup")
|
||||
a.flex.js-field-has-creator(title="{{_ 'creator'}}" class="{{#if allowsCreator}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsCreator}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
span
|
||||
.card-settings-column
|
||||
span
|
||||
i.fa.fa-user
|
||||
| {{_ 'creator'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
span
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-creator-on-minicard(title="{{_ 'creator-on-minicard'}}" class="{{#if allowsCreatorOnMinicard}}is-checked{{/if}}")
|
||||
a.flex.js-field-has-creator-on-minicard(title="{{_ 'creator'}}" class="{{#if allowsCreatorOnMinicard}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsCreatorOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
span
|
||||
i.fa.fa-user
|
||||
| {{_ 'creator-on-minicard'}}
|
||||
| {{_ 'creator'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-assignee(title="{{_ 'assignee'}}" class="{{#if allowsAssignee}}is-checked{{/if}}")
|
||||
@@ -369,27 +360,19 @@ template(name="boardCardSettingsPopup")
|
||||
a.flex.js-field-has-card-sorting-by-number(title="{{_ 'card-sorting-by-number'}}" class="{{#if allowsCardSortingByNumber}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsCardSortingByNumber}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
span
|
||||
a.flex.js-field-has-card-sorting-by-number-on-minicard(title="{{_ 'card-sorting-by-number'}}" class="{{#if allowsCardSortingByNumberOnMinicard}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsCardSortingByNumberOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
span
|
||||
i.fa.fa-sort-numeric-asc
|
||||
| {{_ 'card-sorting-by-number'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
span
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-card-sorting-by-number-on-minicard(title="{{_ 'card-sorting-by-number-on-minicard'}}" class="{{#if allowsCardSortingByNumberOnMinicard}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsCardSortingByNumberOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
span
|
||||
i.fa.fa-sort-numeric-asc
|
||||
| {{_ 'card-sorting-by-number-on-minicard'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-card-show-lists(title="{{_ 'card-show-lists'}}" class="{{#if allowsShowLists}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsShowLists}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
span
|
||||
a.flex.js-field-has-card-show-lists-on-minicard(title="{{_ 'card-show-lists'}}" class="{{#if allowsShowListsOnMinicard}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsShowListsOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
span
|
||||
i.fa.fa-list
|
||||
@@ -405,16 +388,6 @@ template(name="boardCardSettingsPopup")
|
||||
span
|
||||
i.fa.fa-tag
|
||||
| {{_ 'labels'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
span
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-card-show-lists-on-minicard(title="{{_ 'card-show-lists-on-minicard'}}" class="{{#if allowsShowListsOnMinicard}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsShowListsOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
span
|
||||
i.fa.fa-list
|
||||
| {{_ 'card-show-lists-on-minicard'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-card-number(title="{{_ 'card'}} {{_ 'number'}}" class="{{#if allowsCardNumber}}is-checked{{/if}}")
|
||||
@@ -462,6 +435,17 @@ template(name="boardCardSettingsPopup")
|
||||
span
|
||||
i.fa.fa-check
|
||||
| {{_ 'checklists'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-checklist-count(title="{{_ 'checklist-count'}}" class="{{#if allowsChecklistCountBadgeOnCard}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsChecklistCountBadgeOnCard}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-checklist-count-on-minicard(title="{{_ 'checklist-count'}}" class="{{#if allowsChecklistCountBadgeOnMinicard}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsChecklistCountBadgeOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
span
|
||||
i.fa.fa-check-square-o
|
||||
| {{_ 'checklist-count'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-subtasks(title="{{_ 'subtasks'}}" class="{{#if allowsSubtasks}}is-checked{{/if}}")
|
||||
@@ -486,24 +470,26 @@ template(name="boardCardSettingsPopup")
|
||||
| {{_ 'attachments'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
span
|
||||
a.flex.js-field-has-attachment-count(title="{{_ 'attachment-count'}}" class="{{#if allowsAttachmentCountOnCard}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsAttachmentCountOnCard}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-badge-attachment-on-minicard(title="{{_ 'badge-attachment-on-minicard'}}" class="{{#if allowsBadgeAttachmentOnMinicard}}is-checked{{/if}}")
|
||||
a.flex.js-field-has-badge-attachment-on-minicard(title="{{_ 'attachment-count'}}" class="{{#if allowsBadgeAttachmentOnMinicard}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsBadgeAttachmentOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
span
|
||||
i.fa.fa-paperclip
|
||||
| {{_ 'badge-attachment-on-minicard'}}
|
||||
| {{_ 'attachment-count'}}
|
||||
.card-settings-row
|
||||
.card-settings-column
|
||||
span
|
||||
a.flex.js-field-has-cover-attachment-on-card(title="{{_ 'cover-image'}}" class="{{#if allowsCoverAttachmentOnCard}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsCoverAttachmentOnCard}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
a.flex.js-field-has-cover-attachment-on-minicard(title="{{_ 'cover-attachment-on-minicard'}}" class="{{#if allowsCoverAttachmentOnMinicard}}is-checked{{/if}}")
|
||||
a.flex.js-field-has-cover-attachment-on-minicard(title="{{_ 'cover-image'}}" class="{{#if allowsCoverAttachmentOnMinicard}}is-checked{{/if}}")
|
||||
i.fa(class="{{#if allowsCoverAttachmentOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
|
||||
.card-settings-column
|
||||
span
|
||||
i.fa.fa-picture-o
|
||||
| {{_ 'cover-attachment-on-minicard'}}
|
||||
| {{_ 'cover-image'}}
|
||||
//div.check-div
|
||||
//
|
||||
a.flex.js-field-has-comments(class="{{#if allowsComments}}is-checked{{/if}}")
|
||||
|
||||
@@ -294,7 +294,7 @@ Template.memberPopup.helpers({
|
||||
return ReactiveCache.getUser(this.userId);
|
||||
},
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
memberType() {
|
||||
const type = ReactiveCache.getUser(this.userId).isBoardAdmin() ? 'admin' : 'normal';
|
||||
@@ -433,7 +433,7 @@ Template.boardMenuPopup.onCreated(function() {
|
||||
|
||||
Template.boardMenuPopup.helpers({
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
withApi() {
|
||||
return Template.instance().apiEnabled.get();
|
||||
@@ -547,7 +547,7 @@ Template.membersWidget.helpers({
|
||||
}
|
||||
},
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
AtLeastOneOrgWasCreated(){
|
||||
let orgs = ReactiveCache.getOrgs({}, {sort: { createdAt: -1 }});
|
||||
@@ -919,7 +919,7 @@ Template.labelsWidget.events({
|
||||
|
||||
Template.labelsWidget.helpers({
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1496,6 +1496,28 @@ Template.boardCardSettingsPopup.helpers({
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return getMinicardSetting(currentBoard, 'allowsChecklistsOnMinicard', 'allowsChecklists', true);
|
||||
},
|
||||
// #6431: compact checklist count badge on minicard, opt-in and OFF by default.
|
||||
allowsChecklistCountBadgeOnMinicard() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsChecklistCountBadgeOnMinicard : false;
|
||||
},
|
||||
// #6431 follow-up: new opt-in (OFF by default) card-side options.
|
||||
allowsChecklistCountBadgeOnCard() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsChecklistCountBadgeOnCard : false;
|
||||
},
|
||||
allowsAttachmentCountOnCard() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsAttachmentCountOnCard : false;
|
||||
},
|
||||
allowsCoverAttachmentOnCard() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
return currentBoard ? currentBoard.allowsCoverAttachmentOnCard : false;
|
||||
},
|
||||
allowsAttachments() {
|
||||
const boardId = Session.get('currentBoard');
|
||||
const currentBoard = ReactiveCache.getBoard(boardId);
|
||||
@@ -1790,6 +1812,26 @@ Template.boardCardSettingsPopup.events({
|
||||
const newValue = !tpl.currentBoard.allowsChecklistsOnMinicard;
|
||||
Boards.update(tpl.currentBoard._id, { $set: { allowsChecklistsOnMinicard: newValue } });
|
||||
},
|
||||
'click .js-field-has-checklist-count-on-minicard'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
const newValue = !tpl.currentBoard.allowsChecklistCountBadgeOnMinicard;
|
||||
Boards.update(tpl.currentBoard._id, { $set: { allowsChecklistCountBadgeOnMinicard: newValue } });
|
||||
},
|
||||
'click .js-field-has-checklist-count'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
const newValue = !tpl.currentBoard.allowsChecklistCountBadgeOnCard;
|
||||
Boards.update(tpl.currentBoard._id, { $set: { allowsChecklistCountBadgeOnCard: newValue } });
|
||||
},
|
||||
'click .js-field-has-attachment-count'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
const newValue = !tpl.currentBoard.allowsAttachmentCountOnCard;
|
||||
Boards.update(tpl.currentBoard._id, { $set: { allowsAttachmentCountOnCard: newValue } });
|
||||
},
|
||||
'click .js-field-has-cover-attachment-on-card'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
const newValue = !tpl.currentBoard.allowsCoverAttachmentOnCard;
|
||||
Boards.update(tpl.currentBoard._id, { $set: { allowsCoverAttachmentOnCard: newValue } });
|
||||
},
|
||||
'click .js-field-has-attachments'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
const newValue = !tpl.currentBoard.allowsAttachments;
|
||||
|
||||
@@ -215,7 +215,7 @@ function mapSelection(kind, _id) {
|
||||
|
||||
Template.multiselectionSidebar.helpers({
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
isCommentOnly() {
|
||||
return ReactiveCache.getCurrentUser().isCommentOnly();
|
||||
|
||||
@@ -41,7 +41,7 @@ Template.swimlaneHeader.events({
|
||||
|
||||
Template.swimlaneFixedHeader.helpers({
|
||||
isBoardAdmin() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
collapseSwimlane() {
|
||||
const swimlane = Template.currentData();
|
||||
|
||||
@@ -83,8 +83,15 @@
|
||||
}
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-menu {
|
||||
position: absolute;
|
||||
/* Stretch the menu to the full height of the swimlane header and lay the
|
||||
buttons (collapse, swimlane menu, + new swimlane, drag handle) in a row that
|
||||
is vertically centered — instead of anchoring them to the top with per-icon
|
||||
top: calc(50% + Npx) hacks that centered against the menu's own height. */
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
inset-inline-start: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
@@ -93,7 +100,6 @@
|
||||
pointer-events: auto;
|
||||
}
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-menu .js-open-swimlane-menu {
|
||||
top: calc(50% + 6px);
|
||||
padding: 5px;
|
||||
display: inline-block;
|
||||
margin-inline-start: 30px;
|
||||
@@ -107,20 +113,16 @@
|
||||
}
|
||||
}
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-plus-icon {
|
||||
top: calc(50% + 6px);
|
||||
padding: 5px;
|
||||
margin-inline-start: 20px;
|
||||
font-size: 22px;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-menu-icon {
|
||||
top: calc(50% + 6px);
|
||||
padding-inline-start: 5px;
|
||||
font-size: 22px;
|
||||
}
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-handle {
|
||||
position: relative;
|
||||
top: calc(50% + 2px);
|
||||
padding: 2px 5px;
|
||||
font-size: clamp(16px, 3vw, 20px);
|
||||
display: inline-block;
|
||||
@@ -132,9 +134,7 @@
|
||||
line-height: 1.2;
|
||||
}
|
||||
.swimlane .swimlane-header-wrap .swimlane-header-miniscreen-handle {
|
||||
position: relative;
|
||||
padding: 2px 5px;
|
||||
top: calc(50% + 2px);
|
||||
font-size: 24px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
@@ -18,8 +18,16 @@ function saveSorting(ui) {
|
||||
}
|
||||
// To attribute the new index number, we need to get the DOM element
|
||||
// of the previous and the following list -- if any.
|
||||
const prevListDom = ui.item.prev('.js-list').get(0);
|
||||
const nextListDom = ui.item.next('.js-list').get(0);
|
||||
// Use prevAll/nextAll (not prev/next) so non-list siblings between lists do
|
||||
// not break neighbour detection: the lists container also renders the
|
||||
// +addListForm composer and, when a card is open, a +cardDetails element.
|
||||
// jQuery's .prev('.js-list') only matches an *immediately* adjacent sibling,
|
||||
// so an interspersed composer/cardDetails made calculateIndex mis-detect the
|
||||
// first/last position and compute a wrong sort, so the reordered list landed
|
||||
// elsewhere and looked like it did not persist. See
|
||||
// https://github.com/wekan/wekan/issues/5997
|
||||
const prevListDom = ui.item.prevAll('.js-list:not(.js-list-composer)').get(0);
|
||||
const nextListDom = ui.item.nextAll('.js-list:not(.js-list-composer)').get(0);
|
||||
const sortIndex = calculateIndex(prevListDom, nextListDom, 1);
|
||||
|
||||
const listDomElement = ui.item.get(0);
|
||||
@@ -635,7 +643,7 @@ function initializeSwimlaneResize(tpl, retryCount = 0) {
|
||||
|
||||
Template.swimlane.helpers({
|
||||
canSeeAddList() {
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser()?.isBoardAdmin();
|
||||
},
|
||||
lists() {
|
||||
const swimlane = this;
|
||||
|
||||
@@ -139,16 +139,26 @@ template(name="adminChangeAvatarPopup")
|
||||
template(name="cardMemberPopup")
|
||||
.board-member-menu
|
||||
.mini-profile-info
|
||||
+userAvatar(userId=user._id showEdit=true)
|
||||
.info
|
||||
h3= user.profile.fullname
|
||||
p.quiet @{{ user.username }}
|
||||
if user
|
||||
+userAvatar(userId=user._id showEdit=true)
|
||||
.info
|
||||
h3= user.profile.fullname
|
||||
p.quiet @{{ user.username }}
|
||||
else
|
||||
//- #4847: the member's user account was deleted. Still show the entry
|
||||
//- (with its id) and the remove control so the orphaned member can be
|
||||
//- removed from the card — the remove handler keys off userId, not the
|
||||
//- user document.
|
||||
.info
|
||||
h3 {{_ 'deleted-user'}}
|
||||
p.quiet {{ userId }}
|
||||
ul.pop-over-list
|
||||
unless noRemove
|
||||
if currentUser.isNotCommentOnly
|
||||
if currentUser.isNotWorker
|
||||
li: a.js-remove-member {{_ 'remove-member-from-card'}}
|
||||
|
||||
if $eq currentUser._id user._id
|
||||
with currentUser
|
||||
li: a.js-edit-profile {{_ 'edit-profile'}}
|
||||
if user
|
||||
if $eq currentUser._id user._id
|
||||
with currentUser
|
||||
li: a.js-edit-profile {{_ 'edit-profile'}}
|
||||
@@ -113,6 +113,21 @@ body.mobile-mode .auth-layout .auth-dialog .at-form .password-input-container .p
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
/* Auth pages (login / register) must scroll as a normal document on mobile so
|
||||
the content below the form — the sign-up / sign-in link, legal notice and
|
||||
language selector — is reachable, including when the on-screen keyboard is
|
||||
open. body.mobile-mode otherwise locks <body> to position:fixed + 100vh (an
|
||||
iOS board-view anti-bounce fix); the auth layout has no inner #content
|
||||
scroller, so that lock traps the page at viewport height and the on-screen
|
||||
keyboard hides the bottom. .userform-layout is added by userFormsLayout while
|
||||
these pages are mounted (see layouts.js). */
|
||||
body.mobile-mode.userform-layout {
|
||||
position: static !important;
|
||||
height: auto !important;
|
||||
min-height: 100vh;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.auth-layout {
|
||||
width: 100%;
|
||||
@@ -120,6 +135,14 @@ body.mobile-mode .auth-layout .auth-dialog .at-form .password-input-container .p
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
/* Let the auth layout/dialog grow to their content instead of being pinned to
|
||||
the viewport height, so everything below the form scrolls into view. */
|
||||
body.userform-layout .auth-layout {
|
||||
height: auto;
|
||||
}
|
||||
body.userform-layout .auth-layout .auth-dialog {
|
||||
height: auto;
|
||||
}
|
||||
.auth-layout .at-form-landing-logo {
|
||||
width: 125px;
|
||||
position: absolute;
|
||||
|
||||
@@ -309,7 +309,12 @@ Template.changeLanguagePopup.events({
|
||||
'profile.language': this.tag,
|
||||
},
|
||||
});
|
||||
TAPi18n.setLanguage(this.tag);
|
||||
// setLanguage is async; surface a failed load instead of silently leaving
|
||||
// the UI in English (#5756).
|
||||
Promise.resolve(TAPi18n.setLanguage(this.tag)).catch(error => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Failed to switch language to ${this.tag}:`, error);
|
||||
});
|
||||
event.preventDefault();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Template } from 'meteor/templating';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { getCurrentCardFromContext } from '/client/lib/currentCard';
|
||||
import { normalizeDigits } from '/imports/lib/dateUtils';
|
||||
|
||||
// Helper to check if a date is valid
|
||||
function isValidDate(date) {
|
||||
@@ -114,8 +115,9 @@ export function datePickerHelpers() {
|
||||
export function datePickerEvents({ storeDate, deleteDate }) {
|
||||
return {
|
||||
'change .js-date-field'(evt, tpl) {
|
||||
// Native HTML date input validation
|
||||
const dateValue = tpl.find('#date').value;
|
||||
// Native HTML date input validation. Normalize any non-Latin digits
|
||||
// (e.g. Persian/Arabic-Indic) so parsing works in those locales (#5752).
|
||||
const dateValue = normalizeDigits(tpl.find('#date').value);
|
||||
if (dateValue) {
|
||||
// HTML date input format is always YYYY-MM-DD
|
||||
const dateObj = new Date(dateValue + 'T12:00:00');
|
||||
@@ -127,8 +129,9 @@ export function datePickerEvents({ storeDate, deleteDate }) {
|
||||
}
|
||||
},
|
||||
'change .js-time-field'(evt, tpl) {
|
||||
// Native HTML time input validation
|
||||
const timeValue = tpl.find('#time').value;
|
||||
// Native HTML time input validation. Normalize any non-Latin digits
|
||||
// (e.g. Persian/Arabic-Indic) so parsing works in those locales (#5752).
|
||||
const timeValue = normalizeDigits(tpl.find('#time').value);
|
||||
if (timeValue) {
|
||||
// HTML time input format is always HH:mm
|
||||
const timeObj = new Date(`1970-01-01T${timeValue}:00`);
|
||||
@@ -142,8 +145,10 @@ export function datePickerEvents({ storeDate, deleteDate }) {
|
||||
'submit .edit-date'(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
|
||||
const dateValue = evt.target.date.value;
|
||||
const timeValue = evt.target.time.value || '12:00'; // Default to 12:00 if no time given
|
||||
// Normalize any non-Latin digits (e.g. Persian/Arabic-Indic) before
|
||||
// parsing so due/start/end dates work in those locales (#5752).
|
||||
const dateValue = normalizeDigits(evt.target.date.value);
|
||||
const timeValue = normalizeDigits(evt.target.time.value) || '12:00'; // Default to 12:00 if no time given
|
||||
|
||||
if (!dateValue) {
|
||||
tpl.datePicker.error.set('invalid-date');
|
||||
|
||||
@@ -1,27 +1,45 @@
|
||||
import dragscroll from '@wekanteam/dragscroll';
|
||||
import '/client/lib/dragscrollTouch';
|
||||
|
||||
// Enable / disable mouse drag-to-scroll for whole-page layouts.
|
||||
// Enable / disable mouse drag-to-scroll for whole-page layouts (All Boards,
|
||||
// My Cards, Login, Register).
|
||||
//
|
||||
// On pages like All Boards, Login and Register the scroll container is the
|
||||
// <body> element itself (it has `overflow-y: auto; height: 100vh`), not an
|
||||
// inner board canvas or swimlane. The dragscroll library scrolls whichever
|
||||
// element carries the `dragscroll` class and has a dedicated `el ==
|
||||
// document.body` branch that also scrolls document.documentElement, so tagging
|
||||
// <body> makes dragging empty page space scroll the page — the same gesture
|
||||
// that already works on the board swimlanes view.
|
||||
// The dragscroll library scrolls whichever element carries the `dragscroll`
|
||||
// class AND is the actual scroll container. Which element that is depends on
|
||||
// the layout and on mobile vs desktop mode:
|
||||
//
|
||||
// The class is toggled per template (onRendered / onDestroyed) so it only
|
||||
// applies on those pages; dragscroll.reset() re-scans the DOM and (re)binds the
|
||||
// mouse listeners after the class is added or removed. Touch (one-finger)
|
||||
// scrolling is handled separately by client/lib/dragscrollTouch.js.
|
||||
// * Login / Register (userFormsLayout) have no `#content`; the page scroll
|
||||
// container is `<body>` in both modes (it has `overflow-y: auto` and a
|
||||
// `100vh` height).
|
||||
// * All Boards / My Cards (defaultLayout) render inside `#content`. On
|
||||
// desktop the scroll container is `<body>`; in mobile mode `<body>` becomes
|
||||
// `position: fixed` and `#content` becomes the scroller
|
||||
// (`overflow-y: auto; height: calc(100vh - 48px)`), so tagging only
|
||||
// `<body>` would never scroll on a phone.
|
||||
//
|
||||
// To cover every case we tag BOTH `<body>` and `#content` (when present). Only
|
||||
// the element that actually overflows scrolls, so there is no double-scroll:
|
||||
// on desktop `<body>` scrolls and `#content` (no vertical overflow) is a no-op;
|
||||
// in mobile mode `#content` scrolls and `<body>` (fixed, no overflow) is a
|
||||
// no-op. dragscroll has a dedicated `el == document.body` branch for the body
|
||||
// case. dragscroll.reset() re-scans the DOM and (re)binds the mouse listeners.
|
||||
// One-finger touch scrolling is handled by client/lib/dragscrollTouch.js, which
|
||||
// picks the nearest scrollable `.dragscroll` ancestor (so `#content` wins over
|
||||
// `<body>` when both are tagged).
|
||||
|
||||
function pageScrollEls() {
|
||||
const els = [document.body];
|
||||
const content = document.getElementById('content');
|
||||
if (content) els.push(content);
|
||||
return els;
|
||||
}
|
||||
|
||||
export function enablePageDragscroll() {
|
||||
document.body.classList.add('dragscroll');
|
||||
pageScrollEls().forEach(el => el.classList.add('dragscroll'));
|
||||
dragscroll.reset();
|
||||
}
|
||||
|
||||
export function disablePageDragscroll() {
|
||||
document.body.classList.remove('dragscroll');
|
||||
pageScrollEls().forEach(el => el.classList.remove('dragscroll'));
|
||||
dragscroll.reset();
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// i18n (TAPi18n) tests, run on the client as the old tests/main.js did.
|
||||
import '/imports/i18n/i18n.test.js';
|
||||
import './Utils.tests';
|
||||
import './filter.dependencies.tests';
|
||||
import './dueDateColor.tests';
|
||||
|
||||
+41
-21
@@ -99,27 +99,23 @@ export const Utils = {
|
||||
return user.profile.mobileMode;
|
||||
}
|
||||
|
||||
// No explicit preference: pick a sensible default from the device.
|
||||
// Previously this returned true ONLY for iPhone/iPod, so every other phone
|
||||
// (Fairphone 4 postmarketOS Firefox, Ubuntu Touch Morph, Android Firefox,
|
||||
// ...) defaulted to desktop-mode. desktop-mode applies large desktop sizing
|
||||
// (see .desktop-mode rules in boardHeader.css) which on a small phone screen
|
||||
// looks roughly 2x too big. Default to mobile-mode for any phone-sized touch
|
||||
// device, not just iPhone, so non-iPhone phones render at the correct size.
|
||||
const ua = navigator.userAgent;
|
||||
const isIPhone = /iPhone|iPod/i.test(ua);
|
||||
const isPhoneUA =
|
||||
/Mobile|Android|BlackBerry|IEMobile|Opera Mini/i.test(ua) &&
|
||||
!/iPad/i.test(ua);
|
||||
const isUbuntuTouch = /Ubuntu/i.test(ua);
|
||||
const hasTouch =
|
||||
('maxTouchPoints' in navigator && navigator.maxTouchPoints > 0) ||
|
||||
(window.matchMedia && window.matchMedia('(pointer: coarse)').matches);
|
||||
// A small touch screen (phone-sized) — covers touch browsers whose UA does
|
||||
// not contain a phone token. Desktop browsers (no touch, or wide) stay in
|
||||
// desktop-mode.
|
||||
const isSmallTouchScreen = hasTouch && window.innerWidth <= 800;
|
||||
return isIPhone || isPhoneUA || isUbuntuTouch || isSmallTouchScreen;
|
||||
// No explicit preference: detect from the VIEWPORT, not the user-agent.
|
||||
// UA string parsing is unreliable across the many mobile browsers (Firefox,
|
||||
// Chrome, Safari, postmarketOS, Ubuntu Touch Morph, ...) and is exactly the
|
||||
// kind of fragile detection that bites projects — cf. Meteor #12421, where
|
||||
// Mobile Safari version parsing was wrong. matchMedia width/pointer queries
|
||||
// are well-supported and, importantly, match the CSS `@media (max-width:
|
||||
// 800px)` breakpoint, so the body.mobile-mode class and the responsive CSS
|
||||
// agree. A narrow screen, or a coarse (touch) pointer on a not-wide screen,
|
||||
// gets mobile mode; wide desktop windows (fine pointer) stay desktop.
|
||||
// NOTE: requires the width=device-width viewport meta (see
|
||||
// server/lib/customHeadRender.js); without it window.innerWidth reports ~980
|
||||
// on phones and this would never trigger.
|
||||
const mq = q =>
|
||||
typeof window !== 'undefined' && window.matchMedia && window.matchMedia(q).matches;
|
||||
const narrow = mq('(max-width: 800px)');
|
||||
const coarse = mq('(pointer: coarse)');
|
||||
return Boolean(narrow || (coarse && window.innerWidth <= 1024));
|
||||
},
|
||||
|
||||
setMobileMode(enabled) {
|
||||
@@ -262,6 +258,30 @@ export const Utils = {
|
||||
const mobileMode = Utils.getMobileMode();
|
||||
Utils.applyZoomLevel(zoomLevel);
|
||||
Utils.applyMobileMode(mobileMode);
|
||||
|
||||
// #6419: keep mobile-mode in sync with the viewport (resize / orientation /
|
||||
// responsive testing) when the user has NOT explicitly chosen a mode. Bound
|
||||
// once. An explicit toggle (localStorage or profile) must stick, so we skip
|
||||
// auto-switching in that case.
|
||||
if (
|
||||
typeof window !== 'undefined' &&
|
||||
window.matchMedia &&
|
||||
!Utils._mobileModeAutoBound
|
||||
) {
|
||||
Utils._mobileModeAutoBound = true;
|
||||
const reapply = () => {
|
||||
const stored = localStorage.getItem('wekan-mobile-mode');
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const explicit =
|
||||
stored !== null ||
|
||||
(user && user.profile && user.profile.mobileMode !== undefined);
|
||||
if (!explicit) {
|
||||
Utils.applyMobileMode(Utils.getMobileMode());
|
||||
}
|
||||
};
|
||||
window.matchMedia('(max-width: 800px)').addEventListener('change', reapply);
|
||||
window.matchMedia('(pointer: coarse)').addEventListener('change', reapply);
|
||||
}
|
||||
},
|
||||
getCurrentList() {
|
||||
const listId = this.getCurrentListId();
|
||||
|
||||
+114
-11
@@ -1,9 +1,28 @@
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
|
||||
// Readiness-aware, reactive translation — mirrors the `{{_}}` Blaze helper
|
||||
// (imports/i18n/blaze.js). A bare TAPi18n.__() returns the raw key on the initial
|
||||
// (English) render because the language bundle loads asynchronously, and it never
|
||||
// re-runs once ready. Depending on TAPi18n.current / TAPi18n.ready makes the
|
||||
// account-form labels/placeholders re-render as soon as i18n is loaded and on every
|
||||
// language change.
|
||||
function tr(key) {
|
||||
TAPi18n.current.get();
|
||||
TAPi18n.ready.get();
|
||||
if (!TAPi18n.i18n || !TAPi18n.ready.get()) {
|
||||
return key;
|
||||
}
|
||||
return TAPi18n.__(key);
|
||||
}
|
||||
|
||||
const passwordField = AccountsTemplates.removeField('password');
|
||||
passwordField.autocomplete = 'current-password';
|
||||
passwordField.template = 'passwordInput';
|
||||
// Translate the field label/placeholder via WeKan i18n — see the note below the
|
||||
// addFields() call. Set before re-adding the field so it is present when useraccounts
|
||||
// registers it.
|
||||
passwordField.displayName = () => tr('password');
|
||||
const emailField = AccountsTemplates.removeField('email');
|
||||
|
||||
// Don't add current_password to global fields - it should only be used for change password
|
||||
@@ -75,7 +94,7 @@ AccountsTemplates.addFields([
|
||||
{
|
||||
_id: 'password_again',
|
||||
type: 'password',
|
||||
displayName: 'Password (again)',
|
||||
displayName: () => tr('password-again'),
|
||||
required: true,
|
||||
minLength: 6,
|
||||
autocomplete: 'new-password',
|
||||
@@ -91,6 +110,57 @@ AccountsTemplates.addFields([
|
||||
},
|
||||
]);
|
||||
|
||||
// The sign-in / register form field labels and placeholders were rendered through
|
||||
// accounts-t9n (`T9n.get`), whose per-language coverage is incomplete, and
|
||||
// useraccounts' `getPlaceholder()` never invokes a function placeholder (unlike
|
||||
// `getDisplayName()`) — so several placeholders (Username, Email, Password
|
||||
// (again)) and the Email / Password (again) labels showed untranslated English.
|
||||
// Translate the account-form fields through WeKan's own i18n (TAPi18n) instead,
|
||||
// which has complete data/<lang>.i18n.json coverage.
|
||||
//
|
||||
// Password fields (password, password_again) use WeKan's passwordInput template,
|
||||
// which renders {{displayName}} for both its label and its placeholder. Their
|
||||
// field-level displayName functions (set above / in addFields) are invoked both by
|
||||
// getDisplayName() and by Blaze, so they translate regardless of when useraccounts
|
||||
// wires up per-template helpers. Username / email use the atTextInput override below.
|
||||
if (Meteor.isClient) {
|
||||
const { Template } = require('meteor/templating');
|
||||
const { T9n } = require('meteor/communitypackages:core');
|
||||
// Fields WeKan translates itself (complete data/<lang>.i18n.json coverage), keyed
|
||||
// by field id. Other account-form text inputs — notably the sign-in combined
|
||||
// "username_and_email" field and current_password — keep their built-in
|
||||
// accounts-t9n translation, because WeKan has no key for them and accounts-t9n
|
||||
// already ships one (e.g. "usernameOrEmail").
|
||||
const WEKAN_FIELD_KEY = { username: 'username', email: 'email' };
|
||||
function fieldText(field, original) {
|
||||
TAPi18n.current.get();
|
||||
TAPi18n.ready.get();
|
||||
const key = WEKAN_FIELD_KEY[field._id];
|
||||
if (key && TAPi18n.i18n && TAPi18n.ready.get()) {
|
||||
const translated = TAPi18n.__(key);
|
||||
if (translated !== key) {
|
||||
return translated;
|
||||
}
|
||||
}
|
||||
// Fall back to useraccounts' own accounts-t9n translation. The field's
|
||||
// displayName/placeholder is an accounts-t9n key (e.g. "usernameOrEmail").
|
||||
return T9n.get(original, false);
|
||||
}
|
||||
// The unstyled package registers these helpers on Template.atTextInput at load
|
||||
// time, so this app-startup override wins.
|
||||
Template.atTextInput.helpers({
|
||||
displayName() {
|
||||
return fieldText(this, this.getDisplayName());
|
||||
},
|
||||
placeholder() {
|
||||
if (!AccountsTemplates.options.showPlaceholders) {
|
||||
return undefined;
|
||||
}
|
||||
return fieldText(this, this.getPlaceholder());
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
AccountsTemplates.configure({
|
||||
defaultLayout: 'userFormsLayout',
|
||||
defaultContentRegion: 'content',
|
||||
@@ -191,6 +261,16 @@ AccountsTemplates.configureRoute('changePwd', {
|
||||
});
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// #5706: Internal Server Error (500) when attempting to reset a password.
|
||||
// The reset-password email-template builders and the email send must never let
|
||||
// an unhandled exception bubble up through Meteor's `forgotPassword` method (it
|
||||
// surfaces to the client as an opaque HTTP 500). The pure helpers below are
|
||||
// unit-tested in tests/unit/resetPasswordEmail.test.js.
|
||||
const {
|
||||
buildEmailTemplateField,
|
||||
wrapSendResetPasswordEmail,
|
||||
} = require('/server/lib/resetPasswordEmail');
|
||||
|
||||
[
|
||||
'resetPassword-subject',
|
||||
'resetPassword-text',
|
||||
@@ -200,16 +280,39 @@ if (Meteor.isServer) {
|
||||
'enrollAccount-text',
|
||||
].forEach(str => {
|
||||
const [templateName, field] = str.split('-');
|
||||
Accounts.emailTemplates[templateName][field] = (user, url) => {
|
||||
return TAPi18n.__(
|
||||
`email-${str}`,
|
||||
{
|
||||
url,
|
||||
user: user.getName(),
|
||||
siteName: Accounts.emailTemplates.siteName,
|
||||
},
|
||||
user.getLanguage(),
|
||||
Accounts.emailTemplates[templateName][field] = (user, url) =>
|
||||
buildEmailTemplateField(
|
||||
str,
|
||||
(key, params, language) => TAPi18n.__(key, params, language),
|
||||
Accounts.emailTemplates.siteName,
|
||||
user,
|
||||
url,
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
// #5706: When SMTP is not configured (no MAIL_URL / MAIL_FROM, or a bad mail
|
||||
// server), Meteor's `Email.sendAsync` throws. That exception otherwise propagates
|
||||
// out of the `forgotPassword` method as a raw HTTP 500 ("Internal Server Error").
|
||||
// Wrap the reset-password email send so the failure surfaces as a clean,
|
||||
// actionable `Meteor.Error('email-fail', ...)` instead, matching how the rest of
|
||||
// Wekan reports email failures (see server/models/users.js, settings.js).
|
||||
Accounts.sendResetPasswordEmail = wrapSendResetPasswordEmail(
|
||||
Accounts.sendResetPasswordEmail,
|
||||
(code, message) => new Meteor.Error(code, message),
|
||||
);
|
||||
|
||||
// #5672: "Internal Server Error When Signing Up Despite Successful Account
|
||||
// Creation". useraccounts' ATCreateUserServer creates the account and then
|
||||
// calls Accounts.sendVerificationEmail(); when SMTP is not configured that
|
||||
// send throws AFTER the user is inserted, so the createUser method returns a
|
||||
// raw HTTP 500 even though the account exists and can sign in. The
|
||||
// verification email is best-effort at sign-up, so swallow + log a transport
|
||||
// failure (registration then completes and redirects to sign-in). An
|
||||
// "already verified" error is re-thrown so ATResendVerificationEmail still
|
||||
// reports it. Unit-tested in tests/verificationEmail.test.cjs.
|
||||
const { wrapSendVerificationEmail } = require('/server/lib/verificationEmail');
|
||||
Accounts.sendVerificationEmail = wrapSendVerificationEmail(
|
||||
Accounts.sendVerificationEmail,
|
||||
message => console.warn(message),
|
||||
);
|
||||
}
|
||||
+2
-2
@@ -171,7 +171,7 @@ services:
|
||||
# Leave it unset or empty to skip migration.
|
||||
#-------------------------------------------------------------------------------------
|
||||
#wekan-migrate:
|
||||
# image: node:24.15.0-slim
|
||||
# image: node:24.17.0-slim
|
||||
# container_name: wekan-migrate
|
||||
# restart: "no"
|
||||
# networks:
|
||||
@@ -324,7 +324,7 @@ services:
|
||||
#- METEOR_REACTIVITY_ORDER=polling
|
||||
# https://forums.meteor.com/t/meteor-3-5-beta-change-streams-performance-improvements/64461#change-streams-setup-3
|
||||
# https://github.com/meteor/meteor/blob/release-3.5/v3-docs/docs/performance/change-streams-observer-driver.md#choosing-the-reactivity-driver-order
|
||||
# Use oplog,polling to fix performance until changeStreams is fixed at next release of Meteor 3.5 Beta:
|
||||
# Keep oplog/polling fallback available for deployments that cannot use Change Streams:
|
||||
# https://github.com/wekan/wekan/issues/6307#issuecomment-4299349231
|
||||
- METEOR_REACTIVITY_ORDER=changeStreams,oplog,polling
|
||||
- DDP_TRANSPORT=uws
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
|
||||
## Add/Remove Board Member and Change Role
|
||||
|
||||
[Add/Remove Board Member and Change Role admin/normal/nocomments/commentonly](REST-API-Role).
|
||||
[Add/Remove Board Member and Change Role admin/normal/nocomments/commentonly](Role.md).
|
||||
|
||||
## The admin takes the ownership of ALL boards of the user (archived and not archived) where the user is admin on.
|
||||
|
||||
@@ -53,4 +53,4 @@ curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
|
||||
## In Wekan code
|
||||
|
||||
If you believe that code is the best documentation, be our guest: [models/cards.js](https://github.com/wekan/wekan/blob/main/models/boards.js "Board API code")
|
||||
If you believe that code is the best documentation, be our guest: [models/cards.js](../../models/boards.js "Board API code")
|
||||
+1
-1
@@ -91,4 +91,4 @@ The card's ID is returned in the format:
|
||||
|
||||
# In Wekan code
|
||||
|
||||
If you believe that code is the best documentation, be our guest: [models/cards.js](https://github.com/wekan/wekan/blob/main/models/cards.js "Card API code")
|
||||
If you believe that code is the best documentation, be our guest: [models/cards.js](../../models/cards.js "Card API code")
|
||||
+5
-5
@@ -8,26 +8,26 @@ meteor add simple:json-routes
|
||||
|
||||
https://atmospherejs.com/simple/json-routes
|
||||
|
||||
https://github.com/wekan/wekan/blob/main/.meteor/packages#L139
|
||||
[packages](../../.meteor/packages#L139)
|
||||
|
||||
### 2) API code
|
||||
|
||||
At models directory:
|
||||
|
||||
https://github.com/wekan/wekan/blob/main/models/users.js#L2018
|
||||
[users.js](../../models/users.js#L2018)
|
||||
|
||||
### 3) Login to API as JSON
|
||||
|
||||
https://github.com/wekan/wekan/wiki/REST-API#example-call---as-json
|
||||
[REST API](REST-API.md#example-call---as-json)
|
||||
|
||||
Also see at wiki right menu about REST API.
|
||||
|
||||
### 4) Use Python3 CLI to login to API and do REST API action
|
||||
|
||||
https://github.com/wekan/wekan/blob/main/api.py
|
||||
[api.py](../../api.py)
|
||||
|
||||
### 4) API Docs
|
||||
|
||||
https://wekan.fi/api/
|
||||
|
||||
Building API Docs: https://github.com/wekan/wekan/blob/main/releases/rebuild-docs.sh
|
||||
Building API Docs: [rebuild-docs.sh](../../releases/rebuild-docs.sh)
|
||||
@@ -1,5 +1,5 @@
|
||||
1) Login as Admin user as Form Data to get Bearer token
|
||||
https://github.com/wekan/wekan/wiki/REST-API#example-call---as-form-data
|
||||
[REST API](REST-API.md#example-call---as-form-data)
|
||||
|
||||
2) There needs to be Custom Field added to board
|
||||
https://wekan.fi/api/v4.42/#wekan-rest-api-customfields
|
||||
|
||||
@@ -2,7 +2,7 @@ Wekan provides a python script to ease the call of the REST API from command lin
|
||||
|
||||
# Context
|
||||
|
||||
- [API Login to get Bearer token](REST-API#example-call---as-form-data)
|
||||
- [API Login to get Bearer token](REST-API.md#example-call---as-form-data)
|
||||
- [API docs and examples for various programming languages](https://wekan.fi/api/), there is Boards / Export for exporting board with API
|
||||
- In the right menu, scroll down REST API Docs etc links =====>
|
||||
- Wekan-Gogs integration with Node.js https://github.com/wekan/wekan-gogs
|
||||
|
||||
+12
-12
@@ -31,21 +31,21 @@ When calling a production Wekan server, ensure it is running via HTTPS and has a
|
||||
### Users
|
||||
| HTTP Method | Url | Short Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `POST` | `/users/register` | [Register a new user.](REST-API-User#user-register) |
|
||||
| `POST` | `/api/users` | [Create a new user.](REST-API-User#user-create) |
|
||||
| `PUT` | `/api/users/:id` | [Disable an existing user.](REST-API-User#disable-a-user-the-user-is-not-allowed-to-login-and-his-login-tokens-are-purged) |
|
||||
| `PUT` | `/api/users/:id` | [Enable an existing user.](REST-API-User#enable-a-user) |
|
||||
| `PUT` | `/api/users/:id` | [Admin takes the ownership.](REST-API-User#the-admin-takes-the-ownership-of-all-boards-of-the-user-archived-and-not-archived-where-the-user-is-admin-on) |
|
||||
| `DELETE` | `/api/users/:id` | [Delete an existing user.](REST-API-User#user-delete) ([Warning](https://github.com/wekan/wekan/issues/1289))|
|
||||
| `GET` | `/api/users/:id` | [Gets a user's information.](REST-API-User#user-information) |
|
||||
| `GET` | `/api/users` | [All of the users.](REST-API-User#user-list) |
|
||||
| `GET` | `/api/user` | [Gets a logged-in user.](REST-API-User#user-logged-in) |
|
||||
| `POST` | `/users/register` | [Register a new user.](User.md#user-register) |
|
||||
| `POST` | `/api/users` | [Create a new user.](User.md#user-create) |
|
||||
| `PUT` | `/api/users/:id` | [Disable an existing user.](User.md#disable-a-user-the-user-is-not-allowed-to-login-and-his-login-tokens-are-purged) |
|
||||
| `PUT` | `/api/users/:id` | [Enable an existing user.](User.md#enable-a-user) |
|
||||
| `PUT` | `/api/users/:id` | [Admin takes the ownership.](User.md#the-admin-takes-the-ownership-of-all-boards-of-the-user-archived-and-not-archived-where-the-user-is-admin-on) |
|
||||
| `DELETE` | `/api/users/:id` | [Delete an existing user.](User.md#user-delete) ([Warning](https://github.com/wekan/wekan/issues/1289))|
|
||||
| `GET` | `/api/users/:id` | [Gets a user's information.](User.md#user-information) |
|
||||
| `GET` | `/api/users` | [All of the users.](User.md#user-list) |
|
||||
| `GET` | `/api/user` | [Gets a logged-in user.](User.md#user-logged-in) |
|
||||
### Cards
|
||||
| HTTP Method | Url | Short Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `POST` | `/api/boards/:boardId/lists/:listId/cards` | [Add a card to a list, board, and swimlane.](REST-API-Cards#add-card-to-list-board-swimlane) |
|
||||
| `PUT` | `/api/boards/:boardId/lists/:fromListId/cards/:cardId` | [Update a card.](REST-API-Cards#update-a-card) |
|
||||
| `DELETE` | `/api/boards/:boardId/lists/:listId/cards/:cardId` | [Delete a card.](REST-API-Cards#update-a-card) |
|
||||
| `POST` | `/api/boards/:boardId/lists/:listId/cards` | [Add a card to a list, board, and swimlane.](Cards.md#add-card-to-list-board-swimlane) |
|
||||
| `PUT` | `/api/boards/:boardId/lists/:fromListId/cards/:cardId` | [Update a card.](Cards.md#update-a-card) |
|
||||
| `DELETE` | `/api/boards/:boardId/lists/:listId/cards/:cardId` | [Delete a card.](Cards.md#update-a-card) |
|
||||
### Board Domain Sharing
|
||||
| HTTP Method | Url | Short Description |
|
||||
| :--- | :--- | :--- |
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ BoardAdmin can change role of user at right sidebar / click avatar / select role
|
||||
|
||||
- https://wekan.fi/api/
|
||||
- api.py at https://github.com/wekan/wekan
|
||||
- Login to API https://github.com/wekan/wekan/wiki/REST-API#example-call---as-json
|
||||
- Login to API [REST API](REST-API.md#example-call---as-json)
|
||||
|
||||
# Disclaimer
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ This page tries to be as up to date as possible. If you see something wrong here
|
||||
|
||||
Please somebody add example by looking this:
|
||||
|
||||
[Issue](https://github.com/wekan/wekan/issues/1934) and [code](https://github.com/wekan/wekan/pull/1944/commits/be42b8d4cbdfa547ca019ab2dc9a590a115cc0e2). Also add to [Cards page](REST-API-Cards)
|
||||
[Issue](https://github.com/wekan/wekan/issues/1934) and [code](https://github.com/wekan/wekan/pull/1944/commits/be42b8d4cbdfa547ca019ab2dc9a590a115cc0e2). Also add to [Cards page](Cards.md)
|
||||
|
||||
# Add Swimlane to Board
|
||||
|
||||
| API URL / Code Link | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| [/api/boards/:boardId/swimlanes](https://github.com/wekan/wekan/blob/main/models/swimlanes.js#L223) | `yes` | `POST` |
|
||||
| [/api/boards/:boardId/swimlanes](../../models/swimlanes.js#L223) | `yes` | `POST` |
|
||||
|
||||
```shell
|
||||
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
@@ -33,7 +33,7 @@ The new swimlane's ID is returned in the format:
|
||||
|
||||
| API URL / Code Link | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| [/api/boards/:boardId/swimlanes/:swimlaneId](https://github.com/wekan/wekan/blob/main/models/swimlanes.js#L257) | `yes` | `DELETE` |
|
||||
| [/api/boards/:boardId/swimlanes/:swimlaneId](../../models/swimlanes.js#L257) | `yes` | `DELETE` |
|
||||
|
||||
```shell
|
||||
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
@@ -51,4 +51,4 @@ The swimlane's ID is returned in the format:
|
||||
|
||||
# In Wekan code
|
||||
|
||||
If you believe that code is the best documentation, be our guest: [models/cards.js](https://github.com/wekan/wekan/blob/main/models/swimlanes.js "Swimlane API code")
|
||||
If you believe that code is the best documentation, be our guest: [models/cards.js](../../models/swimlanes.js "Swimlane API code")
|
||||
+1
-1
@@ -332,4 +332,4 @@ curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
|
||||
# In Wekan code
|
||||
|
||||
If you believe that code is the best documentation, be our guest: [models/cards.js](https://github.com/wekan/wekan/blob/main/models/users.js "User API code")
|
||||
If you believe that code is the best documentation, be our guest: [models/cards.js](../../models/users.js "User API code")
|
||||
@@ -1,4 +1,4 @@
|
||||
[Sandstorm](Sandstorm) - [Sandstorm Backup](Export-from-Wekan-Sandstorm-grain-.zip-file)
|
||||
[Sandstorm](../Platforms/FOSS/Sandstorm/Sandstorm.md) - [Sandstorm Backup](../Platforms/FOSS/Sandstorm/Export-from-Wekan-Sandstorm-grain-.zip-file.md)
|
||||
|
||||
# Related
|
||||
|
||||
@@ -104,7 +104,7 @@ sudo snap refresh wekan --channel=latest/candidate --amend
|
||||
```
|
||||
5. [Restore Snap](#restore-wekan-snap)
|
||||
6. Copy back files directory, if it is there: `sudo cp -pR /root/common/files /var/snap/wekan/common/`
|
||||
7. If you use [Caddy](https://github.com/wekan/wekan/blob/main/docs/Webserver/Caddy.md), that is included in WeKan, edit /var/snap/wekan/Caddyfile to new syntax:
|
||||
7. If you use [Caddy](../Webserver/Caddy.md), that is included in WeKan, edit /var/snap/wekan/Caddyfile to new syntax:
|
||||
```
|
||||
wekan.yourcompany.com {
|
||||
tls {
|
||||
@@ -211,7 +211,7 @@ https://nosqlbooster.com/downloads
|
||||
|
||||
For below scheduled backup scripts, no info from above of this wiki page is required. Backup scripts below have the required settings.
|
||||
|
||||
This does backup of [Wekan+RocketChat snap databases](OAuth2) and php website etc.
|
||||
This does backup of [Wekan+RocketChat snap databases](../Login/OAuth2.md) and php website etc.
|
||||
|
||||
If you need to backup some remote server or cloud, you can use scp, or read [rclone docs](https://rclone.org/docs/) about how to configure saving to some other remote server or cloud.
|
||||
|
||||
@@ -520,7 +520,7 @@ cat board.json | xclip -se c
|
||||
```
|
||||
Then paste to webbrowser Wekan Add Board / Import / From previous export.
|
||||
|
||||
You can [save all MongoDB database content as JSON files](Export-from-Wekan-Sandstorm-grain-.zip-file). Files are base64 encoded in JSON files.
|
||||
You can [save all MongoDB database content as JSON files](../Platforms/FOSS/Sandstorm/Export-from-Wekan-Sandstorm-grain-.zip-file.md). Files are base64 encoded in JSON files.
|
||||
|
||||
Export board to Wekan JSON, and import as Wekan JSON can make some part of board to load, but you should check is some data missing.
|
||||
|
||||
@@ -762,7 +762,7 @@ makesRestore $1
|
||||
|
||||
## Docker Backup and Restore
|
||||
|
||||
[Docker Backup and Restore](Export-Docker-Mongo-Data)
|
||||
[Docker Backup and Restore](../Platforms/FOSS/Docker/Export-Docker-Mongo-Data.md)
|
||||
|
||||
[Wekan Docker Upgrade](https://github.com/wekan/wekan-mongodb#backup-before-upgrading)
|
||||
|
||||
@@ -776,11 +776,11 @@ makesRestore $1
|
||||
|
||||
Download Wekan grain with arrow down download button to .zip file. You can restore it later.
|
||||
|
||||
[Export data from Wekan Sandstorm grain .zip file](Export-from-Wekan-Sandstorm-grain-.zip-file)
|
||||
[Export data from Wekan Sandstorm grain .zip file](../Platforms/FOSS/Sandstorm/Export-from-Wekan-Sandstorm-grain-.zip-file.md)
|
||||
|
||||
## <a name="cloudron">Cloudron
|
||||
|
||||
If those [Backup](Backup) ways are not easily found at [Cloudron](Cloudron), one way is to install [Redash](https://redash.io/) and then backup this way:
|
||||
If those [Backup](Backup.md) ways are not easily found at [Cloudron](../Platforms/Propietary/Cloud/Cloudron/Cloudron.md), one way is to install [Redash](https://redash.io/) and then backup this way:
|
||||
|
||||
Redash works with this kind of queries:
|
||||
```json
|
||||
@@ -841,8 +841,8 @@ but downloading with API script still works:
|
||||
|
||||
7) Repeat steps 1-4 and 6 for every collection/table like boards,cards, etc
|
||||
|
||||
8) Remove from downloaded .json files extra query related data, so that it is similar like [any other Wekan database backup JSON files](Export-from-Wekan-Sandstorm-grain-.zip-file)
|
||||
8) Remove from downloaded .json files extra query related data, so that it is similar like [any other Wekan database backup JSON files](../Platforms/FOSS/Sandstorm/Export-from-Wekan-Sandstorm-grain-.zip-file.md)
|
||||
|
||||
9) Insert data to some other Wekan install with nosqlbooster like mentioned at page [Backup](Backup)
|
||||
9) Insert data to some other Wekan install with nosqlbooster like mentioned at page [Backup](Backup.md)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Current working way to upgrade is:
|
||||
|
||||
## Docker
|
||||
|
||||
1. Create Mongodump of your database like at [Backup page](https://github.com/wekan/wekan/wiki/Backup)
|
||||
1. Create Mongodump of your database like at [Backup page](../Backup.md)
|
||||
2. Use newest WeKan docker-compose.yml from https://github.com/wekan/wekan with WRITABLE_PATH setup to Rclone/MinIO like below this page
|
||||
3. Mongorestore to MongoDB 6.0.3 database
|
||||
4. Use WeKan Admin Panel `Move all attachments to filesystem` button to move all attachments to MinIO.
|
||||
|
||||
@@ -52,7 +52,7 @@ apt install ntfs-3g
|
||||
```
|
||||
mount /dev/sdd1 /mnt
|
||||
```
|
||||
4. Stop WeKan Docker containers. If copying raw database files, WeKan and MongoDB should not be running, so that MongoDB would not be even more corrupted. (If MongoDB is running, and you have mongodump backup, you can [Backup with mongorestore](Backup)
|
||||
4. Stop WeKan Docker containers. If copying raw database files, WeKan and MongoDB should not be running, so that MongoDB would not be even more corrupted. (If MongoDB is running, and you have mongodump backup, you can [Backup with mongorestore](Backup.md)
|
||||
```
|
||||
docker stop wekan-db wekan-app
|
||||
```
|
||||
|
||||
@@ -3,11 +3,11 @@ If you know updates to this, please add new issue about it. Make changes to this
|
||||
```
|
||||
git clone https://github.com/wekan/wekan.wiki
|
||||
```
|
||||
There edit Browser-compatibility-matrix.md and attach it with .txt extension or in .zip file to https://github.com/wekan/wekan/wiki/issues
|
||||
There edit Browser-compatibility-matrix.md and attach it with .txt extension or in .zip file to https://github.com/wekan/wekan/issues
|
||||
|
||||
## Webkit based
|
||||
|
||||
Browser | [PWA](PWA) | Mobile | Desktop | OS
|
||||
Browser | [PWA](PWA.md) | Mobile | Desktop | OS
|
||||
------- | ----| ------ | ------- | -------
|
||||
Safari | iOS Yes | iOS Newest | Newest | iOS, macOS
|
||||
Iris | No | No | Newest | [Iris browser news](https://www.riscosopen.org/forum/forums/1/topics/19545), Download [RISC OS Direct](https://www.riscosdev.com/direct/) for Raspberry Pi etc. [Other versions of RISC OS for many ARM CPU devices](https://www.riscosopen.org/content/downloads) do not have Iris browser yet, but there could be some way to transfer Iris browser files from RISC OS Direct.
|
||||
@@ -16,7 +16,7 @@ Iris | No | No | Newest | [Iris browser news](https://www.riscosopen.org/forum/f
|
||||
|
||||
Any telemetry at any Firefox based browser can be additionally most locked down with arkenfox user JS.
|
||||
|
||||
Browser | [PWA](PWA) | Mobile | Desktop | OS
|
||||
Browser | [PWA](PWA.md) | Mobile | Desktop | OS
|
||||
------- | ----| ------ | ------- | -------
|
||||
[uWolf](https://open-store.io/app/uwolf.chromiumos-guy) | No | Yes | No | Ubuntu Touch webbrowser based of LibreWolf
|
||||
[LibreWolf](https://librewolf.net) | No | No | Yes | [Win/Mac/Linux](https://librewolf.net/installation/), [Repos](https://codeberg.org/librewolf)
|
||||
@@ -30,7 +30,7 @@ Firefox | No | Newest | Newest | iOS/Android/Win/Mac/Linux/BSD/RasPi. At iOS use
|
||||
|
||||
## Chromium based
|
||||
|
||||
Browser | [PWA](PWA) | Mobile | Desktop | OS
|
||||
Browser | [PWA](PWA.md) | Mobile | Desktop | OS
|
||||
------- | ----| ------ | ------- | -------
|
||||
Chrome | Android Yes | Android Newest | Newest | Win/Mac/Linux
|
||||
Chromium | Android Yes | Android Newest | Newest | Win/Mac/Linux/RasPi
|
||||
@@ -47,13 +47,13 @@ Morph Browser | No | Yes | Yes | [Ubuntu Touch](https://ubports.com) based on Ub
|
||||
|
||||
## EdgeHTML based
|
||||
|
||||
Browser | [PWA](PWA) | Mobile | Desktop | OS
|
||||
Browser | [PWA](PWA.md) | Mobile | Desktop | OS
|
||||
------- | ----| ------ | ------- | -------
|
||||
Legacy Edge | No | No | Not | Not compatible with newest WeKan. Worked with Wekan v4.19 and before.
|
||||
|
||||
## MSHTML based
|
||||
|
||||
Browser | [PWA](PWA) | Mobile | Desktop | OS
|
||||
Browser | [PWA](PWA.md) | Mobile | Desktop | OS
|
||||
------- | ----| ------ | ------- | -------
|
||||
Internet Explorer | No | No | No | No | No
|
||||
|
||||
@@ -73,7 +73,7 @@ Internet Explorer | No | No | No | No | No
|
||||
|
||||
## TUI webbrowsers, not yet supported
|
||||
|
||||
Browser | [PWA](PWA) | Mobile | Desktop | OS
|
||||
Browser | [PWA](PWA.md) | Mobile | Desktop | OS
|
||||
------- | ----| ------ | ------- | -------
|
||||
[Chawan](https://chawan.net) | No | No | Yes | Linux/BSD. [Release 0.2.0](https://chawan.net/news/chawan-0-2-0.html), [HN](https://news.ycombinator.com/item?id=44293260)
|
||||
Elinks | No | No | Yes | Win/Mac/Linux/BSD/DOS TUI.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[Browser compatibility matrix](../Browser/Browser-compatibility-matrix)
|
||||
[Browser compatibility matrix](Browser-compatibility-matrix.md)
|
||||
|
||||
## Install your Wekan server as an app
|
||||
|
||||
@@ -85,17 +85,17 @@
|
||||
|
||||
These are mostly just notes to WeKan maintainer xet7 itself, how xet7 did get official WeKan Android Play Store app working at fullscreen of Android phone and Android tablet. Requires a lot time to setup, not recommended.
|
||||
|
||||
Related, for creating apps to other appstores: https://github.com/wekan/wekan/wiki/Browser-compatibility-matrix
|
||||
Related, for creating apps to other appstores: [Browser compatibility matrix](Browser-compatibility-matrix.md)
|
||||
|
||||
1. https://www.pwabuilder.com to create Android app. Select fullscreen when creating app, also requires correctly installed assetlinks.json with below Caddy config to get fullscreen. When creating app, make your own signing key if you don't have one yet. Releasing Android app is at Play Console https://play.google.com/console/ .
|
||||
|
||||
2. PWABuilder has about 100+ downloadable icons etc webmanifest requirements like is at https://github.com/wekan/wekan/tree/main/public
|
||||
2. PWABuilder has about 100+ downloadable icons etc webmanifest requirements like is at [public](../../public)
|
||||
|
||||
3. Clone WeKan repo, add favicons etc from step 2 to wekan/public/ , and build WeKan bundle from source like at https://github.com/wekan/wekan/wiki/Emoji . Note: Currently WeKan does not have feature for custom favicons, it would require a lot of work for 100+ favicons etc customizations.
|
||||
3. Clone WeKan repo, add favicons etc from step 2 to wekan/public/ , and build WeKan bundle from source like at [Emoji](../Features/Emoji.md) . Note: Currently WeKan does not have feature for custom favicons, it would require a lot of work for 100+ favicons etc customizations.
|
||||
|
||||
4. Run bundle at server like https://github.com/wekan/wekan/wiki/Offline or https://github.com/wekan/wekan/wiki/Raspberry-Pi
|
||||
4. Run bundle at server like [Offline](../Platforms/Propietary/Windows/Offline.md) or [Raspberry Pi](../Platforms/FOSS/RaspberryPi/Raspberry-Pi.md)
|
||||
|
||||
5. Install Caddy like https://github.com/wekan/wekan/wiki/Caddy-Webserver-Config , with this config for PWA at https://boards.example.com , there add assetlinks.json details you got when you downloaded Android .zip file from https://pwabuilder.com , see assetlinks issue about where at Play Console those 2 SHA256 keys https://github.com/pwa-builder/PWABuilder/issues/3867#issuecomment-1450826565
|
||||
5. Install Caddy like [Caddy Webserver Config](../Webserver/Caddy.md) , with this config for PWA at https://boards.example.com , there add assetlinks.json details you got when you downloaded Android .zip file from https://pwabuilder.com , see assetlinks issue about where at Play Console those 2 SHA256 keys https://github.com/pwa-builder/PWABuilder/issues/3867#issuecomment-1450826565
|
||||
|
||||
6. At Play Console https://play.google.com/console/ there is `App Integrity` button in the sidemenu (highlighted blue in the screenshot) where you find required 2 SHA256 keys for Caddyfile:
|
||||
|
||||
|
||||
@@ -158,13 +158,13 @@ sudo systemctl enable wekan
|
||||
sudo systemctl start wekan
|
||||
```
|
||||
For SSL/TLS, I run Caddy at front of Node.js:
|
||||
https://github.com/wekan/wekan/blob/main/docs/Webserver/Caddy.md
|
||||
[Caddy](../Webserver/Caddy.md)
|
||||
|
||||
Related is docs about Raspberry Pi:
|
||||
https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/RaspberryPi/Raspberry-Pi.md
|
||||
[Raspberry Pi](../Platforms/FOSS/RaspberryPi/Raspberry-Pi.md)
|
||||
|
||||
And also about Windows bundle:
|
||||
https://github.com/wekan/wekan/blob/main/docs/Platforms/Propietary/Windows/Offline.md
|
||||
[Offline](../Platforms/Propietary/Windows/Offline.md)
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -161,9 +161,9 @@ top -p $(pgrep node)
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [MongoDB-Oplog-Configuration.md](../docs/Databases/MongoDB-Oplog-Configuration.md) - Full setup guide
|
||||
- [AWS.md](../docs/Platforms/Propietary/Cloud/AWS.md) - AWS oplog configuration
|
||||
- [LDAP.md](../docs/Login/LDAP.md) - LDAP with oplog setup
|
||||
- [MongoDB-Oplog-Configuration.md](MongoDB-Oplog-Configuration.md) - Full setup guide
|
||||
- [AWS.md](../Platforms/Propietary/Cloud/AWS.md) - AWS oplog configuration
|
||||
- [LDAP.md](../Login/LDAP.md) - LDAP with oplog setup
|
||||
- [ToroDB-PostgreSQL](../docs/Databases/ToroDB-PostgreSQL/docker-compose.yml) - ToroDB oplog config
|
||||
|
||||
## Files Modified This Session
|
||||
|
||||
@@ -79,7 +79,7 @@ docker-compose up -d
|
||||
9) Wekan and databases bind to address 0.0.0.0 so could be also available to other
|
||||
computers in network. I have not tested this.
|
||||
|
||||
10) [Restore your MongoDB data](https://github.com/wekan/wekan/wiki/Export-Docker-Mongo-Data).
|
||||
10) [Restore your MongoDB data](../../Platforms/FOSS/Docker/Export-Docker-Mongo-Data.md).
|
||||
|
||||
## Feedback
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ version: '2'
|
||||
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
|
||||
#---------------------------------------------------------------------------------------------------------
|
||||
# ==== CREATING USERS AND LOGGING IN TO WEKAN ====
|
||||
# https://github.com/wekan/wekan/wiki/Adding-users
|
||||
# ../../Login/Adding-users.md
|
||||
#---------------------------------------------------------------------------------------------------------
|
||||
# ==== FORGOT PASSWORD ====
|
||||
# https://github.com/wekan/wekan/wiki/Forgot-Password
|
||||
# ../../Login/Forgot-Password.md
|
||||
#---------------------------------------------------------------------------------------------------------
|
||||
# ==== Upgrading Wekan to new version =====
|
||||
# NOTE: MongoDB has changed from 3.x to 4.x, in that case you need backup/restore with --noIndexRestore
|
||||
# see https://github.com/wekan/wekan/wiki/Backup
|
||||
# see ../../Backup/Backup.md
|
||||
# 1) Stop Wekan:
|
||||
# docker-compose stop
|
||||
# 2) Remove old Wekan app (wekan-app only, not that wekan-db container that has all your data)
|
||||
@@ -48,7 +48,7 @@ version: '2'
|
||||
# docker-compose stop
|
||||
# ----------------------------------------------------------------------------------
|
||||
# ===== INSIDE DOCKER CONTAINERS, AND BACKUP/RESTORE ====
|
||||
# https://github.com/wekan/wekan/wiki/Backup
|
||||
# ../../Backup/Backup.md
|
||||
# If really necessary, repair MongoDB: https://github.com/wekan/wekan-mongodb/issues/6#issuecomment-424004116
|
||||
# 1) Going inside containers:
|
||||
# a) Wekan app, does not contain data
|
||||
@@ -195,7 +195,7 @@ services:
|
||||
# ==== EMAIL SETTINGS ====
|
||||
# Email settings are only at MAIL_URL and MAIL_FROM.
|
||||
# Admin Panel has test button, but it's not used for settings.
|
||||
# see https://github.com/wekan/wekan/wiki/Troubleshooting-Mail
|
||||
# see ../../Email/Troubleshooting-Mail.md
|
||||
# For SSL in email, change smtp:// to smtps://
|
||||
# NOTE: Special characters need to be url-encoded in MAIL_URL.
|
||||
# You can encode those characters for example at: https://www.urlencoder.org
|
||||
@@ -236,7 +236,7 @@ services:
|
||||
# - export KADIRA_OPTIONS_ENDPOINT=http://127.0.0.1:11011
|
||||
#---------------------------------------------------------------
|
||||
# ==== OPTIONAL: LOGS AND STATS ====
|
||||
# https://github.com/wekan/wekan/wiki/Logs
|
||||
# ../../Features/Logs.md
|
||||
#
|
||||
# Daily export of Wekan changes as JSON to Logstash and ElasticSearch / Kibana (ELK)
|
||||
# https://github.com/wekan/wekan-logstash
|
||||
@@ -256,7 +256,7 @@ services:
|
||||
#---------------------------------------------------------------
|
||||
# ==== WEKAN API AND EXPORT BOARD ====
|
||||
# Wekan Export Board works when WITH_API=true.
|
||||
# https://github.com/wekan/wekan/wiki/REST-API
|
||||
# ../../API/REST-API.md
|
||||
# https://github.com/wekan/wekan-gogs
|
||||
# If you disable Wekan API with false, Export Board does not work.
|
||||
- WITH_API=true
|
||||
@@ -361,7 +361,7 @@ services:
|
||||
#- MATOMO_WITH_USERNAME=false
|
||||
#-----------------------------------------------------------------
|
||||
# ==== METRICS ALLOWED IP ADDRESSES ====
|
||||
# https://github.com/wekan/wekan/wiki/Metrics
|
||||
# ../../Features/Metrics.md
|
||||
#- METRICS_ALLOWED_IP_ADDRESSES=192.168.0.100,192.168.0.200
|
||||
#-----------------------------------------------------------------
|
||||
# ==== BROWSER POLICY AND TRUSTED IFRAME URL ====
|
||||
@@ -378,7 +378,7 @@ services:
|
||||
#- WEBHOOKS_ATTRIBUTES=
|
||||
#-----------------------------------------------------------------
|
||||
# ==== AUTOLOGIN WITH OIDC/OAUTH2 ====
|
||||
# https://github.com/wekan/wekan/wiki/autologin
|
||||
# ../../Login/autologin.md
|
||||
#- OIDC_REDIRECTION_ENABLED=true
|
||||
#---------------------------------------------
|
||||
# ==== OAUTH2 ORACLE on premise identity manager OIM ====
|
||||
@@ -386,7 +386,7 @@ services:
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 ONLY WITH OIDC AND DOORKEEPER AS INDENTITY PROVIDER
|
||||
# https://github.com/wekan/wekan/issues/1874
|
||||
# https://github.com/wekan/wekan/wiki/OAuth2
|
||||
# ../../Login/OAuth2.md
|
||||
# Enable the OAuth2 connection
|
||||
# example: OAUTH2_ENABLED=true
|
||||
#- OAUTH2_ENABLED=false
|
||||
@@ -396,7 +396,7 @@ services:
|
||||
#- OAUTH2_ADFS_ENABLED=false
|
||||
# Azure AD B2C. https://github.com/wekan/wekan/issues/5242
|
||||
#- OAUTH2_B2C_ENABLED=false
|
||||
# OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2
|
||||
# OAuth2 docs: ../../Login/OAuth2.md
|
||||
# OAuth2 Client ID, for example from Rocket.Chat. Example: abcde12345
|
||||
# example: OAUTH2_CLIENT_ID=abcde12345
|
||||
#- OAUTH2_CLIENT_ID=
|
||||
@@ -417,7 +417,7 @@ services:
|
||||
#- OAUTH2_TOKEN_ENDPOINT=
|
||||
#-----------------------------------------------------------------
|
||||
# ==== LDAP ====
|
||||
# https://github.com/wekan/wekan/wiki/LDAP
|
||||
# ../../Login/LDAP.md
|
||||
# For Snap settings see https://github.com/wekan/wekan-snap/wiki/Supported-settings-keys
|
||||
# Most settings work both on Snap and Docker below.
|
||||
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
|
||||
|
||||
@@ -13,11 +13,11 @@ https://user-images.githubusercontent.com/70445835/184324324-b4bd466e-6873-4eef-
|
||||
|
||||
## <a name="Time"></a>Time
|
||||
|
||||
* [Due Date](Due-Date)
|
||||
* [Day of week start](Day-of-week-start)
|
||||
* [Due Date](Due-Date.md)
|
||||
* [Day of week start](Day-of-week-start.md)
|
||||
|
||||
## What features are missing
|
||||
|
||||
* [Calendar issues](https://github.com/wekan/wekan/issues?q=is%3Aissue+is%3Aopen+calendar+label%3AFeature%3ACalendar-view)
|
||||
* [Time Tracking](Time-Tracking)
|
||||
* [Gantt Chart](Gantt)
|
||||
* [Time Tracking](Time-Tracking.md)
|
||||
* [Gantt Chart](../Features/Gantt.md)
|
||||
@@ -15,7 +15,7 @@ sudo apt -y install ntpdate
|
||||
sudo ntpdate ntp.ubuntu.com
|
||||
```
|
||||
|
||||
5. Now local time shows correctly at webbrowser, like for example at Friend Desktop https://github.com/wekan/wekan/wiki/Friend
|
||||
5. Now local time shows correctly at webbrowser, like for example at Friend Desktop [Friend](../Platforms/FOSS/Friend/Friend.md)
|
||||
|
||||
For testing any other time related code, that it shows correct local time, compare time functions results to this:
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ https://www.youtube.com/watch?v=ke-mbnZM3zE
|
||||
- Translated to 70+ languages https://explore.transifex.com/wekan/wekan/
|
||||
- Used in most countries of the world https://snapcraft.io/wekan
|
||||
- Biggest user have about 30k users, using many pods at Kubernetes
|
||||
- Changelog https://github.com/wekan/wekan/blob/main/CHANGELOG.md
|
||||
- Changelog [CHANGELOG.md](../../CHANGELOG.md)
|
||||
- Commercial Support at https://wekan.fi/commercial-support/ . Currently looking who could sponsor more of maintenance and development.
|
||||
|
||||
## How much effort to create Meteor WeKan, in COCOMO model
|
||||
@@ -26,39 +26,39 @@ https://www.youtube.com/watch?v=ke-mbnZM3zE
|
||||
## Meteor WeKan Browser support
|
||||
|
||||
- Javascript enabled Desktop and Mobile Safari/Chromium/Firefox based browsers
|
||||
- Apps at Play Store, Windows Microsoft Store, Ubuntu Touch OpenStore https://github.com/wekan/wekan/wiki/Browser-compatibility-matrix
|
||||
- Alternatively, for self-hosted WeKan, create PWA icon https://github.com/wekan/wekan/wiki/PWA
|
||||
- Using WeKan with big touchscreen https://github.com/wekan/wekan/wiki/Touch-Screen-support
|
||||
- Apps at Play Store, Windows Microsoft Store, Ubuntu Touch OpenStore [Browser compatibility matrix](../Browsers/Browser-compatibility-matrix.md)
|
||||
- Alternatively, for self-hosted WeKan, create PWA icon [PWA](../Browsers/PWA.md)
|
||||
- Using WeKan with big touchscreen [Touch Screen support](../DragDrop/Touch-Screen-support.md)
|
||||
|
||||
## Meteor WeKan Server
|
||||
|
||||
- Meteor.js 2.x, Node.js 14.x https://github.com/wekan/node-v14-esm/releases/tag/v14.21.4 , MongoDB 6.x
|
||||
- CPU: amd64, arm64, s390x, Source Bundle at https://github.com/wekan/wekan/wiki/Raspberry-Pi
|
||||
- Windows On-Premise: https://github.com/wekan/wekan/wiki/Offline
|
||||
- Mac: Docker, or at https://github.com/wekan/wekan/wiki/Mac
|
||||
- CPU: amd64, arm64, s390x, Source Bundle at [Raspberry Pi](../Platforms/FOSS/RaspberryPi/Raspberry-Pi.md)
|
||||
- Windows On-Premise: [Offline](../Platforms/Propietary/Windows/Offline.md)
|
||||
- Mac: Docker, or at [Mac](../Platforms/Propietary/Mac.md)
|
||||
- Newest at platforms: Source Bundle, Snap Candidate, Docker, Kubernetes https://wekan.fi
|
||||
- Platforms that will be updated to be up-to-date: Snap Stable, Sandstorm, UCS https://wekan.fi
|
||||
|
||||
## Meteor WeKan Features
|
||||
|
||||
- [WeKan Design Principles](https://github.com/wekan/wekan/wiki/Design-Principles)
|
||||
- [WeKan Design Principles](Design-Principles.md)
|
||||
- Realtime UI. When someone makes change, like drag card or add text, everyone will see changes immediately. This is because Meteor listens to MongoDB change stream, and updates UI. For some rare cases, there needs to be reload of page, those need to be fixed.
|
||||
- Whitelabel: Change product name and product image at login screen and board left top corner. Not changing favicon, because there are 100+ favicons for different devices.
|
||||
- Drag Drop at Desktop and Mobile:
|
||||
- Board Icons, Swimlanes, Lists, Cards, Checklists, Checklist Items
|
||||
- Drag handles, per screen https://github.com/wekan/wekan/wiki/Drag-Drop
|
||||
- Drag handles, per screen [Drag Drop](../DragDrop/Drag-Drop.md)
|
||||
- Some features similar to Trello and Jira. Some features not in Trello and Jira.
|
||||
- IFTTT Rules like Trello Butler https://github.com/wekan/wekan/wiki/IFTTT , translated to all supported languages. Trello Butler is only In English.
|
||||
- IFTTT Rules like Trello Butler [IFTTT](../Features/IFTTT/IFTTT.md) , translated to all supported languages. Trello Butler is only In English.
|
||||
- Many Assignees like in Jira.
|
||||
- Change background image and Card Cover. Change Theme. Change color of Boards, Swimlane, List, Card, Label.
|
||||
- Markdown at: Board name and description, Swimlane, List, Card title and description, Checklist and Checklist Item, Subtask title, https://github.com/wekan/wekan/wiki/Wekan-Markdown
|
||||
- Emoji https://github.com/wekan/wekan/wiki/Emoji
|
||||
- Markdown at: Board name and description, Swimlane, List, Card title and description, Checklist and Checklist Item, Subtask title, [Wekan Markdown](../Features/Markdown/Markdown.md)
|
||||
- Emoji [Emoji](../Features/Emoji.md)
|
||||
- MathML
|
||||
- Preview card attachment image and PDF
|
||||
- Show Start at End Dates of card at Calendar of Swimlanes/Lists/Calendar dropdown menu
|
||||
- Custom Fields https://github.com/wekan/wekan/wiki/Custom-Fields
|
||||
- Subtasks https://github.com/wekan/wekan/wiki/Subtasks
|
||||
- Planning Poker / Scrum Poker at Card https://github.com/wekan/wekan/wiki/Planning-Poker . No other card games yet ;)
|
||||
- Custom Fields [Custom Fields](../API/Custom-Fields.md)
|
||||
- Subtasks [Subtasks](../Features/Subtasks.md)
|
||||
- Planning Poker / Scrum Poker at Card [Planning Poker](../Features/Planning-Poker.md) . No other card games yet ;)
|
||||
- For board actions, like move card etc, Per Board and Global Outgoing and 2-way webhooks to Chat (RocketChat, Slack, Discord etc), NodeRED, or PHP webhook receiver like https://github.com/wekan/webhook
|
||||
- At top right username menu:
|
||||
- Search All Boards, with search options
|
||||
@@ -69,10 +69,10 @@ https://www.youtube.com/watch?v=ke-mbnZM3zE
|
||||
- Filter cards, by user etc
|
||||
- Multi-Selection, checkmark and drag many cards at once
|
||||
- Sort current board
|
||||
- Python and PHP based additional features https://github.com/wekan/wekan/wiki/Python
|
||||
- Python and PHP based additional features [Python](../Features/Python.md)
|
||||
- Admin Panel:
|
||||
- Organizations/Teams/People. Impersonate User, logged to database when someone uses that feature.
|
||||
- Customize Translations https://github.com/wekan/wekan/wiki/Customize-Translations
|
||||
- Customize Translations [Customize Translations](../Translations/Customize-Translations.md)
|
||||
|
||||
## Technical details
|
||||
|
||||
@@ -82,38 +82,38 @@ https://www.youtube.com/watch?v=ke-mbnZM3zE
|
||||
- Big performance improvements https://github.com/wekan/wekan/issues/5000
|
||||
- Using same style of API for client and serverside
|
||||
- Use more ReactiveCache than MiniMongo
|
||||
- Optimizing and Debugging Meteor, reducing size, links to WeKan dependencies https://github.com/wekan/wekan/wiki/Debugging
|
||||
- Optimizing and Debugging Meteor, reducing size, links to WeKan dependencies [Debugging](../DeveloperDocs/Debugging.md)
|
||||
- Custom OIDC/OAuth2 login
|
||||
- Repo https://github.com/wekan/wekan/tree/main/packages/wekan-oidc
|
||||
- Repo [wekan-oidc](../../packages/wekan-oidc)
|
||||
- Features:
|
||||
- At Admin Panel, you can change text of login page button `Login with OIDC`
|
||||
- Auth0 https://github.com/wekan/wekan/wiki/OAuth2#auth0 . This did take about half year to implement by xet7, was not available anywhere else for Meteor.
|
||||
- Oracle OIM. https://github.com/wekan/wekan/wiki/Oracle-OIM . For this, customer provided some code. xet7 added fix to make login work even when some login field was missing data, by taking data from other field, all this while xet7 did not have access to customers Oracle OIM, just guessing about what code to add, and it worked.
|
||||
- GitLab https://github.com/wekan/wekan/wiki/OAuth2#gitlab-providing-oauth2-login-to-wekan
|
||||
- RocketChat https://github.com/wekan/wekan/wiki/OAuth2#rocketchat-providing-oauth2-login-to-wekan
|
||||
- Nextcloud https://github.com/wekan/wekan/wiki/Nextcloud
|
||||
- ADFS 4.0 using OAuth2 and OpenID https://github.com/wekan/wekan/wiki/ADFS
|
||||
- Azure https://github.com/wekan/wekan/wiki/Azure
|
||||
- Keycloak https://github.com/wekan/wekan/wiki/Keycloak
|
||||
- Google login https://github.com/wekan/wekan/wiki/Google-login
|
||||
- Auth0 [OAuth2](../Login/OAuth2.md#auth0) . This did take about half year to implement by xet7, was not available anywhere else for Meteor.
|
||||
- Oracle OIM. [Oracle OIM](../Login/Oracle-OIM.md) . For this, customer provided some code. xet7 added fix to make login work even when some login field was missing data, by taking data from other field, all this while xet7 did not have access to customers Oracle OIM, just guessing about what code to add, and it worked.
|
||||
- GitLab [OAuth2](../Login/OAuth2.md#gitlab-providing-oauth2-login-to-wekan)
|
||||
- RocketChat [OAuth2](../Login/OAuth2.md#rocketchat-providing-oauth2-login-to-wekan)
|
||||
- Nextcloud [Nextcloud](../Login/Nextcloud.md)
|
||||
- ADFS 4.0 using OAuth2 and OpenID [ADFS](../Login/ADFS.md)
|
||||
- Azure [Azure](../Login/Azure/Azure.md)
|
||||
- Keycloak [Keycloak](../Login/Keycloak/Keycloak.md)
|
||||
- Google login [Google login](../Login/Google-login.md)
|
||||
- Custom LDAP
|
||||
- LDAP https://github.com/wekan/wekan/wiki/LDAP
|
||||
- LDAP AD Simple Auth https://github.com/wekan/wekan/wiki/LDAP-AD-Simple-Auth
|
||||
- LDAP [LDAP](../Login/LDAP.md)
|
||||
- LDAP AD Simple Auth [LDAP AD Simple Auth](../Login/LDAP-AD-Simple-Auth.md)
|
||||
- Default board for users https://github.com/wekan/wekan/pull/5098
|
||||
- Brute Force Accounts Lockout https://github.com/wekan/wekan/wiki/Accounts-Lockout
|
||||
- Markdown, Emoji, MathML, sanitizing https://github.com/wekan/wekan/blob/main/packages/markdown/src/template-integration.js
|
||||
- Brute Force Accounts Lockout [Accounts Lockout](../Login/Accounts-Lockout.md)
|
||||
- Markdown, Emoji, MathML, sanitizing [template-integration.js](../../packages/markdown/src/template-integration.js)
|
||||
- Many security fixes from Responsible Disclosure https://wekan.fi/hall-of-fame/
|
||||
|
||||
## Upcoming Features
|
||||
|
||||
- CAS https://github.com/wekan/wekan/wiki/CAS
|
||||
- SAML https://github.com/wekan/wekan/wiki/SAML
|
||||
- CAS [CAS](../Login/CAS.md)
|
||||
- SAML [SAML](../Login/SAML.md)
|
||||
|
||||
## Video
|
||||
|
||||
- Creator of WeKan, mquandalle (Maxime Quandalle) https://www.youtube.com/watch?v=N3iMLwCNOro
|
||||
- https://github.com/wekan/wekan/wiki/FAQ#what-was-wekan-fork--wefork
|
||||
- Maintainer of WeKan since December 2016, xet7 (Lauri Ojansivu), about WeKan Multiverse at EU NGI Dapsi https://www.youtube.com/watch?v=BPPjiZHVeyM . More about Multiverse at https://github.com/wekan/wekan/wiki/WeKan-Multiverse-Roadmap
|
||||
- [FAQ](../FAQ/FAQ.md#what-was-wekan-fork--wefork)
|
||||
- Maintainer of WeKan since December 2016, xet7 (Lauri Ojansivu), about WeKan Multiverse at EU NGI Dapsi https://www.youtube.com/watch?v=BPPjiZHVeyM . More about Multiverse at [WeKan Multiverse Roadmap](Multiverse/WeKan-Multiverse-Roadmap.md)
|
||||
|
||||
## Maybe?
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
- Original WeKan Design:
|
||||
- Uses WeKan style confirm popups `Popup.afterConfirm(` , translations etc. Please look at existing code to keep same style.
|
||||
- Kanban, the good parts. Like was done when figuring out best design for Swimlanes https://github.com/wekan/wekan/issues/955
|
||||
- Intuitive, easy to figure out, prevent messing up something https://github.com/wekan/wekan/wiki/Monkey-Proof-Software
|
||||
- Intuitive, easy to figure out, prevent messing up something [Monkey Proof Software](Monkey-Proof-Software.md)
|
||||
- Polished UI/UX
|
||||
- Menu options are there where you would expect then to be.
|
||||
- Try to fix anything unexpected.
|
||||
@@ -14,24 +14,24 @@
|
||||
- Swimlanes
|
||||
- All the use cases of WeKan feature/fix contributors
|
||||
- Cross-platform. Support many CPU/OS/Browsers for Desktop and Mobile.
|
||||
- Support many browsers https://github.com/wekan/wekan/wiki/Browser-compatibility-matrix . Add support for more.
|
||||
- PWA https://github.com/wekan/wekan/wiki/PWA
|
||||
- Support many browsers [Browser compatibility matrix](../Browsers/Browser-compatibility-matrix.md) . Add support for more.
|
||||
- PWA [PWA](../Browsers/PWA.md)
|
||||
- On-Premise:
|
||||
- Linux amd64/arm64/s390x
|
||||
- https://wekan.fi/install/
|
||||
- https://github.com/wekan/wekan/wiki/Raspberry-Pi
|
||||
- Windows https://github.com/wekan/wekan/wiki/Offline
|
||||
- Mac https://github.com/wekan/wekan/wiki/Mac
|
||||
- [Raspberry Pi](../Platforms/FOSS/RaspberryPi/Raspberry-Pi.md)
|
||||
- Windows [Offline](../Platforms/Propietary/Windows/Offline.md)
|
||||
- Mac [Mac](../Platforms/Propietary/Mac.md)
|
||||
- Not like other software:
|
||||
- But note that design of all software changes often, when they are in active development.
|
||||
- Not bad parts of Trello:
|
||||
- Not the exact design of Trello https://github.com/wekan/wekan/wiki/FAQ#why-does-wekan-look-so-different-now-compared-to--v09
|
||||
- Not the exact design of Trello [FAQ](../FAQ/FAQ.md#why-does-wekan-look-so-different-now-compared-to--v09)
|
||||
- Not so many duplicate menus and texts like Trello
|
||||
- Not so much empty space
|
||||
- Some good parts of Trello:
|
||||
- Import from Trello
|
||||
- Shortcuts https://github.com/wekan/wekan/issues/1878
|
||||
- IFTTT Rules like Trello Butler https://github.com/wekan/wekan/wiki/IFTTT
|
||||
- IFTTT Rules like Trello Butler [IFTTT](../Features/IFTTT/IFTTT.md)
|
||||
- Progress bar of checklist
|
||||
- Some good parts of Jira:
|
||||
- Multiple assignees
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
Existing WeKan features
|
||||
|
||||
- 2025-04 Opened card has comments at right, copied from [original WeKan feature Maximize Card 2021-06-14](https://github.com/wekan/wekan/blob/main/CHANGELOG.md#v535-2021-06-14-wekan-release). Trello does not have WeKan feature Minimize Card.
|
||||
- 2025-04 Opened card has comments at right, copied from [original WeKan feature Maximize Card 2021-06-14](../../CHANGELOG.md#v535-2021-06-14-wekan-release). Trello does not have WeKan feature Minimize Card.
|
||||
- https://community.atlassian.com/forums/Trello-questions/comments-in-the-ticket-on-the-right/qaq-p/3029030
|
||||
- https://community.atlassian.com/forums/Trello-questions/Trello-card-comments-now-appear-in-a-side-tab-how-to-go-back-to/qaq-p/3003380
|
||||
- 2025-02 Mirror Card, copied from [original WeKan feature Linked Cards from 2018-04-18](https://github.com/wekan/wekan/pull/1592), discussed at [WeKan issue 5683](https://github.com/wekan/wekan/issues/5683)
|
||||
|
||||
@@ -57,7 +57,7 @@ Custom CSS and Javascript is very dangerous: https://github.com/wekan/wekan/issu
|
||||
|
||||
> I had to previously find a way [how to fix Custom Javascript in RocketChat](https://forums.rocket.chat/t/big-issue-with-custom-javascript/261/4?u=xet7) because it broke my RocketChat install.
|
||||
>
|
||||
> Also, previously I had to [fix XSS bug](https://github.com/wekan/wekan/blob/main/CHANGELOG.md#v385-2020-03-23-wekan-release) because adding Javascript to input fields did run that Javascript code.
|
||||
> Also, previously I had to [fix XSS bug](../../CHANGELOG.md#v385-2020-03-23-wekan-release) because adding Javascript to input fields did run that Javascript code.
|
||||
>
|
||||
> I'll try to find is there a safe way to do this.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## About AVX
|
||||
|
||||
If CPU does not have AVX, Snap Candidate Meteor 2 WeKan now uses qemu-user to run MongoDB, using Qemu AVX support.
|
||||
This was added at [WeKan v7.93](https://github.com/wekan/wekan/blob/main/CHANGELOG.md#v793-2025-07-18-wekan--release).
|
||||
This was added at [WeKan v7.93](../../../CHANGELOG.md#v793-2025-07-18-wekan--release).
|
||||
|
||||
## What is not Cross-Platform
|
||||
|
||||
@@ -46,7 +46,7 @@ This was added at [WeKan v7.93](https://github.com/wekan/wekan/blob/main/CHANGEL
|
||||
|
||||
## Future
|
||||
|
||||
- Will this happen? With what tech? See https://github.com/wekan/wekan/blob/main/FUTURE.md
|
||||
- Will this happen? With what tech? See [FUTURE.md](../../../FUTURE.md)
|
||||
- You can comment at https://github.com/wekan/wekan/issues/4578
|
||||
- You can help by sending PR to any prototype repo mentioned below
|
||||
|
||||
@@ -249,7 +249,7 @@ https://github.com/wekan/php/blob/main/page/allboardschar.php
|
||||
- Redbean amd64 binary works at Win/Mac/Linux/BSD/BIOS without any modifications, because it is based on Cosmopolitan https://github.com/jart/cosmopolitan
|
||||
- Uses HTMX https://htmx.org at UI
|
||||
- Saves to SQLite database
|
||||
- Petclinic fork, using Blink to run at s390x https://github.com/wekan/wekan/wiki/s390x#petclinic-s390x
|
||||
- Petclinic fork, using Blink to run at s390x [s390x](../../Platforms/FOSS/s390x.md#petclinic-s390x)
|
||||
|
||||
### PHP
|
||||
|
||||
@@ -290,7 +290,7 @@ https://github.com/wekan/php/blob/main/page/allboardschar.php
|
||||
- Deno also includes Node.js as Deno's Node.js compatibility layer.
|
||||
- Node.js supports many CPU/OS
|
||||
- Node.js is used in production and has traceability. https://changelog.com/jsparty/294
|
||||
- https://github.com/wekan/wekan/wiki/s390x
|
||||
- [s390x](../../Platforms/FOSS/s390x.md)
|
||||
- https://nodejs.org/dist/latest-v20.x/
|
||||
- https://github.com/wekan/node-v14-esm/releases/tag/v14.21.4
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
MIT:
|
||||
|
||||
- WeKan (MIT, Node.js/Meteor/MongoDB) https://github.com/wekan/wekan is maintained, major features being added, see https://github.com/wekan/wekan/wiki/Deep-Dive-Into-WeKan
|
||||
- WeKan (MIT, Node.js/Meteor/MongoDB) https://github.com/wekan/wekan is maintained, major features being added, see [Deep Dive Into WeKan](Deep-Dive-Into-WeKan.md)
|
||||
- Kanboard (MIT, PHP) https://github.com/kanboard/kanboard is at maintenance mode, no major new feature development. There is still security fixes etc.
|
||||
- 4gaBoards (MIT, Node.js/Sails.js/PostgreSQL) https://github.com/RARgames/4gaBoards is maintained fork of Planka
|
||||
- Godello (MIT, Godot) https://github.com/alfredbaudisch/Godello can save locally, maybe no multi-user support yet
|
||||
@@ -29,9 +29,9 @@ Other kanbans maybe listed at https://github.com/KanRule
|
||||
- Atlassian is retiring self-managed server offerings https://www.atlassian.com/blog/platform/atlassian-server-is-going-away-next-steps
|
||||
|
||||
|
||||
## Please [search from ChangeLog page](https://github.com/wekan/wekan/blob/main/CHANGELOG.md) instead
|
||||
## Please [search from ChangeLog page](../../CHANGELOG.md) instead
|
||||
|
||||
Please [search from ChangeLog page](https://github.com/wekan/wekan/blob/main/CHANGELOG.md) instead about does Wekan have some feature.
|
||||
Please [search from ChangeLog page](../../CHANGELOG.md) instead about does Wekan have some feature.
|
||||
|
||||
This comparison below is over one year old and very outdated. All of Wekan/Trello/Restyaboard have changed very much and have many new features and fixes.
|
||||
|
||||
@@ -39,11 +39,11 @@ This comparison below is over one year old and very outdated. All of Wekan/Trell
|
||||
|
||||
Features | Wekan | Trello | Restyaboard
|
||||
------------ | ------------- | ------------- | -------------
|
||||
Price | Free and Open Source, [MIT license](https://github.com/wekan/wekan/blob/main/LICENSE). Free for Commercial Use. | Free with limitations, Monthly payment, Annual Subscription, Quote-based | Open Core
|
||||
Price | Free and Open Source, [MIT license](../../LICENSE). Free for Commercial Use. | Free with limitations, Monthly payment, Annual Subscription, Quote-based | Open Core
|
||||
Whitelabeling | Yes. Admin Panel/Layout: Hide Logo, Custom Product Name. | No | $ Yes
|
||||
Theming | [Yes](Custom-CSS-themes) | No | $ Yes
|
||||
Theming | [Yes](../Theme/Custom-CSS-themes.md) | No | $ Yes
|
||||
Redistributing | Yes | No | $ Yes
|
||||
Hosting | [Self-host or SaaS provider](Platforms) | SaaS | Self-host
|
||||
Hosting | [Self-host or SaaS provider](../Platforms/FOSS/Platforms.md) | SaaS | Self-host
|
||||
|
||||
## Basic features: Board
|
||||
|
||||
@@ -56,7 +56,7 @@ Boards | Yes | Yes | Yes
|
||||
Closed boards listing | Yes, at Archive | Yes | Yes
|
||||
Starred boards listing | No, starred and non-starred at All Boards | No | Yes
|
||||
Add board with predefined templates | Personal templates, Import Board from Trello or Wekan | No | Yes
|
||||
Board stats | [Yes](Features#stats) | No | Yes
|
||||
Board stats | [Yes](../Features/Features.md#stats) | No | Yes
|
||||
Board - Add members | Yes | Yes | Yes
|
||||
Board - Remove members | Yes | Yes | Yes
|
||||
Close board | Yes, move to Archive | Yes | Yes
|
||||
@@ -189,12 +189,12 @@ Undo from activities | No | No | Yes
|
||||
Features | Wekan | Trello | Restyaboard
|
||||
------------ | ------------- | ------------- | -------------
|
||||
API explorer | No | No | Yes
|
||||
OpenAPI | [Yes](https://github.com/wekan/wekan/tree/main/openapi) [here](https://wekan.fi/api/) | ? | ?
|
||||
OpenAPI | [Yes](../../openapi) [here](https://wekan.fi/api/) | ? | ?
|
||||
Developer applications | Yes, using REST API | Yes | Yes
|
||||
Authorized OAuth applications | No, REST API [login as admin to get Bearer token](REST-API#example-call---as-form-data) | Yes | Yes
|
||||
Authorized OAuth applications | No, REST API [login as admin to get Bearer token](../API/REST-API.md#example-call---as-form-data) | Yes | Yes
|
||||
Webhooks | Yes, per board or global at Admin Panel | Yes | Yes
|
||||
Zapier (IFTTT like workflow automation with 500+ websites) | Yes | Yes | $ Yes
|
||||
Integrated IFTTT | [Yes](IFTTT) | No | No
|
||||
Integrated IFTTT | [Yes](../Features/IFTTT/IFTTT.md) | No | No
|
||||
|
||||
## Basic features: Email and Notifications
|
||||
|
||||
@@ -215,7 +215,7 @@ Features | Wekan | Trello | Restyaboard
|
||||
Additional settings | No in Web UI, most settings at CLI | Yes | Yes (Basic only)
|
||||
Profile | Yes | Yes | Yes
|
||||
Add a new email address | Register / Invite | Yes | No
|
||||
Roles management | On web board+[API](REST-API-Role) | No | Yes
|
||||
Roles management | On web board+[API](../API/Role.md) | No | Yes
|
||||
Settings management | Only some at Web UI, most settings at CLI | No | Yes
|
||||
Users management | Add/Remove Orgs/Teams/Users | Yes? | Yes
|
||||
Permanently delete your entire account forever? | No | Yes | Yes (Admin can delete)
|
||||
@@ -225,10 +225,10 @@ Permanently delete your entire account forever? | No | Yes | Yes (Admin can dele
|
||||
Features | Wekan | Trello | Restyaboard
|
||||
------------ | ------------- | ------------- | -------------
|
||||
Login with username or email | Yes | Yes | Yes
|
||||
LDAP login | Standalone: [Yes](LDAP). Sandstorm: Yes. | No | $ Yes
|
||||
LDAP login | Standalone: [Yes](../Login/LDAP.md). Sandstorm: Yes. | No | $ Yes
|
||||
SAML login | Standalone: [Not yet](https://github.com/wekan/wekan/issues/708). Sandstorm: Yes | No | No
|
||||
Google login | Not yet, needs fixes to [OAuth2](OAuth2) | Yes | No
|
||||
GitHub login | Standalone: Not yet, needs fixes to [OAuth2](OAuth2). Sandstorm: Yes. | No | No
|
||||
Google login | Not yet, needs fixes to [OAuth2](../Login/OAuth2.md) | Yes | No
|
||||
GitHub login | Standalone: Not yet, needs fixes to [OAuth2](../Login/OAuth2.md). Sandstorm: Yes. | No | No
|
||||
Passwordless email login | Standalone: No. Sandstorm: Yes. | No | No
|
||||
|
||||
## Apps for productivity: Import / Export
|
||||
@@ -257,7 +257,7 @@ Show attachments in board | No, only at each card | No | Yes
|
||||
Features | Wekan | Trello | Restyaboard
|
||||
------------ | ------------- | ------------- | -------------
|
||||
Apps (Power-ups) | All integrated in, no separate plugin/app install | Yes | Yes
|
||||
Custom Field App | [Yes](Custom-Fields) | Yes | $ Yes
|
||||
Custom Field App | [Yes](../API/Custom-Fields.md) | Yes | $ Yes
|
||||
Estimated Time Custom Field App / Time Tracking | Reveived/Start/End/Due/End Date, Spent time, Requested By, Assigned By. No reports. For more advanced, [Not yet](https://github.com/wekan/wekan/issues/812) | Yes, 3rd party Burndown, Harvest etc | $ Yes
|
||||
Analytics | [Magento integration](https://github.com/wekan/wekan-snap/wiki/Supported-settings-keys) | No | No
|
||||
Hide card additional information | Yes, card and board activities | Yes, card activities | Yes, card activities
|
||||
@@ -270,7 +270,7 @@ Introduction video | No | No | Yes
|
||||
List sorting by due date | No | No | Yes
|
||||
Home screen | No | No | Yes
|
||||
Apps Integration | All integrated in | Yes | Yes
|
||||
Chat | No. You could use [Rocket.Chat](OAuth2) | No | $ Yes
|
||||
Chat | No. You could use [Rocket.Chat](../Login/OAuth2.md) | No | $ Yes
|
||||
Dashboard Charts | [Not yet](https://github.com/wekan/wekan-dashing-go) | No | $ Yes
|
||||
Hide Card Created Date App | No | No | Yes
|
||||
Hide Card ID App | No | No | Yes
|
||||
@@ -278,9 +278,9 @@ Canned Response App | No | No | $ Yes
|
||||
Auto Archive Expired Cards App | No | No | $ Yes
|
||||
Support Desk | No | No | $ Yes
|
||||
Card Template App | Copy Checklist Template to Multiple Cards | Yes | $ Yes
|
||||
Slack | [Yes](Outgoing-Webhook-to-Discord) | Yes | $ Yes
|
||||
Slack | [Yes](../Webhooks/Discord/Outgoing-Webhook-to-Discord.md) | Yes | $ Yes
|
||||
Amazon Echo | No | No | $ Yes
|
||||
Collaborate/TogetherJS | [Not yet](Friend) | No | Yes
|
||||
Collaborate/TogetherJS | [Not yet](../Platforms/FOSS/Friend/Friend.md) | No | Yes
|
||||
Gmail Add-on | No | Yes | Yes
|
||||
Hangouts Chat bot | No | Yes | $ Yes
|
||||
Print board | No | No | $ Yes
|
||||
@@ -296,8 +296,8 @@ SEO Checklist | No | No | $ Yes
|
||||
|
||||
Features | Wekan | Trello | Restyaboard
|
||||
------------ | ------------- | ------------- | -------------
|
||||
iOS Mobile App | [Not yet](Friend). Mobile Chrome browser works. | Yes | Yes
|
||||
Android Mobile App | [Yes](Browser-compatibility-matrix) | Yes | Yes
|
||||
Windows Microsoft Store App | [Yes](Browser-compatibility-matrix) | ? | ?
|
||||
Ubuntu Touch OpenStore App | [Yes](Browser-compatibility-matrix) | ? | ?
|
||||
iOS Mobile App | [Not yet](../Platforms/FOSS/Friend/Friend.md). Mobile Chrome browser works. | Yes | Yes
|
||||
Android Mobile App | [Yes](../Browsers/Browser-compatibility-matrix.md) | Yes | Yes
|
||||
Windows Microsoft Store App | [Yes](../Browsers/Browser-compatibility-matrix.md) | ? | ?
|
||||
Ubuntu Touch OpenStore App | [Yes](../Browsers/Browser-compatibility-matrix.md) | ? | ?
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
Add all necessary settings that you find on code.
|
||||
|
||||
After doing all changes, [fix lint errors](Developer-Documentation#preventing-travis-ci-lint-errors-before-submitting-pull-requests) and if possible warnings too. You can silence errors if you really can't find solution.
|
||||
After doing all changes, [fix lint errors](Developer-Documentation.md#preventing-travis-ci-lint-errors-before-submitting-pull-requests) and if possible warnings too. You can silence errors if you really can't find solution.
|
||||
|
||||
Submit pull request to Wekan edge branch https://github.com/wekan/wekan/tree/edge
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ That does:
|
||||
- 2 builds wekan
|
||||
- 3 starts wekan in development mode with command `meteor`, so it can detect if some file changes and try to rebuild automatically.
|
||||
|
||||
And then [register and login](Adding-users) with webbrowser at http://localhost:4000
|
||||
And then [register and login](../Login/Adding-users.md) with webbrowser at http://localhost:4000
|
||||
|
||||
### 4. Make and test your changes
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ cd wekan
|
||||
```
|
||||
That does: 1 install dependencies, 2 builds wekan, 3 starts wekan in development mode with command `meteor`, so it can detect if some file changes and try to rebuild automatically and reload webbroser. But, still sometimes, it may need stopping with Ctrl-c and full build with option 2.
|
||||
|
||||
And then [register and login](Adding-users) at http://localhost:4000
|
||||
And then [register and login](../Login/Adding-users.md) at http://localhost:4000
|
||||
|
||||
### OPTIONAL, NOT NEEDED: 5. Add new plugin package
|
||||
```
|
||||
|
||||
@@ -9,19 +9,19 @@ This can happen, when there is too much or incompatible code at browserside, for
|
||||
To fix it:
|
||||
|
||||
1. Move ExcelJS from browserside to run at serverside https://github.com/wekan/wekan/pull/3871
|
||||
2. Use Bundle Visualizer to see what is the size of dependencies, and try what can be moved to serverside like at step 1, that bundle visualizer is used in this script https://github.com/wekan/wekan/blob/main/rebuild-wekan.sh
|
||||
2. Use Bundle Visualizer to see what is the size of dependencies, and try what can be moved to serverside like at step 1, that bundle visualizer is used in this script [rebuild-wekan.sh](../../rebuild-wekan.sh)
|
||||
```
|
||||
meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000 --extra-packages bundle-visualizer --production 2>&1 | tee ../log.txt
|
||||
```
|
||||
3. Make dependencies smaller. For example, use only required files, and do not include all dependencies: https://github.com/wekan/wekan/commit/23e5e1e3bd081699ce39ce5887db7e612616014d . In that commit, package was forked to packages directory, then renamed, and added with `meteor add packagename`, where package name does not have character `:`
|
||||
4. Use Browserstack.com to see errors at browser / inspect / console, or use iOS or other device emulators, to see error messages. Testing at real device is more important, because they could work differently than emulators, emulators sometimes do not emulate all same features. Those error messages have file where error happened, and line number, like `something.js:301` . From there, scroll up a little, look at what function or what package dependency it is where it happened. If possible, try to move that package serverside, like at step 1. Or alternatively, look is it possible to remove or change to some other compatible dependency.
|
||||
5. See what are the dependencies at your Meteor based software, compared to WeKan dependencies that are usually already upgraded to newest Meteor, is there any differences where changing to correct dependencies could help you to upgrade to newest Meteor:
|
||||
- https://github.com/wekan/wekan/blob/main/package.json
|
||||
- https://github.com/wekan/wekan/blob/main/.meteor/packages
|
||||
- https://github.com/wekan/wekan/blob/main/.meteor/versions
|
||||
- https://github.com/wekan/wekan/blob/main/.meteor/release
|
||||
- [package.json](../../package.json)
|
||||
- [packages](../../.meteor/packages)
|
||||
- [versions](../../.meteor/versions)
|
||||
- [release](../../.meteor/release)
|
||||
5. If you get some errors, search are those same already fixed in WeKan/Meteor/RocketChat, could you fix them same way:
|
||||
- https://github.com/wekan/wekan/blob/main/CHANGELOG.md
|
||||
- [CHANGELOG.md](../../CHANGELOG.md)
|
||||
- https://github.com/wekan/wekan/issues
|
||||
- https://github.com/wekan/wekan/issues?q=is%3Aissue+is%3Aclosed
|
||||
- https://github.com/meteor/meteor/issues
|
||||
@@ -29,12 +29,12 @@ meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000 --extra-pa
|
||||
- https://github.com/RocketChat/Rocket.Chat/issues
|
||||
- https://github.com/RocketChat/Rocket.Chat/issues?q=is%3Aissue+is%3Aclosed
|
||||
6. If you have some webserver providing SSL/TLS, check that you have websockets enabled:
|
||||
- https://github.com/wekan/wekan/wiki/Caddy-Webserver-Config
|
||||
- https://github.com/wekan/wekan/wiki/Nginx-Webserver-Config
|
||||
- https://github.com/wekan/wekan/wiki/Apache
|
||||
- [Caddy Webserver Config](../Webserver/Caddy.md)
|
||||
- [Nginx Webserver Config](../Webserver/Nginx.md)
|
||||
- [Apache](../Webserver/Apache.md)
|
||||
- OpenLiteSpeed https://github.com/wekan/wekan/issues/3334#issuecomment-723651328
|
||||
- https://github.com/wekan/wekan/wiki/Local-self-signed-TLS
|
||||
- https://github.com/wekan/wekan/wiki/Traefik-and-self-signed-SSL-certs
|
||||
- [Local self signed TLS](../Webserver/Local-self-signed-TLS.md)
|
||||
- [Traefik and self signed SSL certs](../Webserver/Traefik-and-self-signed-SSL-certs.md)
|
||||
|
||||
## OLD: TODO
|
||||
|
||||
@@ -70,23 +70,23 @@ meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000 --extra-pa
|
||||
|
||||
1) Increase ulimit system wide to 100 000 in systemd config.
|
||||
|
||||
2) Wekan Javascript code has [increaded fiber poolsize](https://github.com/wekan/wekan/blob/main/server/authentication.js#L5-L9).
|
||||
2) Wekan Javascript code has [increaded fiber poolsize](../../server/authentication.js#L5-L9).
|
||||
|
||||
3) There is [on-going 100% CPU usage Meteor issue](https://github.com/meteor/meteor/issues/9796#issuecomment-400079380) and hopefully [fixes to Node.js will land in Node v8.12](https://github.com/nodejs/node/pull/21593#issuecomment-403636667) sometime. Node 8.12 is now released and official version included at Wekan.
|
||||
|
||||
## Scaling to thousands of users
|
||||
|
||||
[Production setup at AWS](AWS)
|
||||
[Production setup at AWS](../Platforms/Propietary/Cloud/AWS.md)
|
||||
|
||||
## Current versions of dependencies
|
||||
|
||||
[Dockerfile](https://github.com/wekan/wekan/blob/main/Dockerfile), versions of Meteor.js, Node etc listed at beginning.
|
||||
[Dockerfile](../../Dockerfile), versions of Meteor.js, Node etc listed at beginning.
|
||||
|
||||
[Included Meteor packages](https://github.com/wekan/wekan/blob/main/.meteor/packages)
|
||||
[Included Meteor packages](../../.meteor/packages)
|
||||
|
||||
[Included Meteor package versions](https://github.com/wekan/wekan/blob/main/.meteor/versions)
|
||||
[Included Meteor package versions](../../.meteor/versions)
|
||||
|
||||
[Added packages at package.json](https://github.com/wekan/wekan/blob/main/package.json)
|
||||
[Added packages at package.json](../../package.json)
|
||||
|
||||
## Build from source
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ Please read the meteor style guide before making any significant contribution.
|
||||
|
||||
## Building code and submitting pull request
|
||||
|
||||
- [Building code and submitting pull request](emoji#how-you-could-add-another-plugin)
|
||||
- [Building code and submitting pull request](../Features/Emoji.md#how-you-could-add-another-plugin)
|
||||
- When you start `meteor` command, it watches for changes to files in wekan directory and subdirectories, and if it detects changed code, it starts rebuilding bundle and then reloads webbrowser after that automatically
|
||||
- Also look at meteor changelog mentioned new feature `hot reload` how that could make reloading faster
|
||||
|
||||
@@ -43,10 +43,10 @@ When you run this, you see .jade template files where is search input box, and .
|
||||
|
||||
## Getting Started
|
||||
|
||||
- Currently Wekan development is done mainly at Ubuntu 20.10 64bit, but building Wekan does work on any Debian, Ubuntu, WSL Ubuntu 20.04, [Mac](Mac). Sametime maybe on [Windows](Windows) with `choco install -y meteor` and installing packages it then asks for with `meteor add ...` or `meteor npm install --save ...` and then running meteor again.
|
||||
- Currently Wekan development is done mainly at Ubuntu 20.10 64bit, but building Wekan does work on any Debian, Ubuntu, WSL Ubuntu 20.04, [Mac](../Platforms/Propietary/Mac.md). Sametime maybe on [Windows](../Platforms/Propietary/Windows/Windows.md) with `choco install -y meteor` and installing packages it then asks for with `meteor add ...` or `meteor npm install --save ...` and then running meteor again.
|
||||
- Look through old [pull requests](https://github.com/wekan/wekan/pulls)
|
||||
- Read Wekan source code, you can use some git history viewer GUI like gitk
|
||||
- Read [Meteor documentation](https://docs.meteor.com/) for [Meteor version](https://github.com/wekan/wekan/blob/main/.meteor/release) in use, other versions mentioned at [Dockerfile](https://github.com/wekan/wekan/blob/main/Dockerfile)
|
||||
- Read [Meteor documentation](https://docs.meteor.com/) for [Meteor version](../../.meteor/release) in use, other versions mentioned at [Dockerfile](../../Dockerfile)
|
||||
- Docker up-to-date way: You can clone wekan/wekan repo and update docker-compose.yml file ROOT_URL etc as documented at https://github.com/wekan/wekan commented `docker-compose.yml` file, install docker, and build from source with docker with `docker compose up -d --build`
|
||||
- Docker not up-to-date way: [Docker environment for Wekan development](https://github.com/wekan/wekan-dev).
|
||||
|
||||
@@ -95,7 +95,7 @@ It's possible to import your existing boards from Trello. Instructions [[here|mi
|
||||
|
||||
# Directory Structure Details
|
||||
|
||||
[Directory Structure](Directory-Structure)
|
||||
[Directory Structure](Directory-Structure.md)
|
||||
|
||||
# Chat
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Routing
|
||||
|
||||
We're using [FlowRouter](https://github.com/kadirahq/flow-router) client side router inside **[config/router.js](https://github.com/wekan/wekan/tree/main/config/router.js)**.
|
||||
For accounts there is [AccountsTemplates](https://github.com/meteor-useraccounts) configured in **[config/accounts.js](https://github.com/wekan/wekan/tree/main/config/accounts.js)**.
|
||||
We're using [FlowRouter](https://github.com/kadirahq/flow-router) client side router inside **[config/router.js](../../config/router.js)**.
|
||||
For accounts there is [AccountsTemplates](https://github.com/meteor-useraccounts) configured in **[config/accounts.js](../../config/accounts.js)**.
|
||||
|
||||
# Client
|
||||
|
||||
@@ -12,172 +12,172 @@ Files in this directory are served by meteor as-is to the client. It hosts some
|
||||
|
||||
## components
|
||||
|
||||
* **[activities](https://github.com/wekan/wekan/tree/main/client/components/activities)**:
|
||||
* **[activities.jade](https://github.com/wekan/wekan/tree/main/client/components/activities/activities.jade)**: `activities` template for the list of activities placed inside a `sidebar-content`; uses `boardActivities` or `cardActivities` depending on `mode`; <span style="color:red">*XXX: does this mean that sidebar should be visible in board list mode? when does the `board` activity gets shown?*</span>
|
||||
* **[comments.jade](https://github.com/wekan/wekan/tree/main/client/components/activities/comments.jade)**: `commentForm` template used in `card-details-canvas` for adding comments;
|
||||
* **[boards](https://github.com/wekan/wekan/tree/main/client/components/boards)**:
|
||||
* **[boardArchive.jade](https://github.com/wekan/wekan/tree/main/client/components/boards/boardArchive.jade)**: `archivedBoards` template for the modal dialog showing the list of archived boards that might be restored;
|
||||
* **[boardBody.jade](https://github.com/wekan/wekan/tree/main/client/components/boards/boardBody.jade)**: top level template for presenting a board is `board` and, based on screen size and current state, it uses either `cardDetails` or `boardBody` templates; `boardBody` is the one including the `sidebar`, each `list`, `cardDetails` for larger screens when a card is selected and the `addListForm` for adding a new list (also defined in this file);
|
||||
* **[boardHeader.jade](https://github.com/wekan/wekan/tree/main/client/components/boards/boardHeader.jade)**: `boardHeaderBar`, `boardMenuPopup`, `boardVisibilityList`, `boardChangeVisibilityPopup`, `boardChangeWatchPopup`, `boardChangeColorPopup`, `createBoard`, `chooseBoardSource`, `boardChangeTitlePopup`, `archiveBoardPopup`, `outgoingWebhooksPopup`;
|
||||
* **[boardsList.jade](https://github.com/wekan/wekan/tree/main/client/components/boards/boardsList.jade)**: `boardList` and `boardListHeaderBar` for the list of boards in the initial screen;
|
||||
* **[cards](https://github.com/wekan/wekan/tree/main/client/components/cards)**:
|
||||
* **[attachments.jade](https://github.com/wekan/wekan/tree/main/client/components/cards/attachments.jade)**: `cardAttachmentsPopup`, `previewClipboardImagePopup`, `previewAttachedImagePopup`, `attachmentDeletePopup`, `attachmentsGalery`;
|
||||
* **[cardDate.jade](https://github.com/wekan/wekan/tree/main/client/components/cards/cardDate.jade)**: `editCardDate` and `dateBadge` templates;
|
||||
* **[cardDetails.jade](https://github.com/wekan/wekan/tree/main/client/components/cards/cardDetails.jade)**: `boardsAndLists` is the usual layout for a board display with `boardLists` being used in sandstorm where each board is independent;`cardDetails`, `editCardTitleForm`, `cardDetailsActionsPopup`, `moveCardPopup`, `copyCardPopup`,`cardMembersPopup`,`cardMorePopup`, `cardDeletePopup`;
|
||||
* **[cardTime.jade](https://github.com/wekan/wekan/tree/main/client/components/cards/cardTime.jade)**: `editCardSpentTime` and `timeBadge` templates;
|
||||
* **[checklists.jade](https://github.com/wekan/wekan/tree/main/client/components/cards/checklists.jade)**: `checklists`, `checklistDetail`, `checklistDeleteDialog`, `addChecklistItemForm`, `editChecklistItemForm`, `checklistItems`, `itemDetail`;
|
||||
* **[labels.jade](https://github.com/wekan/wekan/tree/main/client/components/cards/labels.jade)**: `formLabel`, `createLabelPopup`, `editLabelPopup`, `deleteLabelPopup`, `cardLabelsPopup`;
|
||||
* **[minicard.jade](https://github.com/wekan/wekan/tree/main/client/components/cards/minicard.jade)**: has the `minicard` template
|
||||
* **[forms](https://github.com/wekan/wekan/tree/main/client/components/forms)**: **[inlinedform.jade](https://github.com/wekan/wekan/tree/main/client/components/forms/inlinedform.jade)** has the`inlinedForm` template;
|
||||
* **[import](https://github.com/wekan/wekan/tree/main/client/components/import)**:
|
||||
* **[import.jade](https://github.com/wekan/wekan/tree/main/client/components/import/import.jade)**: `importHeaderBar`, `import`, `importTextarea`, `importMapMembers`, `importMapMembersAddPopup` are all templates used for importing Trello (via **[trelloMembersMapper.js](https://github.com/wekan/wekan/tree/main/client/components/import/trelloMembersMapper.js)**) and Wekan (via **[wekanMembersMapper.js](https://github.com/wekan/wekan/tree/main/client/components/import/wekanMembersMapper.js)**) boards;
|
||||
* **[lists](https://github.com/wekan/wekan/tree/main/client/components/lists)**:
|
||||
* **[list.jade](https://github.com/wekan/wekan/tree/main/client/components/lists/list.jade)**: `list` is the simple, main template for lists;
|
||||
* **[listBody.jade](https://github.com/wekan/wekan/tree/main/client/components/lists/listBody.jade)**: `listBody`, `addCardForm`, `autocompleteLabelLine` templates;
|
||||
* **[listHeader.jade](https://github.com/wekan/wekan/tree/main/client/components/lists/listHeader.jade)**: `listHeader`, `editListTitleForm`, `listActionPopup`, `boardLists`, `listMorePopup`, `listDeletePopup`, `setWipLimitPopup`, `wipLimitErrorPopup` templates;
|
||||
* **[main](https://github.com/wekan/wekan/tree/main/client/components/main)**:
|
||||
* **[editor.jade](https://github.com/wekan/wekan/tree/main/client/components/main/editor.jade)**: `editor` and `viewer` templates;
|
||||
* **[header.jade](https://github.com/wekan/wekan/tree/main/client/components/main/header.jade)**: `header` and `offlineWarning` templates; if the user is connected we display a small "quick-access" top bar that list all starred boards with a link to go there (this is inspired by the Reddit "subreddit" bar); the first link goes to the boards page;
|
||||
* **[keyboardShortcuts.jade](https://github.com/wekan/wekan/tree/main/client/components/main/keyboardShortcuts.jade)**: `shortcutsHeaderBar`, `shortcutsModalTitle`, `keyboardShortcuts` - all for the shortcuts that are presented when you press `?`re implemented inhere;
|
||||
* **[layouts.jade](https://github.com/wekan/wekan/tree/main/client/components/main/layouts.jade)**: has the template for head portion of the html page and other general purpose templates: `userFormsLayout`, `defaultLayout`, `notFound`, `message`;
|
||||
* **[popup.tpl.jade](https://github.com/wekan/wekan/tree/main/client/components/main/popup.tpl.jade)**: tpl files only define a single template so there's no need to wrap content in a template tag; the name of the template is the base name of the file (`popup` in this case);
|
||||
* **[activities](../../client/components/activities)**:
|
||||
* **[activities.jade](../../client/components/activities/activities.jade)**: `activities` template for the list of activities placed inside a `sidebar-content`; uses `boardActivities` or `cardActivities` depending on `mode`; <span style="color:red">*XXX: does this mean that sidebar should be visible in board list mode? when does the `board` activity gets shown?*</span>
|
||||
* **[comments.jade](../../client/components/activities/comments.jade)**: `commentForm` template used in `card-details-canvas` for adding comments;
|
||||
* **[boards](../../client/components/boards)**:
|
||||
* **[boardArchive.jade](../../client/components/boards/boardArchive.jade)**: `archivedBoards` template for the modal dialog showing the list of archived boards that might be restored;
|
||||
* **[boardBody.jade](../../client/components/boards/boardBody.jade)**: top level template for presenting a board is `board` and, based on screen size and current state, it uses either `cardDetails` or `boardBody` templates; `boardBody` is the one including the `sidebar`, each `list`, `cardDetails` for larger screens when a card is selected and the `addListForm` for adding a new list (also defined in this file);
|
||||
* **[boardHeader.jade](../../client/components/boards/boardHeader.jade)**: `boardHeaderBar`, `boardMenuPopup`, `boardVisibilityList`, `boardChangeVisibilityPopup`, `boardChangeWatchPopup`, `boardChangeColorPopup`, `createBoard`, `chooseBoardSource`, `boardChangeTitlePopup`, `archiveBoardPopup`, `outgoingWebhooksPopup`;
|
||||
* **[boardsList.jade](../../client/components/boards/boardsList.jade)**: `boardList` and `boardListHeaderBar` for the list of boards in the initial screen;
|
||||
* **[cards](../../client/components/cards)**:
|
||||
* **[attachments.jade](../../client/components/cards/attachments.jade)**: `cardAttachmentsPopup`, `previewClipboardImagePopup`, `previewAttachedImagePopup`, `attachmentDeletePopup`, `attachmentsGalery`;
|
||||
* **[cardDate.jade](../../client/components/cards/cardDate.jade)**: `editCardDate` and `dateBadge` templates;
|
||||
* **[cardDetails.jade](../../client/components/cards/cardDetails.jade)**: `boardsAndLists` is the usual layout for a board display with `boardLists` being used in sandstorm where each board is independent;`cardDetails`, `editCardTitleForm`, `cardDetailsActionsPopup`, `moveCardPopup`, `copyCardPopup`,`cardMembersPopup`,`cardMorePopup`, `cardDeletePopup`;
|
||||
* **[cardTime.jade](../../client/components/cards/cardTime.jade)**: `editCardSpentTime` and `timeBadge` templates;
|
||||
* **[checklists.jade](../../client/components/cards/checklists.jade)**: `checklists`, `checklistDetail`, `checklistDeleteDialog`, `addChecklistItemForm`, `editChecklistItemForm`, `checklistItems`, `itemDetail`;
|
||||
* **[labels.jade](../../client/components/cards/labels.jade)**: `formLabel`, `createLabelPopup`, `editLabelPopup`, `deleteLabelPopup`, `cardLabelsPopup`;
|
||||
* **[minicard.jade](../../client/components/cards/minicard.jade)**: has the `minicard` template
|
||||
* **[forms](../../client/components/forms)**: **[inlinedform.jade](../../client/components/forms/inlinedform.jade)** has the`inlinedForm` template;
|
||||
* **[import](../../client/components/import)**:
|
||||
* **[import.jade](../../client/components/import/import.jade)**: `importHeaderBar`, `import`, `importTextarea`, `importMapMembers`, `importMapMembersAddPopup` are all templates used for importing Trello (via **[trelloMembersMapper.js](../../client/components/import/trelloMembersMapper.js)**) and Wekan (via **[wekanMembersMapper.js](../../client/components/import/wekanMembersMapper.js)**) boards;
|
||||
* **[lists](../../client/components/lists)**:
|
||||
* **[list.jade](../../client/components/lists/list.jade)**: `list` is the simple, main template for lists;
|
||||
* **[listBody.jade](../../client/components/lists/listBody.jade)**: `listBody`, `addCardForm`, `autocompleteLabelLine` templates;
|
||||
* **[listHeader.jade](../../client/components/lists/listHeader.jade)**: `listHeader`, `editListTitleForm`, `listActionPopup`, `boardLists`, `listMorePopup`, `listDeletePopup`, `setWipLimitPopup`, `wipLimitErrorPopup` templates;
|
||||
* **[main](../../client/components/main)**:
|
||||
* **[editor.jade](../../client/components/main/editor.jade)**: `editor` and `viewer` templates;
|
||||
* **[header.jade](../../client/components/main/header.jade)**: `header` and `offlineWarning` templates; if the user is connected we display a small "quick-access" top bar that list all starred boards with a link to go there (this is inspired by the Reddit "subreddit" bar); the first link goes to the boards page;
|
||||
* **[keyboardShortcuts.jade](../../client/components/main/keyboardShortcuts.jade)**: `shortcutsHeaderBar`, `shortcutsModalTitle`, `keyboardShortcuts` - all for the shortcuts that are presented when you press `?`re implemented inhere;
|
||||
* **[layouts.jade](../../client/components/main/layouts.jade)**: has the template for head portion of the html page and other general purpose templates: `userFormsLayout`, `defaultLayout`, `notFound`, `message`;
|
||||
* **[popup.tpl.jade](../../client/components/main/popup.tpl.jade)**: tpl files only define a single template so there's no need to wrap content in a template tag; the name of the template is the base name of the file (`popup` in this case);
|
||||
* **[spinner.tpl.jade](https://github.com/wekan/wekan/tree/main/client/components/main/spinner.tpl.jade)**: is the template for a "waiting" dialog;
|
||||
* **[settings](https://github.com/wekan/wekan/tree/main/client/components/settings)**:
|
||||
* **[informationBody.jade](https://github.com/wekan/wekan/tree/main/client/components/settings/informationBody.jade)**: `information`, `statistics` templates;
|
||||
* **[invitationCode.jade](https://github.com/wekan/wekan/tree/main/client/components/settings/invitationCode.jade)**: `invitationCode` template;
|
||||
* **[peopleBody.jade](https://github.com/wekan/wekan/tree/main/client/components/settings/peopleBody.jade)**: `people`, `peopleGeneral`, `peopleRow`, `editUserPopup`;
|
||||
* **[settingBody.jade](https://github.com/wekan/wekan/tree/main/client/components/settings/settingBody.jade)**: `setting`, `general`, `email`, `accountSettings`, `announcementSettings`
|
||||
* **[settingHeader.jade](https://github.com/wekan/wekan/tree/main/client/components/settings/settingHeader.jade)**: `settingHeaderBar` template;
|
||||
* **[sidebar](https://github.com/wekan/wekan/tree/main/client/components/sidebar)**:
|
||||
* **[sidebar.jade](https://github.com/wekan/wekan/tree/main/client/components/sidebar/sidebar.jade)**: `sidebar`, `homeSidebar`, `membersWidget`, `labelsWidget`, `memberPopup`, `removeMemberPopup`, `leaveBoardPopup`, `addMemberPopup`, `changePermissionsPopup`
|
||||
* **[sidebarArchives.jade](https://github.com/wekan/wekan/tree/main/client/components/sidebar/sidebarArchives.jade)**: `archivesSidebar`
|
||||
* **[sidebarFilters.jade](https://github.com/wekan/wekan/tree/main/client/components/sidebar/sidebarFilters.jade)**: `filterSidebar`, `multiselectionSidebar`, `disambiguateMultiLabelPopup`, `disambiguateMultiMemberPopup`, `moveSelectionPopup`;
|
||||
* **[users](https://github.com/wekan/wekan/tree/main/client/components/users)**:
|
||||
* **[userAvatar.jade](https://github.com/wekan/wekan/tree/main/client/components/users/userAvatar.jade)**: `userAvatar`, `userAvatarInitials`, `userPopup`, `memberName`, `changeAvatarPopup`, `cardMemberPopup`
|
||||
* **[userHeader.jade](https://github.com/wekan/wekan/tree/main/client/components/users/userHeader.jade)**: `headerUserBar`, `memberMenuPopup`, `editProfilePopup`, `editNotificationPopup`, `changePasswordPopup`, `changeLanguagePopup`, `changeSettingsPopup`;
|
||||
* **[settings](../../client/components/settings)**:
|
||||
* **[informationBody.jade](../../client/components/settings/informationBody.jade)**: `information`, `statistics` templates;
|
||||
* **[invitationCode.jade](../../client/components/settings/invitationCode.jade)**: `invitationCode` template;
|
||||
* **[peopleBody.jade](../../client/components/settings/peopleBody.jade)**: `people`, `peopleGeneral`, `peopleRow`, `editUserPopup`;
|
||||
* **[settingBody.jade](../../client/components/settings/settingBody.jade)**: `setting`, `general`, `email`, `accountSettings`, `announcementSettings`
|
||||
* **[settingHeader.jade](../../client/components/settings/settingHeader.jade)**: `settingHeaderBar` template;
|
||||
* **[sidebar](../../client/components/sidebar)**:
|
||||
* **[sidebar.jade](../../client/components/sidebar/sidebar.jade)**: `sidebar`, `homeSidebar`, `membersWidget`, `labelsWidget`, `memberPopup`, `removeMemberPopup`, `leaveBoardPopup`, `addMemberPopup`, `changePermissionsPopup`
|
||||
* **[sidebarArchives.jade](../../client/components/sidebar/sidebarArchives.jade)**: `archivesSidebar`
|
||||
* **[sidebarFilters.jade](../../client/components/sidebar/sidebarFilters.jade)**: `filterSidebar`, `multiselectionSidebar`, `disambiguateMultiLabelPopup`, `disambiguateMultiMemberPopup`, `moveSelectionPopup`;
|
||||
* **[users](../../client/components/users)**:
|
||||
* **[userAvatar.jade](../../client/components/users/userAvatar.jade)**: `userAvatar`, `userAvatarInitials`, `userPopup`, `memberName`, `changeAvatarPopup`, `cardMemberPopup`
|
||||
* **[userHeader.jade](../../client/components/users/userHeader.jade)**: `headerUserBar`, `memberMenuPopup`, `editProfilePopup`, `editNotificationPopup`, `changePasswordPopup`, `changeLanguagePopup`, `changeSettingsPopup`;
|
||||
* **[mixins](https://github.com/wekan/wekan/tree/main/client/components/mixins)**: [extends](http://www.meteorpedia.com/read/Infinite_Scrolling) **[infiniteScrolling.js](https://github.com/wekan/wekan/tree/main/client/components/mixins/infiniteScrolling.js)** for card details, sidebar and also extends **[perfectScrollbar.js](https://github.com/wekan/wekan/tree/main/client/components/mixins/perfectScrollbar.js)**;
|
||||
|
||||
## config
|
||||
|
||||
* **[blazeHelpers.js](https://github.com/wekan/wekan/tree/main/client/config/blazeHelpers.js)**: following [Blaze](http://blazejs.org/) helpers are registered here:`currentBoard()`, `currentCard()`, `getUser()` and `concat()`;
|
||||
* **[gecko-fix.js](https://github.com/wekan/wekan/tree/main/client/config/gecko-fix.js)**: removes [deprecated](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch)`watch` and `unwatch` from Firefox prior to version 58;
|
||||
* **[blazeHelpers.js](../../client/config/blazeHelpers.js)**: following [Blaze](http://blazejs.org/) helpers are registered here:`currentBoard()`, `currentCard()`, `getUser()` and `concat()`;
|
||||
* **[gecko-fix.js](../../client/config/gecko-fix.js)**: removes [deprecated](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch)`watch` and `unwatch` from Firefox prior to version 58;
|
||||
* **[presence.js](https://github.com/wekan/wekan/tree/main/client/config/presence.js)**: custom state function for [Presence](https://github.com/dburles/meteor-presence) that keeps track of current board;
|
||||
* **[reactiveTabs.js](https://github.com/wekan/wekan/tree/main/client/config/reactiveTabs.js)**: [ReactiveTabs](https://github.com/meteortemplates/tabs) are configured to use `basicTabs` template.
|
||||
|
||||
## lib
|
||||
|
||||
* **[accessibility.js](https://github.com/wekan/wekan/tree/main/client/lib/accessibility.js)**: define a set of DOM transformations that are specifically intended for blind screen readers;
|
||||
* **[cssEvents.js](https://github.com/wekan/wekan/tree/main/client/lib/cssEvents.js)**: the `CSSEvents`object has methods that select the name of the event based on the specific transitions and animations;
|
||||
* **[pasteImage.js](https://github.com/wekan/wekan/tree/main/client/lib/pasteImage.js)** and **[dropImage.js](https://github.com/wekan/wekan/tree/main/client/lib/dropImage.js)**: utility for pasting and dropping images on a web app; <span style="color:red">*XXX: add comments; not same style as the rest of the code*</span>
|
||||
* **[accessibility.js](../../client/lib/accessibility.js)**: define a set of DOM transformations that are specifically intended for blind screen readers;
|
||||
* **[cssEvents.js](../../client/lib/cssEvents.js)**: the `CSSEvents`object has methods that select the name of the event based on the specific transitions and animations;
|
||||
* **[pasteImage.js](../../client/lib/pasteImage.js)** and **[dropImage.js](../../client/lib/dropImage.js)**: utility for pasting and dropping images on a web app; <span style="color:red">*XXX: add comments; not same style as the rest of the code*</span>
|
||||
* **[emoji-values.js](https://github.com/wekan/wekan/tree/main/client/lib/emoji-values.js)**: sets Emoji.values;
|
||||
* **[escapeActions.js](https://github.com/wekan/wekan/tree/main/client/lib/escapeActions.js)**: defines the behavior (mostly canceling current edit) for escape keyboard key;
|
||||
* **[i18n.js](https://github.com/wekan/wekan/tree/main/client/lib/i18n.js)**: at startup we choose the language for the ui based on user profile or browser language;
|
||||
* **[inlinedform.js](https://github.com/wekan/wekan/tree/main/client/lib/inlinedform.js)**: forms for editing a single field (like adding a card); <span style="color:red">*XXX: comments in code suggest that a form that is not submitted will retain its value to prevent data loss using [unsavedEdits.js](https://github.com/wekan/wekan/tree/main/client/lib/unsavedEdits.js);bug?*</span>; <span style="color:red">*XXX: edit button to save and open*</span>;
|
||||
* **[keyboard.js](https://github.com/wekan/wekan/tree/main/client/lib/keyboard.js)**: the shortcuts that are presented when you press `?`re implemented inhere;
|
||||
* **[escapeActions.js](../../client/lib/escapeActions.js)**: defines the behavior (mostly canceling current edit) for escape keyboard key;
|
||||
* **[i18n.js](../../client/lib/i18n.js)**: at startup we choose the language for the ui based on user profile or browser language;
|
||||
* **[inlinedform.js](../../client/lib/inlinedform.js)**: forms for editing a single field (like adding a card); <span style="color:red">*XXX: comments in code suggest that a form that is not submitted will retain its value to prevent data loss using [unsavedEdits.js](../../client/lib/unsavedEdits.js);bug?*</span>; <span style="color:red">*XXX: edit button to save and open*</span>;
|
||||
* **[keyboard.js](../../client/lib/keyboard.js)**: the shortcuts that are presented when you press `?`re implemented inhere;
|
||||
* **[mixins.js](https://github.com/wekan/wekan/tree/main/client/lib/mixins.js)**: stub; no `Mixins` at this point; <span style="color:red">*XXX: what does `new class` do? exlint: missing () invoking a constructor*</span>
|
||||
* **[popup.js](https://github.com/wekan/wekan/tree/main/client/lib/popup.js)**: defines `Popup` class for things likes electing a date; <span style="color:red">*XXX: not a Blaze helper?*</span>
|
||||
* **[textComplete.js](https://github.com/wekan/wekan/tree/main/client/lib/textComplete.js)**: extends [jquery-textcomplete](https://yuku-t.com/jquery-textcomplete/) to integrate with the rest of the system (like escape actions, tab and enter key handling); <span style="color:red">*XXX: deprecated?*</span>
|
||||
* **[utils.js](https://github.com/wekan/wekan/tree/main/client/lib/utils.js)**: various methods all over the place (resize, screen size, sort, capitalize, navigate to board and card);
|
||||
* **[popup.js](../../client/lib/popup.js)**: defines `Popup` class for things likes electing a date; <span style="color:red">*XXX: not a Blaze helper?*</span>
|
||||
* **[textComplete.js](../../client/lib/textComplete.js)**: extends [jquery-textcomplete](https://yuku-t.com/jquery-textcomplete/) to integrate with the rest of the system (like escape actions, tab and enter key handling); <span style="color:red">*XXX: deprecated?*</span>
|
||||
* **[utils.js](../../client/lib/utils.js)**: various methods all over the place (resize, screen size, sort, capitalize, navigate to board and card);
|
||||
* **Blaze helpers**:
|
||||
* **[filter.js](https://github.com/wekan/wekan/tree/main/client/lib/filter.js)**: registers `Filter` [Blaze](http://blazejs.org/) helper to support filtering cards by labels and by members;
|
||||
* **[modal.js](https://github.com/wekan/wekan/tree/main/client/lib/modal.js)**: registers `Modal` [Blaze](http://blazejs.org/) helper to support showing modal windows like the one for archived boards;
|
||||
* **[multiSelection.js](https://github.com/wekan/wekan/tree/main/client/lib/multiSelection.js)**: registers `Modal` [Blaze](http://blazejs.org/) helper to support multiple selection mode;
|
||||
* **[unsavedEdits.js](https://github.com/wekan/wekan/tree/main/client/lib/unsavedEdits.js)**: registers `getUnsavedValue` and `hasUnsavedValue` [Blaze](http://blazejs.org/) helpers to preserve content entered in fields but not saved;
|
||||
* **[filter.js](../../client/lib/filter.js)**: registers `Filter` [Blaze](http://blazejs.org/) helper to support filtering cards by labels and by members;
|
||||
* **[modal.js](../../client/lib/modal.js)**: registers `Modal` [Blaze](http://blazejs.org/) helper to support showing modal windows like the one for archived boards;
|
||||
* **[multiSelection.js](../../client/lib/multiSelection.js)**: registers `Modal` [Blaze](http://blazejs.org/) helper to support multiple selection mode;
|
||||
* **[unsavedEdits.js](../../client/lib/unsavedEdits.js)**: registers `getUnsavedValue` and `hasUnsavedValue` [Blaze](http://blazejs.org/) helpers to preserve content entered in fields but not saved;
|
||||
|
||||
# Server
|
||||
|
||||
.js files in this directory are not available to the client.
|
||||
|
||||
* **[statistics.js](https://github.com/wekan/wekan/tree/main/server/statistics.js)** implements a Meteor server-only [method](https://guide.meteor.com/methods.html) for general-purpose information such as OS, memory, CPUs, PID of the process and so on.
|
||||
* **[statistics.js](../../server/statistics.js)** implements a Meteor server-only [method](https://guide.meteor.com/methods.html) for general-purpose information such as OS, memory, CPUs, PID of the process and so on.
|
||||
* **[migrations.js](https://github.com/wekan/wekan/tree/main/server/migrations.js)** is where code that update sold databases to new schema is located. Anytime the schema of one of the collection changes in a non-backward compatible way a migration needs to be written in this file.
|
||||
* **[authentication.js](https://github.com/wekan/wekan/tree/main/server/authentication.js)** add the `Authentication`object to Meteor that provides methods for checking access rights.
|
||||
* **[lib/utils.js](https://github.com/wekan/wekan/tree/main/server/lib/utils.js)** defines some checks used by [checklists.js](https://github.com/wekan/wekan/tree/main/models/checklists.js)** model. <span style="color:red">*XXX: these methods are defined in server-only code by are used in models, which are visible by the client (in Checklists.allow)?*</span>
|
||||
* **[notifications](https://github.com/wekan/wekan/tree/main/server/notifications)**
|
||||
* **[notifications.js](https://github.com/wekan/wekan/tree/main/server/notifications/notifications.js)**: defines the `Notifications` object that supports [Activities](models/activities.js) and holds a list of functions to call when its `notify()` method is called along with convenience methods to subscribe, unsubscribe and a way to filter recipients according to user settings for notification;
|
||||
* **[email.js](https://github.com/wekan/wekan/tree/main/server/notifications/email.js)**: makes use of the notification system to send an email to a user;
|
||||
* **[profile.js](https://github.com/wekan/wekan/tree/main/server/notifications/profile.js)**: *stub*; will allow associating notifications with user ids to be consumed by mobile apps;
|
||||
* **[notifications.js](https://github.com/wekan/wekan/tree/main/server/notifications/notifications.js)**: adds the `watch()` Meteor server-only [method](https://guide.meteor.com/methods.html) that may watch boards, lists or cards using [models/watchable.js](https://github.com/wekan/wekan/tree/main/models/watchable.js);
|
||||
* **[outgoing.js](https://github.com/wekan/wekan/tree/main/server/notifications/outgoing.js)**: adds the `outgoingWebhooks()` Meteor server-only [method](https://guide.meteor.com/methods.html) that can call external API <span style="color:red">*XXX: I guess*</span>
|
||||
* **[publications](https://github.com/wekan/wekan/tree/main/server/publications)** defines sets of records that are [published](https://docs.meteor.com/api/pubsub.html#Meteor-publish) by the server and how clients can subscribe to those:
|
||||
* **[accountSettings.js](https://github.com/wekan/wekan/tree/main/server/publications/accountSettings.js)**: [AccountSettings](models/accountSettings.js) collection;
|
||||
* **[activities.js](https://github.com/wekan/wekan/tree/main/server/publications/activities.js)**: [Activities](models/activities.js) collection filtered and paginated;
|
||||
* **[announcements.js](https://github.com/wekan/wekan/tree/main/server/publications/announcements.js)**: [Announcements](models/announcements.js) collection;
|
||||
* **[avatars.js](https://github.com/wekan/wekan/tree/main/server/publications/avatars.js)**: [Avatars](models/avatars.js) collection for current user;
|
||||
* **[boards.js](https://github.com/wekan/wekan/tree/main/server/publications/boards.js)**: [Boards](models/boards.js) collection for current user, archived boards collection and individual board as a [relation](https://atmospherejs.com/cottz/publish-relations);
|
||||
* **[cards.js](https://github.com/wekan/wekan/tree/main/server/publications/cards.js)**: a [Card](https://github.com/wekan/wekan/tree/main/models/cards.js) by its id;
|
||||
* **[authentication.js](../../server/authentication.js)** add the `Authentication`object to Meteor that provides methods for checking access rights.
|
||||
* **[lib/utils.js](../../server/lib/utils.js)** defines some checks used by [checklists.js](../../models/checklists.js)** model. <span style="color:red">*XXX: these methods are defined in server-only code by are used in models, which are visible by the client (in Checklists.allow)?*</span>
|
||||
* **[notifications](../../server/notifications)**
|
||||
* **[notifications.js](../../server/notifications/notifications.js)**: defines the `Notifications` object that supports [Activities](models/activities.js) and holds a list of functions to call when its `notify()` method is called along with convenience methods to subscribe, unsubscribe and a way to filter recipients according to user settings for notification;
|
||||
* **[email.js](../../server/notifications/email.js)**: makes use of the notification system to send an email to a user;
|
||||
* **[profile.js](../../server/notifications/profile.js)**: *stub*; will allow associating notifications with user ids to be consumed by mobile apps;
|
||||
* **[notifications.js](../../server/notifications/notifications.js)**: adds the `watch()` Meteor server-only [method](https://guide.meteor.com/methods.html) that may watch boards, lists or cards using [models/watchable.js](../../models/watchable.js);
|
||||
* **[outgoing.js](../../server/notifications/outgoing.js)**: adds the `outgoingWebhooks()` Meteor server-only [method](https://guide.meteor.com/methods.html) that can call external API <span style="color:red">*XXX: I guess*</span>
|
||||
* **[publications](../../server/publications)** defines sets of records that are [published](https://docs.meteor.com/api/pubsub.html#Meteor-publish) by the server and how clients can subscribe to those:
|
||||
* **[accountSettings.js](../../server/publications/accountSettings.js)**: [AccountSettings](models/accountSettings.js) collection;
|
||||
* **[activities.js](../../server/publications/activities.js)**: [Activities](models/activities.js) collection filtered and paginated;
|
||||
* **[announcements.js](../../server/publications/announcements.js)**: [Announcements](models/announcements.js) collection;
|
||||
* **[avatars.js](../../server/publications/avatars.js)**: [Avatars](models/avatars.js) collection for current user;
|
||||
* **[boards.js](../../server/publications/boards.js)**: [Boards](models/boards.js) collection for current user, archived boards collection and individual board as a [relation](https://atmospherejs.com/cottz/publish-relations);
|
||||
* **[cards.js](../../server/publications/cards.js)**: a [Card](../../models/cards.js) by its id;
|
||||
* **[fast-render.js](https://github.com/wekan/wekan/tree/main/server/publications/fast-render.js)**: configures [FastRender](https://github.com/kadirahq/fast-render) to use the board data; <span style="color:red">*XXX: FastRender docs say "Make sure you're using Meteor.subscribe and not this.subscribe"*</span>
|
||||
* **[people.js](https://github.com/wekan/wekan/tree/main/server/publications/people.js)**: [Users](models/users.js) collection;
|
||||
* **[settings.js](https://github.com/wekan/wekan/tree/main/server/publications/settings.js)**: [Settings](models/settings.js) collection and, separately, the mail server;
|
||||
* **[unsavedEdits.js](https://github.com/wekan/wekan/tree/main/server/publications/unsavedEdits.js)**: [UnsavedEdits](models/unsavedEdits.js) collection;
|
||||
* **[users.js](https://github.com/wekan/wekan/tree/main/server/publications/users.js)**: provides a "mini-profile" for individual users and a [way](https://docs.meteor.com/api/collections.html#fieldspecifiers) to check if current user is admin.
|
||||
* **[people.js](../../server/publications/people.js)**: [Users](models/users.js) collection;
|
||||
* **[settings.js](../../server/publications/settings.js)**: [Settings](models/settings.js) collection and, separately, the mail server;
|
||||
* **[unsavedEdits.js](../../server/publications/unsavedEdits.js)**: [UnsavedEdits](models/unsavedEdits.js) collection;
|
||||
* **[users.js](../../server/publications/users.js)**: provides a "mini-profile" for individual users and a [way](https://docs.meteor.com/api/collections.html#fieldspecifiers) to check if current user is admin.
|
||||
|
||||
# Models
|
||||
|
||||
The files in **[models](https://github.com/wekan/wekan/tree/main/models)** directory mainly define collections; most of them have [aldeed SimpleSchema](https://atmospherejs.com/aldeed/simple-schema) for automatic validation of insert and update of collections. This is also where helpers, mutations, methods, hooks and bootstrap code is to be found. [Server side code](https://docs.meteor.com/api/core.html#Meteor-isServer) also implements json REST API.
|
||||
The files in **[models](../../models)** directory mainly define collections; most of them have [aldeed SimpleSchema](https://atmospherejs.com/aldeed/simple-schema) for automatic validation of insert and update of collections. This is also where helpers, mutations, methods, hooks and bootstrap code is to be found. [Server side code](https://docs.meteor.com/api/core.html#Meteor-isServer) also implements json REST API.
|
||||
|
||||
Collections (mostly `Mongo.Collection` except as noted) are defined in:
|
||||
* **[accountSettings.js](https://github.com/wekan/wekan/tree/main/models/accountSettings.js)**;
|
||||
* **[activities.js](https://github.com/wekan/wekan/tree/main/models/activities.js)**: does not have a SimpleSchema;
|
||||
* **[announcements.js](https://github.com/wekan/wekan/tree/main/models/announcements.js)**;
|
||||
* **[attachments.js](https://github.com/wekan/wekan/tree/main/models/attachments.js)**: file-system collection;
|
||||
* **[avatars.js](https://github.com/wekan/wekan/tree/main/models/avatars.js)**: file-system collection;
|
||||
* **[boards.js](https://github.com/wekan/wekan/tree/main/models/boards.js)**;
|
||||
* **[cardComments.js](https://github.com/wekan/wekan/tree/main/models/cardComments.js)**;
|
||||
* **[cards.js](https://github.com/wekan/wekan/tree/main/models/cards.js)**;
|
||||
* **[checklists.js](https://github.com/wekan/wekan/tree/main/models/checklists.js)**;
|
||||
* **[integrations.js](https://github.com/wekan/wekan/tree/main/models/integrations.js)**;
|
||||
* **[invitationCodes.js](https://github.com/wekan/wekan/tree/main/models/invitationCodes.js)**;
|
||||
* **[lists.js](https://github.com/wekan/wekan/tree/main/models/lists.js)**;
|
||||
* **[settings.js](https://github.com/wekan/wekan/tree/main/models/settings.js)**;
|
||||
* **[unsavedEdits.js](https://github.com/wekan/wekan/tree/main/models/unsavedEdits.js)**;
|
||||
* **[users.js](https://github.com/wekan/wekan/tree/main/models/users.js)**: extends the `Meteor.users` collection.
|
||||
* **[accountSettings.js](../../models/accountSettings.js)**;
|
||||
* **[activities.js](../../models/activities.js)**: does not have a SimpleSchema;
|
||||
* **[announcements.js](../../models/announcements.js)**;
|
||||
* **[attachments.js](../../models/attachments.js)**: file-system collection;
|
||||
* **[avatars.js](../../models/avatars.js)**: file-system collection;
|
||||
* **[boards.js](../../models/boards.js)**;
|
||||
* **[cardComments.js](../../models/cardComments.js)**;
|
||||
* **[cards.js](../../models/cards.js)**;
|
||||
* **[checklists.js](../../models/checklists.js)**;
|
||||
* **[integrations.js](../../models/integrations.js)**;
|
||||
* **[invitationCodes.js](../../models/invitationCodes.js)**;
|
||||
* **[lists.js](../../models/lists.js)**;
|
||||
* **[settings.js](../../models/settings.js)**;
|
||||
* **[unsavedEdits.js](../../models/unsavedEdits.js)**;
|
||||
* **[users.js](../../models/users.js)**: extends the `Meteor.users` collection.
|
||||
|
||||
Other files:
|
||||
* **[watchable.js](https://github.com/wekan/wekan/tree/main/models/watchable.js)**: extends the schema, helpers and mutations of `Boards`, `Lists` and `Cards`.
|
||||
* **[export.js](https://github.com/wekan/wekan/tree/main/models/export.js)**: has some code to support the REST API.
|
||||
* **[import.js](https://github.com/wekan/wekan/tree/main/models/import.js)**: implements `importBoard()` method so that Trello (in **[trelloCreator.js](https://github.com/wekan/wekan/tree/main/models/trelloCreator.js)**) and Wekan (in **[wekanCreator.js](https://github.com/wekan/wekan/tree/main/models/wekanCreator.js)**) boards can be imported. <span style="color:red">*XXX: Solid candidates for a directory of their own.*</span>
|
||||
* **[watchable.js](../../models/watchable.js)**: extends the schema, helpers and mutations of `Boards`, `Lists` and `Cards`.
|
||||
* **[export.js](../../models/export.js)**: has some code to support the REST API.
|
||||
* **[import.js](../../models/import.js)**: implements `importBoard()` method so that Trello (in **[trelloCreator.js](../../models/trelloCreator.js)**) and Wekan (in **[wekanCreator.js](../../models/wekanCreator.js)**) boards can be imported. <span style="color:red">*XXX: Solid candidates for a directory of their own.*</span>
|
||||
|
||||
# Tools
|
||||
|
||||
* Git:
|
||||
* **.git**;
|
||||
* **[.gitignore](https://github.com/wekan/wekan/tree/main/.gitignore)**;
|
||||
* **[.gitignore](../../.gitignore)**;
|
||||
* Docker:
|
||||
* **[docker-compose.yml](https://github.com/wekan/wekan/tree/main/docker-compose.yml)**: the compose file is a YAML file defining services, networks and volumes;
|
||||
* **[Dockerfile](https://github.com/wekan/wekan/tree/main/Dockerfile)**;
|
||||
* **[docker-compose.yml](../../docker-compose.yml)**: the compose file is a YAML file defining services, networks and volumes;
|
||||
* **[Dockerfile](../../Dockerfile)**;
|
||||
* Snap:
|
||||
* **[snapcraft.yaml](https://github.com/wekan/wekan/tree/main/snapcraft.yaml)**: [Snapcraft](https://snapcraft.io/) packages any app for every Linux desktop, server, cloud or device, and deliver updates directly;
|
||||
* **[snap](https://github.com/wekan/wekan/tree/main/snap)**;
|
||||
* **[snap-src](https://github.com/wekan/wekan/tree/main/snap-src)**;
|
||||
* **[snapcraft.yaml](../../snapcraft.yaml)**: [Snapcraft](https://snapcraft.io/) packages any app for every Linux desktop, server, cloud or device, and deliver updates directly;
|
||||
* **[snap](../../snap)**;
|
||||
* **[snap-src](../../snap-src)**;
|
||||
* Sandstorm:
|
||||
* **[sandstorm.js](https://github.com/wekan/wekan/tree/main/sandstorm.js)**: [Sandstorm](https://sandstorm.io/) specific code;
|
||||
* **[sandstorm-pkgdef.capnp](https://github.com/wekan/wekan/tree/main/sandstorm-pkgdef.capnp)**: used the meteor-spk tool to generate a sandstorm package;
|
||||
* **[sandstorm.js](../../sandstorm.js)**: [Sandstorm](https://sandstorm.io/) specific code;
|
||||
* **[sandstorm-pkgdef.capnp](../../sandstorm-pkgdef.capnp)**: used the meteor-spk tool to generate a sandstorm package;
|
||||
* Node:
|
||||
* **[package.json](https://github.com/wekan/wekan/tree/main/package.json)**;
|
||||
* **[package.json](../../package.json)**;
|
||||
* **node_modules**
|
||||
* **[app.json](https://github.com/wekan/wekan/tree/main/app.json)**: is a manifest format for describing web apps (build requirements, environment variables, addons, and other information);
|
||||
* **[app.env](https://github.com/wekan/wekan/tree/main/app.env)**: environment variables;
|
||||
* **[app.json](../../app.json)**: is a manifest format for describing web apps (build requirements, environment variables, addons, and other information);
|
||||
* **[app.env](../../app.env)**: environment variables;
|
||||
* Meteor: is a full-stack JavaScript platform for developing modern web and mobile applications.
|
||||
* **[.meteor](https://github.com/wekan/wekan/tree/main/.meteor)**;
|
||||
* **[.meteor](../../.meteor)**;
|
||||
* Translation:
|
||||
* **[i18n](https://github.com/wekan/wekan/tree/main/i18n)** directory has one .json file for each supported language
|
||||
* **[.tx](https://github.com/wekan/wekan/tree/main/.tx)**: configuration for [Transifex](https://app.transifex.com/wekan/) tool used to manage translation;
|
||||
* **[.tx](../../.tx)**: configuration for [Transifex](https://app.transifex.com/wekan/) tool used to manage translation;
|
||||
* Text editors:
|
||||
* **[.vscode](https://github.com/wekan/wekan/tree/main/.vscode)**: [Visual Studio Code Editor](https://code.visualstudio.com/docs/getstarted/settings);
|
||||
* **[.editorconfig](https://github.com/wekan/wekan/tree/main/.editorconfig)**: [EditorConfig](http://EditorConfig.org) provides consistent coding styles between different editors and IDEs;
|
||||
* **[.github](https://github.com/wekan/wekan/tree/main/.github)**: hosts the issues template;
|
||||
* **[.eslintrc.json](https://github.com/wekan/wekan/tree/main/.eslintrc.json)**: [ESLint](https://eslint.org/docs/user-guide/configuring) configuration;
|
||||
* **[.travis.yml](https://github.com/wekan/wekan/tree/main/.travis.yml)**: configuration for [Travis CI](https://travis-ci.org/);
|
||||
* **[scalingo.json](https://github.com/wekan/wekan/tree/main/scalingo.json)**: [Scalingo](https://scalingo.com/) is a deploy solution;
|
||||
* **[.vscode](../../.vscode)**: [Visual Studio Code Editor](https://code.visualstudio.com/docs/getstarted/settings);
|
||||
* **[.editorconfig](../../.editorconfig)**: [EditorConfig](http://EditorConfig.org) provides consistent coding styles between different editors and IDEs;
|
||||
* **[.github](../../.github)**: hosts the issues template;
|
||||
* **[.eslintrc.json](../../.eslintrc.json)**: [ESLint](https://eslint.org/docs/user-guide/configuring) configuration;
|
||||
* **[.travis.yml](../../.travis.yml)**: configuration for [Travis CI](https://travis-ci.org/);
|
||||
* **[scalingo.json](../../scalingo.json)**: [Scalingo](https://scalingo.com/) is a deploy solution;
|
||||
* **[fix-download-unicode](https://github.com/wekan/wekan/tree/main/fix-download-unicode)**: `cfs_access-point.txt` from this folder is copied to `bundle/programs/server/packages/cfs_access-point.js` in Docker build and in snapcraft build; this is a monkey patch fix for [downloading files that have unicode in filename](https://github.com/wekan/wekan/issues/784).
|
||||
|
||||
# Info
|
||||
|
||||
* **[meta](https://github.com/wekan/wekan/tree/main/meta)**: binary signatures, project description, icons, screenshots and, oui, a French change-log;
|
||||
* **[CHANGELOG.md](https://github.com/wekan/wekan/tree/main/CHANGELOG.md)**;
|
||||
* **[meta](../../meta)**: binary signatures, project description, icons, screenshots and, oui, a French change-log;
|
||||
* **[CHANGELOG.md](../../CHANGELOG.md)**;
|
||||
* **[Contributing.md](https://github.com/wekan/wekan/tree/main/Contributing.md)**;
|
||||
* **[LICENSE](https://github.com/wekan/wekan/tree/main/LICENSE)**;
|
||||
* **[README.md](https://github.com/wekan/wekan/tree/main/README.md)**.
|
||||
* **[LICENSE](../../LICENSE)**;
|
||||
* **[README.md](../../README.md)**.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- Read [Wekan new release ChangeLog](https://github.com/wekan/wekan/blob/main/CHANGELOG.md)
|
||||
- [Install Meteor.js](https://www.meteor.com/install). Note: Windows install is very slow, please use other option at [Windows wiki page](Windows)
|
||||
- Read [Wekan new release ChangeLog](../../CHANGELOG.md)
|
||||
- [Install Meteor.js](https://www.meteor.com/install). Note: Windows install is very slow, please use other option at [Windows wiki page](../Platforms/Propietary/Windows/Windows.md)
|
||||
- Download newest bundle from https://releases.wekan.team
|
||||
- Unarchive bundle .tar.gz file: `tar -zxvf wekan-VERSION.tar.gz`
|
||||
- `cd bundle`
|
||||
@@ -8,6 +8,6 @@
|
||||
|
||||
[Wekan for Meteor.js bundle bug reports and feature requests](https://github.com/wekan/wekan/issues)
|
||||
|
||||
[Build Meteor bundle from source](Source)
|
||||
[Build Meteor bundle from source](../Platforms/FOSS/Source/Source.md)
|
||||
|
||||
[Install from source without root](Install-from-source-without-root)
|
||||
[Install from source without root](../Platforms/FOSS/Source/Install-from-source-without-root.md)
|
||||
@@ -20,7 +20,7 @@
|
||||
- https://www.youtube.com/watch?v=ZTtXRJTUCIU
|
||||
- https://github.com/jamauro/pwa-kit
|
||||
- https://wekan.fi/app/
|
||||
- https://github.com/wekan/wekan/wiki/PWA
|
||||
- [PWA](../Browsers/PWA.md)
|
||||
- Over 100 icons for different platforms https://github.com/wekan/wekan/blob/main/public/site.webmanifest
|
||||
|
||||
## Tip to any programmers
|
||||
@@ -76,7 +76,7 @@ https://www.youtube.com/watch?v=8pDqJVdNa44
|
||||
- Look at progress of migrated packages https://forums.meteor.com/t/looking-for-help-migrating-packages-to-meteor-3-0/60985
|
||||
- Migrate from Fibers (works only at Meteor 2 and Node.js 14) to Async/Await (works with Meteor 3 and Node.js 20)
|
||||
- Helper tool https://forums.meteor.com/t/blaze-async-migration-helper-babel-plugin-tracker-async-enhance-for-the-client-side-released/60842
|
||||
- Info how Async/Await works, from Storyteller: https://github.com/wekan/wekan/wiki/AsyncAwait
|
||||
- Info how Async/Await works, from Storyteller: [AsyncAwait](AsyncAwait.md)
|
||||
- Forums posts about migrating: https://forums.meteor.com
|
||||
- ( Older version upgrade to newest 2.14 https://guide.meteor.com/2.14-migration )
|
||||
|
||||
@@ -99,8 +99,8 @@ From Meteor Discord chat from someone:
|
||||
|
||||
## Building WeKan
|
||||
|
||||
- https://github.com/wekan/wekan/wiki/Emoji
|
||||
- Docs: https://github.com/wekan/wekan/tree/main/docs
|
||||
- [Emoji](../Features/Emoji.md)
|
||||
- Docs: [docs](..)
|
||||
|
||||
## Similar technology
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
## Testers to ping with new Wekan Edge release
|
||||
- All those whose issues were fixed [at ChangeLog](https://github.com/wekan/wekan/blob/main/CHANGELOG.md)
|
||||
- All those whose issues were fixed [at ChangeLog](../../CHANGELOG.md)
|
||||
- @saschafoerster
|
||||
|
||||
Those should reply to new issue that has name `Test Edge Wekan (version-number-here)` at https://github.com/wekan/wekan/issues .
|
||||
@@ -14,7 +14,7 @@ https://github.com/wekan/wekan/issues/2811
|
||||
|
||||
## What usually happens when Wekan gets broken
|
||||
|
||||
1. There is many new issues about some same new feature or bug. Yes, also when some notices new feature that is already [at ChangeLog](https://github.com/wekan/wekan/blob/main/CHANGELOG.md), they add issue about it, usually asking "Can this feature be turned off or made optional?". This is usually if new feature changes some old workflow radically.
|
||||
1. There is many new issues about some same new feature or bug. Yes, also when some notices new feature that is already [at ChangeLog](../../CHANGELOG.md), they add issue about it, usually asking "Can this feature be turned off or made optional?". This is usually if new feature changes some old workflow radically.
|
||||
2. Many do tag @xet7 that are you aware of the issue.
|
||||
3. Someone yells at IRC that "this is too much, it's time to fork Wekan". Well, that's because he gets yelled at by all users of Wekan at his company.
|
||||
4. xet7 does not have time to answer those, he just fixes the issue - usually at the same day, makes new release, closes all related issues with last comment "Please try Wekan (version-number-here)".
|
||||
@@ -23,7 +23,7 @@ https://github.com/wekan/wekan/issues/2811
|
||||
|
||||
## ChangeLog
|
||||
|
||||
https://github.com/wekan/wekan/blob/main/CHANGELOG.md
|
||||
[CHANGELOG.md](../../CHANGELOG.md)
|
||||
|
||||
## Backup before testing. And backup daily in production.
|
||||
|
||||
@@ -31,7 +31,7 @@ Why? Well, Wekan has no undo yet. If you delete Swimlane/List/Card, it's gone.
|
||||
That's why it's harder to reach in submenu. It's better to Arhive those,
|
||||
and unarchive.
|
||||
|
||||
https://github.com/wekan/wekan/wiki/Backup
|
||||
[Backup](../Backup/Backup.md)
|
||||
|
||||
You could test at other computer than your production.
|
||||
|
||||
@@ -81,7 +81,7 @@ image: wekanteam/wekan:v3.55
|
||||
### How to change Docker version
|
||||
|
||||
1) Backup first
|
||||
https://github.com/wekan/wekan/wiki/Backup
|
||||
[Backup](../Backup/Backup.md)
|
||||
|
||||
```
|
||||
docker-compose stop
|
||||
@@ -137,7 +137,7 @@ docker-compose up -d --build
|
||||
1) Install local development version of Sandstorm to your laptop from https://sandstorm.io/install
|
||||
|
||||
2) Download and install experimental version to your local Sandstorm:
|
||||
https://github.com/wekan/wekan/wiki/Sandstorm
|
||||
[Sandstorm](../Platforms/FOSS/Sandstorm/Sandstorm.md)
|
||||
Local sandstorm is at http://local.sandstorm.io:6080/ .
|
||||
|
||||
3) Download your production Wekan grain .zip file with down arrow button. Upload to your dev local Sandstorm.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Roles
|
||||
|
||||
It depends on role of user what drag drop is allowed https://github.com/wekan/wekan/wiki/REST-API-Role
|
||||
It depends on role of user what drag drop is allowed [REST API Role](../API/Role.md)
|
||||
|
||||
## Mobile Drag Drop
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
## Newest Wekan
|
||||
|
||||
In [Wekan v5.02](https://github.com/wekan/wekan/blob/main/CHANGELOG.md#v502-2021-03-02-wekan-release) is fix for STMP settings [that works with very happy feedback](https://github.com/wekan/wekan/issues/3529#issuecomment-789085999). It requires:
|
||||
In [Wekan v5.02](../../CHANGELOG.md#v502-2021-03-02-wekan-release) is fix for STMP settings [that works with very happy feedback](https://github.com/wekan/wekan/issues/3529#issuecomment-789085999). It requires:
|
||||
- **There is no email settings at Admin Panel anymore**
|
||||
- Email settings are made only with snap commands like `sudo snap set wekan mail-url....` or similar in Docker/Source etc `MAIL_URL=...`, more details below.
|
||||
- For any errors like SSLv3 and STARTTLS, check [newest AWS SES info](#example-aws-ses) and use similar settings, other SSLv3/STARTTLS info could be outdated.
|
||||
@@ -50,7 +50,7 @@ await Email.sendAsync({
|
||||
|
||||
## Debugging your SMTP mail server
|
||||
|
||||
If you can't get your email server working with smtp/stmps or MAIL_SERVICE, look is there more config options for your email server at https://nodemailer.com docs, and add new issue about your email server to https://github.com/wekan/wekan/issues or [pull request](Emoji#how-you-could-add-another-plugin).
|
||||
If you can't get your email server working with smtp/stmps or MAIL_SERVICE, look is there more config options for your email server at https://nodemailer.com docs, and add new issue about your email server to https://github.com/wekan/wekan/issues or [pull request](../Features/Emoji.md#how-you-could-add-another-plugin).
|
||||
|
||||
For example, for https://nodemailer.com/smtp/
|
||||
|
||||
@@ -82,7 +82,7 @@ let info = transporter.sendMail({
|
||||
```
|
||||
|
||||
|
||||
If with some code example sending email works, xet7 can add it to wekan, like in [Wekan v5.52](https://github.com/wekan/wekan/blob/main/CHANGELOG.md#v552-2021-08-26-wekan-release) xet7 added direct nodemailer usage for MAIL_SERVICE.
|
||||
If with some code example sending email works, xet7 can add it to wekan, like in [Wekan v5.52](../../CHANGELOG.md#v552-2021-08-26-wekan-release) xet7 added direct nodemailer usage for MAIL_SERVICE.
|
||||
|
||||
## MAIL_SERVICE (not currently in use)
|
||||
|
||||
@@ -304,7 +304,7 @@ Mail configuration if done by settings of `MAIL_URL` & `MAIL_FROM` environment p
|
||||
|
||||
## No mail server
|
||||
|
||||
You can choose to _NOT_ configure a mail server, by not providing the `MAIL_URL` & `MAIL_FROM` environment parameters. Instead the mail message will be send to the terminal output. See [FAQ](FAQ#show-mails-with-a-docker-image-without-mail-configuration) for more info.
|
||||
You can choose to _NOT_ configure a mail server, by not providing the `MAIL_URL` & `MAIL_FROM` environment parameters. Instead the mail message will be send to the terminal output. See [FAQ](../FAQ/FAQ.md#show-mails-with-a-docker-image-without-mail-configuration) for more info.
|
||||
|
||||
## Email servers: SMTP or Exchange
|
||||
|
||||
|
||||
+13
-13
@@ -18,10 +18,10 @@ that by providing one-click installation on various platforms.
|
||||
- Wekan is used in [most countries of the world](https://snapcraft.io/wekan).
|
||||
- Wekan largest user has 30k users using Wekan in their company.
|
||||
- Wekan has been [translated](https://app.transifex.com/wekan/) to about 70+ languages.
|
||||
- [Features](Features): Wekan has real-time user interface.
|
||||
- [Platforms](Platforms): Wekan supports many platforms.
|
||||
- [Features](../Features/Features.md): Wekan has real-time user interface.
|
||||
- [Platforms](../Platforms/FOSS/Platforms.md): Wekan supports many platforms.
|
||||
Wekan is critical part of new platforms Wekan is currently being integrated to.
|
||||
- [Integrations](Integrations): Current possible integrations and future plans.
|
||||
- [Integrations](../ImportExport/Integrations.md): Current possible integrations and future plans.
|
||||
|
||||
## About niche of WeKan
|
||||
|
||||
@@ -30,7 +30,7 @@ It's a very specific niche, with limited amount competitors, with all of this ap
|
||||
- Kanban with very many features, polish and usability
|
||||
- Permissive MIT license (if some other changes to GPL, Open Core, has https://sso.tax , is propietary etc, it's not in same niche anymore)
|
||||
- Self-hosted (or SaaS)
|
||||
- All code https://github.com/wekan/wekan and docs https://github.com/wekan/wekan/wiki Open Source
|
||||
- All code https://github.com/wekan/wekan and docs [Wekan documentation](../README.md) Open Source
|
||||
- Available for many OS and CPU platforms, listed at download/install section of https://wekan.fi
|
||||
- Translated to 70+ languages at https://app.transifex.com/wekan
|
||||
- Has optional affordable Commercial Support for Features/Fixes/Support/Hosting available https://wekan.fi/commercial-support/ that funds development of WeKan. Commercial Support provides private support chat with maintainer of WeKan.
|
||||
@@ -41,18 +41,18 @@ It's a very specific niche, with limited amount competitors, with all of this ap
|
||||
## Requirements
|
||||
|
||||
- 64bit: Linux [Snap](https://github.com/wekan/wekan-snap/wiki/Install) or [Sandstorm](https://sandstorm.io) /
|
||||
[Mac](Mac) / [Windows](Install-Wekan-from-source-on-Windows).
|
||||
[More Platforms](Platforms). [ARM progress](https://github.com/wekan/wekan/issues/1053#issuecomment-410919264).
|
||||
[Mac](../Platforms/Propietary/Mac.md) / [Windows](../Platforms/Propietary/Windows/Install-Wekan-from-source-on-Windows.md).
|
||||
[More Platforms](../Platforms/FOSS/Platforms.md). [ARM progress](https://github.com/wekan/wekan/issues/1053#issuecomment-410919264).
|
||||
- 1 GB RAM minimum free for Wekan. Production server should have miminum total 4 GB RAM.
|
||||
For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/main/docker-compose.yml): 3 frontend servers,
|
||||
For thousands of users, for example with [Docker](../../docker-compose.yml): 3 frontend servers,
|
||||
each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
|
||||
- Enough disk space and alerts about low disk space. If you run out disk space, MongoDB database gets corrupted.
|
||||
- SECURITY: Updating to newest Wekan version very often. Please check you do not have automatic updates of Sandstorm or Snap turned off.
|
||||
Old versions have security issues because of old versions Node.js etc. Only newest Wekan is supported.
|
||||
Wekan on Sandstorm is not usually affected by any Standalone Wekan (Snap/Docker/Source) security issues.
|
||||
- [Reporting all new bugs immediately](https://github.com/wekan/wekan/issues).
|
||||
New features and fixes are added to Wekan [many times a day](https://github.com/wekan/wekan/blob/main/CHANGELOG.md).
|
||||
- [Backups](Backup) of Wekan database once a day miminum.
|
||||
New features and fixes are added to Wekan [many times a day](../../CHANGELOG.md).
|
||||
- [Backups](../Backup/Backup.md) of Wekan database once a day miminum.
|
||||
Bugs, updates, users deleting list or card, harddrive full, harddrive crash etc can eat your data. There is no undo yet.
|
||||
Some bug can cause Wekan board to not load at all, requiring manual fixing of database content.
|
||||
|
||||
@@ -65,7 +65,7 @@ It's a very specific niche, with limited amount competitors, with all of this ap
|
||||
[Lauri Ojansivu](https://github.com/xet7) is CEO at [WeKan Team](https://wekan.fi),
|
||||
Cross-Platform FOSS maintainer,
|
||||
Cloud Architect, Full-Stack Developer, SysAdmin and SysOp.
|
||||
He has experience of [having added and removed over 4 million lines of code](https://github.com/wekan/wekan/blob/main/releases/count-lines-of-code-per-committer.sh) to
|
||||
He has experience of [having added and removed over 4 million lines of code](../../releases/count-lines-of-code-per-committer.sh) to
|
||||
[Meteor Full-Stack Web Framework](https://www.meteor.com)
|
||||
based [WeKan Open Source kanban](https://wekan.fi),
|
||||
that has been [translated to 70+ languages](https://explore.transifex.com/wekan/wekan/),
|
||||
@@ -119,7 +119,7 @@ Usually:
|
||||
* Slowest: Once a month.
|
||||
* One release contains anything from one typo fix to many major features and bugfixes.
|
||||
|
||||
For [Wekan Platforms](Platforms), it means these choices:
|
||||
For [Wekan Platforms](../Platforms/FOSS/Platforms.md), it means these choices:
|
||||
|
||||
* Snap: You get updates automatically immediately, or updates automatically scheduled at midnight
|
||||
* Source/Docker/VirtualBox: You need to test and install new Wekan version yourself
|
||||
@@ -150,7 +150,7 @@ c) Develop feature yourself and submit it as pull requests to devel [Wekan repo]
|
||||
|
||||
For Sandstorm-specific features, have the feature enabled in Sandstorm by using environment variable isSandstorm = true like is at wekan/sandstorm.js .
|
||||
|
||||
In wiki there is [Developer Documentation](Developer-Documentation).
|
||||
In wiki there is [Developer Documentation](../DeveloperDocs/Developer-Documentation.md).
|
||||
|
||||
## Will you accept my pull request?
|
||||
We totally rely on pull requests for new features and bug fixes. If your pull request works, it's very likely to be accepted by xet7.
|
||||
@@ -176,7 +176,7 @@ Yes, Libreboard was the old project name, which superseded the even older projec
|
||||
After renaming it to Libreboard, a [new logo] was designed and the project continued to live on as Libreboard. Unfortunately it turned out, that the new logo was apparently ripped-off from a [concept] published at Dribbble, and so a new logo had to be found. There were a lot of [ideas from the community][logo-ticket], and at the end Maxime [proposed][wekan-proposal] a completely new name, Wekan, together with a design proposal for a new logo.
|
||||
|
||||
## What was Wekan fork / Wefork?
|
||||
After 2016-09-02 there were no pull requests reviewed and integrated for nearly 2 months. At 2016-10-20 Wekan community created fork and started merging many bugfixes and new features into Wefork. 2017-01-29 Wekan author mquandalle gave access to Wekan and at 2017-01-31 xet7 started merging Wefork back to Wekan. 2017-02-08 All of Wefork is now merged and moved back to official Wekan. Wefork will not accept any new issues and pull requests. All development happens on Wekan. [Wefork announcement and merging back](https://github.com/wekan/wekan/issues/640#issuecomment-276383458), more info: [Team](Team)
|
||||
After 2016-09-02 there were no pull requests reviewed and integrated for nearly 2 months. At 2016-10-20 Wekan community created fork and started merging many bugfixes and new features into Wefork. 2017-01-29 Wekan author mquandalle gave access to Wekan and at 2017-01-31 xet7 started merging Wefork back to Wekan. 2017-02-08 All of Wefork is now merged and moved back to official Wekan. Wefork will not accept any new issues and pull requests. All development happens on Wekan. [Wefork announcement and merging back](https://github.com/wekan/wekan/issues/640#issuecomment-276383458), more info: [Team](Team.md)
|
||||
|
||||
## What is the difference between Wekan and Trello?
|
||||
The main difference between the two is that Wekan is completely open source and available under the permissive MIT license. That makes it possible to host it on your own server (or your company's or organization's server) and you keep the full control over all data. No need to fear it will disappear some day, like a commercial service like Trello could.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[Other things that are not related to WeKan kanban](https://github.com/wekan/wekan/wiki/NOT-related-to-Wekan)
|
||||
[Other things that are not related to WeKan kanban](NOT-related-to-Wekan.md)
|
||||
|
||||
## Description
|
||||
|
||||
|
||||
Loaded 100 of 462 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user