Compare commits

...
37 Commits
Author SHA1 Message Date
Lauri Ojansivu 6b8939c02e v4.80 2021-01-18 14:33:15 +02:00
Lauri Ojansivu 86e5356b0f Update ChangeLog. 2021-01-18 13:58:34 +02:00
Lauri Ojansivu c0f748bcb5 Update dependencies.
Thanks to developers of dependencies!
2021-01-18 13:56:52 +02:00
Lauri Ojansivu 0acb8f5d69 Update translations. 2021-01-18 13:55:37 +02:00
Lauri Ojansivu 37db870475 Update ChangeLog. 2021-01-18 13:47:28 +02:00
Lauri Ojansivu 0d5f33299e Fixed Linked card makes board not load when CustomField definition is undefined.
Thanks to olivierlambert and xet7 !

Fixes #3367
2021-01-18 13:43:53 +02:00
Lauri Ojansivu 78f818bbb0 Update ChangeLog. 2021-01-18 13:18:23 +02:00
Lauri Ojansivu edafb6a6d2 Update translations. 2021-01-18 12:39:30 +02:00
Lauri Ojansivu c0984aa740 Update translations. 2021-01-18 12:28:20 +02:00
Lauri Ojansivu 6253bbdc0b Fix file permissions. 2021-01-18 12:22:00 +02:00
Lauri Ojansivu 254a9abad2 Try to use buster base images, because when using Wekan Alpine, registering new user of Wekan does not work (maybe because of glibc/musl).
Thanks to GavinLilly and xet7 !

Related #2762
2021-01-18 11:58:00 +02:00
Lauri Ojansivu 83e77f8d82 Update translations. 2021-01-18 11:33:19 +02:00
Lauri Ojansivu 2726643586 Try Alpine base image for running Wekan. 2021-01-18 10:27:02 +02:00
Lauri Ojansivu b8f2a468d8 Update ChangeLog. 2021-01-18 09:49:45 +02:00
Lauri Ojansivu fb771d46dd Added npm and curl to Dockerfile, because Docker build failed.
Thanks to xet7 !
2021-01-18 09:47:52 +02:00
Lauri Ojansivu ecd4c7fb02 Update ChangeLog. 2021-01-18 09:34:24 +02:00
Lauri Ojansivu da2be59185 Use Ubuntu base image in Dockerfile, because according to Quay.io security scan it does not have any vulnerabilities, that node base images do have.
Thanks to xet7 !

Fixes #2762,
fixes https://github.com/wekan/wekan/pull/3438
2021-01-18 09:29:14 +02:00
Lauri Ojansivu 93a3fa9c75 Update ChangeLog. 2021-01-18 09:16:08 +02:00
Lauri Ojansivu cf546c71e3 Merge branch 'master' of github.com:wekan/wekan 2021-01-18 09:09:10 +02:00
Lauri Ojansivu 2791b7da22 Use MongoDB setFeatureCompatibilityVersion 4.2 on Snap. TODO: Docker.
Thanks to GuidoDr and xet7 !

Related #3247
2021-01-18 09:08:02 +02:00
Lauri Ojansivu 48fc442b85 Merge pull request #3438 from GavinLilly/multi_stage_build
Use multi stage build based on Node images
2021-01-18 07:46:30 +02:00
Gavin Lilly 2e6b18dfa7 Move final npm install to builder and run node as non-root 2021-01-18 00:14:51 +00:00
Lauri Ojansivu 7f0da49e0a Merge pull request #3437 from jrsupplee/search
Global Search fixes and updates
2021-01-17 23:53:18 +02:00
John R. Supplee b5124d0f6a cleanup debug code 2021-01-17 21:43:56 +02:00
John R. Supplee e70a1f0a60 Fix bug when no search param specified 2021-01-17 21:18:45 +02:00
John R. Supplee 7e8475e64d Add link to search header for storing search 2021-01-17 21:04:05 +02:00
Gavin Lilly e161ada214 Use multi stage build based on Node images 2021-01-17 17:49:39 +00:00
John R. Supplee 409b8559d7 Merge branch 'master' of https://github.com/wekan/wekan into search 2021-01-17 16:07:43 +02:00
John R. Supplee d74dc92681 Global Search improvements
* support for searching from the URL
* add support for searching by assignee and member
2021-01-17 16:01:42 +02:00
Lauri Ojansivu 5f62d7307f Update ChangeLog. 2021-01-17 14:26:41 +02:00
Lauri Ojansivu 3866ed3196 Use table-cell instead of inline-block in my-cards-list-wrapper CSS.
Thanks to johappel and xet7 !

Related #1667
2021-01-17 14:22:54 +02:00
John R. Supplee 8059856c39 Convert global search instructions to translatable tags 2021-01-17 00:35:39 +02:00
John R. Supplee a3518a3bcf cleanup 2021-01-16 21:08:15 +02:00
John R. Supplee 7b8d67de63 Global search - fix label not found 2021-01-16 21:07:49 +02:00
John R. Supplee 7a5aa469cd Modify search results translations
* use named place holders in translation tag with multiple values
2021-01-16 20:45:05 +02:00
John R. Supplee 1e415b38d2 Fix URL reference in resultCard template 2021-01-16 19:26:39 +02:00
John R. Supplee ff626fb559 Add a new SessionData collection and limit user fields
* Add new SessionData collection to store user session data available to
server and client
* Limit the Users fields sent to the client by `myCards`, `dueCards`,
`brokenCards`, and `globalSearch` using new `Users.safeFields`
* clean-up
2021-01-16 19:26:06 +02:00
81 changed files with 50815 additions and 49268 deletions

No files matched your search

+1 -1
View File
@@ -72,7 +72,7 @@ horka:swipebox@1.0.2
hot-code-push@1.0.4
html-tools@1.0.11
htmljs@1.0.11
http@1.4.2
http@1.4.3
id-map@1.1.0
idmontie:migrations@1.0.3
inter-process-messaging@0.1.1
+28
View File
@@ -1,3 +1,31 @@
# v4.80 2021-01-18 Wekan release
This release adds the following improvements:
- [Global Search fixes and updates](https://github.com/wekan/wekan/pulls/3437).
Thanks to jrsupplee.
- [Use table-cell instead of inline-block in my-cards-list-wrapper CSS](https://github.com/wekan/wekan/commit/3866ed31965eb5b722e88c4d3e7628d516375088).
Thanks to johappel and xet7.
- [Use multi stage build based on Node images](https://github.com/wekan/wekan/pull/3438).
Thanks to GavinLilly.
- [Try to use buster base images, because when using Wekan Alpine, registering new user of Wekan does not work,
maybe because of glibc/musl](https://github.com/wekan/wekan/commit/254a9abad2dec620d95c02ac9209e9f569407986).
Thanks to GavinLilly and xet7.
- [Use MongoDB setFeatureCompatibilityVersion 4.2 on Snap. TODO: Docker](https://github.com/wekan/wekan/commit/2791b7da22ddb0ff5588eca56f1dc90ff5ffdd2d).
Thanks to GuidoDr and xet7.
and adds the following updates:
- [Update dependencies](https://github.com/wekan/wekan/commit/c0f748bcb5dfebe7fa90be647a1ed23f0edcc304).
Thanks to developers of dependencies.
and fixes the following bugs:
- [Fixed Linked card makes board not load when CustomField definition is undefined](https://github.com/wekan/wekan/commit/0d5f33299ee25e1bee4ca4fc3b3c2483c29e367c).
Thanks to olivierlambert and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.79 2021-01-17 Wekan release
This release adds the following new features:
+45 -193
View File
@@ -1,26 +1,46 @@
FROM quay.io/wekan/ubuntu:groovy-20201125.2
FROM quay.io/wekan/node:12.20.1-buster AS builder
# This is our builder image. It will take the Wekan meteor code and
# build it into a standalone NodeJS package
LABEL maintainer="wekan"
# 2020-12-03:
# - Above Ubuntu base image copied from Docker Hub ubuntu:groovy-20201125.2
# to Quay to avoid Docker Hub rate limits.
# Allow Meteor to run inside the container as container runs as root
ENV METEOR_ALLOW_SUPERUSER=true
# Set the environment variables (defaults where required)
# DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
# ENV BUILD_DEPS="paxctl"
ARG DEBIAN_FRONTEND=noninteractive
# Copy our source code into the container
COPY . /usr/src/app
ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-essential git ca-certificates python3" \
DEBUG=false \
NODE_VERSION=v12.20.1 \
METEOR_RELEASE=1.10.2 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
NPM_VERSION=latest \
FIBERS_VERSION=4.0.1 \
ARCHITECTURE=linux-x64 \
SRC_PATH=./ \
WITH_API=true \
# Allow the user accounts package to work with more Meteor versions
RUN sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /usr/src/app/packages/meteor-useraccounts-core/package.js
# Move our current working directory
WORKDIR /usr/src/app
# Install our NPM packages
RUN npm install
# Install Meteor
RUN curl "https://install.meteor.com/" | sh
# Build our Meteor package into a standalone NodeJS package
RUN meteor build --directory /build
# Include the fix for CFS
RUN cp /usr/src/app/fix-download-unicode/cfs_access-point.txt /build/bundle/programs/server/packages/cfs_access-point.js
# Install server side NPM depedencies
RUN cd /build/bundle/programs/server/ && \
npm install
# Remove the legacy web browser bundle, so that Wekan works in Android Firefox, iOS Safari, etc.
RUN rm -rf /build/bundle/programs/web.browser.legacy
FROM quay.io/wekan/node:12.20.1-buster-slim as app
# This is our runtime image. It takes the built NodeJS package and runs it using
# the lighter Alpine NodeJS base image to keep image size down
ENV WITH_API=true \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \
ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \
@@ -138,182 +158,14 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-
SAML_ATTRIBUTES="" \
ORACLE_OIM_ENABLED=false
# Copy the app to the image
COPY ${SRC_PATH} /home/wekan/app
# Copy our built NodeJS package to this image
COPY --from=builder /build/bundle /bundle
RUN \
set -o xtrace && \
# Add non-root user wekan
useradd --user-group --system --home-dir /home/wekan wekan && \
\
# OS dependencies
apt-get update -y && apt-get install -y --no-install-recommends ${BUILD_DEPS} && \
#pip3 install -U pip setuptools wheel && \
\
# Meteor installer doesn't work with the default tar binary, so using bsdtar while installing.
# https://github.com/coreos/bugs/issues/1095#issuecomment-350574389
cp $(which tar) $(which tar)~ && \
ln -sf $(which bsdtar) $(which tar) && \
\
# Download nodejs
wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
#---------------------------------------------------------------------------------------------
# Node Fibers 100% CPU usage issue:
# https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-381453161
# https://github.com/meteor/meteor/issues/9796#issuecomment-381676326
# https://github.com/sandstorm-io/sandstorm/blob/0f1fec013fe7208ed0fd97eb88b31b77e3c61f42/shell/server/00-startup.js#L99-L129
# Also see beginning of wekan/server/authentication.js
# import Fiber from "fibers";
# Fiber.poolSize = 1e9;
# OLD: Download node version 8.12.0 prerelease that has fix included, => Official 8.12.0 has been released
# Description at https://releases.wekan.team/node.txt
#wget https://releases.wekan.team/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
#echo "1ed54adb8497ad8967075a0b5d03dd5d0a502be43d4a4d84e5af489c613d7795 node-v8.12.0-linux-x64.tar.gz" >> SHASUMS256.txt.asc && \
\
# Verify nodejs authenticity
grep ${NODE_VERSION}-${ARCHITECTURE}.tar.gz SHASUMS256.txt.asc | shasum -a 256 -c - && \
#export GNUPGHOME="$(mktemp -d)" && \
#\
# Try other key servers if ha.pool.sks-keyservers.net is unreachable
# Code from https://github.com/chorrell/docker-node/commit/2b673e17547c34f17f24553db02beefbac98d23c
# gpg keys listed at https://github.com/nodejs/node#release-team
# and keys listed here from previous version of this Dockerfile
#for key in \
#9554F04D7259F04124DE6B476D5A82AC7E37093B \
#94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
#FD3A5288F042B6850C66B31F09FE44734EB7990E \
#71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
#DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
#C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
#B9AE9905FFD7803F25714661B63B535A4C206CA9 \
#; do \
#gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key" || \
#gpg --keyserver pgp.mit.edu --recv-keys "$key" || \
#gpg --keyserver keyserver.pgp.com --recv-keys "$key" ; \
#done && \
#gpg --verify SHASUMS256.txt.asc && \
# Ignore socket files then delete files then delete directories
#find "$GNUPGHOME" -type f | xargs rm -f && \
#find "$GNUPGHOME" -type d | xargs rm -fR && \
rm -f SHASUMS256.txt.asc && \
\
# Install Node
tar xvzf node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
rm node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
mv node-${NODE_VERSION}-${ARCHITECTURE} /opt/nodejs && \
ln -s /opt/nodejs/bin/node /usr/bin/node && \
ln -s /opt/nodejs/bin/npm /usr/bin/npm && \
mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/8.16.1 /home/wekan/.config && \
chown wekan --recursive /home/wekan/.config && \
\
#DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
#paxctl -mC `which node` && \
\
# Install Node dependencies. Python path for node-gyp.
npm install -g npm@${NPM_VERSION} && \
#npm config set python python2.7 && \
#npm install -g node-gyp && \
#npm install -g fibers@${FIBERS_VERSION} && \
\
# Change user to wekan and install meteor
cd /home/wekan/ && \
chown wekan --recursive /home/wekan && \
#curl "https://install.meteor.com" -o /home/wekan/install_meteor.sh && \
#curl "https://install.meteor.com/?release=${METEOR_RELEASE}" -o /home/wekan/install_meteor.sh && \
# OLD: sed -i "s|RELEASE=.*|RELEASE=${METEOR_RELEASE}\"\"|g" ./install_meteor.sh && \
# Install Meteor forcing its progress
#sed -i 's/VERBOSITY="--silent"/VERBOSITY="--progress-bar"/' ./install_meteor.sh && \
echo "Starting meteor ${METEOR_RELEASE} installation... \n" && \
gosu wekan:wekan curl https://install.meteor.com/ | /bin/sh && \
mv /root/.meteor /home/wekan/ && \
chown wekan --recursive /home/wekan/.meteor && \
\
# Check if opting for a release candidate instead of major release
#if [ "$USE_EDGE" = false ]; then \
#gosu wekan:wekan sh /home/wekan/install_meteor.sh; \
# gosu wekan:wekan curl https://install.meteor.com/ | sh; \
#else \
# gosu wekan:wekan git clone --recursive --depth 1 -b release/METEOR@${METEOR_EDGE} https://github.com/meteor/meteor.git /home/wekan/.meteor; \
#fi; \
#\
# Get additional packages
#mkdir -p /home/wekan/app/packages && \
#chown wekan:wekan --recursive /home/wekan && \
# REPOS BELOW ARE INCLUDED TO WEKAN REPO
#cd /home/wekan/app/packages && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git && \
#gosu wekan:wekan git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git && \
#gosu wekan:wekan mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan-accounts-oidc && \
#gosu wekan:wekan mv meteor-accounts-oidc/packages/switch_oidc wekan-oidc && \
#gosu wekan:wekan rm -rf meteor-accounts-oidc && \
sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js && \
cd /home/wekan/.meteor && \
gosu wekan:wekan /home/wekan/.meteor/meteor -- help; \
\
# extract the OpenAPI specification
#npm install -g api2html@0.3.3 && \
#mkdir -p /home/wekan/python && \
#chown wekan --recursive /home/wekan/python && \
#cd /home/wekan/python && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/Kronuz/esprima-python && \
#cd /home/wekan/python/esprima-python && \
#python3 setup.py install --record files.txt && \
#cd /home/wekan/app && \
#mkdir -p /home/wekan/app/public/api && \
#chown wekan --recursive /home/wekan/app && \
#gosu wekan:wekan python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml && \
#gosu wekan:wekan /opt/nodejs/bin/api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml; \
# Build app
cd /home/wekan/app && \
mkdir -p /home/wekan/.npm && \
chown wekan --recursive /home/wekan/.npm /home/wekan/.config /home/wekan/.meteor && \
#gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js && \
gosu wekan:wekan npm install && \
gosu wekan:wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build && \
cp /home/wekan/app/fix-download-unicode/cfs_access-point.txt /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \
#rm /home/wekan/app_build/bundle/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs && \
chown wekan /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \
#Removed binary version of bcrypt because of security vulnerability that is not fixed yet.
#https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac
#https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c
#cd /home/wekan/app_build/bundle/programs/server/npm/node_modules/meteor/npm-bcrypt && \
#gosu wekan:wekan rm -rf node_modules/bcrypt && \
#gosu wekan:wekan npm install bcrypt && \
#
# Delete phantomjs
#cd /home/wekan/app_build/bundle && \
#find . -name "*phantomjs*" | xargs rm -rf && \
#
cd /home/wekan/app_build/bundle/programs/server/ && \
gosu wekan:wekan npm install && \
#gosu wekan:wekan npm install bcrypt && \
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy && \
mv /home/wekan/app_build/bundle /build && \
\
# Put back the original tar
mv $(which tar)~ $(which tar) && \
\
# Cleanup
apt-get remove --purge -y ${BUILD_DEPS} && \
apt-get autoremove -y && \
npm uninstall -g api2html &&\
rm -R /var/lib/apt/lists/* && \
rm -R /home/wekan/.meteor && \
rm -R /home/wekan/app && \
rm -R /home/wekan/app_build
#cat /home/wekan/python/esprima-python/files.txt | xargs rm -R && \
#rm -R /home/wekan/python
#rm /home/wekan/install_meteor.sh
WORKDIR /bundle
ENV PORT=8080
EXPOSE $PORT
USER wekan
CMD ["node", "/build/main.js"]
USER node
CMD ["node", "/bundle/main.js"]
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v4.79.0"
appVersion: "v4.80.0"
files:
userUploads:
- README.md
+1 -1
View File
@@ -1,6 +1,6 @@
template(name="resultCard")
.result-card-wrapper
a.minicard-wrapper.card-title(href=card.absoluteUrl)
a.minicard-wrapper.card-title(href=absoluteUrl)
+minicard(this)
//= card.title
ul.result-card-context-list
Executable → Regular
View File
File mode changed.
+9 -35
View File
@@ -14,52 +14,26 @@ template(name="globalSearch")
if currentUser
.wrapper
form.global-search-instructions.js-search-query-form
input.global-search-query-input(type="text" name="searchQuery" placeholder="{{_ 'search-example'}}" autofocus dir="auto")
input.global-search-query-input(type="text" name="searchQuery" placeholder="{{_ 'search-example'}}" value="{{ query.get }}" autofocus dir="auto")
if searching.get
+spinner
else if hasResults.get
.global-search-dueat-list-wrapper
h1
if $eq resultsCount.get 0
| {{_ 'no-cards-found' }}
else if $eq resultsCount.get 1
| {{_ 'one-card-found' }}
else if $eq resultsCount.get totalHits.get
| {{_ 'n-cards-found' resultsCount.get }}
else
| {{_ 'n-n-of-n-cards-found' 1 resultsCount.get totalHits.get }}
if queryErrors.get
.global-search-results-list-wrapper
if hasQueryErrors.get
div
each msg in errorMessages
span.global-search-error-messages
| {{_ msg.tag msg.value }}
each card in results
a.minicard-wrapper(href=card.absoluteUrl)
else
h1
= resultsHeading.get
a.fa.fa-link(title="{{_ 'link-to-search' }}" href="{{ getSearchHref }}")
each card in results
+resultCard(card)
else
.global-search-instructions
h1 Search Operators
+viewer
= 'Searches can include operators to refine the search. Operators are specified by writing the operator'
= 'name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search'
= 'to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters'
= 'it must be enclosed in quotation marks (e.g. `list:"To Review"`).\n'
= 'Available operators are:\n'
= '* `board:title` - cards in boards matching the specified title\n'
= '* `list:title` - cards in lists matching the specified title\n'
= '* `swimlane:title` - cards in swimlanes matching the specified title\n'
= '* `label:color` - cards that have a label matching the given color\n'
= '* `label:name` - cards that have a label matching the given name\n'
= '* `user:username` - cards where the specified user is a member or assignee\n'
= '* `@username` - shorthand for `user:username`\n'
= '* `#label` - shorthand for `label:color-or-name`\n'
= '## Notes\n'
= '* Multiple operators may be specified.\n'
= '* Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n'
= ' `list:Available list:Blocked` would return cards contained in any list named *Blocked* or *Available*.\n'
= '* Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned.\n'
= '`list:Available label:red` returns only cards in the list *Available* with a *red* label.\n'
= '* Text searches are case insensitive.\n'
= searchInstructions
template(name="globalSearchViewChangePopup")
if currentUser
+267 -131
View File
@@ -36,164 +36,300 @@ BlazeComponent.extendComponent({
BlazeComponent.extendComponent({
onCreated() {
this.isPageReady = new ReactiveVar(true);
this.searching = new ReactiveVar(false);
this.hasResults = new ReactiveVar(false);
this.hasQueryErrors = new ReactiveVar(false);
this.query = new ReactiveVar('');
this.resultsHeading = new ReactiveVar('');
this.searchLink = new ReactiveVar(null);
this.queryParams = null;
this.resultsCount = new ReactiveVar(0);
this.totalHits = new ReactiveVar(0);
this.queryErrors = new ReactiveVar(null);
this.parsingErrors = [];
this.resultsCount = 0;
this.totalHits = 0;
this.queryErrors = null;
Meteor.subscribe('setting');
if (Session.get('globalQuery')) {
this.searchAllBoards(Session.get('globalQuery'));
}
},
resetSearch() {
this.searching.set(false);
this.hasResults.set(false);
this.hasQueryErrors.set(false);
this.resultsHeading.set('');
this.parsingErrors = [];
this.resultsCount = 0;
this.totalHits = 0;
this.queryErrors = null;
},
results() {
// eslint-disable-next-line no-console
// console.log('getting results');
if (this.queryParams) {
const results = Cards.globalSearch(this.queryParams);
this.queryErrors = results.errors;
// eslint-disable-next-line no-console
// console.log('user:', Meteor.user());
// eslint-disable-next-line no-console
// console.log('user:', Meteor.user().sessionData);
// console.log('errors:', results.errors);
this.totalHits.set(Meteor.user().sessionData.totalHits);
this.resultsCount.set(results.cards.count());
this.queryErrors.set(results.errors);
return results.cards;
// console.log('errors:', this.queryErrors);
if (this.errorMessages().length) {
this.hasQueryErrors.set(true);
return null;
}
if (results.cards) {
const sessionData = SessionData.findOne({ userId: Meteor.userId() });
this.totalHits = sessionData.totalHits;
this.resultsCount = results.cards.count();
this.resultsHeading.set(this.getResultsHeading());
return results.cards;
}
}
this.resultsCount.set(0);
this.resultsCount = 0;
return [];
},
errorMessages() {
const errors = this.queryErrors.get();
const messages = [];
errors.notFound.boards.forEach(board => {
messages.push({ tag: 'board-title-not-found', value: board });
});
errors.notFound.swimlanes.forEach(swim => {
messages.push({ tag: 'swimlane-title-not-found', value: swim });
});
errors.notFound.lists.forEach(list => {
messages.push({ tag: 'list-title-not-found', value: list });
});
errors.notFound.users.forEach(user => {
messages.push({ tag: 'user-username-not-found', value: user });
});
if (this.queryErrors) {
this.queryErrors.notFound.boards.forEach(board => {
messages.push({ tag: 'board-title-not-found', value: board });
});
this.queryErrors.notFound.swimlanes.forEach(swim => {
messages.push({ tag: 'swimlane-title-not-found', value: swim });
});
this.queryErrors.notFound.lists.forEach(list => {
messages.push({ tag: 'list-title-not-found', value: list });
});
this.queryErrors.notFound.labels.forEach(label => {
messages.push({ tag: 'label-not-found', value: label });
});
this.queryErrors.notFound.users.forEach(user => {
messages.push({ tag: 'user-username-not-found', value: user });
});
this.queryErrors.notFound.members.forEach(user => {
messages.push({ tag: 'user-username-not-found', value: user });
});
this.queryErrors.notFound.assignees.forEach(user => {
messages.push({ tag: 'user-username-not-found', value: user });
});
}
if (this.parsingErrors.length) {
this.parsingErrors.forEach(err => {
messages.push(err);
});
}
return messages;
},
searchAllBoards(query) {
this.query.set(query);
this.resetSearch();
if (!query) {
return;
}
this.searching.set(true);
// eslint-disable-next-line no-console
// console.log('query:', query);
const reOperator1 = /^((?<operator>\w+):|(?<abbrev>[#@]))(?<value>\w+)(\s+|$)/;
const reOperator2 = /^((?<operator>\w+):|(?<abbrev>[#@]))(?<quote>["']*)(?<value>.*?)\k<quote>(\s+|$)/;
const reText = /^(?<text>\S+)(\s+|$)/;
const reQuotedText = /^(?<quote>["'])(?<text>\w+)\k<quote>(\s+|$)/;
const operatorMap = {};
operatorMap[TAPi18n.__('operator-board')] = 'boards';
operatorMap[TAPi18n.__('operator-board-abbrev')] = 'boards';
operatorMap[TAPi18n.__('operator-swimlane')] = 'swimlanes';
operatorMap[TAPi18n.__('operator-swimlane-abbrev')] = 'swimlanes';
operatorMap[TAPi18n.__('operator-list')] = 'lists';
operatorMap[TAPi18n.__('operator-list-abbrev')] = 'lists';
operatorMap[TAPi18n.__('operator-label')] = 'labels';
operatorMap[TAPi18n.__('operator-label-abbrev')] = 'labels';
operatorMap[TAPi18n.__('operator-user')] = 'users';
operatorMap[TAPi18n.__('operator-user-abbrev')] = 'users';
operatorMap[TAPi18n.__('operator-member')] = 'members';
operatorMap[TAPi18n.__('operator-member-abbrev')] = 'members';
operatorMap[TAPi18n.__('operator-assignee')] = 'assignees';
operatorMap[TAPi18n.__('operator-assignee-abbrev')] = 'assignees';
operatorMap[TAPi18n.__('operator-is')] = 'is';
// eslint-disable-next-line no-console
// console.log('operatorMap:', operatorMap);
const params = {
boards: [],
swimlanes: [],
lists: [],
users: [],
members: [],
assignees: [],
labels: [],
is: [],
};
let text = '';
while (query) {
m = query.match(reOperator1);
if (!m) {
m = query.match(reOperator2);
if (m) {
query = query.replace(reOperator2, '');
}
} else {
query = query.replace(reOperator1, '');
}
if (m) {
let op;
if (m.groups.operator) {
op = m.groups.operator.toLowerCase();
} else {
op = m.groups.abbrev;
}
if (op in operatorMap) {
params[operatorMap[op]].push(m.groups.value);
} else {
this.parsingErrors.push({
tag: 'operator-unknown-error',
value: op,
});
}
continue;
}
m = query.match(reQuotedText);
if (!m) {
m = query.match(reText);
if (m) {
query = query.replace(reText, '');
}
} else {
query = query.replace(reQuotedText, '');
}
if (m) {
text += (text ? ' ' : '') + m.groups.text;
}
}
// eslint-disable-next-line no-console
// console.log('text:', text);
params.text = text;
// eslint-disable-next-line no-console
// console.log('params:', params);
this.queryParams = params;
this.autorun(() => {
const handle = subManager.subscribe('globalSearch', params);
Tracker.nonreactive(() => {
Tracker.autorun(() => {
// eslint-disable-next-line no-console
// console.log('ready:', handle.ready());
if (handle.ready()) {
this.searching.set(false);
this.hasResults.set(true);
}
});
});
});
},
getResultsHeading() {
if (this.resultsCount === 0) {
return TAPi18n.__('no-cards-found');
} else if (this.resultsCount === 1) {
return TAPi18n.__('one-card-found');
} else if (this.resultsCount === this.totalHits) {
return TAPi18n.__('n-cards-found', this.resultsCount);
}
return TAPi18n.__('n-n-of-n-cards-found', {
start: 1,
end: this.resultsCount,
total: this.totalHits,
});
},
getSearchHref() {
const baseUrl = window.location.href.replace(/([?#].*$|\s*$)/, '');
return `${baseUrl}?q=${encodeURIComponent(this.query.get())}`;
},
searchInstructions() {
tags = {
operator_board: TAPi18n.__('operator-board'),
operator_list: TAPi18n.__('operator-list'),
operator_swimlane: TAPi18n.__('operator-swimlane'),
operator_label: TAPi18n.__('operator-label'),
operator_label_abbrev: TAPi18n.__('operator-label-abbrev'),
operator_user: TAPi18n.__('operator-user'),
operator_user_abbrev: TAPi18n.__('operator-user-abbrev'),
operator_member: TAPi18n.__('operator-member'),
operator_member_abbrev: TAPi18n.__('operator-member-abbrev'),
operator_assignee: TAPi18n.__('operator-assignee'),
operator_assignee_abbrev: TAPi18n.__('operator-assignee-abbrev'),
};
text = `# ${TAPi18n.__('globalSearch-instructions-heading')}`;
text += `\n${TAPi18n.__('globalSearch-instructions-description', tags)}`;
text += `\n${TAPi18n.__('globalSearch-instructions-operators', tags)}`;
text += `\n* ${TAPi18n.__(
'globalSearch-instructions-operator-board',
tags,
)}`;
text += `\n* ${TAPi18n.__(
'globalSearch-instructions-operator-list',
tags,
)}`;
text += `\n* ${TAPi18n.__(
'globalSearch-instructions-operator-swimlane',
tags,
)}`;
text += `\n* ${TAPi18n.__(
'globalSearch-instructions-operator-label',
tags,
)}`;
text += `\n* ${TAPi18n.__(
'globalSearch-instructions-operator-hash',
tags,
)}`;
text += `\n* ${TAPi18n.__(
'globalSearch-instructions-operator-user',
tags,
)}`;
text += `\n* ${TAPi18n.__('globalSearch-instructions-operator-at', tags)}`;
text += `\n* ${TAPi18n.__(
'globalSearch-instructions-operator-member',
tags,
)}`;
text += `\n* ${TAPi18n.__(
'globalSearch-instructions-operator-assignee',
tags,
)}`;
text += `\n## ${TAPi18n.__('heading-notes')}`;
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-1', tags)}`;
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-2', tags)}`;
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-3', tags)}`;
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-4', tags)}`;
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-5', tags)}`;
return text;
},
events() {
return [
{
'submit .js-search-query-form'(evt) {
evt.preventDefault();
this.query.set(evt.target.searchQuery.value);
this.queryErrors.set(null);
if (!this.query.get()) {
this.searching.set(false);
this.hasResults.set(false);
return;
}
this.searching.set(true);
this.hasResults.set(false);
let query = this.query.get();
// eslint-disable-next-line no-console
// console.log('query:', query);
const reOperator1 = /^((?<operator>\w+):|(?<abbrev>[#@]))(?<value>\w+)(\s+|$)/;
const reOperator2 = /^((?<operator>\w+):|(?<abbrev>[#@]))(?<quote>["']*)(?<value>.*?)\k<quote>(\s+|$)/;
const reText = /^(?<text>\S+)(\s+|$)/;
const reQuotedText = /^(?<quote>["'])(?<text>\w+)\k<quote>(\s+|$)/;
const operatorMap = {};
operatorMap[TAPi18n.__('operator-board')] = 'boards';
operatorMap[TAPi18n.__('operator-board-abbrev')] = 'boards';
operatorMap[TAPi18n.__('operator-swimlane')] = 'swimlanes';
operatorMap[TAPi18n.__('operator-swimlane-abbrev')] = 'swimlanes';
operatorMap[TAPi18n.__('operator-list')] = 'lists';
operatorMap[TAPi18n.__('operator-list-abbrev')] = 'lists';
operatorMap[TAPi18n.__('operator-label')] = 'labels';
operatorMap[TAPi18n.__('operator-label-abbrev')] = 'labels';
operatorMap[TAPi18n.__('operator-user')] = 'users';
operatorMap[TAPi18n.__('operator-user-abbrev')] = 'users';
operatorMap[TAPi18n.__('operator-is')] = 'is';
// eslint-disable-next-line no-console
// console.log('operatorMap:', operatorMap);
const params = {
boards: [],
swimlanes: [],
lists: [],
users: [],
labels: [],
is: [],
};
let text = '';
while (query) {
m = query.match(reOperator1);
if (!m) {
m = query.match(reOperator2);
if (m) {
query = query.replace(reOperator2, '');
}
} else {
query = query.replace(reOperator1, '');
}
if (m) {
let op;
if (m.groups.operator) {
op = m.groups.operator.toLowerCase();
} else {
op = m.groups.abbrev;
}
if (op in operatorMap) {
params[operatorMap[op]].push(m.groups.value);
}
continue;
}
m = query.match(reQuotedText);
if (!m) {
m = query.match(reText);
if (m) {
query = query.replace(reText, '');
}
} else {
query = query.replace(reQuotedText, '');
}
if (m) {
text += (text ? ' ' : '') + m.groups.text;
}
}
// eslint-disable-next-line no-console
// console.log('text:', text);
params.text = text;
// eslint-disable-next-line no-console
// console.log('params:', params);
this.queryParams = params;
this.autorun(() => {
const handle = subManager.subscribe('globalSearch', params);
Tracker.nonreactive(() => {
Tracker.autorun(() => {
// eslint-disable-next-line no-console
// console.log('ready:', handle.ready());
if (handle.ready()) {
this.searching.set(false);
this.hasResults.set(true);
}
});
});
});
this.searchAllBoards(evt.target.searchQuery.value);
},
},
];
+3 -3
View File
@@ -51,7 +51,7 @@
margin-top: 0
margin-bottom: 10px
.global-search-dueat-list-wrapper
.global-search-results-list-wrapper
max-width: 500px
margin-right: auto
margin-left: auto
@@ -91,7 +91,7 @@
font-style: italic
code
color: white
background-color: grey
color: black
background-color: lightgrey
padding: 0.1rem !important
font-size: 0.7rem !important
+1 -1
View File
@@ -43,7 +43,7 @@
border-radius: 5px
padding: 1.5rem
padding-top: 0.75rem
display: inline-block
display: table-cell
min-width: 250px
max-width: 350px
+7
View File
@@ -158,7 +158,14 @@ FlowRouter.route('/global-search', {
Utils.manageCustomUI();
Utils.manageMatomo();
DocHead.setTitle(TAPi18n.__('globalSearch-title'));
if (FlowRouter.getQueryParam('q')) {
Session.set(
'globalQuery',
decodeURIComponent(FlowRouter.getQueryParam('q')),
);
}
BlazeLayout.render('defaultLayout', {
headerBar: 'globalSearchHeaderBar',
content: 'globalSearch',
-8
View File
@@ -131,14 +131,6 @@ services:
#build:
# context: .
# dockerfile: Dockerfile
# args:
# - NODE_VERSION=${NODE_VERSION}
# - METEOR_RELEASE=${METEOR_RELEASE}
# - NPM_VERSION=${NPM_VERSION}
# - ARCHITECTURE=${ARCHITECTURE}
# - SRC_PATH=${SRC_PATH}
# - METEOR_EDGE=${METEOR_EDGE}
# - USE_EDGE=${USE_EDGE}
#-------------------------------------------------------------------------------------
ports:
# Docker outsideport:insideport. Do not add anything extra here.
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+27 -2
View File
@@ -868,12 +868,13 @@
"board-title-not-found": "Board „%s“ nicht gefunden.",
"swimlane-title-not-found": "Swimlane „%s“ nicht gefunden.",
"list-title-not-found": "Liste „%s“ nicht gefunden.",
"label-not-found": "Label „%s“ nicht gefunden.",
"user-username-not-found": "Nutzername „%s“ nicht gefunden.",
"globalSearch-title": "Alle Boards durchsuchen",
"no-cards-found": "Keine Karten gefunden",
"one-card-found": "Eine Karte gefunden",
"n-cards-found": "%s Karten gefunden",
"n-n-of-n-cards-found": "%s–%s von %s Karten gefunden",
"n-n-of-n-cards-found": "__start__–__end__ von __total__ Karten gefunden",
"operator-board": "Board",
"operator-board-abbrev": "b",
"operator-swimlane": "Swimlane",
@@ -884,5 +885,29 @@
"operator-label-abbrev": "#",
"operator-user": "Nutzer",
"operator-user-abbrev": "@",
"operator-is": "wird"
"operator-member": "Mitglied",
"operator-member-abbrev": "m",
"operator-assignee": "Beauftragter",
"operator-assignee-abbrev": "a",
"operator-is": "wird",
"operator-unknown-error": "„%s“ ist kein Operator",
"heading-notes": "Bemerkungen",
"globalSearch-instructions-heading": "Hinweise zur Suche",
"globalSearch-instructions-description": "Suchanfragen können Operatoren enthalten, um die Suche zu verfeinern. Operatoren bestehen aus ihrem Namen und ihrem Wert, getrennt durch einen Doppelpunkt. Beispielsweise würde die Operatorangabe `list:Blockiert` die Suche beschränken auf Karten in einer Liste namens *Blockiert*. Wenn der Wet Leerschritte oder andere Spezialzeichen enthält, muss er in Anführungszeichen gesetzt sein (z.B. `__operator_list__:\"Im Review\"`).",
"globalSearch-instructions-operators": "Mögliche Operatoren:",
"globalSearch-instructions-operator-board": "`__operator_board__:Titel` – Karten in Boards mit dem angegebenen Titel",
"globalSearch-instructions-operator-list": "`__operator_list__:Titel` – Karten in Listen, auf die der angegebene Titel passt",
"globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:Titel` – Karten in Swimlanes, auf die der angegebene Titel passt",
"globalSearch-instructions-operator-label": "`__operator_label__:Farbe` `__operator_label__:Name` – Karten, die ein Label haben, auf das die angegebene Farbe oder Name passt",
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__Label` – Kurzform von `__operator_label__:Label`",
"globalSearch-instructions-operator-user": "`__operator_user__:Nutzername` – Karten, für die der angegebene Nutzer ein *Mitglied* oder ein *Beauftragter* ist",
"globalSearch-instructions-operator-at": "`__operator_user_abbrev__Nutzername` – Kurzform von `user:Nutzername`",
"globalSearch-instructions-operator-member": "`__operator_member__:Nutzername` – Karten, für die der angegebene Nutzer ein *Mitglied* ist",
"globalSearch-instructions-operator-assignee": "`__operator_member__:Nutzername` – Karten, für die der angegebene Nutzer ein *Beauftragter* ist",
"globalSearch-instructions-notes-1": "Mehrere Operatoren können angegeben werden.",
"globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.",
"globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned.\n`__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.",
"globalSearch-instructions-notes-4": "Text searches are case insensitive.",
"globalSearch-instructions-notes-5": "Currently archived cards are not searched.",
"link-to-search": "Link to this search"
}
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+27 -2
View File
@@ -868,12 +868,13 @@
"board-title-not-found": "Board '%s' not found.",
"swimlane-title-not-found": "Swimlane '%s' not found.",
"list-title-not-found": "List '%s' not found.",
"label-not-found": "Label '%s' not found.",
"user-username-not-found": "Username '%s' not found.",
"globalSearch-title": "Search All Boards",
"no-cards-found": "No Cards Found",
"one-card-found": "One Card Found",
"n-cards-found": "%s Cards Found",
"n-n-of-n-cards-found": "%s-%s of %s Cards Found",
"n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found",
"operator-board": "board",
"operator-board-abbrev": "b",
"operator-swimlane": "swimlane",
@@ -884,5 +885,29 @@
"operator-label-abbrev": "#",
"operator-user": "user",
"operator-user-abbrev": "@",
"operator-is": "is"
"operator-member": "member",
"operator-member-abbrev": "m",
"operator-assignee": "assignee",
"operator-assignee-abbrev": "a",
"operator-is": "is",
"operator-unknown-error": "%s is not an operator",
"heading-notes": "Notes",
"globalSearch-instructions-heading": "Search Instructions",
"globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).",
"globalSearch-instructions-operators": "Available operators:",
"globalSearch-instructions-operator-board": "`__operator_board__:title` - cards in boards matching the specified title",
"globalSearch-instructions-operator-list": "`__operator_list__:title` - cards in lists matching the specified title",
"globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:title` - cards in swimlanes matching the specified title",
"globalSearch-instructions-operator-label": "`__operator_label__:color` `__operator_label__:name` - cards that have a label matching the given color or name",
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__label` - shorthand for `__operator_label__:label`",
"globalSearch-instructions-operator-user": "`__operator_user__:username` - cards where the specified user is a *member* or *assignee*",
"globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:username`",
"globalSearch-instructions-operator-member": "`__operator_member__:username` - cards where the specified user is a *member*",
"globalSearch-instructions-operator-assignee": "`__operator_assignee__:username` - cards where the specified user is an *assignee*",
"globalSearch-instructions-notes-1": "Multiple operators may be specified.",
"globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.",
"globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned.\n`__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.",
"globalSearch-instructions-notes-4": "Text searches are case insensitive.",
"globalSearch-instructions-notes-5": "Currently archived cards are not searched.",
"link-to-search": "Link to this search"
}
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+27 -2
View File
@@ -868,12 +868,13 @@
"board-title-not-found": "הלוח ‚%s’ לא נמצא.",
"swimlane-title-not-found": "המסלול ‚%s’ לא נמצא.",
"list-title-not-found": "הרשימה ‚%s’ לא נמצאה.",
"label-not-found": "Label '%s' not found.",
"user-username-not-found": "שם המשתמש ‚%s’ לא נמצא.",
"globalSearch-title": "חיפוש בכל הלוחות",
"no-cards-found": "לא נמצאו כרטיסים",
"one-card-found": "נמצא כרטיס אחד",
"n-cards-found": "נמצאו %s כרטיסים",
"n-n-of-n-cards-found": "נמצאו %s-%s מתוך %s כרטיסים",
"n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found",
"operator-board": "לוח",
"operator-board-abbrev": "ל",
"operator-swimlane": "מסלול",
@@ -884,5 +885,29 @@
"operator-label-abbrev": "#",
"operator-user": "משתמש",
"operator-user-abbrev": "@",
"operator-is": "הוא"
"operator-member": "חבר",
"operator-member-abbrev": "m",
"operator-assignee": "assignee",
"operator-assignee-abbrev": "a",
"operator-is": "הוא",
"operator-unknown-error": "%s is not an operator",
"heading-notes": "Notes",
"globalSearch-instructions-heading": "Search Instructions",
"globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).",
"globalSearch-instructions-operators": "Available operators:",
"globalSearch-instructions-operator-board": "`__operator_board__:title` - cards in boards matching the specified title",
"globalSearch-instructions-operator-list": "`__operator_list__:title` - cards in lists matching the specified title",
"globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:title` - cards in swimlanes matching the specified title",
"globalSearch-instructions-operator-label": "`__operator_label__:color` `__operator_label__:name` - cards that have a label matching the given color or name",
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__label` - shorthand for `__operator_label__:label`",
"globalSearch-instructions-operator-user": "`__operator_user__:username` - cards where the specified user is a *member* or *assignee*",
"globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:username`",
"globalSearch-instructions-operator-member": "`__operator_member__:username` - cards where the specified user is a *member*",
"globalSearch-instructions-operator-assignee": "`__operator_assignee__:username` - cards where the specified user is an *assignee*",
"globalSearch-instructions-notes-1": "Multiple operators may be specified.",
"globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.",
"globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned.\n`__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.",
"globalSearch-instructions-notes-4": "Text searches are case insensitive.",
"globalSearch-instructions-notes-5": "Currently archived cards are not searched.",
"link-to-search": "Link to this search"
}
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+27 -2
View File
@@ -868,12 +868,13 @@
"board-title-not-found": "Доска '%s' не найдена.",
"swimlane-title-not-found": "Дорожка '%s' не найдена.",
"list-title-not-found": "Список '%s' не найден.",
"label-not-found": "Label '%s' not found.",
"user-username-not-found": "Имя пользователя '%s' не найдено.",
"globalSearch-title": "Искать на всех досках",
"no-cards-found": "Ни одной карточки не найдено",
"one-card-found": "Найдена одна карточка",
"n-cards-found": "Карточек найдено %s",
"n-n-of-n-cards-found": "Карточки %s - %s из %s найденных",
"n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found",
"operator-board": "доска",
"operator-board-abbrev": "д",
"operator-swimlane": "дорожка",
@@ -884,5 +885,29 @@
"operator-label-abbrev": "#",
"operator-user": "пользователь",
"operator-user-abbrev": "@",
"operator-is": " "
"operator-member": "участника",
"operator-member-abbrev": "m",
"operator-assignee": "assignee",
"operator-assignee-abbrev": "a",
"operator-is": " ",
"operator-unknown-error": "%s is not an operator",
"heading-notes": "Notes",
"globalSearch-instructions-heading": "Search Instructions",
"globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).",
"globalSearch-instructions-operators": "Available operators:",
"globalSearch-instructions-operator-board": "`__operator_board__:title` - cards in boards matching the specified title",
"globalSearch-instructions-operator-list": "`__operator_list__:title` - cards in lists matching the specified title",
"globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:title` - cards in swimlanes matching the specified title",
"globalSearch-instructions-operator-label": "`__operator_label__:color` `__operator_label__:name` - cards that have a label matching the given color or name",
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__label` - shorthand for `__operator_label__:label`",
"globalSearch-instructions-operator-user": "`__operator_user__:username` - cards where the specified user is a *member* or *assignee*",
"globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:username`",
"globalSearch-instructions-operator-member": "`__operator_member__:username` - cards where the specified user is a *member*",
"globalSearch-instructions-operator-assignee": "`__operator_assignee__:username` - cards where the specified user is an *assignee*",
"globalSearch-instructions-notes-1": "Multiple operators may be specified.",
"globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.",
"globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned.\n`__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.",
"globalSearch-instructions-notes-4": "Text searches are case insensitive.",
"globalSearch-instructions-notes-5": "Currently archived cards are not searched.",
"link-to-search": "Link to this search"
}
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+912 -887
View File
File diff suppressed because it is too large. Load diff
+71 -24
View File
@@ -693,7 +693,7 @@ Cards.helpers({
const definition = definitions.find(definition => {
return definition._id === customField._id;
});
if (!definition) {
if (definition === undefined) {
return {};
}
//search for "True Value" which is for DropDowns other then the Value (which is the id)
@@ -1866,16 +1866,29 @@ Cards.globalSearch = queryParams => {
// eslint-disable-next-line no-console
// console.log('userId:', userId);
const errors = {
notFound: {
boards: [],
swimlanes: [],
lists: [],
labels: [],
users: [],
is: [],
},
};
const errors = new (class {
constructor() {
this.notFound = {
boards: [],
swimlanes: [],
lists: [],
labels: [],
users: [],
members: [],
assignees: [],
is: [],
};
}
hasErrors() {
for (const prop in this.notFound) {
if (this.notFound[prop].length) {
return true;
}
}
return false;
}
})();
const selector = {
archived: false,
@@ -1939,25 +1952,63 @@ Cards.globalSearch = queryParams => {
selector.listId.$in = queryLists;
}
const queryMembers = [];
const queryAssignees = [];
if (queryParams.users.length) {
const queryUsers = [];
queryParams.users.forEach(query => {
const users = Users.find({
username: query,
});
if (users.count()) {
users.forEach(user => {
queryUsers.push(user._id);
queryMembers.push(user._id);
queryAssignees.push(user._id);
});
} else {
errors.notFound.users.push(query);
}
});
}
if (queryParams.members.length) {
queryParams.members.forEach(query => {
const users = Users.find({
username: query,
});
if (users.count()) {
users.forEach(user => {
queryMembers.push(user._id);
});
} else {
errors.notFound.members.push(query);
}
});
}
if (queryParams.assignees.length) {
queryParams.assignees.forEach(query => {
const users = Users.find({
username: query,
});
if (users.count()) {
users.forEach(user => {
queryAssignees.push(user._id);
});
} else {
errors.notFound.assignees.push(query);
}
});
}
if (queryMembers.length && queryAssignees.length) {
selector.$or = [
{ members: { $in: queryUsers } },
{ assignees: { $in: queryUsers } },
{ members: { $in: queryMembers } },
{ assignees: { $in: queryAssignees } },
];
} else if (queryMembers.length) {
selector.members = { $in: queryMembers };
} else if (queryAssignees.length) {
selector.assignees = { $in: queryAssignees };
}
if (queryParams.labels.length) {
@@ -2003,7 +2054,7 @@ Cards.globalSearch = queryParams => {
});
});
} else {
errors.notFound.labels.push({ tag: 'label', value: label });
errors.notFound.labels.push(label);
}
}
@@ -2011,6 +2062,10 @@ Cards.globalSearch = queryParams => {
});
}
if (errors.hasErrors()) {
return { cards: null, errors };
}
if (queryParams.text) {
const regex = new RegExp(queryParams.text, 'i');
@@ -2045,14 +2100,6 @@ Cards.globalSearch = queryParams => {
// eslint-disable-next-line no-console
// console.log('count:', cards.count());
if (Meteor.isServer) {
Users.update(userId, {
$set: {
'sessionData.totalHits': cards.count(),
'sessionData.lastHit': cards.count() > 50 ? 50 : cards.count(),
},
});
}
return { cards, errors };
};
+8
View File
@@ -377,6 +377,14 @@ Users.initEasySearch(searchInFields, {
returnFields: [...searchInFields, 'profile.avatarUrl'],
});
Users.safeFields = {
_id: 1,
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1,
'profile.initials': 1,
};
if (Meteor.isClient) {
Users.helpers({
isBoardMember() {
+73
View File
@@ -0,0 +1,73 @@
SessionData = new Mongo.Collection('sessiondata');
/**
* A UserSessionData in Wekan. Organization in Trello.
*/
SessionData.attachSchema(
new SimpleSchema({
_id: {
/**
* the organization id
*/
type: Number,
optional: true,
// eslint-disable-next-line consistent-return
autoValue() {
if (this.isInsert && !this.isSet) {
return incrementCounter('counters', 'orgId', 1);
}
},
},
userId: {
/**
* userId of the user
*/
type: String,
optional: false,
},
totalHits: {
/**
* total number of hits in the last report query
*/
type: Number,
optional: true,
},
lastHit: {
/**
* the last hit returned from a report query
*/
type: Number,
optional: true,
},
createdAt: {
/**
* creation date of the team
*/
type: Date,
// eslint-disable-next-line consistent-return
autoValue() {
if (this.isInsert) {
return new Date();
} else if (this.isUpsert) {
return { $setOnInsert: new Date() };
} else {
this.unset();
}
},
},
modifiedAt: {
type: Date,
denyUpdate: false,
// eslint-disable-next-line consistent-return
autoValue() {
if (this.isInsert || this.isUpsert || this.isUpdate) {
return new Date();
} else {
this.unset();
}
},
},
}),
);
export default SessionData;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v4.79.0",
"version": "v4.80.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v4.79.0",
"version": "v4.80.0",
"description": "Open-Source kanban",
"private": true,
"scripts": {
+2 -2
View File
@@ -1524,7 +1524,7 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
<ul class="toc-list-h1">
<li>
<a href="#wekan-rest-api" class="toc-h1 toc-link" data-title="Wekan REST API v4.79">Wekan REST API v4.79</a>
<a href="#wekan-rest-api" class="toc-h1 toc-link" data-title="Wekan REST API v4.80">Wekan REST API v4.80</a>
</li>
@@ -2037,7 +2037,7 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
<div class="page-wrapper">
<div class="dark-box"></div>
<div class="content">
<h1 id="wekan-rest-api">Wekan REST API v4.79</h1>
<h1 id="wekan-rest-api">Wekan REST API v4.80</h1>
<blockquote>
<p>Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu.</p>
</blockquote>
+1 -1
View File
@@ -1,7 +1,7 @@
swagger: '2.0'
info:
title: Wekan REST API
version: v4.79
version: v4.80
description: |
The REST API allows you to control and extend Wekan with ease.
+2 -2
View File
@@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = (
appTitle = (defaultText = "Wekan"),
# The name of the app as it is displayed to the user.
appVersion = 479,
appVersion = 480,
# Increment this for every release.
appMarketingVersion = (defaultText = "4.79.0~2021-01-17"),
appMarketingVersion = (defaultText = "4.80.0~2021-01-18"),
# Human-readable presentation of the app version.
minUpgradableAppVersion = 0,
+24 -69
View File
@@ -72,18 +72,7 @@ Meteor.publish('myCards', function() {
Boards.find({ _id: { $in: boards } }),
Swimlanes.find({ _id: { $in: swimlanes } }),
Lists.find({ _id: { $in: lists } }),
Users.find(
{ _id: { $in: users } },
{
fields: {
_id: 1,
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1,
'profile.initials': 1,
},
},
),
Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
];
});
@@ -93,18 +82,7 @@ Meteor.publish('dueCards', function(allUsers = false) {
// eslint-disable-next-line no-console
// console.log('all users:', allUsers);
const user = Users.findOne(
{ _id: this.userId },
{
fields: {
_id: 1,
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1,
'profile.initials': 1,
},
},
);
const user = Users.findOne({ _id: this.userId });
const archivedBoards = [];
Boards.find({ archived: true }).forEach(board => {
@@ -115,14 +93,12 @@ Meteor.publish('dueCards', function(allUsers = false) {
let selector = {
archived: false,
};
// for admins and users, allow her to see cards only from boards where
// she is a member
//if (!user.isAdmin) {
selector.$or = [
{ permission: 'public' },
{ members: { $elemMatch: { userId: user._id, isActive: true } } },
];
//}
Boards.find(selector).forEach(board => {
permiitedBoards.push(board._id);
});
@@ -193,18 +169,7 @@ Meteor.publish('dueCards', function(allUsers = false) {
Boards.find({ _id: { $in: boards } }),
Swimlanes.find({ _id: { $in: swimlanes } }),
Lists.find({ _id: { $in: lists } }),
Users.find(
{ _id: { $in: users } },
{
fields: {
_id: 1,
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1,
'profile.initials': 1,
},
},
),
Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
];
});
@@ -216,6 +181,20 @@ Meteor.publish('globalSearch', function(queryParams) {
const cards = Cards.globalSearch(queryParams).cards;
if (!cards) {
return [];
}
SessionData.upsert(
{ userId: this.userId },
{
$set: {
totalHits: cards.count(),
lastHit: cards.count() > 50 ? 50 : cards.count(),
},
},
);
const boards = [];
const swimlanes = [];
const lists = [];
@@ -244,34 +223,21 @@ Meteor.publish('globalSearch', function(queryParams) {
Boards.find({ _id: { $in: boards } }),
Swimlanes.find({ _id: { $in: swimlanes } }),
Lists.find({ _id: { $in: lists } }),
Users.find({ _id: { $in: users } }),
Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
SessionData.find({ userId: this.userId }),
];
});
Meteor.publish('brokenCards', function() {
const user = Users.findOne(
{ _id: this.userId },
{
fields: {
_id: 1,
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1,
'profile.initials': 1,
},
},
);
const user = Users.findOne({ _id: this.userId });
const permiitedBoards = [null];
let selector = {};
// for admins and users, if user is not an admin allow her to see cards only from boards where
// she is a member
//if (!user.isAdmin) {
selector.$or = [
{ permission: 'public' },
{ members: { $elemMatch: { userId: user._id, isActive: true } } },
];
//}
Boards.find(selector).forEach(board => {
permiitedBoards.push(board._id);
});
@@ -328,17 +294,6 @@ Meteor.publish('brokenCards', function() {
Boards.find({ _id: { $in: boards } }),
Swimlanes.find({ _id: { $in: swimlanes } }),
Lists.find({ _id: { $in: lists } }),
Users.find(
{ _id: { $in: users } },
{
fields: {
_id: 1,
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1,
'profile.initials': 1,
},
},
),
Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
];
});
+1 -1
View File
@@ -95,6 +95,6 @@ else
# Drop indexes on database upgrade, when starting MongoDB
#mongo wekan --eval "db.getCollectionNames().forEach(function(col_name) { var coll = db.getCollection(col_name); coll.dropIndexes(); });" $BIND_OPTIONS
mongo wekan --eval 'db.adminCommand({ setFeatureCompatibilityVersion: "4.4" });' $BIND_OPTIONS
mongo wekan --eval 'db.adminCommand({ setFeatureCompatibilityVersion: "4.2" });' $BIND_OPTIONS
fi