Merge branch 'chapril' of ssh://forge.april.org:222/Chapril/mobilizon.chapril.org-mobilizon into chapril

This commit is contained in:
Tykayn 2022-06-02 11:42:19 +02:00 committed by root
commit 6198507117
562 changed files with 85426 additions and 36994 deletions

66
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,66 @@
# Update the VARIANT arg in docker-compose.yml to pick an Elixir version: 1.9, 1.10, 1.10.4
ARG VARIANT="1.12.3"
FROM elixir:${VARIANT}
# This Dockerfile adds a non-root user with sudo access. Update the “remoteUser” property in
# devcontainer.json to use it. More info: https://aka.ms/vscode-remote/containers/non-root-user.
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
# Options for common package install script
ARG INSTALL_ZSH="true"
ARG UPGRADE_PACKAGES="true"
ARG COMMON_SCRIPT_SOURCE="https://raw.githubusercontent.com/microsoft/vscode-dev-containers/v0.209.6/script-library/common-debian.sh"
ARG COMMON_SCRIPT_SHA="d35dd1711454156c9a59cc41ebe04fbff681ca0bd304f10fd5b13285d0de13b2"
# Optional Settings for Phoenix
ARG PHOENIX_VERSION="1.6.2"
# [Optional] Setup nodejs
ARG NODE_SCRIPT_SOURCE="https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/script-library/node-debian.sh"
ARG NODE_SCRIPT_SHA="dev-mode"
ARG NODE_VERSION="none"
ENV NVM_DIR=/usr/local/share/nvm
ENV NVM_SYMLINK_CURRENT=true
ENV PATH=${NVM_DIR}/current/bin:${PATH}
# [Optional, Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
# Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies.
RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends curl ca-certificates 2>&1 \
&& curl -sSL ${COMMON_SCRIPT_SOURCE} -o /tmp/common-setup.sh \
&& ([ "${COMMON_SCRIPT_SHA}" = "dev-mode" ] || (echo "${COMMON_SCRIPT_SHA} */tmp/common-setup.sh" | sha256sum -c -)) \
&& /bin/bash /tmp/common-setup.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \
#
# [Optional] Install Node.js for use with web applications
&& if [ "$NODE_VERSION" != "none" ]; then \
curl -sSL ${NODE_SCRIPT_SOURCE} -o /tmp/node-setup.sh \
&& ([ "${NODE_SCRIPT_SHA}" = "dev-mode" ] || (echo "${NODE_SCRIPT_SHA} */tmp/node-setup.sh" | sha256sum -c -)) \
&& /bin/bash /tmp/node-setup.sh "${NVM_DIR}" "${NODE_VERSION}" "${USERNAME}"; \
fi \
#
# Install dependencies
&& apt-get install -y build-essential \
#
# Clean up
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/* /tmp/common-setup.sh /tmp/node-setup.sh
RUN su ${USERNAME} -c "mix local.hex --force \
&& mix local.rebar --force \
&& mix archive.install --force hex phx_new ${PHOENIX_VERSION}"
RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends cmake webp bash libncurses6 git python3 inotify-tools \
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
# [Optional] Uncomment this line to install additional package.
# RUN mix ...

View File

@ -0,0 +1,44 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/elixir-phoenix-postgres
{
"name": "Elixir, Phoenix, Node.js & PostgresSQL (Community)",
"dockerComposeFile": "docker-compose.yml",
"service": "elixir",
"workspaceFolder": "/workspace",
// Set *default* container specific settings.json values on container create.
"settings": {
"sqltools.connections": [{
"name": "Container database",
"driver": "PostgreSQL",
"previewLimit": 50,
"server": "localhost",
"port": 5432,
"database": "postgres",
"username": "postgres",
"password": "postgres"
}]
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"jakebecker.elixir-ls",
"mtxr.sqltools",
"mtxr.sqltools-driver-pg"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [4000, 4001, 5432],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "mix deps.get",
// "runArgs": ["--userns=keep-id", "--privileged"],
// "containerUser": "vscode",
// "containerEnv": {
// "HOME": "/home/vscode",
// },
// "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,Z",
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}

View File

@ -0,0 +1,46 @@
version: "3.8"
services:
elixir:
build:
context: .
dockerfile: Dockerfile
args:
# Elixir Version: 1.9, 1.10, 1.10.4, ...
VARIANT: "1.13.1"
# Phoenix Version: 1.4.17, 1.5.4, ...
PHOENIX_VERSION: "1.6.6"
# Node Version: 10, 11, ...
NODE_VERSION: "16"
volumes:
- ..:/workspace:z
# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
environment:
MOBILIZON_INSTANCE_NAME: My Mobilizon Instance
MOBILIZON_INSTANCE_HOST: localhost
MOBILIZON_INSTANCE_HOST_PORT: 4000
MOBILIZON_INSTANCE_PORT: 4000
MOBILIZON_INSTANCE_EMAIL: noreply@mobilizon.me
MOBILIZON_INSTANCE_REGISTRATIONS_OPEN: "true"
MOBILIZON_DATABASE_PASSWORD: postgres
MOBILIZON_DATABASE_USERNAME: postgres
MOBILIZON_DATABASE_DBNAME: mobilizon
MOBILIZON_DATABASE_HOST: db
db:
image: postgis/postgis:latest
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app
volumes:
postgres-data: null

View File

@ -19,7 +19,6 @@ MOBILIZON_REPLY_EMAIL=contact@mobilizon.lan
# Email settings # Email settings
MOBILIZON_SMTP_SERVER=localhost MOBILIZON_SMTP_SERVER=localhost
MOBILIZON_SMTP_PORT=25 MOBILIZON_SMTP_PORT=25
MOBILIZON_SMTP_HOSTNAME=localhost
MOBILIZON_SMTP_USERNAME=noreply@mobilizon.lan MOBILIZON_SMTP_USERNAME=noreply@mobilizon.lan
MOBILIZON_SMTP_PASSWORD=password MOBILIZON_SMTP_PASSWORD=password
MOBILIZON_SMTP_SSL=false MOBILIZON_SMTP_SSL=false

View File

@ -1,3 +1,4 @@
[ [
inputs: ["{mix,.formatter}.exs", "{config,lib,test,priv}/**/*.{ex,exs}"] plugins: [Phoenix.LiveView.HTMLFormatter],
inputs: ["{mix,.formatter}.exs", "{config,lib,test,priv}/**/*.{ex,exs,heex}"]
] ]

View File

@ -59,8 +59,11 @@ lint-elixir:
- mix deps.get - mix deps.get
script: script:
- export EXITVALUE=0 - export EXITVALUE=0
- git fetch origin ${CI_DEFAULT_BRANCH}
- TARGET_SHA1=$(git show-ref -s ${CI_DEFAULT_BRANCH})
- echo "$TARGET_SHA1"
- mix format --check-formatted --dry-run || export EXITVALUE=1 - mix format --check-formatted --dry-run || export EXITVALUE=1
- mix credo --strict -a || export EXITVALUE=1 - mix credo diff --from-git-merge-base $TARGET_SHA1 --strict -a || export EXITVALUE=1
- mix sobelow --config || export EXITVALUE=1 - mix sobelow --config || export EXITVALUE=1
- exit $EXITVALUE - exit $EXITVALUE
@ -107,7 +110,7 @@ deps:
exunit: exunit:
stage: test stage: test
services: services:
- name: postgis/postgis:13-3.1 - name: postgis/postgis:14-3.2
alias: postgres alias: postgres
variables: variables:
MIX_ENV: test MIX_ENV: test
@ -180,7 +183,7 @@ pages:
.docker: &docker .docker: &docker
stage: docker stage: docker
image: docker:stable image: docker:20.10.12
variables: variables:
DOCKER_TLS_CERTDIR: "/certs" DOCKER_TLS_CERTDIR: "/certs"
DOCKER_HOST: tcp://docker:2376 DOCKER_HOST: tcp://docker:2376
@ -188,13 +191,13 @@ pages:
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client" DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
services: services:
- docker:stable-dind - docker:20.10.12-dind
cache: {} cache: {}
before_script: before_script:
# Install buildx # Install buildx
- wget https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64 - wget https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.linux-amd64
- mkdir -p ~/.docker/cli-plugins/ - mkdir -p ~/.docker/cli-plugins/
- mv buildx-v0.6.3.linux-amd64 ~/.docker/cli-plugins/docker-buildx - mv buildx-v0.8.1.linux-amd64 ~/.docker/cli-plugins/docker-buildx
- chmod a+x ~/.docker/cli-plugins/docker-buildx - chmod a+x ~/.docker/cli-plugins/docker-buildx
# Create env # Create env
- docker context create tls-environment - docker context create tls-environment
@ -205,16 +208,8 @@ pages:
# Login to DockerHub # Login to DockerHub
- mkdir -p ~/.docker - mkdir -p ~/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$CI_REGISTRY_AUTH\",\"email\":\"$CI_REGISTRY_EMAIL\"}}}" > ~/.docker/config.json - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$CI_REGISTRY_AUTH\",\"email\":\"$CI_REGISTRY_EMAIL\"}}}" > ~/.docker/config.json
script:
- >
docker buildx build
--push
--platform linux/amd64,linux/arm64,linux/arm
-t $DOCKER_IMAGE_NAME
-f docker/production/Dockerfile .
tags: tags:
- "privileged" - "privileged"
timeout: 3 hours
build-docker-main: build-docker-main:
<<: *docker <<: *docker
@ -222,8 +217,8 @@ build-docker-main:
- if: '$CI_PROJECT_NAMESPACE != "framasoft"' - if: '$CI_PROJECT_NAMESPACE != "framasoft"'
when: never when: never
- if: '$CI_PIPELINE_SOURCE == "schedule"' - if: '$CI_PIPELINE_SOURCE == "schedule"'
variables: script:
DOCKER_IMAGE_NAME: framasoft/mobilizon:main - docker buildx build --push --platform linux/amd64 -t framasoft/mobilizon:main -f docker/production/Dockerfile .
build-docker-tag: build-docker-tag:
<<: *docker <<: *docker
@ -231,14 +226,25 @@ build-docker-tag:
- if: '$CI_PROJECT_NAMESPACE != "framasoft"' - if: '$CI_PROJECT_NAMESPACE != "framasoft"'
when: never when: never
- if: $CI_COMMIT_TAG - if: $CI_COMMIT_TAG
variables: timeout: 3 hours
DOCKER_IMAGE_NAME: framasoft/mobilizon:$CI_COMMIT_TAG script:
- >
docker buildx build
--push
--platform linux/amd64,linux/arm64,linux/arm
-t framasoft/mobilizon:$CI_COMMIT_TAG
-t framasoft/mobilizon:latest
-f docker/production/Dockerfile .
# Packaging app for amd64 # Packaging app for amd64
package-app: package-app:
image: mobilizon/buildpack:1.13.4-erlang-24.3.3-debian-buster
stage: package stage: package
variables: &release-variables variables: &release-variables
MIX_ENV: "prod" MIX_ENV: "prod"
DEBIAN_FRONTEND: noninteractive
TZ: Etc/UTC
APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz"
script: &release-script script: &release-script
- mix local.hex --force - mix local.hex --force
- mix local.rebar --force - mix local.rebar --force
@ -254,7 +260,7 @@ package-app:
only: only:
- tags@framasoft/mobilizon - tags@framasoft/mobilizon
artifacts: artifacts:
expire_in: 30 days expire_in: 2 days
paths: paths:
- ${APP_ASSET} - ${APP_ASSET}
@ -272,7 +278,7 @@ package-app-dev:
# Packaging app for multi-arch # Packaging app for multi-arch
multi-arch-release: multi-arch-release:
stage: package stage: package
image: docker:stable image: docker:20.10.12
variables: variables:
DOCKER_TLS_CERTDIR: "/certs" DOCKER_TLS_CERTDIR: "/certs"
DOCKER_HOST: tcp://docker:2376 DOCKER_HOST: tcp://docker:2376
@ -280,14 +286,15 @@ multi-arch-release:
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client" DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz" APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz"
OS: debian-buster
services: services:
- docker:stable-dind - docker:20.10.12-dind
cache: {} cache: {}
before_script: before_script:
# Install buildx # Install buildx
- wget https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64 - wget https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.linux-amd64
- mkdir -p ~/.docker/cli-plugins/ - mkdir -p ~/.docker/cli-plugins/
- mv buildx-v0.6.3.linux-amd64 ~/.docker/cli-plugins/docker-buildx - mv buildx-v0.8.1.linux-amd64 ~/.docker/cli-plugins/docker-buildx
- chmod a+x ~/.docker/cli-plugins/docker-buildx - chmod a+x ~/.docker/cli-plugins/docker-buildx
# Create env # Create env
- docker context create tls-environment - docker context create tls-environment
@ -303,7 +310,7 @@ multi-arch-release:
tags: tags:
- "privileged" - "privileged"
artifacts: artifacts:
expire_in: 30 days expire_in: 2 days
paths: paths:
- ${APP_ASSET} - ${APP_ASSET}
parallel: parallel:
@ -319,7 +326,7 @@ multi-arch-release:
# Release # Release
release-upload: release-upload:
stage: upload stage: upload
image: framasoft/yakforms-assets-deploy:latest image: framasoft/upload-packages:latest
variables: variables:
APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz" APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz"
rules: *tag-rules rules: *tag-rules

View File

@ -8,5 +8,5 @@
out: "", out: "",
threshold: "medium", threshold: "medium",
ignore: ["Config.HTTPS", "Config.CSP"], ignore: ["Config.HTTPS", "Config.CSP"],
ignore_files: ["config/dev.1.secret.exs", "config/dev.2.secret.exs", "config/dev.3.secret.exs", "config/dev.secret.exs", "config/e2e.secret.exs", "config/prod.secret.exs", "config/test.secret.exs", "config/runtime.1.secret.exs", "config/runtime.2.secret.exs", "config/runtime.3.secret.exs", "config/runtime.exs"] ignore_files: ["config/runtime.exs"]
] ]

View File

@ -1,12 +1,16 @@
5048AE33D6269B15E21CF28C6F545AB6 02CE4963DFD1B0D6D5C567357CAFFE97
752C0E897CA81ACD81F4BB215FA5F8E4
23412CF16549E4E88366DC9DECF39071
81C1F600C5809C7029EE32DE4818CD7D
155A1FB53DE39EC8EFCFD7FB94EA823D 155A1FB53DE39EC8EFCFD7FB94EA823D
73B351E4CB3AF715AD450A085F5E6304 2262742E5C8944D5BF6698EC61F5DE50
BBACD7F0BACD4A6D3010C26604671692 25BEE162A99754480967216281E9EF33
6D4D4A4821B93BCFAC9CDBB367B34C4B 2A6F71CD6F1246F0B152C2376E2E398A
5674F0D127852889ED0132DC2F442AAB 30552A09D485A6AA73401C1D54F63C21
1600B7206E47F630D94AB54C360906F0 52900CE4EE3598F6F178A651FB256770
6151F44368FC19F2394274F513C29151
765526195D4C6D770EAF4DC944A8CBF4
B2FF1A12F13B873507C85091688C1D6D
B9AF8A342CD7FF39E10CC10A408C28E1
C042E87389F7BDCFF4E076E95731AE69
C42BFAEF7100F57BED75998B217C857A
D11958E86F1B6D37EF656B63405CA8A4
F16F054F2628609A726B9FF2F089D484

2
.tool-versions Normal file
View File

@ -0,0 +1,2 @@
elixir 1.13.4-otp-24
erlang 24.3.3

View File

@ -1,9 +1,373 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 2.1.0 - 2022-05-16
### Added
- Added an event category field. Administrators can extend the pre-configured list of categories through configuration.
- Added possibility for administrators to have analytics (Matomo, Plausible supported) and error handling (Sentry supported) on front-end.
- Redesigned federation admin section with dedicated instance pages
- Allow to filter moderation reports by domain
- Added a button to go to past events of a group if it has no upcoming events
- Add Überauth CAS Strategy
- Add a CLI command to delete actors
### Changed
- Changed mailer library from Bamboo to Swoosh, should fix emails being considered spam. **Some configuration changes are required, see below.**
- Expose some fields to ActivityStreams event representation: `isOnline`, `remainingAttendeeCapacity` and `participantCount`
- Expose a new field to ActivityStreams group representation: `memberCount`
- Improve group creation errors feedback
- Only display locality in event card
- Stale groups are now excluded from group search
- Event default visibility is now set according to group privacy setting
- Remove Koena Connect button
- Hide the whole metadata block if group has no description
- Increase task timeout in Refresher to 60 seconds
- Allow webfinger to be fetched over http (not https) in dev mode
- Improve reactions when approving/rejecting an instance follow
- Improve instance admin view for mobile
- Allow to reject instance following
- Allow instance to have non-standard ports
- Add pagination to the instances list
- Eventually fetch actors in mentions
- Improve IdentityPicker, JoinGroupWithAccount and ActorInline components
- Various group and posts improvements
- Update schema.graphql file
- Add "Accept-Language" header to sentry request metadata
- Hide address blocks when address has no real data
- Remove obsolete attribute type="text/css" from <style> tags
- Improve actor cards integration
- Use upstream dependencies for Ueberauth providers
- Include ongoing events in search
- Send push notification into own task
- Add appropriate timeouts for Repo.transactions
- Add a proper error message when adding an instance follow that doesn't respond
- Allow the instance to be followed from Mastodon (through relays)
- Remove unused fragment from FETCH_PERSON GraphQL query
### Fixed
- Fixed actor refreshment being impossible
- Fixed ical export for undefined datetimes
- Fixed parsing links with hashtag characters
- Fixed fetching link details from Twitter
- Fixed Thunderbird accessing ICS feed endpoint with special `Accept` HTTP header
- Make sure every ICS/Feed caches are emptied when modifying entities
- Fixed time issues with DST changes
- Fixed group preview card not truncating description
- Fixed redirection after login
- Fixed user admin section showing button to confirm user when the user is already confirmed
- Fixed creating event from group view not always setting the group as organizer
- Fixed invalid addresses blocking event metadata preview rendering
- Fixed group deletion with comments that caused foreign key issues
- Fixed incoming Accept activities from participations we don't already have
- Fixed resources that didn't have metadata size limits
- Properly fallback to UTC when sending notifications and the user doesn't have a timezone setting set
- Fix posts creation
- Fix rejecting instance follow
- Fix pagination of group events
- Add proper fallback for when a TZ isn't registered
- Hide side of report modal on low width screens
- Fix Telegram Logo being replaced with Mastodon logo in ShareGroupModal
- Change URL for Mastodon Share Manager
- Fix receiving Flag activities on federated events
- Fix activity notifications by preloading user.activity_settings
- Fix text overflow on group card description
- Exclude tags with more than 40 characters from being extracted
- Avoid duplicate tags with different casing
- Fix invalid HTML (<div> inside <label>)
- Fix latest group not refreshing in admin section
- Add missing "relay@" part of federated address to follow
- Fix Ueberauth use of CSRF with session
- Fix being an administrator when using 3rd-party auth provider
- Make sure activity recipient can't be nil
- Make sure users can't create profiles or groups with non-valid patterns
- Add description field to address representation
- Make sure prompt show the correct message and not just "Continue?" in mix mode
- Make sure activity notification recaps can't be sent multiple times
- Fix group notification of new event being sent multiple times
- Fix links to group page in group membership emails and participation
- Fix clicking on map crashing the app
### Translations
- Arabic
- Basque
- Belarusian
- Bengali
- Catalan
- Chinese (Traditional)
- Croatian
- Czech
- Danish
- Dutch
- Esperanto
- Finnish
- French
- Gaelic
- Galician
- German
- Hebrew
- Hungarian
- Indonesian
- Italian
- Japanese
- Kabyle
- Kannada
- Norwegian Nynorsk
- Occitan
- Persian
- Polish
- Portuguese
- Portuguese (Brazil)
- Russian
- Slovenian
- Spanish
- Swedish
- Welsh
## 2.1.0-rc.6 - 2022-05-11
Changes since rc.5:
- Allow the instance to be followed from Mastodon (through relays)
- Make sure activity recipient can't be nil
- Make sure users can't create profiles or groups with non-valid patterns
- Add description field to address representation
- Make sure prompt show the correct message and not just "Continue?" in mix mode
- Add a CLI command to delete actors
- Make sure activity notification recaps can't be sent multiple times
- Fix group notification of new event being sent multiple times
- Fix links to group page in group membership emails and participation
- Fix clicking on map crashing the app
- Remove unused fragment from FETCH_PERSON GraphQL query
## 2.1.0-rc.5 - 2022-05-06
Changes since rc.4:
- Add appropriate timeouts for Repo.transactions
- Remove OS-specific packages
- Remove refresh instance triggers
- Add a proper error message when adding an instance follow that doesn't respond
## 2.1.0-rc.4 - 2022-05-03
Changes since rc.3:
- Use upstream dependencies for Ueberauth providers
- Fix Ueberauth use of CSRF with session
- Fix being an administrator when using 3rd-party auth provider
- Include ongoing events in search
- Send push notification into own task
- Add Überauth CAS Strategy
## 2.1.0-rc.3 - 2022-04-24
Changes since rc.2:
- Fix activity notifications by preloading user.activity_settings
- Add "Accept-Language" header to sentry request metadata
- Hide address blocks when address has no real data
- Fix text overflow on group card description
- Exclude tags with more than 40 characters from being extracted
- Avoid duplicate tags with different casing
- Fix invalid HTML (<div> inside <label>)
- Remove attribute type="text/css" from <style> tags
- Improve actor cards integration
- Fix latest group not refreshing in admin section
- Add missing "relay@" part of federated address to follow
## 2.1.0-rc.2 - 2022-04-20
Changes since rc.1:
- Hide the whole metadata block if group has no description
- Increase task timeout in Refresher to 60 seconds
- Allow webfinger to be fetched over http (not https) in dev mode
- Fix rejecting instance follow
- Allow instance to have non-standard ports
- Improve reactions when approving/rejecting an instance follow
- Improve instance admin view for mobile
- Allow to reject instance following
- Fix pagination of group events
- Add pagination to the instances list
- Upgrade deps
- Eventually fetch actors in mentions
- Add proper fallback for when a TZ isn't registered
- Improve IdentityPicker
- Hide side of report modal on low width screens
- Improve JoinGroupWithAccount component
- Various group and posts improvements
- Fix Telegram Logo being replaced with Mastodon logo in ShareGroupModal
- Change URL to Mastodon Share Manager
- Improve ActorInline component
- Avoid assuming we're on Debian-based in release build
- Fix receiving Flag activities on federated events
- Update schema.graphql file
## 2.1.0-rc.1 - 2022-04-18
Changes since beta.3:
- Fix posts creation
- Fix some typespecs
- Remove Koena Connect button
- Update dependencies
## 2.1.0-beta.3 - 2022-04-09
Changes since beta.2:
- Add Fedora and Alpine builds
## 2.1.0-beta.2 - 2022-04-08
Changes since beta.1 :
- Build release packages for several distributions (Debian Bullseye, Debian Buster, Ubuntu Focal, Ubuntu Bionic) because of libc version changes
## 2.1.0-beta.1 - 2022-04-07
### Added
- Added an event category field. Administrators can extend the pre-configured list of categories through configuration.
- Added possibility for administrators to have analytics (Matomo, Plausible supported) and error handling (Sentry supported) on front-end.
- Redesigned federation admin section with dedicated instance pages
- Allow to filter moderation reports by domain
- Added a button to go to past events of a group if it has no upcoming events
### Changed
- Changed mailer library from Bamboo to Swoosh, should fix emails being considered spam. **Some configuration changes are required, see below.**
- Expose some fields to ActivityStreams event representation: `isOnline`, `remainingAttendeeCapacity` and `participantCount`
- Expose a new field to ActivityStreams group representation: `memberCount`
- Improve group creation errors feedback
- Only display locality in event card
- Stale groups are now excluded from group search
- Event default visibility is now set according to group privacy setting
### Fixed
- Fixed actor refreshment being impossible
- Fixed ical export for undefined datetimes
- Fixed parsing links with hashtag characters
- Fixed fetching link details from Twitter
- Fixed Thunderbird accessing ICS feed endpoint with special `Accept` HTTP header
- Make sure every ICS/Feed caches are emptied when modifying entities
- Fixed time issues with DST changes
- Fixed group preview card not truncating description
- Fixed redirection after login
- Fixed user admin section showing button to confirm user when the user is already confirmed
- Fixed creating event from group view not always setting the group as organizer
- Fixed invalid addresses blocking event metadata preview rendering
- Fixed group deletion with comments that caused foreign key issues
- Fixed incoming Accept activities from participations we don't already have
- Fixed resources that didn't have metadata size limits
- Properly fallback to UTC when sending notifications and the user doesn't have a timezone setting set
### Translations
- Arabic
- Basque
- Belarusian
- Bengali
- Catalan
- Chinese (Traditional)
- Croatian
- Czech
- Danish
- Dutch
- Esperanto
- Finnish
- French
- Gaelic
- Galician
- German
- Hebrew
- Hungarian
- Indonesian
- Italian
- Japanese
- Kabyle
- Kannada
- Norwegian Nynorsk
- Occitan
- Persian
- Polish
- Portuguese
- Portuguese (Brazil)
- Russian
- Slovenian
- Spanish
- Swedish
- Welsh
## 2.0.2 - 2021-12-22
### Changed
- Improved handling of media file deletion
- Releases and Docker image are now using Elixir 1.13
### Fixed
- Fixed position of tentative tag on event cards
- Fixed text overflow when a link is too long in event mobile view
- Fixed filtering user own memberships and group members in event organizer & contacts picker
- Fixed first day of week not depending on locale in the datetime picker
- Fixed the admin page when a group/profile/user was not found
- Fixed group members pagination on admin group profile view
- Fixed admin edition of the instance's language
### Translations
- Croatian
- Czech
- Esperanto
- German
- Hebrew
- Occitan
- Persian
- Russian
- Spanish
## 2.0.1 - 2021-11-26
### Changed
- Remove litepub context
### Fixed
- Make sure my group upcoming events are ordered by their start date
- Fix event participants pagination
- Always focus the search field after results have been fetched
- Don't sign fetches to instance actor when refreshing their keys
- Fix reject of already following instances
- Added missing timezone data to the Docker image
- Replace @tiptap/starter-kit with indidual extensions, removing unneeded extensions that caused issues on old Firefox versions
- Better handling of Friendica Update activities without actor information
- Always show pending/cancelled status on event cards
- Fixed nightly docker build
- Refresh loggeduser information before the final step of onboarding, avoiding loop when finishing onboarding
- Handle tz_world data being absent
### Translations
- Croatian (New !)
- Czech
- Gaelic
- Hungarian
- Indonesian
- Welsh (New !)
## 2.0.0 - 2021-11-23 ## 2.0.0 - 2021-11-23
Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well. Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well.
@ -134,7 +498,6 @@ Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/mai
- Slovenian - Slovenian
- Spanish - Spanish
## 2.0.0-rc.3 - 2021-11-22 ## 2.0.0-rc.3 - 2021-11-22
This lists changes since 2.0.0-rc.3. Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well. This lists changes since 2.0.0-rc.3. Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well.
@ -155,11 +518,13 @@ This lists changes since 2.0.0-rc.1. Please read the [UPGRADE.md](https://framag
- Improve MyEvents page description text - Improve MyEvents page description text
### Fixed ### Fixed
- Fix spacing in organizer picker - Fix spacing in organizer picker
- Increase number of close events and follow group events - Increase number of close events and follow group events
- Fix accessing user profile in admin section - Fix accessing user profile in admin section
- Set initial values for some EventMetadata elements, fixing submitting them right away with no value - Set initial values for some EventMetadata elements, fixing submitting them right away with no value
- Avoid giving an error page if the apollo futureParticipations query is undefined - Avoid giving an error page if the apollo futureParticipations query is undefined
### Translations ### Translations
- German - German
@ -180,6 +545,7 @@ This lists changes since 2.0.0-beta.2. Please read the [UPGRADE.md](https://fram
- Add "formerType" and "delete" attributes on Tombstones ActivityPub objects representation - Add "formerType" and "delete" attributes on Tombstones ActivityPub objects representation
### Fixed ### Fixed
- Fixed creating group activities when creating events with some fields - Fixed creating group activities when creating events with some fields
- Move release package at correct path for CI upload - Move release package at correct path for CI upload
- Fixed event contacts that were not exposed and fetched over federation - Fixed event contacts that were not exposed and fetched over federation
@ -198,6 +564,7 @@ This lists changes since 2.0.0-beta.2. Please read the [UPGRADE.md](https://fram
## 2.0.0-beta.2 - 2021-11-15 ## 2.0.0-beta.2 - 2021-11-15
This lists changes since 2.0.0-beta.1. Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well. This lists changes since 2.0.0-beta.1. Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well.
### Added ### Added
- Group followers and members get an notification email by default when a group publishes a new event (subject to activity notification settings) - Group followers and members get an notification email by default when a group publishes a new event (subject to activity notification settings)
@ -234,6 +601,7 @@ This lists changes since 2.0.0-beta.1. Please read the [UPGRADE.md](https://fram
## 2.0.0-beta.1 - 2021-11-09 ## 2.0.0-beta.1 - 2021-11-09
Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well. Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md#upgrading-from-13-to-20) file as well.
### Added ### Added
- Added possibility to follow groups and be notified from new upcoming events - Added possibility to follow groups and be notified from new upcoming events
@ -298,6 +666,7 @@ Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/mai
### Security ### Security
- Fixed private messages sent as event replies from Mastodon that were shown publically as public comments. They are now discarded. - Fixed private messages sent as event replies from Mastodon that were shown publically as public comments. They are now discarded.
### Translations ### Translations
- Czech - Czech
@ -387,7 +756,6 @@ Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/mai
- Fixed token refreshment issues - Fixed token refreshment issues
- Fixed search from 404 page - Fixed search from 404 page
### Translations ### Translations
- Catalan - Catalan
@ -413,6 +781,7 @@ Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/mai
- Fixed group discussions with deleted comments - Fixed group discussions with deleted comments
## 1.2.2 - 2021-07-01 ## 1.2.2 - 2021-07-01
### Changed ### Changed
- Improved UI for participations when message is too long - Improved UI for participations when message is too long
@ -439,6 +808,7 @@ Please read the [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/mai
- Fixed compatibility check in Notification section for service workers - Fixed compatibility check in Notification section for service workers
## 1.2.0 - 2021-06-29 ## 1.2.0 - 2021-06-29
### Added ### Added
- **Notifications for various group and event activity, both by email and browser push notifications. Daily and weekly digests are also available.** - **Notifications for various group and event activity, both by email and browser push notifications. Daily and weekly digests are also available.**
@ -806,6 +1176,7 @@ This version introduces a new way to install and host Mobilizon : Elixir releas
- Hungarian - Hungarian
- Russian - Russian
- Spanish - Spanish
## 1.1.0-rc.1 - 2021-03-29 ## 1.1.0-rc.1 - 2021-03-29
### Added ### Added
@ -853,11 +1224,13 @@ This version introduces a new way to install and host Mobilizon : Elixir releas
## 1.1.0-beta.6 - 2021-03-17 ## 1.1.0-beta.6 - 2021-03-17
### Fixed ### Fixed
- Fixed a typo in range/radius showing the wrong radius for close events on homepage - Fixed a typo in range/radius showing the wrong radius for close events on homepage
## 1.1.0-beta.5 - 2021-03-17 ## 1.1.0-beta.5 - 2021-03-17
### Fixed ### Fixed
- Fixed a typo in range/radius preventing close events from showing up - Fixed a typo in range/radius preventing close events from showing up
## 1.1.0-beta.4 - 2021-03-17 ## 1.1.0-beta.4 - 2021-03-17
@ -871,15 +1244,18 @@ This version introduces a new way to install and host Mobilizon : Elixir releas
## 1.1.0-beta.3 - 2021-03-16 ## 1.1.0-beta.3 - 2021-03-16
### Fixed ### Fixed
- Handle ActivityPub Fetcher returning text that's not JSON - Handle ActivityPub Fetcher returning text that's not JSON
- Fix accessing a group profile when not a member - Fix accessing a group profile when not a member
## 1.1.0-beta.2 - 2021-03-16 ## 1.1.0-beta.2 - 2021-03-16
### Fixed ### Fixed
- Fixed geospatial configuration only being evaluated at compile-time, not at runtime - Fixed geospatial configuration only being evaluated at compile-time, not at runtime
### Translations ### Translations
- Slovenian - Slovenian
## 1.1.0-beta.1 - 2021-03-10 ## 1.1.0-beta.1 - 2021-03-10
@ -1021,23 +1397,23 @@ This version introduces a new way to install and host Mobilizon : Elixir releas
### Special operations ### Special operations
* **Reattach media files to their entity.** - **Reattach media files to their entity.**
When media files were uploaded and added in events and posts bodies, they were only attached to the profile that uploaded them, not to the event or post. This task attaches them back to their entity so that the command to clean orphan media files doesn't remove them. When media files were uploaded and added in events and posts bodies, they were only attached to the profile that uploaded them, not to the event or post. This task attaches them back to their entity so that the command to clean orphan media files doesn't remove them.
* Source install - Source install
`MIX_ENV=prod mix mobilizon.maintenance.fix_unattached_media_in_body` `MIX_ENV=prod mix mobilizon.maintenance.fix_unattached_media_in_body`
* Docker - Docker
`docker-compose exec mobilizon mobilizon_ctl maintenance.fix_unattached_media_in_body` `docker-compose exec mobilizon mobilizon_ctl maintenance.fix_unattached_media_in_body`
* **Refresh remote profiles to save avatars locally** - **Refresh remote profiles to save avatars locally**
Profile avatars and banners were previously only proxified and cached. Now we save them locally. Refreshing all remote actors will save profile media locally instead. Profile avatars and banners were previously only proxified and cached. Now we save them locally. Refreshing all remote actors will save profile media locally instead.
* Source install - Source install
`MIX_ENV=prod mix mobilizon.actors.refresh --all` `MIX_ENV=prod mix mobilizon.actors.refresh --all`
* Docker - Docker
`docker-compose exec mobilizon mobilizon_ctl actors.refresh --all` `docker-compose exec mobilizon mobilizon_ctl actors.refresh --all`
* **imagemagick and webp are now a required dependency** to build Mobilizon. - **imagemagick and webp are now a required dependency** to build Mobilizon.
Optimized versions of Mobilizon's pictures are now produced during front-end build. Optimized versions of Mobilizon's pictures are now produced during front-end build.
See [the documentation](https://docs.joinmobilizon.org/administration/dependencies/#misc) to make sure these dependencies are installed. See [the documentation](https://docs.joinmobilizon.org/administration/dependencies/#misc) to make sure these dependencies are installed.
@ -1089,6 +1465,7 @@ This version introduces a new way to install and host Mobilizon : Elixir releas
### Translations ### Translations
Updated translations: Updated translations:
- Catalan - Catalan
- Dutch - Dutch
- English - English
@ -1261,14 +1638,15 @@ Updated translations:
### Special operations ### Special operations
* We added `application/ld+json` as acceptable MIME type for ActivityPub requests, so you'll need to recompile the `mime` library we use before recompiling Mobilizon: - We added `application/ld+json` as acceptable MIME type for ActivityPub requests, so you'll need to recompile the `mime` library we use before recompiling Mobilizon:
``` ```
MIX_ENV=prod mix deps.clean mime --build MIX_ENV=prod mix deps.clean mime --build
``` ```
* The [nginx configuration](https://framagit.org/framasoft/mobilizon/-/blob/main/support/nginx/mobilizon.conf) has been changed with improvements and support for custom error pages. - The [nginx configuration](https://framagit.org/framasoft/mobilizon/-/blob/main/support/nginx/mobilizon.conf) has been changed with improvements and support for custom error pages.
* The cmake dependency has been added (see [our documentation](https://docs.joinmobilizon.org/administration/dependencies/#basic-tools)) - The cmake dependency has been added (see [our documentation](https://docs.joinmobilizon.org/administration/dependencies/#basic-tools))
### Added ### Added
@ -1303,6 +1681,7 @@ Updated translations:
## [1.0.0-beta.3] - 2020-06-24 ## [1.0.0-beta.3] - 2020-06-24
### Special operations ### Special operations
Config has moved from `.env` files to a more traditional way to handle things in the Elixir world, with `.exs` files. Config has moved from `.env` files to a more traditional way to handle things in the Elixir world, with `.exs` files.
To migrate existing configuration, you can simply run `mix mobilizon.instance gen` and fill in the adequate values previously in `.env` files (you don't need to perform the operations to create the database). To migrate existing configuration, you can simply run `mix mobilizon.instance gen` and fill in the adequate values previously in `.env` files (you don't need to perform the operations to create the database).
@ -1312,6 +1691,7 @@ A minimal file template [is available](https://framagit.org/framasoft/mobilizon/
Also make sure to remove the `EnvironmentFile=` line from the systemd service and set `Environment=MIX_ENV=prod` instead. See [the updated file](https://framagit.org/framasoft/mobilizon/blob/main/support/systemd/mobilizon.service). Also make sure to remove the `EnvironmentFile=` line from the systemd service and set `Environment=MIX_ENV=prod` instead. See [the updated file](https://framagit.org/framasoft/mobilizon/blob/main/support/systemd/mobilizon.service).
### Added ### Added
- Possibility to participate to an event without an account (confirmation through email required) - Possibility to participate to an event without an account (confirmation through email required)
- Possibility to participate to a remote event (being redirected by providing federated identity) - Possibility to participate to a remote event (being redirected by providing federated identity)
- Possibility to add a note as a participant when event participation is manually validated (required when participating without an account) - Possibility to add a note as a participant when event participation is manually validated (required when participating without an account)
@ -1328,6 +1708,7 @@ Also make sure to remove the `EnvironmentFile=` line from the systemd service an
- Allow user to change language - Allow user to change language
### Changed ### Changed
- Configuration handling (see above) - Configuration handling (see above)
- Improved a bit color theme - Improved a bit color theme
- Signature validation also now checks if `Date` header has acceptable values - Signature validation also now checks if `Date` header has acceptable values
@ -1338,6 +1719,7 @@ Also make sure to remove the `EnvironmentFile=` line from the systemd service an
- Improved public event page - Improved public event page
### Fixed ### Fixed
- Fixed URL search - Fixed URL search
- Fixed content accessed through URL search being public - Fixed content accessed through URL search being public
- Fix event links in some emails - Fix event links in some emails
@ -1345,17 +1727,21 @@ Also make sure to remove the `EnvironmentFile=` line from the systemd service an
## [1.0.0-beta.2] - 2019-12-18 ## [1.0.0-beta.2] - 2019-12-18
### Special operations ### Special operations
These two operations couldn't be handled during migrations. These two operations couldn't be handled during migrations.
They are optional, but you won't be able to search or get participant stats on existing events if they are not executed. They are optional, but you won't be able to search or get participant stats on existing events if they are not executed.
These commands will be removed in Mobilizon 1.0.0-beta.3. These commands will be removed in Mobilizon 1.0.0-beta.3.
In order to populate search index for existing events, you need to run the following command (with prod environment): In order to populate search index for existing events, you need to run the following command (with prod environment):
* `mix mobilizon.setup_search`
- `mix mobilizon.setup_search`
In order to move participant stats to the event table for existing events, you need to run the following command (with prod environment): In order to move participant stats to the event table for existing events, you need to run the following command (with prod environment):
* `mix mobilizon.move_participant_stats`
- `mix mobilizon.move_participant_stats`
### Added ### Added
- Federation is active - Federation is active
- Added an interface for admins to view and manage instance followers and followings - Added an interface for admins to view and manage instance followers and followings
- Ability to comment below events - Ability to comment below events
@ -1380,6 +1766,7 @@ In order to move participant stats to the event table for existing events, you n
- Upgraded frontend and backend dependencies - Upgraded frontend and backend dependencies
### Changed ### Changed
- Move participant stats to event table **(read special instructions above)** - Move participant stats to event table **(read special instructions above)**
- Limit length (20 characters) and number (10) of tags allowed - Limit length (20 characters) and number (10) of tags allowed
- Added some backend changes and validation for field length - Added some backend changes and validation for field length
@ -1393,6 +1780,7 @@ In order to move participant stats to the event table for existing events, you n
- Also consider the PeerTube `CommentsEnabled` property to know if you can reply to an event - Also consider the PeerTube `CommentsEnabled` property to know if you can reply to an event
### Fixed ### Fixed
- Fix event URL validation and check if hostname is correct before showing it - Fix event URL validation and check if hostname is correct before showing it
- Fix participations stats on the MyEvents page - Fix participations stats on the MyEvents page
- Fix event description lists margin - Fix event description lists margin
@ -1422,8 +1810,11 @@ In order to move participant stats to the event table for existing events, you n
- Fixed event HTML representation when `GET` request has no `Accept` header - Fixed event HTML representation when `GET` request has no `Accept` header
### Security ### Security
- Sanitize event title to avoid XSS - Sanitize event title to avoid XSS
## [1.0.0-beta.1] - 2019-10-15 ## [1.0.0-beta.1] - 2019-10-15
### Added ### Added
- Initial release - Initial release

View File

@ -1,7 +1,49 @@
# Upgrading from 2.0 to 2.1
## Mailer library change
### Docker
The change is already applied. You may remove the `MOBILIZON_SMTP_HOSTNAME` environment key which is not used anymore.
### Release and source mode
In your configuration file under `config :mobilizon, Mobilizon.Web.Email.Mailer`,
- Change `Bamboo.SMTPAdapter` to `Swoosh.Adapters.SMTP`,
- rename the `server` key to `relay`
- remove the `hostname` key,
- the default value of the username and password fields is an empty string and no longer `nil`.
```diff
config :mobilizon, Mobilizon.Web.Email.Mailer,
- adapter: Bamboo.SMTPAdapter,
+ adapter: Swoosh.Adapters.SMTP,
- server: "localhost",
+ relay: "localhost",
- hostname: "localhost",
# usually 25, 465 or 587
port: 25,
- username: nil,
+ username: "",
- password: nil,
+ password: "",
# can be `:always` or `:never`
tls: :if_available,
allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
retries: 1,
# can be `true`
no_mx_lookups: false,
# can be `:always`. If your smtp relay requires authentication set it to `:always`.
auth: :if_available
```
# Upgrading from 1.3 to 2.0 # Upgrading from 1.3 to 2.0
Requirements dependencies depend on the way Mobilizon is installed. Requirements dependencies depend on the way Mobilizon is installed.
## New Elixir version requirement ## New Elixir version requirement
### Docker and Release install ### Docker and Release install
You are already using latest Elixir version in the release tarball and Docker images. You are already using latest Elixir version in the release tarball and Docker images.
@ -17,24 +59,27 @@ Mobilizon 2.0 uses data based on [timezone-boundary-builder](https://github.com/
### Docker install ### Docker install
The geographic timezone data is already bundled into the image, you have nothing to do. The geographic timezone data is already bundled into the image, you have nothing to do.
### Release install ### Release install
In order to keep the release tarballs light, the geographic timezone data is not bundled directly. You need to download the data : In order to keep the release tarballs light, the geographic timezone data is not bundled directly. You need to download the data :
* either raw from Github, but **requires an extra ~1Gio of memory** to process the data
- either raw from Github, but **requires an extra ~1Gio of memory** to process the data
```sh ```sh
sudo -u mobilizon mkdir /var/lib/mobilizon/timezones sudo -u mobilizon mkdir /var/lib/mobilizon/timezones
sudo -u mobilizon ./bin/mobilizon_ctl tz_world.update sudo -u mobilizon ./bin/mobilizon_ctl tz_world.update
``` ```
* either already processed from our own distribution server - either already processed from our own distribution server
```sh ```sh
sudo -u mobilizon mkdir /var/lib/mobilizon/timezones sudo -u mobilizon mkdir /var/lib/mobilizon/timezones
sudo -u mobilizon curl -L 'https://packages.joinmobilizon.org/tz_world/timezones-geodata.dets' -o /var/lib/mobilizon/timezones/timezones-geodata.dets sudo -u mobilizon curl -L 'https://packages.joinmobilizon.org/tz_world/timezones-geodata.dets' -o /var/lib/mobilizon/timezones/timezones-geodata.dets
``` ```
In both cases, ~700Mio of disk will be used. You may use the following configuration to specify where the data is expected: In both cases, ~700Mio of disk will be used. You may use the following configuration to specify where the data is expected if you decide to change it from the default location (`/var/lib/mobilizon/timezones`) :
```elixir ```elixir
config :tz_world, data_dir: "/some/place" config :tz_world, data_dir: "/some/place"
``` ```
@ -42,14 +87,15 @@ config :tz_world, data_dir: "/some/place"
### Source install ### Source install
You need to download the data : You need to download the data :
* either raw from Github, but **requires an extra ~1Gio of memory** to process the data
- either raw from Github, but **requires an extra ~1Gio of memory** to process the data
```sh ```sh
sudo -u mobilizon mkdir /var/lib/mobilizon/timezones sudo -u mobilizon mkdir /var/lib/mobilizon/timezones
sudo -u mobilizon mix mobilizon.tz_world.update sudo -u mobilizon mix mobilizon.tz_world.update
``` ```
* either already processed from our own distribution server - either already processed from our own distribution server
```sh ```sh
sudo -u mobilizon mkdir /var/lib/mobilizon/timezones sudo -u mobilizon mkdir /var/lib/mobilizon/timezones
@ -57,6 +103,7 @@ You need to download the data :
``` ```
In both cases, ~700Mio of disk will be used. You may use the following configuration to specify where the data is expected: In both cases, ~700Mio of disk will be used. You may use the following configuration to specify where the data is expected:
```elixir ```elixir
config :tz_world, data_dir: "/some/place" config :tz_world, data_dir: "/some/place"
``` ```
@ -75,14 +122,18 @@ Files in this folder are temporary and are cleaned once an hour.
## New optional dependencies ## New optional dependencies
These are optional, installing them will allow Mobilizon to export to PDF and ODS as well. Mobilizon 2.0 allows to export the participant list, but more is planned. These are optional, installing them will allow Mobilizon to export to PDF and ODS as well. Mobilizon 2.0 allows to export the participant list, but more is planned.
### Docker ### Docker
Everything is included in our Docker image. Everything is included in our Docker image.
### Release and source install ### Release and source install
New optional Python dependencies: New optional Python dependencies:
* `Python` >= 3.6
* `weasyprint` for PDF export (with [a few extra dependencies](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html)) - `Python` >= 3.6
* `pyexcel-ods3` for ODS export (no extra dependencies) - `weasyprint` for PDF export (with [a few extra dependencies](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html))
- `pyexcel-ods3` for ODS export (no extra dependencies)
Both can be installed through pip. You need to enable and configure exports for PDF and ODS in the configuration afterwards. Read [the dedicated docs page about this](https://docs.joinmobilizon.org/administration/configure/exports/). Both can be installed through pip. You need to enable and configure exports for PDF and ODS in the configuration afterwards. Read [the dedicated docs page about this](https://docs.joinmobilizon.org/administration/configure/exports/).
@ -91,35 +142,41 @@ Both can be installed through pip. You need to enable and configure exports for
The 1.1 version of Mobilizon brings Elixir releases support. An Elixir release is a self-contained directory that contains all of Mobilizon's code (front-end and backend), it's dependencies, as well as the Erlang Virtual Machine and runtime (only the parts you need). As long as the release has been assembled on the same OS and architecture, it can be deploy and run straight away. [Read more about releases](https://elixir-lang.org/getting-started/mix-otp/config-and-releases.html#releases). The 1.1 version of Mobilizon brings Elixir releases support. An Elixir release is a self-contained directory that contains all of Mobilizon's code (front-end and backend), it's dependencies, as well as the Erlang Virtual Machine and runtime (only the parts you need). As long as the release has been assembled on the same OS and architecture, it can be deploy and run straight away. [Read more about releases](https://elixir-lang.org/getting-started/mix-otp/config-and-releases.html#releases).
## Comparison ## Comparison
Migrating to releases means: Migrating to releases means:
* You only get a precompiled binary, so you avoid compilation times when updating
* No need to have Elixir/NodeJS installed on the system - You only get a precompiled binary, so you avoid compilation times when updating
* Code/data/config location is more common (/opt, /var/lib, /etc) - No need to have Elixir/NodeJS installed on the system
* More efficient, as only what you need from the Elixir/Erlang standard libraries is included and all of the code is directly preloaded - Code/data/config location is more common (/opt, /var/lib, /etc)
* You can't hardcode modifications in Mobilizon's code - More efficient, as only what you need from the Elixir/Erlang standard libraries is included and all of the code is directly preloaded
- You can't hardcode modifications in Mobilizon's code
Staying on source releases means: Staying on source releases means:
* You need to recompile everything with each update
* Compiling frontend and backend has higher system requirements than just running Mobilizon - You need to recompile everything with each update
* You can change things in Mobilizon's code and recompile right away to test changes - Compiling frontend and backend has higher system requirements than just running Mobilizon
- You can change things in Mobilizon's code and recompile right away to test changes
## Releases ## Releases
If you want to migrate to releases, [we provide a full guide](https://docs.joinmobilizon.org/administration/upgrading/source_to_release/). You may do this at any time. If you want to migrate to releases, [we provide a full guide](https://docs.joinmobilizon.org/administration/upgrading/source_to_release/). You may do this at any time.
## Source install ## Source install
To stay on a source release, you just need to check the following things: To stay on a source release, you just need to check the following things:
* Rename your configuration file `config/prod.secret.exs` to `config/runtime.exs`.
* If your config file includes `server: true` under `Mobilizon.Web.Endpoint`, remove it. - Rename your configuration file `config/prod.secret.exs` to `config/runtime.exs`.
- If your config file includes `server: true` under `Mobilizon.Web.Endpoint`, remove it.
```diff ```diff
config :mobilizon, Mobilizon.Web.Endpoint, config :mobilizon, Mobilizon.Web.Endpoint,
- server: true, - server: true,
``` ```
* The uploads default directory is now `/var/lib/mobilizon/uploads`. To keep it in the previous `uploads/` directory, just add the following line to `config/runtime.exs`: - The uploads default directory is now `/var/lib/mobilizon/uploads`. To keep it in the previous `uploads/` directory, just add the following line to `config/runtime.exs`:
```elixir ```elixir
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "uploads" config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "uploads"
``` ```
Or you may use any other directory where the `mobilizon` user has write permissions. Or you may use any other directory where the `mobilizon` user has write permissions.
* The GeoIP database default directory is now `/var/lib/mobilizon/geo/GeoLite2-City.mmdb`. To keep it in the previous `priv/data/GeoLite2-City.mmdb` directory, just add the following line to `config/runtime.exs`: - The GeoIP database default directory is now `/var/lib/mobilizon/geo/GeoLite2-City.mmdb`. To keep it in the previous `priv/data/GeoLite2-City.mmdb` directory, just add the following line to `config/runtime.exs`:
```elixir ```elixir
config :geolix, databases: [ config :geolix, databases: [
%{ %{

View File

@ -106,13 +106,16 @@ config :mobilizon, :media_proxy,
] ]
config :mobilizon, Mobilizon.Web.Email.Mailer, config :mobilizon, Mobilizon.Web.Email.Mailer,
adapter: Bamboo.SMTPAdapter, adapter: Swoosh.Adapters.SMTP,
server: "localhost", relay: "localhost",
hostname: "localhost",
# usually 25, 465 or 587 # usually 25, 465 or 587
port: 25, port: 25,
username: nil, username: "",
password: nil, password: "",
# can be `:always` or `:never`
auth: :if_available,
# can be `true`
ssl: false,
# can be `:always` or `:never` # can be `:always` or `:never`
tls: :if_available, tls: :if_available,
allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"], allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
@ -185,7 +188,13 @@ config :phoenix, :filter_parameters, ["password", "token"]
config :absinthe, schema: Mobilizon.GraphQL.Schema config :absinthe, schema: Mobilizon.GraphQL.Schema
config :absinthe, Absinthe.Logger, filter_variables: ["token", "password", "secret"] config :absinthe, Absinthe.Logger, filter_variables: ["token", "password", "secret"]
config :mobilizon, Mobilizon.Web.Gettext, one_module_per_locale: true config :codepagex, :encodings, [
:ascii,
~r[iso8859]i,
:"VENDORS/MICSFT/WINDOWS/CP1252"
]
config :mobilizon, Mobilizon.Web.Gettext, split_module_by: [:locale, :domain]
config :ex_cldr, config :ex_cldr,
default_locale: "en", default_locale: "en",
@ -206,7 +215,8 @@ config :mobilizon, :activitypub,
# One day # One day
actor_stale_period: 3_600 * 48, actor_stale_period: 3_600 * 48,
actor_key_rotation_delay: 3_600 * 48, actor_key_rotation_delay: 3_600 * 48,
sign_object_fetches: true sign_object_fetches: true,
stale_actor_search_exclusion_after: 3_600 * 24 * 7
config :mobilizon, Mobilizon.Service.Geospatial, service: Mobilizon.Service.Geospatial.Nominatim config :mobilizon, Mobilizon.Service.Geospatial, service: Mobilizon.Service.Geospatial.Nominatim
@ -290,6 +300,7 @@ config :mobilizon, Oban,
crontab: [ crontab: [
{"@hourly", Mobilizon.Service.Workers.BuildSiteMap, queue: :background}, {"@hourly", Mobilizon.Service.Workers.BuildSiteMap, queue: :background},
{"17 4 * * *", Mobilizon.Service.Workers.RefreshGroups, queue: :background}, {"17 4 * * *", Mobilizon.Service.Workers.RefreshGroups, queue: :background},
{"36 * * * *", Mobilizon.Service.Workers.RefreshInstances, queue: :background},
{"@hourly", Mobilizon.Service.Workers.CleanOrphanMediaWorker, queue: :background}, {"@hourly", Mobilizon.Service.Workers.CleanOrphanMediaWorker, queue: :background},
{"@hourly", Mobilizon.Service.Workers.CleanUnconfirmedUsersWorker, queue: :background}, {"@hourly", Mobilizon.Service.Workers.CleanUnconfirmedUsersWorker, queue: :background},
{"@hourly", Mobilizon.Service.Workers.ExportCleanerWorker, queue: :background}, {"@hourly", Mobilizon.Service.Workers.ExportCleanerWorker, queue: :background},
@ -336,6 +347,8 @@ config :mobilizon, :exports,
Mobilizon.Service.Export.Participants.ODS Mobilizon.Service.Export.Participants.ODS
] ]
config :mobilizon, :analytics, providers: []
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs" import_config "#{config_env()}.exs"

View File

@ -67,7 +67,7 @@ config :phoenix, :stacktrace_depth, 20
# Initialize plugs at runtime for faster development compilation # Initialize plugs at runtime for faster development compilation
config :phoenix, :plug_init_mode, :runtime config :phoenix, :plug_init_mode, :runtime
config :mobilizon, Mobilizon.Web.Email.Mailer, adapter: Bamboo.LocalAdapter config :mobilizon, Mobilizon.Web.Email.Mailer, adapter: Swoosh.Adapters.Local
# Configure your database # Configure your database
config :mobilizon, Mobilizon.Storage.Repo, config :mobilizon, Mobilizon.Storage.Repo,

View File

@ -43,9 +43,8 @@ config :mobilizon, Mobilizon.Storage.Repo,
pool_size: 10 pool_size: 10
config :mobilizon, Mobilizon.Web.Email.Mailer, config :mobilizon, Mobilizon.Web.Email.Mailer,
adapter: Bamboo.SMTPAdapter, adapter: Swoosh.Adapters.SMTP,
server: System.get_env("MOBILIZON_SMTP_SERVER", "localhost"), relay: System.get_env("MOBILIZON_SMTP_SERVER", "localhost"),
hostname: System.get_env("MOBILIZON_SMTP_HOSTNAME", "localhost"),
port: System.get_env("MOBILIZON_SMTP_PORT", "25"), port: System.get_env("MOBILIZON_SMTP_PORT", "25"),
username: System.get_env("MOBILIZON_SMTP_USERNAME", nil), username: System.get_env("MOBILIZON_SMTP_USERNAME", nil),
password: System.get_env("MOBILIZON_SMTP_PASSWORD", nil), password: System.get_env("MOBILIZON_SMTP_PASSWORD", nil),

View File

@ -18,22 +18,27 @@ config :mobilizon, :cldr,
locales: [ locales: [
"ar", "ar",
"be", "be",
"bn",
"ca", "ca",
"cs", "cs",
"cy",
"de", "de",
"en", "en",
"es", "es",
"fa",
"fi", "fi",
"fr", "fr",
"gd",
"gl", "gl",
"hu", "hu",
"id",
"it", "it",
"ja", "ja",
"nl", "nl",
"nn", "nn",
"oc",
"pl", "pl",
"pt", "pt",
"ru", "ru",
"sv" "sv",
"zh_Hant"
] ]

View File

@ -54,7 +54,7 @@ config :mobilizon, :ldap,
bind_uid: System.get_env("LDAP_BIND_UID"), bind_uid: System.get_env("LDAP_BIND_UID"),
bind_password: System.get_env("LDAP_BIND_PASSWORD") bind_password: System.get_env("LDAP_BIND_PASSWORD")
config :mobilizon, Mobilizon.Web.Email.Mailer, adapter: Bamboo.TestAdapter config :mobilizon, Mobilizon.Web.Email.Mailer, adapter: Swoosh.Adapters.Test
config :mobilizon, Mobilizon.Web.Upload, filters: [], link_name: false config :mobilizon, Mobilizon.Web.Upload, filters: [], link_name: false

View File

@ -12,7 +12,7 @@ RUN yarn install --network-timeout 100000 \
&& yarn run build && yarn run build
# Then, build the application binary # Then, build the application binary
FROM elixir:1.12-alpine AS builder FROM elixir:1.13-alpine AS builder
RUN apk add --no-cache build-base git cmake RUN apk add --no-cache build-base git cmake
@ -49,11 +49,14 @@ LABEL org.opencontainers.image.title="mobilizon" \
org.opencontainers.image.revision=$VCS_REF \ org.opencontainers.image.revision=$VCS_REF \
org.opencontainers.image.created=$BUILD_DATE org.opencontainers.image.created=$BUILD_DATE
RUN apk add --no-cache openssl ca-certificates ncurses-libs file postgresql-client libgcc libstdc++ imagemagick python3 py3-pip py3-pillow py3-cffi py3-brotli gcc musl-dev python3-dev pango libxslt-dev RUN apk add --no-cache curl openssl ca-certificates ncurses-libs file postgresql-client libgcc libstdc++ imagemagick python3 py3-pip py3-pillow py3-cffi py3-brotli gcc g++ musl-dev python3-dev pango libxslt-dev ttf-cantarell
RUN pip install weasyprint pyexcel-ods3 RUN pip install weasyprint pyexcel-ods3
RUN mkdir -p /var/lib/mobilizon/uploads && chown nobody:nobody /var/lib/mobilizon/uploads RUN mkdir -p /var/lib/mobilizon/uploads && chown nobody:nobody /var/lib/mobilizon/uploads
RUN mkdir -p /var/lib/mobilizon/timezones && chown nobody:nobody /var/lib/mobilizon/timezones RUN mkdir -p /var/lib/mobilizon/uploads/exports/{csv,pdf,ods} && chown -R nobody:nobody /var/lib/mobilizon/uploads/exports
RUN mkdir -p /var/lib/mobilizon/timezones
RUN curl -L 'https://packages.joinmobilizon.org/tz_world/timezones-geodata.dets' -o /var/lib/mobilizon/timezones/timezones-geodata.dets
RUN chown nobody:nobody /var/lib/mobilizon/timezones
RUN mkdir -p /etc/mobilizon && chown nobody:nobody /etc/mobilizon RUN mkdir -p /etc/mobilizon && chown nobody:nobody /etc/mobilizon
USER nobody USER nobody

View File

@ -1,15 +1,11 @@
FROM elixir:latest FROM elixir:latest
LABEL maintainer="Thomas Citharel <tcit@tcit.fr>" LABEL maintainer="Thomas Citharel <tcit@tcit.fr>"
ENV REFRESHED_AT=2021-10-04 ENV REFRESHED_AT=2022-04-06
RUN apt-get update -yq && apt-get install -yq build-essential inotify-tools postgresql-client git curl gnupg xvfb libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 cmake exiftool python3-pip python3-setuptools RUN apt-get update -yq && apt-get install -yq build-essential inotify-tools postgresql-client git curl gnupg xvfb libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 cmake exiftool python3-pip python3-setuptools
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash && apt-get install nodejs -yq RUN curl -sL https://deb.nodesource.com/setup_16.x | bash && apt-get install nodejs -yq
RUN npm install -g yarn wait-on RUN npm install -g yarn wait-on
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN mix local.hex --force && mix local.rebar --force RUN mix local.hex --force && mix local.rebar --force
# Weasyprint 53 requires pango >= 1.44.0, which is not available in Stretch. RUN pip3 install -Iv weasyprint pyexcel_ods3
# TODO: Remove the version requirement when elixir:latest is based on Bullseye
# https://github.com/erlang/docker-erlang-otp/issues/362
# https://github.com/Kozea/WeasyPrint/issues/1384
RUN pip3 install -Iv weasyprint==52 pyexcel_ods3
RUN curl https://dbip.mirror.framasoft.org/files/dbip-city-lite-latest.mmdb --output GeoLite2-City.mmdb -s && mkdir -p /usr/share/GeoIP && mv GeoLite2-City.mmdb /usr/share/GeoIP/ RUN curl https://dbip.mirror.framasoft.org/files/dbip-city-lite-latest.mmdb --output GeoLite2-City.mmdb -s && mkdir -p /usr/share/GeoIP && mv GeoLite2-City.mmdb /usr/share/GeoIP/

View File

@ -29,7 +29,6 @@ module.exports = {
}, },
], ],
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"cypress/no-unnecessary-waiting": "off",
"vue/max-len": [ "vue/max-len": [
"off", "off",
{ {

View File

@ -1 +1,6 @@
{} {
"trailingComma": "es5",
"semi": true,
"singleQuote": false,
"bracketSpacing": true
}

View File

@ -1,6 +1,6 @@
{ {
"name": "mobilizon", "name": "mobilizon",
"version": "2.0.0", "version": "2.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
@ -8,7 +8,7 @@
"test:unit": "LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 TZ=UTC vue-cli-service test:unit", "test:unit": "LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 TZ=UTC vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e", "test:e2e": "vue-cli-service test:e2e",
"lint": "vue-cli-service lint", "lint": "vue-cli-service lint",
"build:assets": "vue-cli-service build", "build:assets": "vue-cli-service build --report",
"build:pictures": "bash ./scripts/build/pictures.sh" "build:pictures": "bash ./scripts/build/pictures.sh"
}, },
"dependencies": { "dependencies": {
@ -16,23 +16,35 @@
"@absinthe/socket-apollo-link": "^0.2.1", "@absinthe/socket-apollo-link": "^0.2.1",
"@apollo/client": "^3.3.16", "@apollo/client": "^3.3.16",
"@mdi/font": "^6.1.95", "@mdi/font": "^6.1.95",
"@sentry/tracing": "^6.16.1",
"@sentry/vue": "^6.16.1",
"@tailwindcss/line-clamp": "^0.4.0",
"@tiptap/core": "^2.0.0-beta.41", "@tiptap/core": "^2.0.0-beta.41",
"@tiptap/extension-blockquote": "^2.0.0-beta.6", "@tiptap/extension-blockquote": "^2.0.0-beta.25",
"@tiptap/extension-bold": "^2.0.0-beta.24",
"@tiptap/extension-bubble-menu": "^2.0.0-beta.9", "@tiptap/extension-bubble-menu": "^2.0.0-beta.9",
"@tiptap/extension-character-count": "^2.0.0-beta.5", "@tiptap/extension-bullet-list": "^2.0.0-beta.23",
"@tiptap/extension-history": "^2.0.0-beta.5", "@tiptap/extension-document": "^2.0.0-beta.15",
"@tiptap/extension-dropcursor": "^2.0.0-beta.25",
"@tiptap/extension-gapcursor": "^2.0.0-beta.33",
"@tiptap/extension-heading": "^2.0.0-beta.23",
"@tiptap/extension-history": "^2.0.0-beta.21",
"@tiptap/extension-image": "^2.0.0-beta.6", "@tiptap/extension-image": "^2.0.0-beta.6",
"@tiptap/extension-italic": "^2.0.0-beta.24",
"@tiptap/extension-link": "^2.0.0-beta.8", "@tiptap/extension-link": "^2.0.0-beta.8",
"@tiptap/extension-list-item": "^2.0.0-beta.6", "@tiptap/extension-list-item": "^2.0.0-beta.19",
"@tiptap/extension-mention": "^2.0.0-beta.42", "@tiptap/extension-mention": "^2.0.0-beta.42",
"@tiptap/extension-ordered-list": "^2.0.0-beta.6", "@tiptap/extension-ordered-list": "^2.0.0-beta.24",
"@tiptap/extension-paragraph": "^2.0.0-beta.22",
"@tiptap/extension-strike": "^2.0.0-beta.26",
"@tiptap/extension-text": "^2.0.0-beta.15",
"@tiptap/extension-underline": "^2.0.0-beta.7", "@tiptap/extension-underline": "^2.0.0-beta.7",
"@tiptap/starter-kit": "^2.0.0-beta.37",
"@tiptap/vue-2": "^2.0.0-beta.21", "@tiptap/vue-2": "^2.0.0-beta.21",
"@vue-a11y/announcer": "^2.1.0", "@vue-a11y/announcer": "^2.1.0",
"@vue-a11y/skip-to": "^2.1.2", "@vue-a11y/skip-to": "^2.1.2",
"@vue/apollo-option": "4.0.0-alpha.11", "@vue/apollo-option": "4.0.0-alpha.11",
"apollo-absinthe-upload-link": "^1.5.0", "apollo-absinthe-upload-link": "^1.5.0",
"autoprefixer": "^10",
"blurhash": "^1.1.3", "blurhash": "^1.1.3",
"buefy": "^0.9.0", "buefy": "^0.9.0",
"bulma-divider": "^0.2.0", "bulma-divider": "^0.2.0",
@ -44,20 +56,24 @@
"intersection-observer": "^0.12.0", "intersection-observer": "^0.12.0",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"leaflet": "^1.4.0", "leaflet": "^1.4.0",
"leaflet.locatecontrol": "^0.74.0", "leaflet.locatecontrol": "^0.76.0",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"ngeohash": "^0.6.3", "ngeohash": "^0.6.3",
"p-debounce": "^4.0.0", "p-debounce": "^4.0.0",
"phoenix": "^1.6", "phoenix": "^1.6",
"postcss": "^8",
"register-service-worker": "^1.7.2", "register-service-worker": "^1.7.2",
"sanitize-html": "^2.5.3", "sanitize-html": "^2.5.3",
"tailwindcss": "^3",
"tippy.js": "^6.2.3", "tippy.js": "^6.2.3",
"unfetch": "^4.2.0", "unfetch": "^4.2.0",
"v-tooltip": "^2.1.3", "v-tooltip": "^2.1.3",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-class-component": "^7.2.3", "vue-class-component": "^7.2.3",
"vue-i18n": "^8.14.0", "vue-i18n": "^8.14.0",
"vue-matomo": "^4.1.0",
"vue-meta": "^2.3.1", "vue-meta": "^2.3.1",
"vue-plausible": "^1.3.1",
"vue-property-decorator": "^9.0.0", "vue-property-decorator": "^9.0.0",
"vue-router": "^3.1.6", "vue-router": "^3.1.6",
"vue-scrollto": "^2.17.1", "vue-scrollto": "^2.17.1",
@ -65,9 +81,10 @@
"vuedraggable": "^2.24.3" "vuedraggable": "^2.24.3"
}, },
"devDependencies": { "devDependencies": {
"@rushstack/eslint-patch": "^1.1.0",
"@types/jest": "^27.0.2", "@types/jest": "^27.0.2",
"@types/leaflet": "^1.5.2", "@types/leaflet": "^1.5.2",
"@types/leaflet.locatecontrol": "^0.60.7", "@types/leaflet.locatecontrol": "^0.74",
"@types/lodash": "^4.14.141", "@types/lodash": "^4.14.141",
"@types/ngeohash": "^0.6.2", "@types/ngeohash": "^0.6.2",
"@types/phoenix": "^1.5.2", "@types/phoenix": "^1.5.2",
@ -78,22 +95,19 @@
"@types/sanitize-html": "^2.5.0", "@types/sanitize-html": "^2.5.0",
"@typescript-eslint/eslint-plugin": "^5.3.0", "@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0", "@typescript-eslint/parser": "^5.3.0",
"@vue/cli-plugin-babel": "~5.0.0-rc.0", "@vue/cli-plugin-babel": "~5.0.4",
"@vue/cli-plugin-e2e-cypress": "~5.0.0-rc.0", "@vue/cli-plugin-eslint": "~5.0.4",
"@vue/cli-plugin-eslint": "~5.0.0-rc.0", "@vue/cli-plugin-pwa": "~5.0.4",
"@vue/cli-plugin-pwa": "~5.0.0-rc.0", "@vue/cli-plugin-router": "~5.0.4",
"@vue/cli-plugin-router": "~5.0.0-rc.0", "@vue/cli-plugin-typescript": "~5.0.4",
"@vue/cli-plugin-typescript": "~5.0.0-rc.0", "@vue/cli-plugin-unit-jest": "~5.0.4",
"@vue/cli-plugin-unit-jest": "~5.0.0-rc.0", "@vue/cli-service": "~5.0.4",
"@vue/cli-service": "~5.0.0-rc.0", "@vue/eslint-config-typescript": "^10.0.0",
"@vue/eslint-config-typescript": "^9.0.0",
"@vue/test-utils": "^1.1.0", "@vue/test-utils": "^1.1.0",
"@vue/vue2-jest": "^27.0.0-alpha.3", "@vue/vue2-jest": "^27.0.0-alpha.3",
"@vue/vue3-jest": "^27.0.0-alpha.1", "@vue/vue3-jest": "^27.0.0-alpha.1",
"cypress": "^8.3.0",
"eslint": "^8.2.0", "eslint": "^8.2.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-cypress": "^2.10.3",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3", "eslint-plugin-vue": "^8.0.3",
@ -102,11 +116,12 @@
"jest-junit": "^13.0.0", "jest-junit": "^13.0.0",
"mock-apollo-client": "^1.1.0", "mock-apollo-client": "^1.1.0",
"prettier": "^2.2.1", "prettier": "^2.2.1",
"prettier-eslint": "^13.0.0", "prettier-eslint": "^14.0.0",
"sass": "^1.34.1", "sass": "^1.34.1",
"sass-loader": "^12.0.0", "sass-loader": "^12.0.0",
"ts-jest": "27", "ts-jest": "27",
"typescript": "~4.4.3", "typescript": "~4.5.5",
"vue-cli-plugin-tailwind": "~3.0.0",
"vue-i18n-extract": "^2.0.4", "vue-i18n-extract": "^2.0.4",
"vue-template-compiler": "^2.6.11", "vue-template-compiler": "^2.6.11",
"webpack-cli": "^4.7.0" "webpack-cli": "^4.7.0"

6
js/postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@ -203,6 +203,16 @@ export default class App extends Vue {
this.interval = undefined; this.interval = undefined;
} }
@Watch("config")
async initializeStatistics(config: IConfig) {
if (config) {
const { statistics } = (await import("./services/statistics")) as {
statistics: (config: IConfig, environment: Record<string, any>) => void;
};
statistics(config, { router: this.$router, version: config.version });
}
}
@Watch("$route", { immediate: true }) @Watch("$route", { immediate: true })
updateAnnouncement(route: Route): void { updateAnnouncement(route: Route): void {
const pageTitle = this.extractPageTitleFromRoute(route); const pageTitle = this.extractPageTitleFromRoute(route);
@ -216,8 +226,12 @@ export default class App extends Vue {
// Set the focus to the router view // Set the focus to the router view
// https://marcus.io/blog/accessible-routing-vuejs // https://marcus.io/blog/accessible-routing-vuejs
setTimeout(() => { setTimeout(() => {
const focusTarget = this.routerView?.$el as HTMLElement; const focusTarget = (
if (focusTarget) { this.routerView?.$refs?.componentFocusTarget !== undefined
? this.routerView?.$refs?.componentFocusTarget
: this.routerView?.$el
) as HTMLElement;
if (focusTarget && focusTarget instanceof Element) {
// Make focustarget programmatically focussable // Make focustarget programmatically focussable
focusTarget.setAttribute("tabindex", "-1"); focusTarget.setAttribute("tabindex", "-1");

View File

@ -70,6 +70,9 @@ export const typePolicies: TypePolicies = {
participantStats: { merge: replaceMergePolicy }, participantStats: { merge: replaceMergePolicy },
}, },
}, },
Instance: {
keyFields: ["domain"],
},
RootQueryType: { RootQueryType: {
fields: { fields: {
relayFollowers: paginatedLimitPagination<IFollower>(), relayFollowers: paginatedLimitPagination<IFollower>(),

9
js/src/assets/logo.svg Normal file
View File

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60">
<path style="opacity:0;fill:#fea72b;fill-opacity:1;stroke:none;stroke-opacity:0" d="M-5.801-6.164h72.69v72.871h-72.69z" />
<g data-name="Calque 2">
<g data-name="header">
<path d="M26.58 27.06q0 8-4.26 12.3a12.21 12.21 0 0 1-9 3.42 12.21 12.21 0 0 1-9-3.42Q0 35.1 0 27.06q0-8.04 4.26-12.3a12.21 12.21 0 0 1 9-3.42 12.21 12.21 0 0 1 9 3.42q4.32 4.24 4.32 12.3zM13.29 17q-5.67 0-5.67 10.06t5.67 10.08q5.71 0 5.71-10.08T13.29 17z" style="fill:#3a384c;fill-opacity:1" transform="translate(14.627 5.256) scale(1.15671)" />
<path d="M9 6.78a7.37 7.37 0 0 1-.6-3 7.37 7.37 0 0 1 .6-3A8.09 8.09 0 0 1 12.83 0a7.05 7.05 0 0 1 3.69.84 7.37 7.37 0 0 1 .6 3 7.37 7.37 0 0 1-.6 3 7.46 7.46 0 0 1-3.87.84A6.49 6.49 0 0 1 9 6.78z" style="fill:#fff" transform="translate(14.627 5.256) scale(1.15671)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 920 B

View File

@ -0,0 +1,5 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -7,72 +7,18 @@
@import "styles/vue-announcer.scss"; @import "styles/vue-announcer.scss";
@import "styles/vue-skip-to.scss"; @import "styles/vue-skip-to.scss";
// a {
// color: $violet-2;
// }
.title {
margin: 30px auto 45px;
}
.subtitle {
background: $secondary;
display: inline;
padding: 3px 8px;
margin: 15px auto 30px;
}
a.out, a.out,
.content a, .content a,
.ProseMirror a { .ProseMirror a {
text-decoration: underline; text-decoration: underline;
text-decoration-color: $secondary; text-decoration-color: #ed8d07;
text-decoration-thickness: 2px; text-decoration-thickness: 2px;
} }
.step-content {
height: auto;
}
main {
> section > .columns {
min-height: 50vh;
}
> section {
&.container {
min-height: 80vh;
}
}
> .container {
background: $whitest;
min-height: 70vh;
}
> #homepage {
background: $whitest;
#featured_events {
background: $whitest;
}
#picture {
.container,
.section {
background: $whitest;
}
}
> .container {
min-height: 25vh;
}
}
}
.section { .section {
padding: 1rem 1% 4rem; padding: 1rem 1% 4rem;
} }
figure img.is-rounded {
border: 1px solid $chapril_blue;
}
$color-black: #000; $color-black: #000;
.mention { .mention {
@ -115,16 +61,11 @@ body {
overflow-x: hidden; overflow-x: hidden;
} }
#mobilizon { #mobilizon > .container > .message {
> main {
background: $body-background-color;
}
> .container > .message {
margin: 1rem auto auto; margin: 1rem auto auto;
.message-header { .message-header {
button.delete { button.delete {
background: $chapril_grey; background: #4a4a4a;
}
} }
} }
} }
@ -141,7 +82,7 @@ $list-radius: $radius !default;
$list-item-border: 1px solid $border !default; $list-item-border: 1px solid $border !default;
$list-item-color: $text !default; $list-item-color: $text !default;
$list-item-active-background-color: $purple-1 !default; $list-item-active-background-color: $link !default;
$list-item-active-color: $link-invert !default; $list-item-active-color: $link-invert !default;
$list-item-hover-background-color: $background !default; $list-item-hover-background-color: $background !default;
@ -176,74 +117,16 @@ a.list-item {
.setting-title { .setting-title {
margin-top: 2rem; margin-top: 2rem;
margin-bottom: 1rem; margin-bottom: 1rem;
h1,
h2,
h3,
h4,
h5,
h6 {
background: $secondary;
color: $white;
span {
background: $secondary !important;
color: $white !important;
}
}
h2 { h2 {
display: inline; display: inline;
background: $secondary;
padding: 2px 7.5px; padding: 2px 7.5px;
text-transform: uppercase; text-transform: uppercase;
font-size: 1.25rem; font-size: 1.25rem;
} }
} }
.hero-body {
background-color: $chapril_blue_light;
}
.columns {
background: $whitest;
}
.setting-menu-item {
background-color: $yellow-4;
.router-link-active,
.router-link-active {
background-color: $info;
color: $white;
a {
color: $white;
font-weight: 600 !important;
text-decoration: none;
}
}
}
.date-component-container {
.datetime-container {
margin-right: 1em;
}
}
.time.datetime-container {
color: $white;
background: $chapril_blue_light;
span.month {
color: $whitest;
}
}
/**
footer
*/
footer.footer[data-v-40ab164b] span.select select {
background: $chapril_blue_light;
color: $footer-text-color;
}
@mixin focus() { @mixin focus() {
&:focus { &:focus {
border: 2px solid black; border: 2px solid black;

View File

@ -1,118 +0,0 @@
<template>
<b-autocomplete
:data="baseData"
:placeholder="$t('Actor')"
v-model="name"
field="preferredUsername"
:loading="$apollo.loading"
check-infinite-scroll
@typing="getAsyncData"
@select="handleSelect"
@infinite-scroll="getAsyncData"
>
<template #default="props">
<div class="media">
<div class="media-left">
<img
width="32"
:src="props.option.avatar.url"
v-if="props.option.avatar"
alt=""
/>
<b-icon v-else icon="account-circle" />
</div>
<div class="media-content">
<span v-if="props.option.name">
{{ props.option.name }}
<br />
<small>{{ `@${props.option.preferredUsername}` }}</small>
<small v-if="props.option.domain">{{
`@${props.option.domain}`
}}</small>
</span>
<span v-else>
{{ `@${props.option.preferredUsername}` }}
</span>
</div>
</div>
</template>
<template slot="footer">
<span class="has-text-grey" v-show="page > totalPages">
Thats it! No more movies found.
</span>
</template>
</b-autocomplete>
</template>
<script lang="ts">
import { Component, Model, Vue, Watch } from "vue-property-decorator";
import debounce from "lodash/debounce";
import { IPerson } from "@/types/actor";
import { SEARCH_PERSONS } from "@/graphql/search";
import { Paginate } from "@/types/paginate";
const SEARCH_PERSON_LIMIT = 10;
@Component
export default class ActorAutoComplete extends Vue {
@Model("change", { type: Object }) readonly defaultSelected!: IPerson | null;
baseData: IPerson[] = [];
selected: IPerson | null = this.defaultSelected;
name: string = this.defaultSelected
? this.defaultSelected.preferredUsername
: "";
page = 1;
totalPages = 1;
mounted(): void {
this.selected = this.defaultSelected;
}
data(): Record<string, unknown> {
return {
getAsyncData: debounce(this.doGetAsyncData, 500),
};
}
@Watch("defaultSelected")
updateDefaultSelected(defaultSelected: IPerson): void {
console.log("update defaultSelected", defaultSelected);
this.selected = defaultSelected;
this.name = defaultSelected.preferredUsername;
}
handleSelect(selected: IPerson): void {
this.selected = selected;
this.$emit("change", selected);
}
async doGetAsyncData(name: string): Promise<void> {
this.baseData = [];
if (this.name !== name) {
this.name = name;
this.page = 1;
}
if (!name.length) {
this.page = 1;
this.totalPages = 1;
return;
}
const {
data: { searchPersons },
} = await this.$apollo.query<{ searchPersons: Paginate<IPerson> }>({
query: SEARCH_PERSONS,
variables: {
searchText: this.name,
page: this.page,
limit: SEARCH_PERSON_LIMIT,
},
});
this.totalPages = Math.ceil(searchPersons.total / SEARCH_PERSON_LIMIT);
this.baseData.push(...searchPersons.elements);
}
}
</script>

View File

@ -1,31 +1,86 @@
<template> <template>
<div class="media" style="align-items: top" dir="auto"> <div
<div class="media-left"> class="bg-white rounded-lg flex space-x-4 items-center"
<figure class="image is-32x32" v-if="actor.avatar"> :class="{ 'flex-col p-4 shadow-md sm:p-8 pb-10 w-80': !inline }"
<img class="is-rounded" :src="actor.avatar.url" alt="" /> >
<div>
<figure class="w-12 h-12" v-if="actor.avatar">
<img
class="rounded-lg"
:src="actor.avatar.url"
alt=""
width="48"
height="48"
/>
</figure> </figure>
<b-icon v-else size="is-medium" icon="account-circle" /> <b-icon
v-else
:size="inline ? 'is-medium' : 'is-large'"
icon="account-circle"
class="ltr:-mr-0.5 rtl:-ml-0.5"
/>
</div> </div>
<div :class="{ 'text-center': !inline }" class="overflow-hidden w-full">
<div class="media-content"> <h5
<p> class="text-xl font-medium violet-title tracking-tight text-gray-900 whitespace-pre-line line-clamp-2"
{{ actor.name || `@${usernameWithDomain(actor)}` }} >
</p> {{ displayName(actor) }}
<p class="has-text-grey-dark" v-if="actor.name"> </h5>
<p class="text-gray-500 truncate" v-if="actor.name">
<span dir="ltr">@{{ usernameWithDomain(actor) }}</span> <span dir="ltr">@{{ usernameWithDomain(actor) }}</span>
</p> </p>
<div <div
v-if="full" v-if="full"
class="summary" class="only-first-child"
:class="{ limit: limit }" :class="{
'line-clamp-3': limit,
'line-clamp-10': !limit,
}"
v-html="actor.summary" v-html="actor.summary"
/> />
</div> </div>
</div> </div>
<!-- <div
class="p-4 bg-white rounded-lg shadow-md sm:p-8 flex items-center space-x-4"
dir="auto"
>
<div class="flex-shrink-0">
<figure class="w-12 h-12" v-if="actor.avatar">
<img
class="rounded-lg"
:src="actor.avatar.url"
alt=""
width="48"
height="48"
/>
</figure>
<b-icon
v-else
size="is-large"
icon="account-circle"
class="ltr:-mr-0.5 rtl:-ml-0.5"
/>
</div>
<div class="flex-1 min-w-0">
<h5 class="text-xl font-medium violet-title tracking-tight text-gray-900">
{{ displayName(actor) }}
</h5>
<p class="text-gray-500 truncate" v-if="actor.name">
<span dir="ltr">@{{ usernameWithDomain(actor) }}</span>
</p>
<div
v-if="full"
class="line-clamp-3"
:class="{ limit: limit }"
v-html="actor.summary"
/>
</div>
</div> -->
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator"; import { Component, Vue, Prop } from "vue-property-decorator";
import { IActor, usernameWithDomain } from "../../types/actor"; import { displayName, IActor, usernameWithDomain } from "../../types/actor";
@Component @Component
export default class ActorCard extends Vue { export default class ActorCard extends Vue {
@ -33,140 +88,19 @@ export default class ActorCard extends Vue {
@Prop({ required: false, type: Boolean, default: false }) full!: boolean; @Prop({ required: false, type: Boolean, default: false }) full!: boolean;
@Prop({ required: false, type: Boolean, default: false }) inline!: boolean;
@Prop({ required: false, type: Boolean, default: false }) popover!: boolean; @Prop({ required: false, type: Boolean, default: false }) popover!: boolean;
@Prop({ required: false, type: Boolean, default: true }) limit!: boolean; @Prop({ required: false, type: Boolean, default: true }) limit!: boolean;
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
displayName = displayName;
} }
</script> </script>
<style lang="scss" scoped> <style scoped>
.summary.limit { .only-first-child ::v-deep :not(:first-child) {
max-width: 25rem; display: none;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
</style>
<style lang="scss">
@use "@/styles/_mixins" as *;
.media {
.media-left {
margin-right: initial;
@include margin-right(1rem);
}
}
.tooltip {
display: block !important;
z-index: 10000;
.tooltip-inner {
background: black;
color: white;
border-radius: 16px;
padding: 5px 10px 4px;
}
.tooltip-arrow {
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 5px;
border-color: black;
z-index: 1;
}
&[x-placement^="top"] {
margin-bottom: 5px;
.tooltip-arrow {
border-width: 5px 5px 0 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
bottom: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
}
&[x-placement^="bottom"] {
margin-top: 5px;
.tooltip-arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-top-color: transparent !important;
top: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
}
&[x-placement^="right"] {
@include margin-left(5px);
.tooltip-arrow {
border-width: 5px 5px 5px 0;
border-left-color: transparent !important;
border-top-color: transparent !important;
border-bottom-color: transparent !important;
left: -5px;
top: calc(50% - 5px);
@include margin-left(0);
@include margin-right(0);
}
}
&[x-placement^="left"] {
@include margin-right(5px);
.tooltip-arrow {
border-width: 5px 0 5px 5px;
border-top-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
right: -5px;
top: calc(50% - 5px);
@include margin-left(0);
@include margin-right(0);
}
}
&.popover {
$color: #f9f9f9;
.popover-inner {
background: lighten($background-color, 65%);
color: black;
padding: 24px;
border-radius: 5px;
box-shadow: 0 5px 30px rgba(black, 0.1);
}
.popover-arrow {
border-color: $color;
}
}
&[aria-hidden="true"] {
visibility: hidden;
opacity: 0;
transition: opacity 0.15s, visibility 0.15s;
}
&[aria-hidden="false"] {
visibility: visible;
opacity: 1;
transition: opacity 0.15s;
}
} }
</style> </style>

View File

@ -1,28 +1,33 @@
<template> <template>
<div class="actor-inline"> <div class="inline-flex items-start">
<div class="actor-avatar"> <div class="flex-none mr-2">
<figure class="image is-24x24" v-if="actor.avatar"> <figure class="image is-48x48" v-if="actor.avatar">
<img class="is-rounded" :src="actor.avatar.url" alt="" /> <img class="is-rounded" :src="actor.avatar.url" alt="" />
</figure> </figure>
<b-icon v-else size="is-medium" icon="account-circle" /> <b-icon v-else size="is-large" icon="account-circle" />
</div> </div>
<div class="actor-name"> <div class="flex-auto">
<p> <p class="text-base line-clamp-3 md:line-clamp-2 max-w-xl">
{{ actor.name || `@${usernameWithDomain(actor)}` }} {{ displayName(actor) }}
</p>
<p class="text-sm text-gray-500 truncate">
@{{ usernameWithDomain(actor) }}
</p> </p>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator"; import { Component, Vue, Prop } from "vue-property-decorator";
import { IActor, usernameWithDomain } from "../../types/actor"; import { displayName, IActor, usernameWithDomain } from "../../types/actor";
@Component @Component
export default class ActorInline extends Vue { export default class ActorInline extends Vue {
@Prop({ required: true, type: Object }) actor!: IActor; @Prop({ required: true, type: Object }) actor!: IActor;
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
displayName = displayName;
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,6 +1,6 @@
<template> <template>
<div <div
class="ellipsis" class="truncate"
:title=" :title="
isDescriptionDifferentFromLocality isDescriptionDifferentFromLocality
? `${physicalAddress.description}, ${physicalAddress.locality}` ? `${physicalAddress.description}, ${physicalAddress.locality}`
@ -8,8 +8,7 @@
" "
> >
<b-icon icon="map-marker" /> <b-icon icon="map-marker" />
<span v-if="isDescriptionDifferentFromLocality"> <span v-if="physicalAddress.locality">
{{ physicalAddress.description }},
{{ physicalAddress.locality }} {{ physicalAddress.locality }}
</span> </span>
<span v-else> <span v-else>
@ -35,11 +34,3 @@ export default class InlineAddress extends Vue {
} }
} }
</script> </script>
<style lang="scss" scoped>
.ellipsis {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>

View File

@ -1,262 +0,0 @@
<template>
<div>
<b-table
v-show="relayFollowers.elements.length > 0"
:data="relayFollowers.elements"
:loading="$apollo.queries.relayFollowers.loading"
ref="table"
:checked-rows.sync="checkedRows"
detailed
:show-detail-icon="false"
paginated
backend-pagination
:current-page.sync="page"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
:total="relayFollowers.total"
:per-page="FOLLOWERS_PER_PAGE"
@page-change="onFollowersPageChange"
checkable
checkbox-position="left"
>
<b-table-column
field="actor.id"
label="ID"
width="40"
numeric
v-slot="props"
>{{ props.row.actor.id }}</b-table-column
>
<b-table-column
field="actor.type"
:label="$t('Type')"
width="80"
v-slot="props"
>
<b-icon icon="lan" v-if="RelayMixin.isInstance(props.row.actor)" />
<b-icon icon="account-circle" v-else />
</b-table-column>
<b-table-column
field="approved"
:label="$t('Status')"
width="100"
sortable
centered
v-slot="props"
>
<span
:class="`tag ${props.row.approved ? 'is-success' : 'is-danger'}`"
>{{ props.row.approved ? $t("Accepted") : $t("Pending") }}</span
>
</b-table-column>
<b-table-column field="actor.domain" :label="$t('Domain')" sortable>
<template v-slot:default="props">
<a
@click="toggle(props.row)"
v-if="RelayMixin.isInstance(props.row.actor)"
>{{ props.row.actor.domain }}</a
>
<a @click="toggle(props.row)" v-else>{{
`${props.row.actor.preferredUsername}@${props.row.actor.domain}`
}}</a>
</template>
</b-table-column>
<b-table-column
field="targetActor.updatedAt"
:label="$t('Date')"
sortable
v-slot="props"
>
<span
:title="$options.filters.formatDateTimeString(props.row.updatedAt)"
>{{
formatDistanceToNow(new Date(props.row.updatedAt), {
locale: $dateFnsLocale,
})
}}</span
></b-table-column
>
<template #detail="props">
<article>
<div class="content">
<strong>{{ props.row.actor.name }}</strong>
<small v-if="props.row.actor.preferredUsername !== 'relay'"
>@{{ props.row.actor.preferredUsername }}</small
>
<p v-html="props.row.actor.summary" />
</div>
</article>
</template>
<template slot="bottom-left" v-if="checkedRows.length > 0">
<div class="buttons">
<b-button
@click="acceptRelays"
type="is-success"
v-if="checkedRowsHaveAtLeastOneToApprove"
>
{{
$tc(
"No instance to approve|Approve instance|Approve {number} instances",
checkedRows.length,
{ number: checkedRows.length }
)
}}
</b-button>
<b-button @click="rejectRelays" type="is-danger">
{{
$tc(
"No instance to reject|Reject instance|Reject {number} instances",
checkedRows.length,
{ number: checkedRows.length }
)
}}
</b-button>
</div>
</template>
</b-table>
<b-message type="is-danger" v-if="relayFollowers.elements.length === 0">{{
$t("No instance follows your instance yet.")
}}</b-message>
</div>
</template>
<script lang="ts">
import { Component, Mixins } from "vue-property-decorator";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { formatDistanceToNow } from "date-fns";
import {
ACCEPT_RELAY,
REJECT_RELAY,
RELAY_FOLLOWERS,
} from "../../graphql/admin";
import { IFollower } from "../../types/actor/follower.model";
import RelayMixin from "../../mixins/relay";
import RouteName from "@/router/name";
import { Paginate } from "@/types/paginate";
const FOLLOWERS_PER_PAGE = 10;
@Component({
apollo: {
relayFollowers: {
query: RELAY_FOLLOWERS,
variables() {
return {
page: this.page,
limit: FOLLOWERS_PER_PAGE,
};
},
},
},
metaInfo() {
return {
title: this.$t("Followers") as string,
titleTemplate: "%s | Mobilizon",
};
},
})
export default class Followers extends Mixins(RelayMixin) {
RelayMixin = RelayMixin;
formatDistanceToNow = formatDistanceToNow;
relayFollowers: Paginate<IFollower> = { elements: [], total: 0 };
checkedRows: IFollower[] = [];
FOLLOWERS_PER_PAGE = FOLLOWERS_PER_PAGE;
toggle(row: Record<string, unknown>): void {
this.table.toggleDetails(row);
}
get page(): number {
return parseInt((this.$route.query.page as string) || "1", 10);
}
set page(page: number) {
this.pushRouter(RouteName.RELAY_FOLLOWERS, {
page: page.toString(),
});
}
acceptRelays(): void {
this.checkedRows.forEach((row: IFollower) => {
this.acceptRelay(`${row.actor.preferredUsername}@${row.actor.domain}`);
});
}
rejectRelays(): void {
this.checkedRows.forEach((row: IFollower) => {
this.rejectRelay(`${row.actor.preferredUsername}@${row.actor.domain}`);
});
}
async acceptRelay(address: string): Promise<void> {
try {
await this.$apollo.mutate({
mutation: ACCEPT_RELAY,
variables: {
address,
},
});
await this.$apollo.queries.relayFollowers.refetch();
this.checkedRows = [];
} catch (e: any) {
if (e.message) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
}
}
}
async rejectRelay(address: string): Promise<void> {
try {
await this.$apollo.mutate({
mutation: REJECT_RELAY,
variables: {
address,
},
});
await this.$apollo.queries.relayFollowers.refetch();
this.checkedRows = [];
} catch (e: any) {
if (e.message) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
}
}
}
get checkedRowsHaveAtLeastOneToApprove(): boolean {
return this.checkedRows.some((checkedRow) => !checkedRow.approved);
}
async onFollowersPageChange(page: number): Promise<void> {
this.page = page;
try {
await this.$apollo.queries.relayFollowers.fetchMore({
variables: {
page: this.page,
limit: FOLLOWERS_PER_PAGE,
},
});
} catch (err: any) {
console.error(err);
}
}
}
</script>

View File

@ -1,311 +0,0 @@
<template>
<div>
<form @submit="followRelay">
<b-field
:label="$t('Add an instance')"
custom-class="add-relay"
horizontal
>
<b-field grouped expanded size="is-large">
<p class="control">
<b-input
v-model="newRelayAddress"
:placeholder="$t('Ex: mobilizon.fr')"
/>
</p>
<p class="control">
<b-button type="is-primary" native-type="submit">{{
$t("Add an instance")
}}</b-button>
</p>
</b-field>
</b-field>
</form>
<b-table
v-show="relayFollowings.elements.length > 0"
:data="relayFollowings.elements"
:loading="$apollo.queries.relayFollowings.loading"
ref="table"
:checked-rows.sync="checkedRows"
:is-row-checkable="(row) => row.id !== 3"
detailed
:show-detail-icon="false"
paginated
backend-pagination
:current-page.sync="page"
:aria-next-label="$t('Next page')"
:aria-previous-label="$t('Previous page')"
:aria-page-label="$t('Page')"
:aria-current-label="$t('Current page')"
:total="relayFollowings.total"
:per-page="FOLLOWINGS_PER_PAGE"
@page-change="onFollowingsPageChange"
checkable
checkbox-position="left"
>
<b-table-column
field="targetActor.id"
label="ID"
width="40"
numeric
v-slot="props"
>{{ props.row.targetActor.id }}</b-table-column
>
<b-table-column
field="targetActor.type"
:label="$t('Type')"
width="80"
v-slot="props"
>
<b-icon
icon="lan"
v-if="RelayMixin.isInstance(props.row.targetActor)"
/>
<b-icon icon="account-circle" v-else />
</b-table-column>
<b-table-column
field="approved"
:label="$t('Status')"
width="100"
sortable
centered
v-slot="props"
>
<span
:class="`tag ${props.row.approved ? 'is-success' : 'is-danger'}`"
>{{ props.row.approved ? $t("Accepted") : $t("Pending") }}</span
>
</b-table-column>
<b-table-column field="targetActor.domain" :label="$t('Domain')" sortable>
<template v-slot:default="props">
<a
@click="toggle(props.row)"
v-if="RelayMixin.isInstance(props.row.targetActor)"
>{{ props.row.targetActor.domain }}</a
>
<a @click="toggle(props.row)" v-else>{{
`${props.row.targetActor.preferredUsername}@${props.row.targetActor.domain}`
}}</a>
</template>
</b-table-column>
<b-table-column
field="targetActor.updatedAt"
:label="$t('Date')"
sortable
v-slot="props"
>
<span
:title="$options.filters.formatDateTimeString(props.row.updatedAt)"
>{{
formatDistanceToNow(new Date(props.row.updatedAt), {
locale: $dateFnsLocale,
})
}}</span
></b-table-column
>
<template #detail="props">
<article>
<div class="content">
<strong>{{ props.row.targetActor.name }}</strong>
<small v-if="props.row.actor.preferredUsername !== 'relay'"
>@{{ props.row.targetActor.preferredUsername }}</small
>
<p v-html="props.row.targetActor.summary" />
</div>
</article>
</template>
<template slot="bottom-left" v-if="checkedRows.length > 0">
<b-button @click="removeRelays" type="is-danger">
{{
$tc(
"No instance to remove|Remove instance|Remove {number} instances",
checkedRows.length,
{ number: checkedRows.length }
)
}}
</b-button>
</template>
</b-table>
<b-message type="is-danger" v-if="relayFollowings.total === 0">{{
$t("You don't follow any instances yet.")
}}</b-message>
</div>
</template>
<script lang="ts">
import { Component, Mixins } from "vue-property-decorator";
import { SnackbarProgrammatic as Snackbar } from "buefy";
import { formatDistanceToNow } from "date-fns";
import { ADD_RELAY, REMOVE_RELAY } from "../../graphql/admin";
import { IFollower } from "../../types/actor/follower.model";
import RelayMixin from "../../mixins/relay";
import { RELAY_FOLLOWINGS } from "@/graphql/admin";
import { Paginate } from "@/types/paginate";
import RouteName from "@/router/name";
import { ApolloCache, FetchResult, Reference } from "@apollo/client/core";
import gql from "graphql-tag";
const FOLLOWINGS_PER_PAGE = 10;
@Component({
apollo: {
relayFollowings: {
query: RELAY_FOLLOWINGS,
variables() {
return {
page: this.page,
limit: FOLLOWINGS_PER_PAGE,
};
},
},
},
metaInfo() {
return {
title: this.$t("Followings") as string,
titleTemplate: "%s | Mobilizon",
};
},
})
export default class Followings extends Mixins(RelayMixin) {
newRelayAddress = "";
RelayMixin = RelayMixin;
formatDistanceToNow = formatDistanceToNow;
relayFollowings: Paginate<IFollower> = { elements: [], total: 0 };
FOLLOWINGS_PER_PAGE = FOLLOWINGS_PER_PAGE;
checkedRows: IFollower[] = [];
get page(): number {
return parseInt((this.$route.query.page as string) || "1", 10);
}
set page(page: number) {
this.pushRouter(RouteName.RELAY_FOLLOWINGS, {
page: page.toString(),
});
}
async onFollowingsPageChange(page: number): Promise<void> {
this.page = page;
try {
await this.$apollo.queries.relayFollowings.fetchMore({
variables: {
page: this.page,
limit: FOLLOWINGS_PER_PAGE,
},
});
} catch (err: any) {
console.error(err);
}
}
async followRelay(e: Event): Promise<void> {
e.preventDefault();
try {
await this.$apollo.mutate<{ relayFollowings: Paginate<IFollower> }>({
mutation: ADD_RELAY,
variables: {
address: this.newRelayAddress.trim(), // trim to fix copy and paste domain name spaces and tabs
},
update(
cache: ApolloCache<{ relayFollowings: Paginate<IFollower> }>,
{ data }: FetchResult
) {
cache.modify({
fields: {
relayFollowings(
existingFollowings = { elements: [], total: 0 },
{ readField }
) {
const newFollowingRef = cache.writeFragment({
id: `${data?.addRelay.__typename}:${data?.addRelay.id}`,
data: data?.addRelay,
fragment: gql`
fragment NewFollowing on Follower {
id
}
`,
});
if (
existingFollowings.elements.some(
(ref: Reference) =>
readField("id", ref) === data?.addRelay.id
)
) {
return existingFollowings;
}
return {
total: existingFollowings.total + 1,
elements: [newFollowingRef, ...existingFollowings.elements],
};
},
},
broadcast: false,
});
},
});
this.newRelayAddress = "";
} catch (err: any) {
if (err.message) {
Snackbar.open({
message: err.message,
type: "is-danger",
position: "is-bottom",
});
}
}
}
removeRelays(): void {
this.checkedRows.forEach((row: IFollower) => {
this.removeRelay(row);
});
}
async removeRelay(follower: IFollower): Promise<void> {
const address = `${follower.targetActor.preferredUsername}@${follower.targetActor.domain}`;
try {
await this.$apollo.mutate<{ removeRelay: IFollower }>({
mutation: REMOVE_RELAY,
variables: {
address,
},
update(cache: ApolloCache<{ removeRelay: IFollower }>) {
cache.modify({
fields: {
relayFollowings(existingFollowingRefs, { readField }) {
return {
total: existingFollowingRefs.total - 1,
elements: existingFollowingRefs.elements.filter(
(followingRef: Reference) =>
follower.id !== readField("id", followingRef)
),
};
},
},
});
},
});
await this.$apollo.queries.relayFollowings.refetch();
this.checkedRows = [];
} catch (e: any) {
if (e.message) {
Snackbar.open({
message: e.message,
type: "is-danger",
position: "is-bottom",
});
}
}
}
}
</script>

View File

@ -195,10 +195,18 @@
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator"; import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { Editor, EditorContent, BubbleMenu } from "@tiptap/vue-2"; import { Editor, EditorContent, BubbleMenu } from "@tiptap/vue-2";
import StarterKit from "@tiptap/starter-kit"; import Blockquote from "@tiptap/extension-blockquote";
import BulletList from "@tiptap/extension-bullet-list";
import Heading from "@tiptap/extension-heading";
import Document from "@tiptap/extension-document"; import Document from "@tiptap/extension-document";
import Paragraph from "@tiptap/extension-paragraph"; import Paragraph from "@tiptap/extension-paragraph";
import Bold from "@tiptap/extension-bold";
import Italic from "@tiptap/extension-italic";
import Strike from "@tiptap/extension-strike";
import Text from "@tiptap/extension-text"; import Text from "@tiptap/extension-text";
import Dropcursor from "@tiptap/extension-dropcursor";
import Gapcursor from "@tiptap/extension-gapcursor";
import History from "@tiptap/extension-history";
import { IActor, IPerson, usernameWithDomain } from "../types/actor"; import { IActor, IPerson, usernameWithDomain } from "../types/actor";
import CustomImage from "./Editor/Image"; import CustomImage from "./Editor/Image";
import { UPLOAD_MEDIA } from "../graphql/upload"; import { UPLOAD_MEDIA } from "../graphql/upload";
@ -210,7 +218,6 @@ import OrderedList from "@tiptap/extension-ordered-list";
import ListItem from "@tiptap/extension-list-item"; import ListItem from "@tiptap/extension-list-item";
import Underline from "@tiptap/extension-underline"; import Underline from "@tiptap/extension-underline";
import Link from "@tiptap/extension-link"; import Link from "@tiptap/extension-link";
import CharacterCount from "@tiptap/extension-character-count";
import { AutoDir } from "./Editor/Autodir"; import { AutoDir } from "./Editor/Autodir";
import sanitizeHtml from "sanitize-html"; import sanitizeHtml from "sanitize-html";
@ -269,7 +276,9 @@ export default class EditorComponent extends Vue {
transformPastedHTML: this.transformPastedHTML, transformPastedHTML: this.transformPastedHTML,
}, },
extensions: [ extensions: [
StarterKit, Blockquote,
BulletList,
Heading,
Document, Document,
Paragraph, Paragraph,
Text, Text,
@ -279,12 +288,15 @@ export default class EditorComponent extends Vue {
CustomImage, CustomImage,
AutoDir, AutoDir,
Underline, Underline,
Bold,
Italic,
Strike,
Dropcursor,
Gapcursor,
History,
Link.configure({ Link.configure({
HTMLAttributes: { target: "_blank", rel: "noopener noreferrer ugc" }, HTMLAttributes: { target: "_blank", rel: "noopener noreferrer ugc" },
}), }),
CharacterCount.configure({
limit: this.maxSize,
}),
], ],
injectCSS: false, injectCSS: false,
content: this.value, content: this.value,

View File

@ -7,6 +7,8 @@ import apolloProvider from "@/vue-apollo";
import { IPerson } from "@/types/actor"; import { IPerson } from "@/types/actor";
import pDebounce from "p-debounce"; import pDebounce from "p-debounce";
import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types"; import { NormalizedCacheObject } from "@apollo/client/cache/inmemory/types";
import { MentionOptions } from "@tiptap/extension-mention";
import { Editor } from "@tiptap/core";
const client = const client =
apolloProvider.defaultClient as ApolloClient<NormalizedCacheObject>; apolloProvider.defaultClient as ApolloClient<NormalizedCacheObject>;
@ -24,13 +26,21 @@ const fetchItems = async (query: string): Promise<IPerson[]> => {
const debouncedFetchItems = pDebounce(fetchItems, 200); const debouncedFetchItems = pDebounce(fetchItems, 200);
const mentionOptions: Partial<any> = { const mentionOptions: MentionOptions = {
HTMLAttributes: { HTMLAttributes: {
class: "mention", class: "mention",
dir: "ltr", dir: "ltr",
}, },
renderLabel({ options, node }) {
return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`;
},
suggestion: { suggestion: {
items: async (query: string): Promise<IPerson[]> => { items: async ({
query,
}: {
query: string;
editor: Editor;
}): Promise<IPerson[]> => {
if (query === "") { if (query === "") {
return []; return [];
} }
@ -70,8 +80,12 @@ const mentionOptions: Partial<any> = {
return component.ref?.onKeyDown(props); return component.ref?.onKeyDown(props);
}, },
onExit() { onExit() {
if (popup && popup[0]) {
popup[0].destroy(); popup[0].destroy();
}
if (component) {
component.destroy(); component.destroy();
}
}, },
}; };
}, },

View File

@ -7,7 +7,7 @@
:key="index" :key="index"
@click="selectItem(index)" @click="selectItem(index)"
> >
<actor-card :actor="item" /> <actor-inline :actor="item" />
</button> </button>
</div> </div>
</template> </template>
@ -16,11 +16,11 @@
import { Vue, Component, Prop, Watch } from "vue-property-decorator"; import { Vue, Component, Prop, Watch } from "vue-property-decorator";
import { displayName, usernameWithDomain } from "@/types/actor/actor.model"; import { displayName, usernameWithDomain } from "@/types/actor/actor.model";
import { IPerson } from "@/types/actor"; import { IPerson } from "@/types/actor";
import ActorCard from "../../components/Account/ActorCard.vue"; import ActorInline from "../../components/Account/ActorInline.vue";
@Component({ @Component({
components: { components: {
ActorCard, ActorInline,
}, },
}) })
export default class MentionList extends Vue { export default class MentionList extends Vue {

View File

@ -5,10 +5,14 @@
.ProseMirror { .ProseMirror {
position: relative; position: relative;
}
.ProseMirror {
word-wrap: break-word; word-wrap: break-word;
white-space: pre-wrap; white-space: pre-wrap;
white-space: break-spaces;
-webkit-font-variant-ligatures: none; -webkit-font-variant-ligatures: none;
font-variant-ligatures: none; font-variant-ligatures: none;
font-feature-settings: "liga" 0; /* the above doesn't seem to work in Edge */
& [contenteditable="false"] { & [contenteditable="false"] {
white-space: normal; white-space: normal;
@ -16,14 +20,22 @@
& [contenteditable="false"] [contenteditable="true"] { & [contenteditable="false"] [contenteditable="true"] {
white-space: pre-wrap; white-space: pre-wrap;
} }
pre { & pre {
white-space: pre-wrap; white-space: pre-wrap;
} }
} }
img.ProseMirror-separator {
display: inline !important;
border: none !important;
margin: 0 !important;
width: 1px !important;
height: 1px !important;
}
.ProseMirror-gapcursor { .ProseMirror-gapcursor {
display: none; display: none;
pointer-events: none; pointer-events: none;
position: absolute; position: absolute;
margin: 0;
&:after { &:after {
content: ""; content: "";
@ -40,16 +52,17 @@
visibility: hidden; visibility: hidden;
} }
} }
.ProseMirror-hideselection * { .ProseMirror-hideselection {
&::selection { *::selection {
background: transparent; background: transparent;
} }
&::-moz-selection { *::-moz-selection {
background: transparent; background: transparent;
} }
* {
caret-color: transparent; caret-color: transparent;
} }
}
.ProseMirror-focused .ProseMirror-gapcursor { .ProseMirror-focused .ProseMirror-gapcursor {
display: block; display: block;
} }

View File

@ -48,13 +48,75 @@
$t("Mobilizon") $t("Mobilizon")
}}</a> }}</a>
</i18n> </i18n>
<span v-if="sentryEnabled && sentryReady">
{{
$t(
"We collect your feedback and the error information in order to improve this service."
)
}}</span
>
<span v-else>
{{ {{
$t( $t(
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):" "We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):"
) )
}} }}
</span>
</p> </p>
<div class="content"> <form
v-if="sentryEnabled && sentryReady && !submittedFeedback"
@submit.prevent="sendErrorToSentry"
>
<b-field :label="$t('What happened?')" label-for="what-happened">
<b-input
v-model="feedback"
type="textarea"
id="what-happened"
:placeholder="$t(`I've clicked on X, then on Y`)"
/>
</b-field>
<b-button icon-left="send" native-type="submit" type="is-primary">{{
$t("Send feedback")
}}</b-button>
<p class="content">
{{
$t(
"Please add as many details as possible to help identify the problem."
)
}}
</p>
</form>
<b-message type="is-danger" v-else-if="feedbackError">
<p>
{{
$t(
"Sorry, we wen't able to save your feedback. Don't worry, we'll try to fix this issue anyway."
)
}}
</p>
<i18n path="You may now close this page or {return_to_the_homepage}.">
<template #return_to_the_homepage>
<router-link :to="{ name: RouteName.HOME }">{{
$t("return to the homepage")
}}</router-link>
</template>
</i18n>
</b-message>
<b-message type="is-success" v-else-if="submittedFeedback">
<p>{{ $t("Thanks a lot, your feedback was submitted!") }}</p>
<i18n path="You may now close this page or {return_to_the_homepage}.">
<template #return_to_the_homepage>
<router-link :to="{ name: RouteName.HOME }">{{
$t("return to the homepage")
}}</router-link>
</template>
</i18n>
</b-message>
<div
class="content"
v-if="!(sentryEnabled && sentryReady) || submittedFeedback"
>
<p v-if="submittedFeedback">{{ $t("You may also:") }}</p>
<ul> <ul>
<li> <li>
<a <a
@ -65,7 +127,7 @@
</li> </li>
<li> <li>
<a <a
href="https://framagit.org/framasoft/mobilizon/-/issues/new?issuable_template=Bug" href="https://framagit.org/framasoft/mobilizon/-/issues/"
target="_blank" target="_blank"
>{{ >{{
$t("Open an issue on our bug tracker (advanced users)") $t("Open an issue on our bug tracker (advanced users)")
@ -74,7 +136,7 @@
</li> </li>
</ul> </ul>
</div> </div>
<p class="content"> <p class="content" v-if="!sentryEnabled">
{{ {{
$t( $t(
"Please add as many details as possible to help identify the problem." "Please add as many details as possible to help identify the problem."
@ -89,14 +151,14 @@
<p>{{ $t("Error stacktrace") }}</p> <p>{{ $t("Error stacktrace") }}</p>
<pre>{{ error.stack }}</pre> <pre>{{ error.stack }}</pre>
</details> </details>
<p> <p v-if="!sentryEnabled">
{{ {{
$t( $t(
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback." "The technical details of the error can help developers solve the problem more easily. Please add them to your feedback."
) )
}} }}
</p> </p>
<div class="buttons"> <div class="buttons" v-if="!sentryEnabled">
<b-tooltip <b-tooltip
:label="tooltipConfig.label" :label="tooltipConfig.label"
:type="tooltipConfig.type" :type="tooltipConfig.type"
@ -115,14 +177,20 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { CONTACT } from "@/graphql/config"; import { CONFIG } from "@/graphql/config";
import { checkProviderConfig, convertConfig } from "@/services/statistics";
import { IAnalyticsConfig, IConfig } from "@/types/config.model";
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import { LOGGED_USER } from "@/graphql/user";
import { IUser } from "@/types/current-user.model";
import { ISentryConfiguration } from "@/types/analytics/sentry.model";
import { submitFeedback } from "@/services/statistics/sentry";
import RouteName from "@/router/name";
@Component({ @Component({
apollo: { apollo: {
config: { config: CONFIG,
query: CONTACT, loggedUser: LOGGED_USER,
},
}, },
metaInfo() { metaInfo() {
return { return {
@ -138,7 +206,17 @@ export default class ErrorComponent extends Vue {
copied: "success" | "error" | false = false; copied: "success" | "error" | false = false;
config!: { contact: string | null; name: string }; config!: IConfig;
feedback = "";
submittedFeedback = false;
feedbackError = false;
loggedUser!: IUser;
RouteName = RouteName;
async copyErrorToClipboard(): Promise<void> { async copyErrorToClipboard(): Promise<void> {
try { try {
@ -193,6 +271,56 @@ export default class ErrorComponent extends Vue {
document.body.removeChild(textArea); document.body.removeChild(textArea);
} }
get sentryEnabled(): boolean {
return this.sentryProvider?.enabled === true;
}
get sentryProvider(): IAnalyticsConfig | undefined {
return this.config && checkProviderConfig(this.config, "sentry");
}
get sentryConfig(): ISentryConfiguration | undefined {
if (this.sentryProvider?.configuration) {
return convertConfig(
this.sentryProvider?.configuration
) as ISentryConfiguration;
}
return undefined;
}
get sentryReady() {
const eventId = window.sessionStorage.getItem("lastEventId");
const dsn = this.sentryConfig?.dsn;
const organization = this.sentryConfig?.organization;
const project = this.sentryConfig?.project;
const host = this.sentryConfig?.host;
return eventId && dsn && organization && project && host;
}
async sendErrorToSentry() {
try {
const eventId = window.sessionStorage.getItem("lastEventId");
const dsn = this.sentryConfig?.dsn;
const organization = this.sentryConfig?.organization;
const project = this.sentryConfig?.project;
const host = this.sentryConfig?.host;
const endpoint = `https://${host}/api/0/projects/${organization}/${project}/user-feedback/`;
if (eventId && dsn && this.sentryReady) {
await submitFeedback(endpoint, dsn, {
event_id: eventId,
name:
this.loggedUser?.defaultActor?.preferredUsername || "Unknown user",
email: this.loggedUser?.email || "unknown@email.org",
comments: this.feedback,
});
this.submittedFeedback = true;
}
} catch (error) {
console.error(error);
this.feedbackError = true;
}
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -67,7 +67,6 @@
<inline-address <inline-address
dir="auto" dir="auto"
v-if="event.physicalAddress" v-if="event.physicalAddress"
class="event-subtitle"
:physical-address="event.physicalAddress" :physical-address="event.physicalAddress"
/> />
<div <div
@ -188,6 +187,7 @@ a.card {
a { a {
text-decoration: none; text-decoration: none;
}
span.tag { span.tag {
margin: 5px auto; margin: 5px auto;
@ -196,8 +196,13 @@ a.card {
display: block; display: block;
font-size: 0.9em; font-size: 0.9em;
line-height: 1.75em; line-height: 1.75em;
&:not(.is-info, .is-danger) {
background-color: #e6e4f4; background-color: #e6e4f4;
color: #3c376e; color: $violet-3;
}
&.is-info {
color: $violet-3;
} }
} }
} }

View File

@ -169,17 +169,20 @@ export default class EventFullDate extends Vue {
isSameDay(): boolean { isSameDay(): boolean {
const sameDay = const sameDay =
new Date(this.beginsOn).toDateString() === this.beginsOnDate.toDateString() === new Date(this.endsOn).toDateString();
new Date(this.endsOn).toDateString();
return this.endsOn !== undefined && sameDay; return this.endsOn !== undefined && sameDay;
} }
get beginsOnDate(): Date {
return new Date(this.beginsOn);
}
get differentFromUserTimezone(): boolean { get differentFromUserTimezone(): boolean {
return ( return (
!!this.timezone && !!this.timezone &&
!!this.userActualTimezone && !!this.userActualTimezone &&
getTimezoneOffset(this.timezone) !== getTimezoneOffset(this.timezone, this.beginsOnDate) !==
getTimezoneOffset(this.userActualTimezone) && getTimezoneOffset(this.userActualTimezone, this.beginsOnDate) &&
this.timezone !== this.userActualTimezone this.timezone !== this.userActualTimezone
); );
} }

View File

@ -45,6 +45,7 @@ div.eventMetadataBlock {
.content-wrapper { .content-wrapper {
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
max-width: calc(100vw - 32px - 20px);
&.padding-left { &.padding-left {
padding: 0 20px; padding: 0 20px;

View File

@ -34,14 +34,9 @@
class="metadata-organized-by" class="metadata-organized-by"
:title="$t('Organized by')" :title="$t('Organized by')"
> >
<popover-actor-card
:actor="event.organizerActor"
v-if="!event.attributedTo"
>
<actor-card :actor="event.organizerActor" />
</popover-actor-card>
<router-link <router-link
v-if="event.attributedTo" v-if="event.attributedTo"
class="hover:underline"
:to="{ :to="{
name: RouteName.GROUP, name: RouteName.GROUP,
params: { params: {
@ -49,23 +44,21 @@
}, },
}" }"
> >
<popover-actor-card <actor-card
:actor="event.attributedTo"
v-if=" v-if="
!event.attributedTo || !event.options.hideOrganizerWhenGroupEvent !event.attributedTo || !event.options.hideOrganizerWhenGroupEvent
" "
> :actor="event.attributedTo"
<actor-card :actor="event.attributedTo" /> :inline="true"
</popover-actor-card> />
</router-link> </router-link>
<actor-card v-else :actor="event.organizerActor" :inline="true" />
<popover-actor-card <actor-card
:inline="true"
:actor="contact" :actor="contact"
v-for="contact in event.contacts" v-for="contact in event.contacts"
:key="contact.id" :key="contact.id"
> />
<actor-card :actor="contact" />
</popover-actor-card>
</event-metadata-block> </event-metadata-block>
<event-metadata-block <event-metadata-block
v-if="event.onlineAddress && urlToHostname(event.onlineAddress)" v-if="event.onlineAddress && urlToHostname(event.onlineAddress)"
@ -74,6 +67,7 @@
> >
<a <a
target="_blank" target="_blank"
class="hover:underline"
rel="noopener noreferrer ugc" rel="noopener noreferrer ugc"
:href="event.onlineAddress" :href="event.onlineAddress"
:title=" :title="

View File

@ -1,6 +1,6 @@
<template> <template>
<router-link <router-link
class="event-minimalist-card-wrapper" class="event-minimalist-card-wrapper bg-white rounded-lg shadow-md"
dir="auto" dir="auto"
:to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }" :to="{ name: RouteName.EVENT, params: { uuid: event.uuid } }"
> >
@ -18,6 +18,20 @@
</div> </div>
<div class="title-info-wrapper has-text-grey-dark"> <div class="title-info-wrapper has-text-grey-dark">
<h3 class="event-minimalist-title" :lang="event.language" dir="auto"> <h3 class="event-minimalist-title" :lang="event.language" dir="auto">
<b-tag
type="is-info"
class="mr-1"
v-if="event.status === EventStatus.TENTATIVE"
>
{{ $t("Tentative") }}
</b-tag>
<b-tag
type="is-danger"
class="mr-1"
v-if="event.status === EventStatus.CANCELLED"
>
{{ $t("Cancelled") }}
</b-tag>
<b-tag <b-tag
class="mr-2" class="mr-2"
type="is-warning" type="is-warning"
@ -105,7 +119,7 @@
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import { IEvent, organizer, organizerDisplayName } from "@/types/event.model"; import { IEvent, organizer, organizerDisplayName } from "@/types/event.model";
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue"; import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
import { ParticipantRole } from "@/types/enums"; import { EventStatus, ParticipantRole } from "@/types/enums";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue"; import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import InlineAddress from "@/components/Address/InlineAddress.vue"; import InlineAddress from "@/components/Address/InlineAddress.vue";
@ -129,6 +143,8 @@ export default class EventMinimalistCard extends Vue {
organizerDisplayName = organizerDisplayName; organizerDisplayName = organizerDisplayName;
organizer = organizer; organizer = organizer;
EventStatus = EventStatus;
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -45,6 +45,22 @@
</div> </div>
<div class="list-card-content"> <div class="list-card-content">
<div class="title-wrapper" dir="auto"> <div class="title-wrapper" dir="auto">
<b-tag
type="is-info"
class="mr-1 mb-1"
size="is-medium"
v-if="participation.event.status === EventStatus.TENTATIVE"
>
{{ $t("Tentative") }}
</b-tag>
<b-tag
type="is-danger"
class="mr-1 mb-1"
size="is-medium"
v-if="participation.event.status === EventStatus.CANCELLED"
>
{{ $t("Cancelled") }}
</b-tag>
<router-link <router-link
:to="{ :to="{
name: RouteName.EVENT, name: RouteName.EVENT,
@ -257,7 +273,7 @@ import { Component, Prop } from "vue-property-decorator";
import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue"; import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue";
import { mixins } from "vue-class-component"; import { mixins } from "vue-class-component";
import { RawLocation, Route } from "vue-router"; import { RawLocation, Route } from "vue-router";
import { EventVisibility, ParticipantRole } from "@/types/enums"; import { EventStatus, EventVisibility, ParticipantRole } from "@/types/enums";
import { IParticipant } from "../../types/participant.model"; import { IParticipant } from "../../types/participant.model";
import { import {
IEventCardOptions, IEventCardOptions,
@ -326,6 +342,8 @@ export default class EventParticipationCard extends mixins(
RouteName = RouteName; RouteName = RouteName;
EventStatus = EventStatus;
get mergedOptions(): IEventCardOptions { get mergedOptions(): IEventCardOptions {
return { ...defaultOptions, ...this.options }; return { ...defaultOptions, ...this.options };
} }

View File

@ -5,14 +5,33 @@
:placeholder="$t('Filter by profile or group name')" :placeholder="$t('Filter by profile or group name')"
v-model="actorFilter" v-model="actorFilter"
/> />
<b-radio-button <transition-group
v-model="selectedActor" tag="ul"
:native-value="availableActor" class="grid grid-cols-1 gap-y-3 m-5 max-w-md mx-auto"
class="list-item" enter-active-class="duration-300 ease-out"
enter-from-class="transform opacity-0"
enter-to-class="opacity-100"
leave-active-class="duration-200 ease-in"
leave-from-class="opacity-100"
leave-to-class="transform opacity-0"
>
<li
class="relative focus-within:shadow-lg"
v-for="availableActor in actualFilteredAvailableActors" v-for="availableActor in actualFilteredAvailableActors"
:key="availableActor.id" :key="availableActor.id"
> >
<div class="media" dir="auto"> <input
class="sr-only peer"
type="radio"
:value="availableActor"
name="availableActors"
v-model="selectedActor"
:id="`availableActor-${availableActor.id}`"
/>
<label
class="flex flex-wrap p-3 bg-white border border-gray-300 rounded-lg cursor-pointer hover:bg-gray-50 peer-checked:ring-primary peer-checked:ring-2 peer-checked:border-transparent"
:for="`availableActor-${availableActor.id}`"
>
<figure class="image is-48x48" v-if="availableActor.avatar"> <figure class="image is-48x48" v-if="availableActor.avatar">
<img <img
class="image is-rounded" class="image is-rounded"
@ -20,18 +39,14 @@
alt="" alt=""
/> />
</figure> </figure>
<b-icon <b-icon v-else size="is-large" icon="account-circle" />
class="media-left" <div>
v-else
size="is-large"
icon="account-circle"
/>
<div class="media-content">
<h3>{{ availableActor.name }}</h3> <h3>{{ availableActor.name }}</h3>
<small>{{ `@${availableActor.preferredUsername}` }}</small> <small>{{ `@${availableActor.preferredUsername}` }}</small>
</div> </div>
</div> </label>
</b-radio-button> </li>
</transition-group>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -51,6 +66,13 @@ import { MemberRole } from "@/types/enums";
groupMemberships: { groupMemberships: {
query: LOGGED_USER_MEMBERSHIPS, query: LOGGED_USER_MEMBERSHIPS,
update: (data) => data.loggedUser.memberships, update: (data) => data.loggedUser.memberships,
variables() {
return {
page: 1,
limit: 10,
membershipName: this.actorFilter,
};
},
}, },
identities: IDENTITIES, identities: IDENTITIES,
currentActor: CURRENT_ACTOR_CLIENT, currentActor: CURRENT_ACTOR_CLIENT,

View File

@ -1,5 +1,8 @@
<template> <template>
<div class="organizer-picker" v-if="selectedActor"> <div
class="bg-white border border-gray-300 rounded-lg cursor-pointer"
v-if="selectedActor"
>
<!-- If we have a current actor (inline) --> <!-- If we have a current actor (inline) -->
<div <div
v-if="inline && selectedActor.id" v-if="inline && selectedActor.id"
@ -65,19 +68,24 @@
/> />
</div> </div>
<div class="column contact-picker"> <div class="column contact-picker">
<div v-if="isSelectedActorAGroup && actorMembers.length > 0"> <div v-if="isSelectedActorAGroup">
<p>{{ $t("Add a contact") }}</p> <p>{{ $t("Add a contact") }}</p>
<b-input <b-input
:placeholder="$t('Filter by name')" :placeholder="$t('Filter by name')"
v-model="contactFilter" :value="contactFilter"
@input="debounceSetFilterByName"
dir="auto" dir="auto"
/> />
<div v-if="actorMembers.length > 0">
<p <p
class="field" class="field"
v-for="actor in filteredActorMembers" v-for="actor in filteredActorMembers"
:key="actor.id" :key="actor.id"
> >
<b-checkbox v-model="actualContacts" :native-value="actor.id"> <b-checkbox
v-model="actualContacts"
:native-value="actor.id"
>
<div class="media"> <div class="media">
<div class="media-left"> <div class="media-left">
<figure class="image is-48x48" v-if="actor.avatar"> <figure class="image is-48x48" v-if="actor.avatar">
@ -87,7 +95,11 @@
:alt="actor.avatar.alt" :alt="actor.avatar.alt"
/> />
</figure> </figure>
<b-icon v-else size="is-large" icon="account-circle" /> <b-icon
v-else
size="is-large"
icon="account-circle"
/>
</div> </div>
<div class="media-content" v-if="actor.name"> <div class="media-content" v-if="actor.name">
<p class="is-4">{{ actor.name }}</p> <p class="is-4">{{ actor.name }}</p>
@ -102,6 +114,16 @@
</b-checkbox> </b-checkbox>
</p> </p>
</div> </div>
<div
v-else-if="
actorMembers.length === 0 && contactFilter.length > 0
"
>
<empty-content icon="account-multiple" :inline="true">
{{ $t("No group member found") }}
</empty-content>
</div>
</div>
<div v-else class="content has-text-grey-dark has-text-centered"> <div v-else class="content has-text-grey-dark has-text-centered">
<p>{{ $t("Your profile will be shown as contact.") }}</p> <p>{{ $t("Your profile will be shown as contact.") }}</p>
</div> </div>
@ -122,14 +144,16 @@ import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { IMember } from "@/types/actor/member.model"; import { IMember } from "@/types/actor/member.model";
import { IActor, IGroup, IPerson, usernameWithDomain } from "../../types/actor"; import { IActor, IGroup, IPerson, usernameWithDomain } from "../../types/actor";
import OrganizerPicker from "./OrganizerPicker.vue"; import OrganizerPicker from "./OrganizerPicker.vue";
import EmptyContent from "../Utils/EmptyContent.vue";
import { import {
CURRENT_ACTOR_CLIENT, CURRENT_ACTOR_CLIENT,
IDENTITIES, IDENTITIES,
LOGGED_USER_MEMBERSHIPS, PERSON_GROUP_MEMBERSHIPS,
} from "../../graphql/actor"; } from "../../graphql/actor";
import { Paginate } from "../../types/paginate"; import { Paginate } from "../../types/paginate";
import { GROUP_MEMBERS } from "@/graphql/member"; import { GROUP_MEMBERS } from "@/graphql/member";
import { ActorType, MemberRole } from "@/types/enums"; import { ActorType, MemberRole } from "@/types/enums";
import debounce from "lodash/debounce";
const MEMBER_ROLES = [ const MEMBER_ROLES = [
MemberRole.CREATOR, MemberRole.CREATOR,
@ -139,16 +163,17 @@ const MEMBER_ROLES = [
]; ];
@Component({ @Component({
components: { OrganizerPicker }, components: { OrganizerPicker, EmptyContent },
apollo: { apollo: {
members: { members: {
query: GROUP_MEMBERS, query: GROUP_MEMBERS,
variables() { variables() {
return { return {
name: usernameWithDomain(this.selectedActor), groupName: usernameWithDomain(this.selectedActor),
page: this.membersPage, page: this.membersPage,
limit: 10, limit: 10,
roles: MEMBER_ROLES.join(","), roles: MEMBER_ROLES.join(","),
name: this.contactFilter,
}; };
}, },
update: (data) => data.group.members, update: (data) => data.group.members,
@ -159,13 +184,17 @@ const MEMBER_ROLES = [
}, },
}, },
currentActor: CURRENT_ACTOR_CLIENT, currentActor: CURRENT_ACTOR_CLIENT,
userMemberships: { personMemberships: {
query: LOGGED_USER_MEMBERSHIPS, query: PERSON_GROUP_MEMBERSHIPS,
variables: { variables() {
return {
id: this.currentActor?.id,
page: 1, page: 1,
limit: 100, limit: 10,
groupId: this.$route.query?.actorId,
};
}, },
update: (data) => data.loggedUser.memberships, update: (data) => data.person.memberships,
}, },
identities: IDENTITIES, identities: IDENTITIES,
}, },
@ -175,6 +204,9 @@ export default class OrganizerPickerWrapper extends Vue {
@Prop({ default: true, type: Boolean }) inline!: boolean; @Prop({ default: true, type: Boolean }) inline!: boolean;
@Prop({ type: Array, required: false, default: () => [] })
contacts!: IActor[];
currentActor!: IPerson; currentActor!: IPerson;
identities!: IPerson[]; identities!: IPerson[];
@ -185,13 +217,17 @@ export default class OrganizerPickerWrapper extends Vue {
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
@Prop({ type: Array, required: false, default: () => [] })
contacts!: IActor[];
members: Paginate<IMember> = { elements: [], total: 0 }; members: Paginate<IMember> = { elements: [], total: 0 };
membersPage = 1; membersPage = 1;
userMemberships: Paginate<IMember> = { elements: [], total: 0 }; personMemberships: Paginate<IMember> = { elements: [], total: 0 };
data(): Record<string, unknown> {
return {
debounceSetFilterByName: debounce(this.setContactFilter, 1000),
};
}
get actualContacts(): (string | undefined)[] { get actualContacts(): (string | undefined)[] {
return this.contacts.map(({ id }) => id); return this.contacts.map(({ id }) => id);
@ -204,15 +240,17 @@ export default class OrganizerPickerWrapper extends Vue {
); );
} }
@Watch("userMemberships") setContactFilter(contactFilter: string) {
this.contactFilter = contactFilter;
}
@Watch("personMemberships")
setInitialActor(): void { setInitialActor(): void {
if (this.$route.query?.actorId) { if (
const actorId = this.$route.query?.actorId as string; this.personMemberships?.elements[0]?.parent?.id ===
const actor = this.userMemberships.elements.find( this.$route.query?.actorId
({ parent: { id }, role }) => ) {
actorId === id && MEMBER_ROLES.includes(role) this.selectedActor = this.personMemberships?.elements[0]?.parent;
)?.parent as IActor;
this.selectedActor = actor;
} }
} }
@ -254,7 +292,7 @@ export default class OrganizerPickerWrapper extends Vue {
actor.preferredUsername.toLowerCase(), actor.preferredUsername.toLowerCase(),
actor.name?.toLowerCase(), actor.name?.toLowerCase(),
actor.domain?.toLowerCase(), actor.domain?.toLowerCase(),
].some((match) => match?.includes(this.contactFilter.toLowerCase())); ];
}); });
} }

View File

@ -190,7 +190,7 @@ export default class ShareEventModal extends Vue {
} }
get mastodonShareUrl(): string { get mastodonShareUrl(): string {
return `https://toot.karamoff.dev/?text=${encodeURIComponent( return `https://toot.kytta.dev/?text=${encodeURIComponent(
this.basicTextToEncode this.basicTextToEncode
)}`; )}`;
} }

View File

@ -21,7 +21,7 @@
maxlength="20" maxlength="20"
maxtags="10" maxtags="10"
:placeholder="$t('Eg: Stockholm, Dance, Chess…')" :placeholder="$t('Eg: Stockholm, Dance, Chess…')"
@typing="getFilteredTags" @typing="debouncedGetFilteredTags"
:id="id" :id="id"
dir="auto" dir="auto"
> >
@ -33,6 +33,7 @@ import { Component, Prop, Vue } from "vue-property-decorator";
import differenceBy from "lodash/differenceBy"; import differenceBy from "lodash/differenceBy";
import { ITag } from "../../types/tag.model"; import { ITag } from "../../types/tag.model";
import { FILTER_TAGS } from "@/graphql/tags"; import { FILTER_TAGS } from "@/graphql/tags";
import debounce from "lodash/debounce";
@Component({ @Component({
apollo: { apollo: {
@ -63,6 +64,12 @@ export default class TagInput extends Vue {
return `tag-input-${TagInput.componentId}`; return `tag-input-${TagInput.componentId}`;
} }
data(): Record<string, unknown> {
return {
debouncedGetFilteredTags: debounce(this.getFilteredTags, 200),
};
}
async getFilteredTags(text: string): Promise<void> { async getFilteredTags(text: string): Promise<void> {
this.text = text; this.text = text;
await this.$apollo.queries.tags.refetch(); await this.$apollo.queries.tags.refetch();

View File

@ -31,11 +31,11 @@
</span> </span>
</div> </div>
</div> </div>
<div class="content mb-2" dir="auto" v-html="group.summary" /> <div class="mb-2 line-clamp-3" dir="auto" v-html="group.summary" />
<div class="card-custom-footer"> <div>
<inline-address <inline-address
class="has-text-grey-dark" class="has-text-grey-dark"
v-if="group.physicalAddress" v-if="group.physicalAddress && addressFullName(group.physicalAddress)"
:physicalAddress="group.physicalAddress" :physicalAddress="group.physicalAddress"
/> />
<p class="has-text-grey-dark"> <p class="has-text-grey-dark">
@ -61,6 +61,7 @@ import { displayName, IGroup, usernameWithDomain } from "@/types/actor";
import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue"; import LazyImageWrapper from "@/components/Image/LazyImageWrapper.vue";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import InlineAddress from "@/components/Address/InlineAddress.vue"; import InlineAddress from "@/components/Address/InlineAddress.vue";
import { addressFullName } from "@/types/address.model";
@Component({ @Component({
components: { components: {
@ -76,6 +77,8 @@ export default class GroupCard extends Vue {
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
displayName = displayName; displayName = displayName;
addressFullName = addressFullName;
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -10,7 +10,7 @@
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
import RedirectWithAccount from "@/components/Utils/RedirectWithAccount.vue"; import RedirectWithAccount from "@/components/Utils/RedirectWithAccount.vue";
import { FETCH_GROUP } from "@/graphql/group"; import { FETCH_GROUP } from "@/graphql/group";
import { IGroup } from "@/types/actor"; import { displayName, IGroup } from "@/types/actor";
@Component({ @Component({
components: { RedirectWithAccount }, components: { RedirectWithAccount },
@ -52,7 +52,7 @@ export default class JoinGroupWithAccount extends Vue {
} }
get groupTitle(): undefined | string { get groupTitle(): undefined | string {
return this.group?.name || this.group?.preferredUsername; return this.group && displayName(this.group);
} }
sentence = this.$t( sentence = this.$t(

View File

@ -117,7 +117,7 @@ import { Component, Prop, Vue, Ref } from "vue-property-decorator";
import { GroupVisibility } from "@/types/enums"; import { GroupVisibility } from "@/types/enums";
import DiasporaLogo from "../Share/DiasporaLogo.vue"; import DiasporaLogo from "../Share/DiasporaLogo.vue";
import MastodonLogo from "../Share/MastodonLogo.vue"; import MastodonLogo from "../Share/MastodonLogo.vue";
import TelegramLogo from "../Share/MastodonLogo.vue"; import TelegramLogo from "../Share/TelegramLogo.vue";
import { displayName, IGroup } from "@/types/actor"; import { displayName, IGroup } from "@/types/actor";
@Component({ @Component({
@ -177,7 +177,7 @@ export default class ShareGroupModal extends Vue {
} }
get mastodonShareUrl(): string { get mastodonShareUrl(): string {
return `https://toot.karamoff.dev/?text=${encodeURIComponent( return `https://toot.kytta.dev/?text=${encodeURIComponent(
this.basicTextToEncode this.basicTextToEncode
)}`; )}`;
} }

View File

@ -142,8 +142,10 @@ export default class Map extends Vue {
} }
updateDraggableMarkerPosition(e: LatLng): void { updateDraggableMarkerPosition(e: LatLng): void {
if (this.updateDraggableMarkerCallback) {
this.updateDraggableMarkerCallback(e, this.zoom); this.updateDraggableMarkerCallback(e, this.zoom);
} }
}
updateZoom(zoom: number): void { updateZoom(zoom: number): void {
this.zoom = zoom; this.zoom = zoom;

View File

@ -28,7 +28,7 @@ export default class Vue2LeafletLocateControl extends Vue {
unknown unknown
>; >;
@Prop({ type: Boolean, default: true }) visible = true; @Prop({ type: Boolean, default: true }) visible!: boolean;
ready = false; ready = false;

View File

@ -56,21 +56,6 @@
>{{ $t("Create") }}</b-button >{{ $t("Create") }}</b-button
> >
</b-navbar-item> </b-navbar-item>
<b-navbar-item
v-if="config && config.features.koenaConnect"
class="koena"
tag="a"
href="https://mediation.koena.net/framasoft/mobilizon/"
target="_blank"
rel="noopener external"
hreflang="fr"
>
<img
src="/img/koena-a11y.svg"
width="150"
alt="Contact accessibilité"
/>
</b-navbar-item>
</template> </template>
<template slot="end"> <template slot="end">
<b-navbar-item <b-navbar-item
@ -285,6 +270,11 @@ export default class NavBar extends Vue {
// If we don't have any identities, the user has validated their account, // If we don't have any identities, the user has validated their account,
// is logging for the first time but didn't create an identity somehow // is logging for the first time but didn't create an identity somehow
if (this.identities.length === 0) { if (this.identities.length === 0) {
console.debug(
"We have no identities listed for current user",
this.identities
);
console.debug("Pushing route to REGISTER_PROFILE");
try { try {
await this.$router.push({ await this.$router.push({
name: RouteName.REGISTER_PROFILE, name: RouteName.REGISTER_PROFILE,
@ -389,15 +379,6 @@ nav {
background-color: inherit; background-color: inherit;
} }
.koena {
padding-top: 0;
padding-bottom: 0;
& > img {
max-height: 4rem;
padding-top: 0.2rem;
}
}
.identity-wrapper { .identity-wrapper {
display: flex; display: flex;

View File

@ -9,7 +9,7 @@
:rounded="true" :rounded="true"
style="height: 120px" style="height: 120px"
/> />
<div class="title-info-wrapper has-text-grey-dark"> <div class="title-info-wrapper has-text-grey-dark px-1">
<h3 class="post-minimalist-title" :lang="post.language"> <h3 class="post-minimalist-title" :lang="post.language">
{{ post.title }} {{ post.title }}
</h3> </h3>

View File

@ -179,7 +179,7 @@ export default class SharePostModal extends Vue {
} }
get mastodonShareUrl(): string { get mastodonShareUrl(): string {
return `https://toot.karamoff.dev/?text=${encodeURIComponent( return `https://toot.kytta.dev/?text=${encodeURIComponent(
this.basicTextToEncode this.basicTextToEncode
)}`; )}`;
} }

View File

@ -9,7 +9,7 @@
:class="{ 'is-titleless': !title }" :class="{ 'is-titleless': !title }"
> >
<div class="media"> <div class="media">
<div class="media-left"> <div class="media-left hidden md:block">
<b-icon icon="alert" type="is-warning" size="is-large" /> <b-icon icon="alert" type="is-warning" size="is-large" />
</div> </div>
<div class="media-content"> <div class="media-content">

View File

@ -45,7 +45,7 @@
</a> </a>
<resource-dropdown <resource-dropdown
class="actions" class="actions"
v-if="!inline || !preview" v-if="!inline && !preview"
@delete="$emit('delete', resource.id)" @delete="$emit('delete', resource.id)"
@move="$emit('move', resource)" @move="$emit('move', resource)"
@rename="$emit('rename', resource)" @rename="$emit('rename', resource)"

View File

@ -1,7 +1,7 @@
<template> <template>
<div v-if="resource"> <div v-if="resource">
<article class="panel is-primary"> <article class="panel is-primary">
<p class="panel-heading"> <p class="panel-heading truncate">
{{ {{
$t('Move "{resourceName}"', { resourceName: initialResource.title }) $t('Move "{resourceName}"', { resourceName: initialResource.title })
}} }}
@ -28,7 +28,7 @@
</a> </a>
<template v-if="resource.children"> <template v-if="resource.children">
<a <a
class="panel-block" class="panel-block flex-wrap"
v-for="element in resource.children.elements" v-for="element in resource.children.elements"
:class="{ :class="{
clickable: clickable:
@ -37,6 +37,7 @@
:key="element.id" :key="element.id"
@click="goDown(element)" @click="goDown(element)"
> >
<p class="truncate">
<span class="panel-icon"> <span class="panel-icon">
<b-icon <b-icon
icon="folder" icon="folder"
@ -45,7 +46,8 @@
/> />
<b-icon icon="link" size="is-small" v-else /> <b-icon icon="link" size="is-small" v-else />
</span> </span>
{{ element.title }} <span>{{ element.title }}</span>
</p>
<span v-if="element.id === initialResource.id"> <span v-if="element.id === initialResource.id">
<em v-if="element.type === 'folder'"> {{ $t("(this folder)") }}</em> <em v-if="element.type === 'folder'"> {{ $t("(this folder)") }}</em>
<em v-else> {{ $t("(this link)") }}</em> <em v-else> {{ $t("(this link)") }}</em>
@ -89,7 +91,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator"; import { Component, Vue, Prop, Watch } from "vue-property-decorator";
import { GET_RESOURCE } from "../../graphql/resources"; import { GET_RESOURCE } from "../../graphql/resources";
import { IResource } from "../../types/resource"; import { IResource } from "../../types/resource";
@ -119,7 +121,7 @@ export default class ResourceSelector extends Vue {
@Prop({ required: true }) username!: string; @Prop({ required: true }) username!: string;
resource: IResource | undefined = this.initialResource.parent; resource: IResource | undefined = undefined;
RESOURCES_PER_PAGE = 10; RESOURCES_PER_PAGE = 10;
@ -131,6 +133,20 @@ export default class ResourceSelector extends Vue {
} }
} }
data() {
return {
resource: this.initialResource?.parent,
};
}
@Watch("initialResource")
updateResourceFromProp() {
if (this.initialResource) {
this.resource = this.initialResource?.parent;
this.$apollo.queries.resource.refetch();
}
}
updateResource(): void { updateResource(): void {
this.$emit( this.$emit(
"update-resource", "update-resource",

View File

@ -1,18 +1,22 @@
<template> <template>
<label for="navSearchField"> <b-field label-for="navSearchField" class="-mt-2">
<span class="visually-hidden">{{ defaultPlaceHolder }}</span>
<b-input <b-input
custom-class="searchField" :placeholder="defaultPlaceHolder"
type="search"
id="navSearchField" id="navSearchField"
icon="magnify" icon="magnify"
type="search" icon-clickable
rounded rounded
custom-class="searchField"
dir="auto" dir="auto"
:placeholder="defaultPlaceHolder"
v-model="search" v-model="search"
@keyup.native.enter="enter" @keyup.native.enter="enter"
/> >
</label> </b-input>
<template #label>
<span class="sr-only">{{ defaultPlaceHolder }}</span>
</template>
</b-field>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop, Vue } from "vue-property-decorator";
@ -47,6 +51,7 @@ label span.visually-hidden {
input.searchField { input.searchField {
box-shadow: none; box-shadow: none;
border-color: #b5b5b5; border-color: #b5b5b5;
border-radius: 9999px !important;
&::placeholder { &::placeholder {
color: gray; color: gray;

View File

@ -78,7 +78,7 @@
/> />
<SettingMenuItem <SettingMenuItem
:title="$t('Federation')" :title="$t('Federation')"
:to="{ name: RouteName.RELAYS }" :to="{ name: RouteName.INSTANCES }"
/> />
</SettingMenuSection> </SettingMenuSection>
</ul> </ul>

View File

@ -17,7 +17,8 @@ span.tag {
background: $purple-3; background: $purple-3;
color: $violet-2; color: $violet-2;
text-transform: uppercase; text-transform: uppercase;
&::before {
&:not(.category)::before {
content: "#"; content: "#";
} }
} }

View File

@ -7,29 +7,23 @@
<b-field :label="$t('Title')"> <b-field :label="$t('Title')">
<b-input v-model="title" /> <b-input v-model="title" />
</b-field> </b-field>
<b-field :label="$t('Assigned to')"> <b-field :label="$t('Assigned to')"> </b-field>
<actor-auto-complete v-model="assignedTo" />
</b-field>
<b-field :label="$t('Due on')"> <b-field :label="$t('Due on')">
<b-datepicker v-model="dueDate" /> <b-datepicker v-model="dueDate" :first-day-of-week="firstDayOfWeek" />
</b-field> </b-field>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator"; import { Prop, Vue } from "vue-property-decorator";
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
import { DebouncedFunc } from "lodash"; import { DebouncedFunc } from "lodash";
import { SnackbarProgrammatic as Snackbar } from "buefy"; import { SnackbarProgrammatic as Snackbar } from "buefy";
import { ITodo } from "../../types/todos"; import { ITodo } from "../../types/todos";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { UPDATE_TODO } from "../../graphql/todos"; import { UPDATE_TODO } from "../../graphql/todos";
import ActorAutoComplete from "../Account/ActorAutoComplete.vue";
import { IPerson } from "../../types/actor"; import { IPerson } from "../../types/actor";
@Component({
components: { ActorAutoComplete },
})
export default class Todo extends Vue { export default class Todo extends Vue {
@Prop({ required: true, type: Object }) todo!: ITodo; @Prop({ required: true, type: Object }) todo!: ITodo;
@ -99,5 +93,9 @@ export default class Todo extends Vue {
}); });
} }
} }
get firstDayOfWeek(): number {
return this.$dateFnsLocale?.options?.weekStartsOn || 0;
}
} }
</script> </script>

View File

@ -0,0 +1,69 @@
<template>
<nav class="flex mb-3" :aria-label="$t('Breadcrumbs')">
<ol class="inline-flex items-center space-x-1 md:space-x-3 flex-wrap">
<li
class="inline-flex items-center"
v-for="(element, index) in links"
:key="index"
:aria-current="index > 0 ? 'page' : undefined"
>
<router-link
v-if="index === 0"
:to="element"
class="inline-flex items-center text-gray-800 hover:text-gray-900"
>
{{ element.text }}
</router-link>
<div class="flex items-center" v-else-if="index === links.length - 1">
<svg
class="w-6 h-6 text-gray-400 rtl:rotate-180"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"
></path>
</svg>
<span
class="ltr:ml-1 rtl:mr-1 font-medium text-gray-600 md:ltr:ml-2 md:rtl:mr-2"
>{{ element.text }}</span
>
</div>
<div class="flex items-center" v-else>
<svg
class="w-6 h-6 text-gray-400 rtl:rotate-180"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"
></path>
</svg>
<router-link
:to="element"
class="ltr:ml-1 rtl:mr-1 font-medium text-gray-800 hover:text-gray-900 md:ltr:ml-2 md:rtl:mr-2"
>{{ element.text }}</router-link
>
</div>
</li>
<slot></slot>
</ol>
</nav>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { Location } from "vue-router";
type LinkElement = Location & { text: string };
@Component
export default class Breadcrumbs extends Vue {
@Prop({ type: Array, required: true }) links!: LinkElement[];
}
</script>

View File

@ -1,11 +1,15 @@
<template> <template>
<div class="empty-content" :class="{ inline }" role="note"> <div
class="empty-content"
:class="{ inline, 'text-center': center }"
role="note"
>
<b-icon :icon="icon" size="is-large" /> <b-icon :icon="icon" size="is-large" />
<h2 class="empty-content__title"> <h2 class="empty-content__title">
<!-- @slot Mandatory title --> <!-- @slot Mandatory title -->
<slot /> <slot />
</h2> </h2>
<p v-show="$slots.desc"> <p v-show="$slots.desc" :class="descriptionClasses">
<!-- @slot Optional description --> <!-- @slot Optional description -->
<slot name="desc" /> <slot name="desc" />
</p> </p>
@ -17,7 +21,10 @@ import { Component, Prop, Vue } from "vue-property-decorator";
@Component @Component
export default class EmptyContent extends Vue { export default class EmptyContent extends Vue {
@Prop({ type: String, required: true }) icon!: string; @Prop({ type: String, required: true }) icon!: string;
@Prop({ type: String, required: false, default: "" })
descriptionClasses!: string;
@Prop({ type: Boolean, required: false, default: false }) inline!: boolean; @Prop({ type: Boolean, required: false, default: false }) inline!: boolean;
@Prop({ type: Boolean, required: false, default: false }) center!: boolean;
} }
</script> </script>

View File

@ -34,15 +34,6 @@ export const FETCH_PERSON = gql`
feedTokens { feedTokens {
token token
} }
organizedEvents {
total
elements {
id
uuid
title
beginsOn
}
}
} }
} }
${ACTOR_FRAGMENT} ${ACTOR_FRAGMENT}
@ -84,6 +75,7 @@ export const GET_PERSON = gql`
uuid uuid
title title
beginsOn beginsOn
status
} }
} }
participations(page: $participationPage, limit: $participationLimit) { participations(page: $participationPage, limit: $participationLimit) {
@ -95,6 +87,7 @@ export const GET_PERSON = gql`
uuid uuid
title title
beginsOn beginsOn
status
} }
} }
} }
@ -213,6 +206,7 @@ export const LOGGED_USER_DRAFTS = gql`
alt alt
} }
beginsOn beginsOn
status
visibility visibility
attributedTo { attributedTo {
...ActorFragment ...ActorFragment
@ -235,10 +229,14 @@ export const LOGGED_USER_DRAFTS = gql`
`; `;
export const LOGGED_USER_MEMBERSHIPS = gql` export const LOGGED_USER_MEMBERSHIPS = gql`
query LoggedUserMemberships($page: Int, $limit: Int) { query LoggedUserMemberships(
$membershipName: String
$page: Int
$limit: Int
) {
loggedUser { loggedUser {
id id
memberships(page: $page, limit: $limit) { memberships(name: $membershipName, page: $page, limit: $limit) {
total total
elements { elements {
id id
@ -343,6 +341,30 @@ export const PERSON_STATUS_GROUP = gql`
${ACTOR_FRAGMENT} ${ACTOR_FRAGMENT}
`; `;
export const PERSON_GROUP_MEMBERSHIPS = gql`
query PersonGroupMemberships($id: ID!, $groupId: ID!) {
person(id: $id) {
id
memberships(groupId: $groupId) {
total
elements {
id
role
parent {
...ActorFragment
}
invitedBy {
...ActorFragment
}
insertedAt
updatedAt
}
}
}
}
${ACTOR_FRAGMENT}
`;
export const GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED = gql` export const GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED = gql`
subscription GroupMembershipSubscriptionChanged( subscription GroupMembershipSubscriptionChanged(
$actorId: ID! $actorId: ID!

View File

@ -70,13 +70,66 @@ export const RELAY_FOLLOWINGS = gql`
${RELAY_FRAGMENT} ${RELAY_FRAGMENT}
`; `;
export const ADD_RELAY = gql` export const INSTANCE_FRAGMENT = gql`
mutation addRelay($address: String!) { fragment InstanceFragment on Instance {
addRelay(address: $address) { domain
...relayFragment hasRelay
relayAddress
followerStatus
followedStatus
eventCount
personCount
groupCount
followersCount
followingsCount
reportsCount
mediaSize
}
`;
export const INSTANCE = gql`
query instance($domain: ID!) {
instance(domain: $domain) {
...InstanceFragment
} }
} }
${RELAY_FRAGMENT} ${INSTANCE_FRAGMENT}
`;
export const INSTANCES = gql`
query Instances(
$page: Int
$limit: Int
$orderBy: InstancesSortFields
$direction: String
$filterDomain: String
$filterFollowStatus: InstanceFilterFollowStatus
$filterSuspendStatus: InstanceFilterSuspendStatus
) {
instances(
page: $page
limit: $limit
orderBy: $orderBy
direction: $direction
filterDomain: $filterDomain
filterFollowStatus: $filterFollowStatus
filterSuspendStatus: $filterSuspendStatus
) {
total
elements {
...InstanceFragment
}
}
}
${INSTANCE_FRAGMENT}
`;
export const ADD_INSTANCE = gql`
mutation addInstance($domain: String!) {
addInstance(domain: $domain) {
...InstanceFragment
}
}
${INSTANCE_FRAGMENT}
`; `;
export const REMOVE_RELAY = gql` export const REMOVE_RELAY = gql`
@ -190,3 +243,26 @@ export const SAVE_ADMIN_SETTINGS = gql`
} }
${ADMIN_SETTINGS_FRAGMENT} ${ADMIN_SETTINGS_FRAGMENT}
`; `;
export const ADMIN_UPDATE_USER = gql`
mutation AdminUpdateUser(
$id: ID!
$email: String
$role: UserRole
$confirmed: Boolean
$notify: Boolean
) {
adminUpdateUser(
id: $id
email: $email
role: $role
confirmed: $confirmed
notify: $notify
) {
id
email
role
confirmedAt
}
}
`;

View File

@ -11,6 +11,10 @@ export const CONFIG = gql`
demoMode demoMode
countryCode countryCode
languages languages
eventCategories {
id
label
}
anonymous { anonymous {
participation { participation {
allowed allowed
@ -67,7 +71,6 @@ export const CONFIG = gql`
features { features {
groups groups
eventCreation eventCreation
koenaConnect
} }
restrictions { restrictions {
onlyAdminCanCreateGroups onlyAdminCanCreateGroups
@ -92,6 +95,15 @@ export const CONFIG = gql`
enabled enabled
publicKey publicKey
} }
analytics {
id
enabled
configuration {
key
value
type
}
}
} }
} }
`; `;
@ -103,6 +115,10 @@ export const CONFIG_EDIT_EVENT = gql`
features { features {
groups groups
} }
eventCategories {
id
label
}
anonymous { anonymous {
participation { participation {
allowed allowed

View File

@ -23,6 +23,7 @@ const FULL_EVENT_FRAGMENT = gql`
joinOptions joinOptions
draft draft
language language
category
picture { picture {
id id
url url
@ -61,6 +62,7 @@ const FULL_EVENT_FRAGMENT = gql`
uuid uuid
title title
beginsOn beginsOn
status
language language
picture { picture {
id id
@ -202,11 +204,11 @@ export const CREATE_EVENT = gql`
$picture: MediaInput $picture: MediaInput
$onlineAddress: String $onlineAddress: String
$phoneAddress: String $phoneAddress: String
$category: String $category: EventCategory
$physicalAddress: AddressInput $physicalAddress: AddressInput
$options: EventOptionsInput $options: EventOptionsInput
$contacts: [Contact] $contacts: [Contact]
$metadata: EventMetadataInput $metadata: [EventMetadataInput]
) { ) {
createEvent( createEvent(
organizerActorId: $organizerActorId organizerActorId: $organizerActorId
@ -252,11 +254,11 @@ export const EDIT_EVENT = gql`
$phoneAddress: String $phoneAddress: String
$organizerActorId: ID $organizerActorId: ID
$attributedToId: ID $attributedToId: ID
$category: String $category: EventCategory
$physicalAddress: AddressInput $physicalAddress: AddressInput
$options: EventOptionsInput $options: EventOptionsInput
$contacts: [Contact] $contacts: [Contact]
$metadata: EventMetadataInput $metadata: [EventMetadataInput]
) { ) {
updateEvent( updateEvent(
eventId: $id eventId: $id
@ -438,6 +440,7 @@ export const FETCH_GROUP_EVENTS = gql`
uuid uuid
title title
beginsOn beginsOn
status
draft draft
options { options {
...EventOptions ...EventOptions

View File

@ -42,6 +42,7 @@ export const LIST_GROUPS = gql`
id id
uuid uuid
title title
status
beginsOn beginsOn
} }
total total
@ -104,6 +105,7 @@ export const GROUP_FIELDS_FRAGMENTS = gql`
uuid uuid
title title
beginsOn beginsOn
status
draft draft
language language
options { options {
@ -224,6 +226,15 @@ export const FETCH_GROUP = gql`
${RESOURCE_METADATA_BASIC_FIELDS_FRAGMENT} ${RESOURCE_METADATA_BASIC_FIELDS_FRAGMENT}
`; `;
export const FETCH_GROUP_BY_ID = gql`
query FetchGroupById($id: ID!) {
groupById(id: $name) {
...GroupFullFields
}
}
${GROUP_FIELDS_FRAGMENTS}
`;
export const GET_GROUP = gql` export const GET_GROUP = gql`
query GetGroup( query GetGroup(
$id: ID! $id: ID!

View File

@ -36,6 +36,7 @@ export const HOME_USER_QUERIES = gql`
alt alt
} }
beginsOn beginsOn
status
visibility visibility
language language
organizerActor { organizerActor {
@ -77,6 +78,7 @@ export const HOME_USER_QUERIES = gql`
uuid uuid
title title
beginsOn beginsOn
status
picture { picture {
url url
} }
@ -127,6 +129,7 @@ export const CLOSE_CONTENT = gql`
title title
uuid uuid
beginsOn beginsOn
status
picture { picture {
id id
url url

View File

@ -44,10 +44,16 @@ export const REJECT_INVITATION = gql`
`; `;
export const GROUP_MEMBERS = gql` export const GROUP_MEMBERS = gql`
query ($name: String!, $roles: String, $page: Int, $limit: Int) { query (
group(preferredUsername: $name) { $groupName: String!
$name: String
$roles: String
$page: Int
$limit: Int
) {
group(preferredUsername: $groupName) {
...ActorFragment ...ActorFragment
members(page: $page, limit: $limit, roles: $roles) { members(name: $name, page: $page, limit: $limit, roles: $roles) {
elements { elements {
id id
role role

View File

@ -32,6 +32,7 @@ export const LOGGED_USER_PARTICIPATIONS = gql`
alt alt
} }
beginsOn beginsOn
status
visibility visibility
organizerActor { organizerActor {
...ActorFragment ...ActorFragment
@ -98,6 +99,7 @@ export const LOGGED_USER_UPCOMING_EVENTS = gql`
alt alt
} }
beginsOn beginsOn
status
visibility visibility
organizerActor { organizerActor {
...ActorFragment ...ActorFragment
@ -144,6 +146,7 @@ export const LOGGED_USER_UPCOMING_EVENTS = gql`
uuid uuid
title title
beginsOn beginsOn
status
picture { picture {
url url
} }

View File

@ -98,7 +98,7 @@ export const FETCH_POST = gql`
export const CREATE_POST = gql` export const CREATE_POST = gql`
mutation CreatePost( mutation CreatePost(
$title: String! $title: String!
$body: String $body: String!
$attributedToId: ID! $attributedToId: ID!
$visibility: PostVisibility $visibility: PostVisibility
$draft: Boolean $draft: Boolean

View File

@ -2,8 +2,13 @@ import gql from "graphql-tag";
import { ACTOR_FRAGMENT } from "./actor"; import { ACTOR_FRAGMENT } from "./actor";
export const REPORTS = gql` export const REPORTS = gql`
query Reports($status: ReportStatus, $page: Int, $limit: Int) { query Reports(
reports(status: $status, page: $page, limit: $limit) { $status: ReportStatus
$domain: String
$page: Int
$limit: Int
) {
reports(status: $status, domain: $domain, page: $page, limit: $limit) {
total total
elements { elements {
id id

View File

@ -11,6 +11,7 @@ export const SEARCH_EVENTS_AND_GROUPS = gql`
$tags: String $tags: String
$term: String $term: String
$type: EventType $type: EventType
$category: String
$beginsOn: DateTime $beginsOn: DateTime
$endsOn: DateTime $endsOn: DateTime
$eventPage: Int $eventPage: Int
@ -23,6 +24,7 @@ export const SEARCH_EVENTS_AND_GROUPS = gql`
tags: $tags tags: $tags
term: $term term: $term
type: $type type: $type
category: $category
beginsOn: $beginsOn beginsOn: $beginsOn
endsOn: $endsOn endsOn: $endsOn
page: $eventPage page: $eventPage
@ -38,6 +40,7 @@ export const SEARCH_EVENTS_AND_GROUPS = gql`
id id
url url
} }
status
tags { tags {
...TagFragment ...TagFragment
} }
@ -108,6 +111,7 @@ export const INTERACT = gql`
title title
uuid uuid
beginsOn beginsOn
status
picture { picture {
id id
url url

View File

@ -209,14 +209,30 @@ export const UPDATE_ACTIVITY_SETTING = gql`
`; `;
export const LIST_USERS = gql` export const LIST_USERS = gql`
query ListUsers($email: String, $page: Int, $limit: Int) { query ListUsers(
users(email: $email, page: $page, limit: $limit) { $email: String
$currentSignInIp: String
$page: Int
$limit: Int
$sort: SortableUserField
$direction: SortDirection
) {
users(
email: $email
currentSignInIp: $currentSignInIp
page: $page
limit: $limit
sort: $sort
direction: $direction
) {
total total
elements { elements {
id id
email email
locale locale
confirmedAt confirmedAt
currentSignInIp
currentSignInAt
disabled disabled
actors { actors {
...ActorFragment ...ActorFragment

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1263
js/src/i18n/cy.json Normal file

File diff suppressed because it is too large Load Diff

222
js/src/i18n/da.json Normal file
View File

@ -0,0 +1,222 @@
{
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Et brugervenligt, befriende og etisk værktøj for at samles, organisere og mobilisere.",
"A validation email was sent to {email}": "En godkendelsesemail er blevet sendt til {email}",
"Abandon editing": "Afbryd redigering",
"About": "Om",
"About Mobilizon": "Om Mobilizon",
"About this event": "Om denne begivenhed",
"About this instance": "Om denne udbyder",
"Accepted": "Accepteret",
"Account": "Konto",
"Add": "Tilføj",
"Add a note": "Tilføj et notat",
"Add an address": "Tilføj en addresse",
"Add an instance": "Tilføj en udbyder",
"Add some tags": "Tilføj nogle nøgleord",
"Add to my calendar": "Tilføj til min kalender",
"Additional comments": "Yderligere kommentarer",
"Admin": "Administrator",
"Admin settings successfully saved.": "Administrator indstillinger er blevet gemt.",
"Administration": "Administrering",
"All the places have already been taken": "Alle pladserne er allerede optaget",
"Allow registrations": "Tillad registrering",
"Anonymous participant": "Anonym deltager",
"Anonymous participants will be asked to confirm their participation through e-mail.": "Anonyme deltagere vil blive bedt om at bekræfte deres deltagelse via e-mail.",
"Anonymous participations": "Anonyme deltagelser",
"Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.": "Er du helt sikker på at du vil slette hele din konto? Alt vil forsvinde. Identiteter, indstillinger, skabte begivenheder, beskeder og deltagelser vil være borte for evigt.",
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "Er du sikker på at du vil <b>slette</b> kommentaren? Denne handling kan ikke fortrydes.",
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the discussion with the event creator or edit its event instead.": "Er du sikker på at du vil <b>slette</b> begivenheden? Denne handling kan ikke fortrydes. Du kan overveje at snakke med begivenhedens skaber eller at redigere begivenheden i stedet.",
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "Er du sikker på at du vil annullere at skabe begivenheden? Alle ændringer vil gå tabt.",
"Are you sure you want to cancel the event edition? You'll lose all modifications.": "Er du sikker på at du vil annullere at redigere begivenheden? Alle ændringer vil gå tabt.",
"Are you sure you want to cancel your participation at event \"{title}\"?": "Er du sikker på at du vil annullere din deltagelse i begivenheden \"{title}\"?",
"Are you sure you want to delete this event? This action cannot be reverted.": "Er du sikker på du vil slette begivenheden? Denne handling kan ikke fortrydes.",
"Avatar": "Avatar",
"Back to previous page": "Tilbage til forrige side",
"Before you can login, you need to click on the link inside it to validate your account.": "Før du kan logge ind skal du klikke på linket i den for at bekræfte din konto.",
"By {username}": "Af {username}",
"Cancel": "Annuller",
"Cancel anonymous participation": "Annuller anonym deltagelse",
"Cancel creation": "Annuller skabelse",
"Cancel edition": "Annuller redigering",
"Cancel my participation request…": "Annuller min anmodning om deltagelse…",
"Cancel my participation…": "Annuller min deltagelse…",
"Cancelled: Won't happen": "Afbrudt: Sker ikke",
"Change": "Ændre",
"Change my email": "Skift email",
"Change my identity…": "Ændre min identitet…",
"Change my password": "Skift kodeord",
"Clear": "Ryd",
"Click to upload": "Klik for at uploade",
"Close": "Luk",
"Close comments for all (except for admins)": "Luk kommentarer for alle (undtagen administratorer)",
"Closed": "Lukket",
"Comment deleted": "Kommentar slettet",
"Comment from @{username} reported": "Kommentaren fra @{username} er blevet indberettet",
"Comments": "Kommentarer",
"Confirm my participation": "Bekræft min deltagelse",
"Confirmed: Will happen": "Bekræftet: Kommer til at ske",
"Continue editing": "Fortsæt med at redigere",
"Country": "Land",
"Create": "Skab",
"Create a new event": "Lav en ny begivenhed",
"Create a new group": "Lav en ny gruppe",
"Create a new identity": "Lav en ny identitet",
"Create group": "Skab en gruppe",
"Create my event": "Skab min begivenhed",
"Create my group": "Skab min gruppe",
"Create my profile": "Skab min profil",
"Create token": "Skab token",
"Current identity has been changed to {identityName} in order to manage this event.": "Den aktive identitet er blevet ændret til {identityName} for at håndtere denne begivenhed.",
"Current page": "Nuværende side",
"Custom": "Tilpasset",
"Custom URL": "Tilpasset addresse",
"Custom text": "Tilpasset tekst",
"Dashboard": "Dashboard",
"Date": "Dato",
"Date and time settings": "Dato og tidsinstillinger",
"Date parameters": "Datoparametre",
"Default": "Standard",
"Delete": "Slet",
"Delete Comment": "Slet kommentar",
"Delete Event": "Slet begivenhed",
"Delete account": "Slet konto",
"Delete event": "Slet begivenhed",
"Delete everything": "Slet alt",
"Delete my account": "Slet min konto",
"Delete this identity": "Slet denne identitet",
"Delete your identity": "Slet din identitet",
"Delete {eventTitle}": "Slet {eventTitle}",
"Delete {preferredUsername}": "Slet {preferredUsername}",
"Deleting comment": "Sletter kommentar",
"Deleting event": "Sletter begivenhed",
"Deleting my account will delete all of my identities.": "Hvis jeg sletter min konto, bliver alle mine identiteter slettet.",
"Deleting your Mobilizon account": "Sletter din Mobilizon konto",
"Description": "Beskrivelse",
"Display name": "Viste navn",
"Display participation price": "Vis pris for deltagelse",
"Domain": "Domæne",
"Draft": "Kladde",
"Drafts": "Kladder",
"Edit": "Rediger",
"Eg: Stockholm, Dance, Chess…": "F.eks.: Stockholm, Dans, Skak…",
"Either on the {instance} instance or on another instance.": "Enten på {instance} udbyderen eller på en anden udbyder.",
"Either the account is already validated, either the validation token is incorrect.": "Enten er kontoen allerede godkendt, eller valideringskoden forkert.",
"Either the email has already been changed, either the validation token is incorrect.": "Enten er emailadressen allerede blevet ændret, eller valideringskoden er forkert.",
"Either the participation request has already been validated, either the validation token is incorrect.": "Enten er deltagelsesanmodningen allerede blevet godkendt, eller valideringskoden er forkert.",
"Email": "Email",
"Ends on…": "Slutter…",
"Enter the link URL": "Indtast linket",
"Error while changing email": "Fejl under ændring af emailadresse",
"Error while validating account": "Fejl under godkendelse af konto",
"Error while validating participation request": "Fejl under godkendelse af deltagelsesanmodning",
"Event": "Begivenhed",
"Event already passed": "Begivenheden er ovre",
"Event cancelled": "Begivenheden er aflyst",
"Event creation": "Skabelse af begivenhed",
"Event edition": "Redigering af begivenhed",
"Event list": "Liste af begivenheder",
"Event page settings": "Indstillinger for begivenhedens side",
"Event to be confirmed": "Begivenheden skal bekræftes",
"Event {eventTitle} deleted": "Begivenheden {eventTitle} blev slettet",
"Event {eventTitle} reported": "Begivenheden {eventTitle} blev indmeldt",
"Events": "Begivenheder",
"Ex: mobilizon.fr": "F.eks: mobilizon.fr",
"Explore": "Udforsk",
"Failed to save admin settings": "Kunne ikke gemme admin indstillinger",
"Featured events": "Udvalgte begivenheder",
"Federation": "Federation",
"Find an address": "Find en adresse",
"Find an instance": "Find en udbyder",
"Followers": "Følgere",
"Followings": "Følger",
"For instance: London, Taekwondo, Architecture…": "For eksempel: London, Taekwondo, Arkitektur…",
"Forgot your password ?": "Glemt dit kodeord?",
"From the {startDate} at {startTime} to the {endDate}": "Fra d. {startDate} kl. {startTime} til d. {endDate}",
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "Fra d. {startDate} kl. {startTime} til d. {endDate} kl. {endTime}",
"From the {startDate} to the {endDate}": "Fra d. {startDate} til d. {endDate}",
"Gather ⋅ Organize ⋅ Mobilize": "Samles ⋅ Organiser ⋅ Mobiliser",
"General": "Generelt",
"General information": "Generel information",
"Getting location": "Henter placering",
"Go": "Gå",
"Group name": "Gruppenavn",
"Group {displayName} created": "Gruppen {displayName} er oprettet",
"Groups": "Grupper",
"Headline picture": "Hovedbillede",
"Hide replies": "Skjul svar",
"I create an identity": "Jeg skaber en identitet",
"I don't have a Mobilizon account": "Jeg har ikke en Mobilizon konto",
"I have a Mobilizon account": "Jeg har en Mobilizon konto",
"I have an account on another Mobilizon instance.": "Jeg har en konto på en anden Mobilizon udbyder.",
"I participate": "Jeg deltager",
"I want to allow people to participate without an account.": "Jeg vil lade personer uden en konto deltage.",
"I want to approve every participation request": "Jeg vil godkende for alle deltagelsesanmodninger",
"Identity {displayName} created": "Identiteten {displayName} er skabt",
"Identity {displayName} deleted": "Identiteten {displayName} er slettet",
"Identity {displayName} updated": "Identiteten {displayName} er opdateret",
"If an account with this email exists, we just sent another confirmation email to {email}": "Hvis en konto med denne emailadresse findes, har vi lige sendt en bekræftelsesmail til {email}",
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "Hvis denne identitet er den eneste administrator af nogle grupper, skal du slette grupperne før du kan slette identiteten.",
"If you want, you may send a message to the event organizer here.": "Hvis du vil kan du sende en besked til begivenhedens arrangør her.",
"Instance Name": "Udbyderens navn",
"Instance Terms": "Udbyderens brugsvilkår",
"Instance Terms Source": "Kilde til udbyderens vilkår",
"Instance Terms URL": "Adresse til udbyderens vilkår",
"Instance settings": "Indstillinger for udbyderen",
"Instances": "Udbydere",
"Join <b>{instance}</b>, a Mobilizon instance": "Bliv medlem af <b>{instance}</b>, en Mobilizon udbyder",
"Last published event": "Nyeste begivenhed",
"Last week": "Sidste uge",
"Learn more": "Lær mere",
"Learn more about Mobilizon": "Lær mere om Mobilizon",
"Leave event": "Forlad begivenhed",
"Leaving event \"{title}\"": "Forlader begivenheden \"{title}\"",
"License": "Licens",
"Limited number of places": "Begrænset antal pladser",
"Load more": "Indlæs flere",
"Locality": "Sted",
"Log in": "Log ind",
"Log out": "Log ud",
"Login": "Log ind",
"Login on Mobilizon!": "Log ind på Mobilizon!",
"Login on {instance}": "Log ind på {instance}",
"Manage participations": "Håndter deltagelser",
"Mark as resolved": "Marker som løst",
"Members": "Medlemmer",
"Message": "Besked",
"Mobilizon is a federated network. You can interact with this event from a different server.": "Mobilizon er et føderalt netværk. Du kan interagere med denne begivenhed fra andre udbydere.",
"Moderated comments (shown after approval)": "Modererede kommentarer (vist efter godkendelse)",
"Moderation": "Moderering",
"Moderation log": "Moderationslog",
"My account": "Min konto",
"My events": "Mine begivenheder",
"My identities": "Mine identiteter",
"Name": "Navn",
"New email": "Ny email",
"New note": "Nyt notat",
"New password": "Nyt kodeord",
"New profile": "Ny profil",
"Next page": "Næste side",
"No address defined": "Ingen adresse givet",
"No closed reports yet": "Ingen behandlede indmeldinger endnu",
"No comment": "Ingen kommentar",
"No comments yet": "Ingen kommentarer endnu",
"No end date": "Ingen slutdato",
"No events found": "Ingen begivenheder fundet",
"No group found": "Ingen gruppe fundet",
"No groups found": "Ingen grupper fundet",
"No instance follows your instance yet.": "Ingen udbydere følger din udbyder endnu.",
"No instance to approve|Approve instance|Approve {number} instances": "Ingen udbydere at godkende|Godkend udbyder|Godkend {number} udbydere",
"No instance to reject|Reject instance|Reject {number} instances": "Ingen udbydere at afvise|Afvis udbyder|Afvis {number} udbydere",
"No instance to remove|Remove instance|Remove {number} instances": "Ingen udbydere at fjerne|Fjern udbyder|Fjern {number} udbydere",
"No message": "Ingen besked",
"No open reports yet": "Ingen åbne indmeldinger endnu",
"No participant to approve|Approve participant|Approve {number} participants": "Ingen deltagere at godkende|Godkend deltager|Godkend {number} deltagere",
"No participant to reject|Reject participant|Reject {number} participants": "Ingen deltagere at afvise|Afvis deltagere|Afvis {number} deltagere",
"No resolved reports yet": "Ingen løste indmeldinger endnu",
"No results for \"{queryText}\"": "Ingen resultater for \"{queryText}\"",
"Notes": "Notater",
"Number of places": "Antal steder",
"OK": "OK",
"Old password": "Gammelt kodeord",
"Please do not use it in any real way.": "Brug det venligst ikke som andet end en prøve."
}

File diff suppressed because it is too large Load Diff

View File

@ -340,7 +340,6 @@
"Transfer to {outsideDomain}": "Transfer to {outsideDomain}", "Transfer to {outsideDomain}": "Transfer to {outsideDomain}",
"Type": "Type", "Type": "Type",
"URL": "URL", "URL": "URL",
"Unfortunately, this instance isn't opened to registrations": "Unfortunately, this instance isn't opened to registrations",
"Unfortunately, your participation request was rejected by the organizers.": "Unfortunately, your participation request was rejected by the organizers.", "Unfortunately, your participation request was rejected by the organizers.": "Unfortunately, your participation request was rejected by the organizers.",
"Unknown actor": "Unknown actor", "Unknown actor": "Unknown actor",
"Unknown error.": "Unknown error.", "Unknown error.": "Unknown error.",
@ -1253,5 +1252,83 @@
"The membership request from {profile} was rejected": "The membership request from {profile} was rejected", "The membership request from {profile} was rejected": "The membership request from {profile} was rejected",
"The member was approved": "The member was approved", "The member was approved": "The member was approved",
"Emails usually don't contain capitals, make sure you haven't made a typo.": "Emails usually don't contain capitals, make sure you haven't made a typo.", "Emails usually don't contain capitals, make sure you haven't made a typo.": "Emails usually don't contain capitals, make sure you haven't made a typo.",
"To follow groups and be informed of their latest events": "To follow groups and be informed of their latest events" "To follow groups and be informed of their latest events": "To follow groups and be informed of their latest events",
"No group member found": "No group member found",
"This group was not found": "This group was not found",
"Back to group list": "Back to group list",
"This profile was not found": "This profile was not found",
"Back to profile list": "Back to profile list",
"This user was not found": "This user was not found",
"Back to user list": "Back to user list",
"Stop following instance": "Stop following instance",
"Follow instance": "Follow instance",
"Accept follow": "Accept follow",
"Reject follow": "Reject follow",
"This instance doesn't follow yours.": "This instance doesn't follow yours.",
"Only Mobilizon instances can be followed": "",
"Follow a new instance": "Follow a new instance",
"Follow status": "Follow status",
"All": "All",
"Following": "Following",
"Followed": "Followed",
"Followed, pending response": "Followed, pending response",
"Follows us": "Follows us",
"Follows us, pending approval": "Follows us, pending approval",
"No instance found.": "No instance found.",
"No instances match this filter. Try resetting filter fields?": "No instances match this filter. Try resetting filter fields?",
"You haven't interacted with other instances yet.": "You haven't interacted with other instances yet.",
"mobilizon-instance.tld": "mobilizon-instance.tld",
"Report status": "Report status",
"access the corresponding account": "access the corresponding account",
"Organized events": "Organized events",
"Memberships": "Memberships",
"This profile is located on this instance, so you need to {access_the_corresponding_account} to suspend it.": "This profile is located on this instance, so you need to {access_the_corresponding_account} to suspend it.",
"Total number of participations": "Total number of participations",
"Uploaded media total size": "Uploaded media total size",
"0 Bytes": "0 Bytes",
"Change email": "Change email",
"Confirm user": "Confirm user",
"Change role": "Change role",
"The user has been disabled": "The user has been disabled",
"This user doesn't have any profiles": "This user doesn't have any profiles",
"Edit user email": "Edit user email",
"Change user email": "Change user email",
"Previous email": "Previous email",
"Notify the user of the change": "Notify the user of the change",
"Change user role": "Change user role",
"Suspend the account?": "Suspend the account?",
"Do you really want to suspend this account? All of the user's profiles will be deleted.": "Do you really want to suspend this account? All of the user's profiles will be deleted.",
"Suspend the account": "Suspend the account",
"No user matches the filter": "No user matches the filter",
"new@email.com": "new@email.com",
"Other users with the same email domain": "Other users with the same email domain",
"Other users with the same IP address": "Other users with the same IP address",
"IP Address": "IP Address",
"Last seen on": "Last seen on",
"No user matches the filters": "No user matches the filters",
"Reset filters": "Reset filters",
"Category": "Category",
"Select a category": "Select a category",
"Any category": "Any category",
"We collect your feedback and the error information in order to improve this service.": "We collect your feedback and the error information in order to improve this service.",
"What happened?": "What happened?",
"I've clicked on X, then on Y": "I've clicked on X, then on Y",
"Send feedback": "Send feedback",
"Sorry, we wen't able to save your feedback. Don't worry, we'll try to fix this issue anyway.": "Sorry, we wen't able to save your feedback. Don't worry, we'll try to fix this issue anyway.",
"return to the homepage": "return to the homepage",
"Thanks a lot, your feedback was submitted!": "Thanks a lot, your feedback was submitted!",
"You may also:": "You may also:",
"You may now close this page or {return_to_the_homepage}.": "You may now close this page or {return_to_the_homepage}.",
"This group is a remote group, it's possible the original instance has more informations.": "This group is a remote group, it's possible the original instance has more informations.",
"View the group profile on the original instance": "View the group profile on the original instance",
"View past events": "View past events",
"Get informed of the upcoming public events": "Get informed of the upcoming public events",
"Join": "Join",
"Become part of the community and start organizing events": "Become part of the community and start organizing events",
"Follow requests will be approved by a group moderator": "Follow requests will be approved by a group moderator",
"Follow request pending approval": "Follow request pending approval",
"Your membership is pending approval": "Your membership is pending approval",
"Activate notifications": "Activate notifications",
"Deactivate notifications": "Deactivate notifications",
"Membership requests will be approved by a group moderator": "Membership requests will be approved by a group moderator"
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,8 @@
"+ Add a resource": "+ Cuir goireas ris", "+ Add a resource": "+ Cuir goireas ris",
"+ Create a post": "+ Cruthaich post", "+ Create a post": "+ Cruthaich post",
"+ Create an event": "+ Cruthaich tachartas", "+ Create an event": "+ Cruthaich tachartas",
"+ Post a public message": "+ Postaich teachdaireachd phoblach",
"+ Start a discussion": "+ Tòisich air deasbad", "+ Start a discussion": "+ Tòisich air deasbad",
"0 Bytes": "0 baidht",
"<b>{contact}</b> will be displayed as contact.": "Thèid <b>{contact}</b> a shealltain mar neach-conaltraidh.|Thèid <b>{contact}</b> a shealltain mar luchd-conaltraidh.|Thèid <b>{contact}</b> a shealltain mar luchd-conaltraidh.|Thèid <b>{contact}</b> a shealltain mar luchd-conaltraidh.", "<b>{contact}</b> will be displayed as contact.": "Thèid <b>{contact}</b> a shealltain mar neach-conaltraidh.|Thèid <b>{contact}</b> a shealltain mar luchd-conaltraidh.|Thèid <b>{contact}</b> a shealltain mar luchd-conaltraidh.|Thèid <b>{contact}</b> a shealltain mar luchd-conaltraidh.",
"@{group}": "@{group}", "@{group}": "@{group}",
"@{username}": "@{username}", "@{username}": "@{username}",
@ -45,6 +45,7 @@
"About this instance": "Mun ionstans seo", "About this instance": "Mun ionstans seo",
"About {instance}": "Mu {instance}", "About {instance}": "Mu {instance}",
"Accept": "Gabh ris", "Accept": "Gabh ris",
"Accept follow": "Gabh ris an iarrtas leantainn",
"Accepted": "Air a ghabhail ris", "Accepted": "Air a ghabhail ris",
"Accessibility": "So-ruigsinneachd", "Accessibility": "So-ruigsinneachd",
"Accessible only by link": "Inntrigeadh le ceangal a-mhàin", "Accessible only by link": "Inntrigeadh le ceangal a-mhàin",
@ -54,6 +55,7 @@
"Account settings": "Roghainnean a chunntais", "Account settings": "Roghainnean a chunntais",
"Actions": "Gnìomhan", "Actions": "Gnìomhan",
"Activate browser push notifications": "Gnìomhaich brathan putaidh a bhrabhsair", "Activate browser push notifications": "Gnìomhaich brathan putaidh a bhrabhsair",
"Activate notifications": "Gnìomhaich na brathan",
"Activated": "An gnìomh", "Activated": "An gnìomh",
"Active": "Gnìomhach", "Active": "Gnìomhach",
"Activity": "Gnìomhachd", "Activity": "Gnìomhachd",
@ -61,7 +63,6 @@
"Add": "Cuir ris", "Add": "Cuir ris",
"Add / Remove…": "Cuir ris / Thoir air falbh…", "Add / Remove…": "Cuir ris / Thoir air falbh…",
"Add a contact": "Cuir fiosrachadh conaltraidh ris", "Add a contact": "Cuir fiosrachadh conaltraidh ris",
"Add a group": "Cuir buidheann ris",
"Add a new post": "Cuir post ùr ris", "Add a new post": "Cuir post ùr ris",
"Add a note": "Cuir nòta ris", "Add a note": "Cuir nòta ris",
"Add a todo": "Cuir rud ri dhèanamh ris", "Add a todo": "Cuir rud ri dhèanamh ris",
@ -79,6 +80,7 @@
"Admin settings successfully saved.": "Chaidh roghainnean an rianaire a shàbhaladh.", "Admin settings successfully saved.": "Chaidh roghainnean an rianaire a shàbhaladh.",
"Administration": "Rianachd", "Administration": "Rianachd",
"Administrator": "Rianaire", "Administrator": "Rianaire",
"All": "Na h-uile",
"All activities": "A h-uile gnìomhachd", "All activities": "A h-uile gnìomhachd",
"All good, let's continue!": "Tha seo dòigheil, leanamaid oirnn!", "All good, let's continue!": "Tha seo dòigheil, leanamaid oirnn!",
"All the places have already been taken": "Chan eil àite saor air fhàgail", "All the places have already been taken": "Chan eil àite saor air fhàgail",
@ -102,6 +104,7 @@
"Anonymous participant": "Freastalaiche gun ainm", "Anonymous participant": "Freastalaiche gun ainm",
"Anonymous participants will be asked to confirm their participation through e-mail.": "Thèid iarraidh air freastalaichean gun ainm gun dearbh iad an com-pàirteachadh air a phost-d.", "Anonymous participants will be asked to confirm their participation through e-mail.": "Thèid iarraidh air freastalaichean gun ainm gun dearbh iad an com-pàirteachadh air a phost-d.",
"Anonymous participations": "Com-pàirteachaidhean gun ainm", "Anonymous participations": "Com-pàirteachaidhean gun ainm",
"Any category": "Roinn-seòrsa sam bith",
"Any day": "Latha sam bith", "Any day": "Latha sam bith",
"Any type": "Seòrsa sam bith", "Any type": "Seòrsa sam bith",
"Anyone can join freely": "Faodaidh neach sam bith a dhol an sàs ann gu saor", "Anyone can join freely": "Faodaidh neach sam bith a dhol an sàs ann gu saor",
@ -128,9 +131,13 @@
"Atom feed for events and posts": "Inbhir Atom dha na tachartasan is postaichean", "Atom feed for events and posts": "Inbhir Atom dha na tachartasan is postaichean",
"Attending": "An làthair", "Attending": "An làthair",
"Avatar": "Avatar", "Avatar": "Avatar",
"Back to group list": "Air ais gu liosta nam buidhnean",
"Back to previous page": "Air ais dhan duilleag roimhpe", "Back to previous page": "Air ais dhan duilleag roimhpe",
"Back to profile list": "Air ais gu liosta nam pròifilean",
"Back to top": "Air ais gun bhàrr", "Back to top": "Air ais gun bhàrr",
"Back to user list": "Air ais gu liosta nan cleachdaichean",
"Banner": "Bratach", "Banner": "Bratach",
"Become part of the community and start organizing events": "Faigh ballrachd sa choimhearsnachd agus tòisich air tachartasan a chur air dòigh",
"Before you can login, you need to click on the link inside it to validate your account.": "Mus urrainn dhut clàradh a-steach, feumaidh tu briogadh air a cheangal na broinn gus an cunntas agad a dhearbhadh.", "Before you can login, you need to click on the link inside it to validate your account.": "Mus urrainn dhut clàradh a-steach, feumaidh tu briogadh air a cheangal na broinn gus an cunntas agad a dhearbhadh.",
"Begins on": "Tòisichidh e aig", "Begins on": "Tòisichidh e aig",
"Big Blue Button": "Big Blue Button", "Big Blue Button": "Big Blue Button",
@ -140,7 +147,6 @@
"Browser notifications": "Brathan a bhrabhsair", "Browser notifications": "Brathan a bhrabhsair",
"Bullet list": "Liosta pheilearaichte", "Bullet list": "Liosta pheilearaichte",
"By others": "Le daoine eile", "By others": "Le daoine eile",
"By {author}": "Le {author}",
"By {group}": "Le {group}", "By {group}": "Le {group}",
"By {username}": "Le {username}", "By {username}": "Le {username}",
"Can be an email or a link, or just plain text.": "S urrainn dhut post-d no ceangal no teacsa lom a cleachdadh.", "Can be an email or a link, or just plain text.": "S urrainn dhut post-d no ceangal no teacsa lom a cleachdadh.",
@ -155,11 +161,16 @@
"Cancel my participation…": "Sguir dhen chom-pàirteachadh agam…", "Cancel my participation…": "Sguir dhen chom-pàirteachadh agam…",
"Cancelled": "Chaidh a chur gu neoini", "Cancelled": "Chaidh a chur gu neoini",
"Cancelled: Won't happen": "Sguireadh dheth: Cha tachair seo", "Cancelled: Won't happen": "Sguireadh dheth: Cha tachair seo",
"Category": "Roinn-seòrsa",
"Change": "Atharraich", "Change": "Atharraich",
"Change email": "Atharraich am post-d",
"Change my email": "Atharraich am post-d agam", "Change my email": "Atharraich am post-d agam",
"Change my identity…": "Atharraich an dearbh-aithne agam…", "Change my identity…": "Atharraich an dearbh-aithne agam…",
"Change my password": "Atharraich am facal-faire agam", "Change my password": "Atharraich am facal-faire agam",
"Change role": "Atharraich an dreuchd",
"Change timezone": "Atharraich an roinn-tìde", "Change timezone": "Atharraich an roinn-tìde",
"Change user email": "Atharraich post-d a chleachdaiche",
"Change user role": "Atharraich dreuchd a chleachdaiche",
"Check your inbox (and your junk mail folder).": "Thoir sùil air a bhogsa a-steach agad (s air a phasgan airson puist-d thruilleis).", "Check your inbox (and your junk mail folder).": "Thoir sùil air a bhogsa a-steach agad (s air a phasgan airson puist-d thruilleis).",
"Choose the source of the instance's Privacy Policy": "Tagh tùs do phoileasaidh prìobhaideachd an ionstans", "Choose the source of the instance's Privacy Policy": "Tagh tùs do phoileasaidh prìobhaideachd an ionstans",
"Choose the source of the instance's Terms": "Tagh tùs do theirmichean an ionstans", "Choose the source of the instance's Terms": "Tagh tùs do theirmichean an ionstans",
@ -184,7 +195,8 @@
"Confirm my participation": "Dearbh an com-pàirteachadh agam", "Confirm my participation": "Dearbh an com-pàirteachadh agam",
"Confirm my particpation": "Dearbh an com-pàirteachadh agam", "Confirm my particpation": "Dearbh an com-pàirteachadh agam",
"Confirm participation": "Dearbh an com-pàirteachadh", "Confirm participation": "Dearbh an com-pàirteachadh",
"Confirmed": "Air a dhearbhachadh", "Confirm user": "Dearbh an cleachdaiche",
"Confirmed": "Air a dhearbhadh",
"Confirmed at": "Chaidh a dhearbhachadh", "Confirmed at": "Chaidh a dhearbhachadh",
"Confirmed: Will happen": "Air dearbhadh: Tachraidh seo", "Confirmed: Will happen": "Air dearbhadh: Tachraidh seo",
"Congratulations, your account is now created!": "Meal do naidheachd, chaidh an cunntas agad a chruthachadh!", "Congratulations, your account is now created!": "Meal do naidheachd, chaidh an cunntas agad a chruthachadh!",
@ -231,6 +243,7 @@
"Date and time": "Ceann-là s àm", "Date and time": "Ceann-là s àm",
"Date and time settings": "Roghainnean a chinn-là s an ama", "Date and time settings": "Roghainnean a chinn-là s an ama",
"Date parameters": "Paramadairean a chinn-là", "Date parameters": "Paramadairean a chinn-là",
"Deactivate notifications": "Cuir na brathan à gnìomh",
"Decline": "Diùlt", "Decline": "Diùlt",
"Decrease": "Lùghdaich", "Decrease": "Lùghdaich",
"Default": "Bun-roghainn", "Default": "Bun-roghainn",
@ -268,6 +281,7 @@
"Displayed nickname": "Am far-ainm a chithear", "Displayed nickname": "Am far-ainm a chithear",
"Displayed on homepage and meta tags. Describe what Mobilizon is and what makes this instance special in a single paragraph.": "Thèid seo a shealltainn air an duilleag-dhachaigh agus am measg nan tagaichean meata. Mìnich dè th ann am Mobilizon agus dè tha sònraichte mun ionstans agad ann an earrann a-mhàin.", "Displayed on homepage and meta tags. Describe what Mobilizon is and what makes this instance special in a single paragraph.": "Thèid seo a shealltainn air an duilleag-dhachaigh agus am measg nan tagaichean meata. Mìnich dè th ann am Mobilizon agus dè tha sònraichte mun ionstans agad ann an earrann a-mhàin.",
"Do not receive any mail": "Na faigh post-d idir", "Do not receive any mail": "Na faigh post-d idir",
"Do you really want to suspend this account? All of the user's profiles will be deleted.": "A bheil thu cinnteach gu bheil thu airson an cunntas seo a chur à rèim? Thèid gach pròifil a chleachdaiche a sguabadh às.",
"Do you wish to {create_event} or {explore_events}?": "A bheil thu airson {create_event} no {explore_events}?", "Do you wish to {create_event} or {explore_events}?": "A bheil thu airson {create_event} no {explore_events}?",
"Do you wish to {create_group} or {explore_groups}?": "A bheil thu airson {create_group} no {explore_groups}?", "Do you wish to {create_group} or {explore_groups}?": "A bheil thu airson {create_group} no {explore_groups}?",
"Does the event needs to be confirmed later or is it cancelled?": "Am bi an tachartas feumach air dearbhadh uaireigin eile no an deach a chur dheth?", "Does the event needs to be confirmed later or is it cancelled?": "Am bi an tachartas feumach air dearbhadh uaireigin eile no an deach a chur dheth?",
@ -279,6 +293,7 @@
"Edit": "Deasaich", "Edit": "Deasaich",
"Edit post": "Deasaich am post", "Edit post": "Deasaich am post",
"Edit profile {profile}": "Deasaich pròifil {profile}", "Edit profile {profile}": "Deasaich pròifil {profile}",
"Edit user email": "Deasaich post-d a chleachdaiche",
"Edited {ago}": "Air a dheasachadh {ago}", "Edited {ago}": "Air a dheasachadh {ago}",
"Edited {relative_time} ago": "Chaidh a dheasachadh {relative_time} air ais", "Edited {relative_time} ago": "Chaidh a dheasachadh {relative_time} air ais",
"Eg: Stockholm, Dance, Chess…": "M.e.: Steòrnabhagh, Cèilidh, Spòrs…", "Eg: Stockholm, Dance, Chess…": "M.e.: Steòrnabhagh, Cèilidh, Spòrs…",
@ -290,7 +305,6 @@
"Element value": "Luach na h-eileamaid", "Element value": "Luach na h-eileamaid",
"Email": "Post-d", "Email": "Post-d",
"Email address": "Seòladh puist-d", "Email address": "Seòladh puist-d",
"Email notifications": "Brathan puist-d",
"Email validate": "Dearbh am post-d", "Email validate": "Dearbh am post-d",
"Emails usually don't contain capitals, make sure you haven't made a typo.": "Cha bhi litrichean mòra ann am post-d mar as trice, dèan cinnteach nach do rinn thu mearachd sgrìobhaidh.", "Emails usually don't contain capitals, make sure you haven't made a typo.": "Cha bhi litrichean mòra ann am post-d mar as trice, dèan cinnteach nach do rinn thu mearachd sgrìobhaidh.",
"Enabled": "An comas", "Enabled": "An comas",
@ -354,11 +368,21 @@
"Find or add an element": "Lorg no cuir eileamaid ris", "Find or add an element": "Lorg no cuir eileamaid ris",
"First steps": "Na ciad ceuman", "First steps": "Na ciad ceuman",
"Follow": "Lean air", "Follow": "Lean air",
"Follow a new instance": "Lean air ionstans ùr",
"Follow instance": "Lean air an ionstans",
"Follow request pending approval": "Iarrtas leantainn ri aontachadh",
"Follow requests will be approved by a group moderator": "Thèid aontachadh ri iarrtasan leantainn le maor a bhuidhinn",
"Follow status": "Staid na leantainn",
"Followed": "Ga leantainn leinne",
"Followed, pending response": "Ga leantainn leinne, a feitheamh air freagairt",
"Follower": "Neach-leantainn", "Follower": "Neach-leantainn",
"Followers": "Luchd-leantainn", "Followers": "Luchd-leantainn",
"Followers will receive new public events and posts.": "Gheibh an luchd-leantainn tachartasan is postaichean poblach.", "Followers will receive new public events and posts.": "Gheibh an luchd-leantainn tachartasan is postaichean poblach.",
"Following": "A leantainn",
"Following the group will allow you to be informed of the {group_upcoming_public_events}, whereas joining the group means you will {access_to_group_private_content_as_well}, including group discussions, group resources and members-only posts.": "Ma leanas tu air a bhuidheann, gheibh thu fiosrachadh mu {group_upcoming_public_events} ach ma gheibh thu ballrachd sa bhuidheann, gheibh thu {access_to_group_private_content_as_well}, a gabhail a-staigh deasbadan a bhuidhinn, goireasan a bhuidhinn agus postaichean a tha do bhuill a-mhàin.", "Following the group will allow you to be informed of the {group_upcoming_public_events}, whereas joining the group means you will {access_to_group_private_content_as_well}, including group discussions, group resources and members-only posts.": "Ma leanas tu air a bhuidheann, gheibh thu fiosrachadh mu {group_upcoming_public_events} ach ma gheibh thu ballrachd sa bhuidheann, gheibh thu {access_to_group_private_content_as_well}, a gabhail a-staigh deasbadan a bhuidhinn, goireasan a bhuidhinn agus postaichean a tha do bhuill a-mhàin.",
"Followings": "A leantainn", "Followings": "A leantainn",
"Follows us": "A leantainn oirnn",
"Follows us, pending approval": "A leantainn oirnn, a feitheamh air dearbhadh",
"For instance: London": "Mar eisimpleir: Glaschu", "For instance: London": "Mar eisimpleir: Glaschu",
"For instance: London, Taekwondo, Architecture…": "Mar eisimpleir: Glaschu, Camanachd, Ailtireachd…", "For instance: London, Taekwondo, Architecture…": "Mar eisimpleir: Glaschu, Camanachd, Ailtireachd…",
"Forgot your password ?": "Na dhìochuimhnich thu am facal-faire agad?", "Forgot your password ?": "Na dhìochuimhnich thu am facal-faire agad?",
@ -375,6 +399,7 @@
"General information": "Fiosrachadh coitcheann", "General information": "Fiosrachadh coitcheann",
"General settings": "Roghainnean coitcheann", "General settings": "Roghainnean coitcheann",
"Geolocation was not determined in time.": "Cha deach leis a gheò-lorgadh ri àm.", "Geolocation was not determined in time.": "Cha deach leis a gheò-lorgadh ri àm.",
"Get informed of the upcoming public events": "Faigh naidheachdan mu thachartasan poblach ri thighinn",
"Getting location": "A faighinn an ionaid", "Getting location": "A faighinn an ionaid",
"Getting there": "Mar a gheibh thu ann", "Getting there": "Mar a gheibh thu ann",
"Glossary": "Briathrachan", "Glossary": "Briathrachan",
@ -419,8 +444,10 @@
"I want to approve every participation request": "Tha mi airson aontachadh ris a h-uile iarrtas air com-pàirteachadh", "I want to approve every participation request": "Tha mi airson aontachadh ris a h-uile iarrtas air com-pàirteachadh",
"I've been mentionned in a comment under an event": "Chaidh iomradh a thoirt orm ann am beachd fo thachartas", "I've been mentionned in a comment under an event": "Chaidh iomradh a thoirt orm ann am beachd fo thachartas",
"I've been mentionned in a group discussion": "Chaidh iomradh a thoirt orm ann an deasbad buidhinn", "I've been mentionned in a group discussion": "Chaidh iomradh a thoirt orm ann an deasbad buidhinn",
"I've clicked on X, then on Y": "Briog mi air X s an uairsin air Y",
"ICS feed for events": "Inbhir ICS dha na tachartasan", "ICS feed for events": "Inbhir ICS dha na tachartasan",
"ICS/WebCal Feed": "Inbhir ICS/WebCal", "ICS/WebCal Feed": "Inbhir ICS/WebCal",
"IP Address": "Seòladh IP",
"Identities": "Dearbh-aithnean", "Identities": "Dearbh-aithnean",
"Identity {displayName} created": "Chaidh an dearbh-aithne {displayName} a chruthachadh", "Identity {displayName} created": "Chaidh an dearbh-aithne {displayName} a chruthachadh",
"Identity {displayName} deleted": "Chaidh an dearbh-aithne {displayName} a sguabadh às", "Identity {displayName} deleted": "Chaidh an dearbh-aithne {displayName} a sguabadh às",
@ -451,7 +478,6 @@
"Instance administrator": "Rianaire ionstans", "Instance administrator": "Rianaire ionstans",
"Instance configuration": "Rèiteachadh an ionstans", "Instance configuration": "Rèiteachadh an ionstans",
"Instance feeds": "Inbhirean an ionstans", "Instance feeds": "Inbhirean an ionstans",
"Instance follows": "Ionstansan gan leantainn",
"Instance languages": "Cànain an ionstans", "Instance languages": "Cànain an ionstans",
"Instance rules": "Riaghailtean an ionstans", "Instance rules": "Riaghailtean an ionstans",
"Instance settings": "Roghainnean an ionstans", "Instance settings": "Roghainnean an ionstans",
@ -467,6 +493,7 @@
"It is possible that the content is not accessible on this instance, because this instance has blocked the profiles or groups behind this content.": "Dhfhaoidte nach gabh an t-susbaint inntrigeadh air an ionstans seo on a bhac an t-ionstans seo na pròifilean no buidhnean a tha air cùlaibh na susbainte seo.", "It is possible that the content is not accessible on this instance, because this instance has blocked the profiles or groups behind this content.": "Dhfhaoidte nach gabh an t-susbaint inntrigeadh air an ionstans seo on a bhac an t-ionstans seo na pròifilean no buidhnean a tha air cùlaibh na susbainte seo.",
"Italic": "Eadailteach", "Italic": "Eadailteach",
"Jitsi Meet": "Jitsi Meet", "Jitsi Meet": "Jitsi Meet",
"Join": "Faigh ballrachd",
"Join <b>{instance}</b>, a Mobilizon instance": "Faigh ballrachd air <b>{instance}</b>, seo ionstans Mobilizon", "Join <b>{instance}</b>, a Mobilizon instance": "Faigh ballrachd air <b>{instance}</b>, seo ionstans Mobilizon",
"Join group": "Faigh ballrachd sa bhuidheann", "Join group": "Faigh ballrachd sa bhuidheann",
"Join group {group}": "Faigh ballrachd sa bhuidheann {group}", "Join group {group}": "Faigh ballrachd sa bhuidheann {group}",
@ -477,6 +504,7 @@
"Last group created": "Am buidheann mu dheireadh a chruthaich thu", "Last group created": "Am buidheann mu dheireadh a chruthaich thu",
"Last published event": "An tachartas foillsichte as ùire", "Last published event": "An tachartas foillsichte as ùire",
"Last published events": "Na tachartasan foillsichte as ùire", "Last published events": "Na tachartasan foillsichte as ùire",
"Last seen on": "Chunnacas an turas mu dheireadh",
"Last sign-in": "An clàradh a-steach mu dheireadh", "Last sign-in": "An clàradh a-steach mu dheireadh",
"Last week": "An t-seachdain seo chaidh", "Last week": "An t-seachdain seo chaidh",
"Latest posts": "Na puist as ùire", "Latest posts": "Na puist as ùire",
@ -514,6 +542,8 @@
"Member": "Ball", "Member": "Ball",
"Members": "Buill", "Members": "Buill",
"Members-only post": "Post do bhuill a-mhàin", "Members-only post": "Post do bhuill a-mhàin",
"Membership requests will be approved by a group moderator": "Thèid aontachadh ri iarrtasan ballrachd le maor a bhuidhinn",
"Memberships": "Ballrachdan",
"Mentions": "Iomraidhean", "Mentions": "Iomraidhean",
"Message": "Teachdaireachd", "Message": "Teachdaireachd",
"Microsoft Teams": "Microsoft Teams", "Microsoft Teams": "Microsoft Teams",
@ -566,12 +596,15 @@
"No follower matches the filters": "Chan eil neach-leantainn sam bith a maidseadh nan criathragan", "No follower matches the filters": "Chan eil neach-leantainn sam bith a maidseadh nan criathragan",
"No group found": "Cha deach buidheann sam bith a lorg", "No group found": "Cha deach buidheann sam bith a lorg",
"No group matches the filters": "Chan eil buidheann sam bith a maidseadh nan criathragan", "No group matches the filters": "Chan eil buidheann sam bith a maidseadh nan criathragan",
"No group member found": "Cha deach ball a lorg sa bhuidheann",
"No groups found": "Cha deach buidheann sam bith a lorg", "No groups found": "Cha deach buidheann sam bith a lorg",
"No information": "Gun fhiosrachadh", "No information": "Gun fhiosrachadh",
"No instance follows your instance yet.": "Chan eil ionstans sam bith a leantainn air an ionstans agad-sa fhathast.", "No instance follows your instance yet.": "Chan eil ionstans sam bith a leantainn air an ionstans agad-sa fhathast.",
"No instance found.": "Cha deach ionstans a lorg.",
"No instance to approve|Approve instance|Approve {number} instances": "Aontaich ri {number} ionstans|Aontaich ri {number} ionstans|Aontaich ri {number} ionstansan|Aontaich ri {number} ionstans", "No instance to approve|Approve instance|Approve {number} instances": "Aontaich ri {number} ionstans|Aontaich ri {number} ionstans|Aontaich ri {number} ionstansan|Aontaich ri {number} ionstans",
"No instance to reject|Reject instance|Reject {number} instances": "Diùlt {number} ionstans|Diùlt {number} ionstans|Diùlt {number} ionstansan|Diùlt {number} ionstans", "No instance to reject|Reject instance|Reject {number} instances": "Diùlt {number} ionstans|Diùlt {number} ionstans|Diùlt {number} ionstansan|Diùlt {number} ionstans",
"No instance to remove|Remove instance|Remove {number} instances": "Thoir air falbh {number} ionstans|Thoir air falbh {number} ionstans|Thoir air falbh {number} ionstansan|Thoir air falbh {number} ionstans", "No instance to remove|Remove instance|Remove {number} instances": "Thoir air falbh {number} ionstans|Thoir air falbh {number} ionstans|Thoir air falbh {number} ionstansan|Thoir air falbh {number} ionstans",
"No instances match this filter. Try resetting filter fields?": "Chan eil ionstans sam bith a freagairt ris a chriathrag seo. Saoil an ath-shuidhich thu raointean na criathraige?",
"No languages found": "Cha deach cànan a lorg", "No languages found": "Cha deach cànan a lorg",
"No member matches the filters": "Chan eil ball sam bith a maidseadh nan criathragan", "No member matches the filters": "Chan eil ball sam bith a maidseadh nan criathragan",
"No members found": "Cha deach ball a lorg", "No members found": "Cha deach ball a lorg",
@ -590,7 +623,6 @@
"No posts found": "Cha deach post a lorg", "No posts found": "Cha deach post a lorg",
"No posts yet": "Chan eil post ann fhathast", "No posts yet": "Chan eil post ann fhathast",
"No profile matches the filters": "Chan eil pròifil sam bith a maidseadh nan criathragan", "No profile matches the filters": "Chan eil pròifil sam bith a maidseadh nan criathragan",
"No profiles found": "Cha deach pròifil a lorg",
"No public upcoming events": "Cha bhi tachartas poblach ann a dhaithghearr", "No public upcoming events": "Cha bhi tachartas poblach ann a dhaithghearr",
"No resolved reports yet": "Cha deach gearan fuasgladh fhathast", "No resolved reports yet": "Cha deach gearan fuasgladh fhathast",
"No resources in this folder": "Chan eil goireas sa phasgan seo", "No resources in this folder": "Chan eil goireas sa phasgan seo",
@ -599,18 +631,20 @@
"No results for \"{queryText}\"": "Cha deach toradh a lorg airson “{queryText}”", "No results for \"{queryText}\"": "Cha deach toradh a lorg airson “{queryText}”",
"No results for {search}": "Cha deach toradh a lorg airson {search}", "No results for {search}": "Cha deach toradh a lorg airson {search}",
"No rules defined yet.": "Cha deach riaghailt a mhìneachadh fhathast.", "No rules defined yet.": "Cha deach riaghailt a mhìneachadh fhathast.",
"No user matches the filter": "Chan eil cleachdaiche sam bith a maidseadh na criathraige",
"No user matches the filters": "Chan eil cleachdaiche sam bith a maidseadh nan criathragan",
"None": "Chan eil gin", "None": "Chan eil gin",
"Not accessible with a wheelchair": "Cha ghabh a ruigsinn le cathair-chuibhle", "Not accessible with a wheelchair": "Cha ghabh a ruigsinn le cathair-chuibhle",
"Not approved": "Gun aonta", "Not approved": "Gun aonta",
"Not confirmed": "Gun dearbhadh", "Not confirmed": "Gun dearbhadh",
"Notes": "Nòtaichean", "Notes": "Nòtaichean",
"Nothing to see here": "Chan eil dad ri fhaicinn an-seo",
"Notification before the event": "Brath ron tachartas", "Notification before the event": "Brath ron tachartas",
"Notification on the day of the event": "Brath air latha an tachartais", "Notification on the day of the event": "Brath air latha an tachartais",
"Notification settings": "Roghainnean nam brathan", "Notification settings": "Roghainnean nam brathan",
"Notifications": "Brathan", "Notifications": "Brathan",
"Notifications for manually approved participations to an event": "Brathan mu chom-pàirteachaichean air tachartas a chaidh aontachadh riutha à làimh", "Notifications for manually approved participations to an event": "Brathan mu chom-pàirteachaichean air tachartas a chaidh aontachadh riutha à làimh",
"Notify participants": "Cuir brath dha na com-pàirtichean", "Notify participants": "Cuir brath dha na com-pàirtichean",
"Notify the user of the change": "Leig fios leis a chleachdaiche mun atharrachadh",
"Now, create your first profile:": "Nise, cruthaich a chiad phròifil agad:", "Now, create your first profile:": "Nise, cruthaich a chiad phròifil agad:",
"Number of places": "Co mheud àite", "Number of places": "Co mheud àite",
"OK": "Ceart ma-thà", "OK": "Ceart ma-thà",
@ -619,7 +653,6 @@
"On {date} ending at {endTime}": "{date}, a crìochnachadh aig {endTime}", "On {date} ending at {endTime}": "{date}, a crìochnachadh aig {endTime}",
"On {date} from {startTime} to {endTime}": "{date} o {startTime} gu {endTime}", "On {date} from {startTime} to {endTime}": "{date} o {startTime} gu {endTime}",
"On {date} starting at {startTime}": "{date}, a tòiseachadh aig {startTime}", "On {date} starting at {startTime}": "{date}, a tòiseachadh aig {startTime}",
"On {instance}": "Air {instance}",
"On {instance} and other federated instances": "Air {instance} agus ionstansan co-naisgte eile", "On {instance} and other federated instances": "Air {instance} agus ionstansan co-naisgte eile",
"Online": "Air loidhne", "Online": "Air loidhne",
"Online ticketing": "Ticeadan air loidhne", "Online ticketing": "Ticeadan air loidhne",
@ -639,8 +672,8 @@
"Ordered list": "Liosta le òrdugh", "Ordered list": "Liosta le òrdugh",
"Organized": "Ga eagrachadh", "Organized": "Ga eagrachadh",
"Organized by": "Ga eagrachadh le", "Organized by": "Ga eagrachadh le",
"Organized by you": "Air a chur air dòigh leatsa",
"Organized by {name}": "Ga eagrachadh le {ainm}", "Organized by {name}": "Ga eagrachadh le {ainm}",
"Organized events": "Tachartasan gan cur air dòigh",
"Organizer": "Eagraiche", "Organizer": "Eagraiche",
"Organizer notifications": "Brathan an eagraiche", "Organizer notifications": "Brathan an eagraiche",
"Organizers": "Eagraichean", "Organizers": "Eagraichean",
@ -648,6 +681,8 @@
"Other actions": "Gnìomhan eile", "Other actions": "Gnìomhan eile",
"Other notification options:": "Roghainnean eile nam brathan:", "Other notification options:": "Roghainnean eile nam brathan:",
"Other software may also support this.": "Dhfhaoidte gun doir bathar-bog eile taic ri seo cuideachd.", "Other software may also support this.": "Dhfhaoidte gun doir bathar-bog eile taic ri seo cuideachd.",
"Other users with the same IP address": "Cleachdaichean eile aig a bheil an t-aon seòladh IP",
"Other users with the same email domain": "Cleachdaichean eile aig a bheil an aon àrainn puist-d",
"Otherwise this identity will just be removed from the group administrators.": "Air neo thèid an dearbh-aithne seo a thoirt air falbh le rianairean a bhuidhinn.", "Otherwise this identity will just be removed from the group administrators.": "Air neo thèid an dearbh-aithne seo a thoirt air falbh le rianairean a bhuidhinn.",
"Page": "Duilleag", "Page": "Duilleag",
"Page limited to my group (asks for auth)": "Duilleag cuingichte air a bhuidheann agam (thèid dearbhadh iarraidh)", "Page limited to my group (asks for auth)": "Duilleag cuingichte air a bhuidheann agam (thèid dearbhadh iarraidh)",
@ -674,7 +709,6 @@
"Pending": "Ri dhèiligeadh", "Pending": "Ri dhèiligeadh",
"Personal feeds": "Inbhirean pearsanta", "Personal feeds": "Inbhirean pearsanta",
"Pick": "Tagh", "Pick": "Tagh",
"Pick a group": "Tagh buidheann",
"Pick a profile or a group": "Tagh pròifil no buidheann", "Pick a profile or a group": "Tagh pròifil no buidheann",
"Pick an identity": "Tagh dearbh-aithne", "Pick an identity": "Tagh dearbh-aithne",
"Pick an instance": "Tagh ionstans", "Pick an instance": "Tagh ionstans",
@ -697,6 +731,7 @@
"Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.": "Le cumhachd {mobilizon}. © 2018 {date} Luchd-cuideachaidh Mobilizon Le taic maoineachaidh o {contributors}.", "Powered by {mobilizon}. © 2018 - {date} The Mobilizon Contributors - Made with the financial support of {contributors}.": "Le cumhachd {mobilizon}. © 2018 {date} Luchd-cuideachaidh Mobilizon Le taic maoineachaidh o {contributors}.",
"Preferences": "Roghainnean", "Preferences": "Roghainnean",
"Previous": "Air ais", "Previous": "Air ais",
"Previous email": "Am post-d roimhe",
"Previous month": "Am mìos roimhe", "Previous month": "Am mìos roimhe",
"Previous page": "An duilleag roimhpe", "Previous page": "An duilleag roimhpe",
"Price sheet": "Siota phrìsean", "Price sheet": "Siota phrìsean",
@ -745,6 +780,7 @@
"Registrations": "Clàraidhean", "Registrations": "Clàraidhean",
"Registrations are restricted by allowlisting.": "Tha an clàradh cuingichte le liosta ceadachaidh.", "Registrations are restricted by allowlisting.": "Tha an clàradh cuingichte le liosta ceadachaidh.",
"Reject": "Diùlt", "Reject": "Diùlt",
"Reject follow": "Diùlt an iarrtas leantainn",
"Reject member": "Diùlt am ball", "Reject member": "Diùlt am ball",
"Rejected": "Air a dhiùltadh", "Rejected": "Air a dhiùltadh",
"Remember my participation in this browser": "Cùm an com-pàirteachadh agam an cuimhne a bhrabhsair seo", "Remember my participation in this browser": "Cùm an com-pàirteachadh agam an cuimhne a bhrabhsair seo",
@ -757,6 +793,7 @@
"Reply": "Freagair", "Reply": "Freagair",
"Report": "Dèan gearan", "Report": "Dèan gearan",
"Report #{reportNumber}": "Gearan #{report_number}", "Report #{reportNumber}": "Gearan #{report_number}",
"Report status": "Staid a ghearain",
"Report this comment": "Dèan gearan mun bheachd seo", "Report this comment": "Dèan gearan mun bheachd seo",
"Report this event": "Dèan gearan mun tachartas seo", "Report this event": "Dèan gearan mun tachartas seo",
"Report this group": "Dèan gearan mun bhuidheann seo", "Report this group": "Dèan gearan mun bhuidheann seo",
@ -773,6 +810,7 @@
"Resend confirmation email": "Cuir am post-d dearbhaidh a-rithist", "Resend confirmation email": "Cuir am post-d dearbhaidh a-rithist",
"Resent confirmation email": "Chaidh am post-d dearbhaidh a chur a-rithist", "Resent confirmation email": "Chaidh am post-d dearbhaidh a chur a-rithist",
"Reset": "Ath-shuidhich", "Reset": "Ath-shuidhich",
"Reset filters": "Ath-shuidhich na criathragan",
"Reset my password": "Ath-shuidhich am facal-faire agam", "Reset my password": "Ath-shuidhich am facal-faire agam",
"Reset password": "Ath-shuidhich am facal-faire", "Reset password": "Ath-shuidhich am facal-faire",
"Resolved": "Air fhuasgladh", "Resolved": "Air fhuasgladh",
@ -791,7 +829,7 @@
"Search": "Lorg", "Search": "Lorg",
"Search events, groups, etc.": "Lorg tachartasan, buidhnean is msaa.", "Search events, groups, etc.": "Lorg tachartasan, buidhnean is msaa.",
"Searching…": "Ga lorg…", "Searching…": "Ga lorg…",
"Search…": "Lorg…", "Select a category": "Tagh roinn-seòrsa",
"Select a language": "Tagh cànan", "Select a language": "Tagh cànan",
"Select a radius": "Tagh astar", "Select a radius": "Tagh astar",
"Select a timezone": "Tagh roinn-tìde", "Select a timezone": "Tagh roinn-tìde",
@ -799,6 +837,7 @@
"Select the activities for which you wish to receive an email or a push notification.": "Tagh na gnìomhachdan dhan fhaigh thu post-d no brath putaidh.", "Select the activities for which you wish to receive an email or a push notification.": "Tagh na gnìomhachdan dhan fhaigh thu post-d no brath putaidh.",
"Send": "Cuir", "Send": "Cuir",
"Send email": "Cuir post-d", "Send email": "Cuir post-d",
"Send feedback": "Cuir do bheachd thugainn",
"Send notification e-mails": "Cuir puist-d bhrathan", "Send notification e-mails": "Cuir puist-d bhrathan",
"Send password reset": "Cuir ath-shuidheachadh an fhacail-fhaire", "Send password reset": "Cuir ath-shuidheachadh an fhacail-fhaire",
"Send the confirmation email again": "Cuir am post-d dearbhaidh a-rithist", "Send the confirmation email again": "Cuir am post-d dearbhaidh a-rithist",
@ -825,13 +864,17 @@
"Skip to main content": "Thoir leum gun phrìomh shusbaint", "Skip to main content": "Thoir leum gun phrìomh shusbaint",
"Social": "Sòisealta", "Social": "Sòisealta",
"Some terms, technical or otherwise, used in the text below may cover concepts that are difficult to grasp. We have provided a glossary here to help you understand them better:": "Tha cuid dhe na faclan a tha gan cleachdadh san teacsa gu h-ìosal, co-dhiù an e faclan teicnigeach a th annta gus nach e, mu bheachdan a tha caran doirbh a thuigsinn ma dhfhaoidte. Rinn sinn briathrachan ach am bhiod e na b fhasa dhut an tuigsinn:", "Some terms, technical or otherwise, used in the text below may cover concepts that are difficult to grasp. We have provided a glossary here to help you understand them better:": "Tha cuid dhe na faclan a tha gan cleachdadh san teacsa gu h-ìosal, co-dhiù an e faclan teicnigeach a th annta gus nach e, mu bheachdan a tha caran doirbh a thuigsinn ma dhfhaoidte. Rinn sinn briathrachan ach am bhiod e na b fhasa dhut an tuigsinn:",
"Sorry, we wen't able to save your feedback. Don't worry, we'll try to fix this issue anyway.": "Tha sinn duilich ach cha b urrainn dhuinn do bheachd a shàbhaladh. Na gabh dragh, feuchaidh sinn gun càraich sinn an duilgheadas co-dhiù.",
"Starts on…": "Àm-tòiseachaidh…", "Starts on…": "Àm-tòiseachaidh…",
"Status": "Staid", "Status": "Staid",
"Stop following instance": "Na lean tuilleadh air an ionstans",
"Street": "Sràid", "Street": "Sràid",
"Submit": "Cuir a-null", "Submit": "Cuir a-null",
"Subtitles": "Fo-thiotalan", "Subtitles": "Fo-thiotalan",
"Suspend": "Cuir à rèim", "Suspend": "Cuir à rèim",
"Suspend group": "Cuir am buidheann à rèim", "Suspend group": "Cuir am buidheann à rèim",
"Suspend the account": "Cuir an cunntas à rèim",
"Suspend the account?": "A bheil thu airson an cunntas a chur à rèim?",
"Suspended": "Chaidh a chur à rèim", "Suspended": "Chaidh a chur à rèim",
"Tag search": "Lorg taga", "Tag search": "Lorg taga",
"Task lists": "Liostaichean shaothraichean", "Task lists": "Liostaichean shaothraichean",
@ -841,6 +884,7 @@
"Terms": "Teirmichean", "Terms": "Teirmichean",
"Terms of service": "Teirmichean na seirbheise", "Terms of service": "Teirmichean na seirbheise",
"Text": "Teacsa", "Text": "Teacsa",
"Thanks a lot, your feedback was submitted!": "Mòran taing, chaidh do bheachdan a chur thugainn!",
"That you follow or of which you are a member": "A tha thu a leantainn orra no nad bhall ann", "That you follow or of which you are a member": "A tha thu a leantainn orra no nad bhall ann",
"The Big Blue Button video teleconference URL": "URL co-labhairt video Big Blue Button", "The Big Blue Button video teleconference URL": "URL co-labhairt video Big Blue Button",
"The Google Meet video teleconference URL": "URL co-labhairt video Google Meet", "The Google Meet video teleconference URL": "URL co-labhairt video Google Meet",
@ -871,7 +915,6 @@
"The event will show as attributed to this group.": "Thèid an tachartas seo iomruineadh dhan bhuidheann seo.", "The event will show as attributed to this group.": "Thèid an tachartas seo iomruineadh dhan bhuidheann seo.",
"The event will show as attributed to this profile.": "Thèid an tachartas seo iomruineadh dhan phròifil seo.", "The event will show as attributed to this profile.": "Thèid an tachartas seo iomruineadh dhan phròifil seo.",
"The event will show as attributed to your personal profile.": "Thèid an tachartas seo iomruineadh dhan phròifil phearsanta agad.", "The event will show as attributed to your personal profile.": "Thèid an tachartas seo iomruineadh dhan phròifil phearsanta agad.",
"The event will show the group as organizer.": "Seallaidh an tachartas am buidheann mar eagraiche.",
"The event {event} was created by {profile}.": "Chaidh an tachartas {event} a chruthachadh le {profile}.", "The event {event} was created by {profile}.": "Chaidh an tachartas {event} a chruthachadh le {profile}.",
"The event {event} was deleted by {profile}.": "Chaidh an tachartas {event} a sguabadh às le {profile}.", "The event {event} was deleted by {profile}.": "Chaidh an tachartas {event} a sguabadh às le {profile}.",
"The event {event} was updated by {profile}.": "Chaidh an tachartas {event} ùrachadh le {profile}.", "The event {event} was updated by {profile}.": "Chaidh an tachartas {event} ùrachadh le {profile}.",
@ -899,6 +942,7 @@
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Thèid do ghearan a chuir dha na maoir aig an ionstans agad. S urrainn dhut mìneachadh carson a tha thu a dèanamh gearan mun t-susbaint seo gu h-ìosal.", "The report will be sent to the moderators of your instance. You can explain why you report this content below.": "Thèid do ghearan a chuir dha na maoir aig an ionstans agad. S urrainn dhut mìneachadh carson a tha thu a dèanamh gearan mun t-susbaint seo gu h-ìosal.",
"The selected picture is too heavy. You need to select a file smaller than {size}.": "Tha an dealbh a thagh thu ro throm. Feumaidh tu faidhle a thaghadh a tha nas lugha na {size}.", "The selected picture is too heavy. You need to select a file smaller than {size}.": "Tha an dealbh a thagh thu ro throm. Feumaidh tu faidhle a thaghadh a tha nas lugha na {size}.",
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.": "Cuidichidh am fiosrachadh teicnigeach mun mhearachd gum fuasgail an luchd-leasachaidh an duilgheadas nas fhasa. Cuir ri do bheachd e.", "The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.": "Cuidichidh am fiosrachadh teicnigeach mun mhearachd gum fuasgail an luchd-leasachaidh an duilgheadas nas fhasa. Cuir ri do bheachd e.",
"The user has been disabled": "Chaidh an cleachdaiche a chur à comas",
"The {default_privacy_policy} will be used. They will be translated in the user's language.": "Thèid {default_privacy_policy} a chleachdadh. Thèid a h-eadar-theangachadh gu cànan a chleachdaiche.", "The {default_privacy_policy} will be used. They will be translated in the user's language.": "Thèid {default_privacy_policy} a chleachdadh. Thèid a h-eadar-theangachadh gu cànan a chleachdaiche.",
"The {default_terms} will be used. They will be translated in the user's language.": "Thèid {default_terms} a chleachdadh. Thèid an eadar-theangachadh gu cànan a chleachdaiche.", "The {default_terms} will be used. They will be translated in the user's language.": "Thèid {default_terms} a chleachdadh. Thèid an eadar-theangachadh gu cànan a chleachdaiche.",
"There are {participants} participants.": "Tha {participants} com-pàirtiche(an) ann.", "There are {participants} participants.": "Tha {participants} com-pàirtiche(an) ann.",
@ -914,11 +958,13 @@
"This event has been cancelled.": "Chaidh an tachartas seo a chur gu neoini.", "This event has been cancelled.": "Chaidh an tachartas seo a chur gu neoini.",
"This event is accessible only through it's link. Be careful where you post this link.": "Cha ghabh an tachartas seo inntrigeadh ach leis a cheangal aige. Thoir an aire mus postaich thu an ceangal seo am badeigin.", "This event is accessible only through it's link. Be careful where you post this link.": "Cha ghabh an tachartas seo inntrigeadh ach leis a cheangal aige. Thoir an aire mus postaich thu an ceangal seo am badeigin.",
"This group doesn't have a description yet.": "Chan eil tuairisgeul aig a bhuidheann seo fhathast.", "This group doesn't have a description yet.": "Chan eil tuairisgeul aig a bhuidheann seo fhathast.",
"This group is a remote group, it's possible the original instance has more informations.": "S e buidheann cèin a tha seo agus dhfhaoidte gu bheil barrachd fiosrachaidh aig an ionstans tùsail.",
"This group is accessible only through it's link. Be careful where you post this link.": "Cha ghabh an tachartas seo inntrigeadh ach leis a cheangal aige. Thoir an aire mus postaich thu an ceangal seo am badeigin.", "This group is accessible only through it's link. Be careful where you post this link.": "Cha ghabh an tachartas seo inntrigeadh ach leis a cheangal aige. Thoir an aire mus postaich thu an ceangal seo am badeigin.",
"This group is invite-only": "Feumaidh tu cuireadh airson ballrachd fhaighinn sa bhuidheann seo", "This group is invite-only": "Feumaidh tu cuireadh airson ballrachd fhaighinn sa bhuidheann seo",
"This group was not found": "Cha deach am buidheann seo a lorg",
"This identifier is unique to your profile. It allows others to find you.": "Tha an t-aithnichear seo àraidh dhan phròifil agad. Leigidh e le càch do lorg.", "This identifier is unique to your profile. It allows others to find you.": "Tha an t-aithnichear seo àraidh dhan phròifil agad. Leigidh e le càch do lorg.",
"This identity is not a member of any group.": "Chan eil an dearbh-aithne seo na ball ann am buidheann sam bith.",
"This information is saved only on your computer. Click for details": "Tha dèid am fiosrachadh seo a shàbhaladh ach air a choimpiutair agad. Briog airson mion-fhiosrachadh", "This information is saved only on your computer. Click for details": "Tha dèid am fiosrachadh seo a shàbhaladh ach air a choimpiutair agad. Briog airson mion-fhiosrachadh",
"This instance doesn't follow yours.": "Chan eil an t-ionstans seo a leantainn air an fhear agadsa.",
"This instance hasn't got push notifications enabled.": "Chan eil na brathan putaidh an comas aig an ionstans seo.", "This instance hasn't got push notifications enabled.": "Chan eil na brathan putaidh an comas aig an ionstans seo.",
"This instance isn't opened to registrations, but you can register on other instances.": "Chan eil an t-ionstans seo fosgailte a chùm clàraidh ach s urrainn dhut clàradh air ionstansan eile.", "This instance isn't opened to registrations, but you can register on other instances.": "Chan eil an t-ionstans seo fosgailte a chùm clàraidh ach s urrainn dhut clàradh air ionstansan eile.",
"This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.": "S e an t-ionstans seo <b>{instanceName} ({domain})</b> a tha ag òstadh na pròifil agad, mar sin cuir ainm-san nad chuimhne.", "This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.": "S e an t-ionstans seo <b>{instanceName} ({domain})</b> a tha ag òstadh na pròifil agad, mar sin cuir ainm-san nad chuimhne.",
@ -928,7 +974,11 @@
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Chan fhaigh ach na buill cothrom air a phost seo. Faodaidh tu inntrigeadh a chùm maorsainneachd a-mhàin on a tha thu nad mhaor air an ionstans seo.", "This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Chan fhaigh ach na buill cothrom air a phost seo. Faodaidh tu inntrigeadh a chùm maorsainneachd a-mhàin on a tha thu nad mhaor air an ionstans seo.",
"This post is accessible only through it's link. Be careful where you post this link.": "Cha ghabh am post seo inntrigeadh ach leis a cheangal aige. Thoir an aire mus postaich thu an ceangal seo am badeigin.", "This post is accessible only through it's link. Be careful where you post this link.": "Cha ghabh am post seo inntrigeadh ach leis a cheangal aige. Thoir an aire mus postaich thu an ceangal seo am badeigin.",
"This profile is from another instance, the informations shown here may be incomplete.": "Tha a phròifil seo o ionstans eile, dhfhaoidte nach eil am fiosrachadh a chì thu an-seo coileanta.", "This profile is from another instance, the informations shown here may be incomplete.": "Tha a phròifil seo o ionstans eile, dhfhaoidte nach eil am fiosrachadh a chì thu an-seo coileanta.",
"This profile is located on this instance, so you need to {access_the_corresponding_account} to suspend it.": "Tha a phròifil seo air an ionstans seo, mar sin feumaidh tu {access_the_corresponding_account} gus a cur à rèim.",
"This profile was not found": "Cha deach a phròifil seo a lorg",
"This setting will be used to display the website and send you emails in the correct language.": "Thèid an roghainn seo a chleachdadh airson an làrach-lìn a shealltainn agus puist-d a chur thugad sa chànan cheart.", "This setting will be used to display the website and send you emails in the correct language.": "Thèid an roghainn seo a chleachdadh airson an làrach-lìn a shealltainn agus puist-d a chur thugad sa chànan cheart.",
"This user doesn't have any profiles": "Chan eil pròifil sam bith aig a chleachdaiche seo",
"This user was not found": "Cha deach an cleachdaiche seo a lorg",
"This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Chan eil an làrach-lìn seo fo mhaorsainneachd agus thèid an dàta a chuireas tu a-steach a mhilleadh gu fèin-obrachail gach oidhche aig 00:01 (roinn-tìde Pharais).", "This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Chan eil an làrach-lìn seo fo mhaorsainneachd agus thèid an dàta a chuireas tu a-steach a mhilleadh gu fèin-obrachail gach oidhche aig 00:01 (roinn-tìde Pharais).",
"This week": "An t-seachdain seo", "This week": "An t-seachdain seo",
"This weekend": "An deireadh-seachdain seo", "This weekend": "An deireadh-seachdain seo",
@ -949,6 +999,7 @@
"Today": "An-diugh", "Today": "An-diugh",
"Tomorrow": "A-màireach", "Tomorrow": "A-màireach",
"Tools": "Innealan", "Tools": "Innealan",
"Total number of participations": "Com-pàirteachaidhean iomlan",
"Transfer to {outsideDomain}": "Tar-chur gu {outsideDomain}", "Transfer to {outsideDomain}": "Tar-chur gu {outsideDomain}",
"Triggered profile refreshment": "Thèid a phròifil ath-nuadhachadh", "Triggered profile refreshment": "Thèid a phròifil ath-nuadhachadh",
"Twitch live": "Twitch beò", "Twitch live": "Twitch beò",
@ -968,7 +1019,6 @@
"Underline": "Fo-loidhne", "Underline": "Fo-loidhne",
"Undo": "Neo-dhèan", "Undo": "Neo-dhèan",
"Unfollow": "Na lean tuilleadh", "Unfollow": "Na lean tuilleadh",
"Unfortunately, this instance isn't opened to registrations": "Gu mì-fhortanach, chan eil an t-ionstans seo fosgailte a chùm clàraidh",
"Unfortunately, your participation request was rejected by the organizers.": "Gu mì-fhortanach, dhiùlt na h-eagraichean do chom-pàirteachadh.", "Unfortunately, your participation request was rejected by the organizers.": "Gu mì-fhortanach, dhiùlt na h-eagraichean do chom-pàirteachadh.",
"Unknown": "Chan eil fhios", "Unknown": "Chan eil fhios",
"Unknown actor": "Actar nach aithne dhuinn", "Unknown actor": "Actar nach aithne dhuinn",
@ -976,7 +1026,6 @@
"Unknown value for the openness setting.": "Chaidh luach nach aithne dhuinn a shuidheachadh air dè cho fosgailte s a tha am buidheann.", "Unknown value for the openness setting.": "Chaidh luach nach aithne dhuinn a shuidheachadh air dè cho fosgailte s a tha am buidheann.",
"Unlogged participation": "Com-pàirteachadh gun logadh", "Unlogged participation": "Com-pàirteachadh gun logadh",
"Unsaved changes": "Atharraichean gun sàbhaladh", "Unsaved changes": "Atharraichean gun sàbhaladh",
"Unset group": "Dì-shuidhich am buidheann",
"Unsubscribe to browser push notifications": "Cuir crìoch air an fho-sgrìobhadh air brathan putaidh", "Unsubscribe to browser push notifications": "Cuir crìoch air an fho-sgrìobhadh air brathan putaidh",
"Unsuspend": "Cuir an gnìomh a-rithist", "Unsuspend": "Cuir an gnìomh a-rithist",
"Upcoming": "Ri thighinn", "Upcoming": "Ri thighinn",
@ -991,6 +1040,7 @@
"Update post": "Ùraich am post", "Update post": "Ùraich am post",
"Updated": "Air ùrachadh", "Updated": "Air ùrachadh",
"Uploaded media size": "Meud a mheadhain a chaidh a luchdadh suas", "Uploaded media size": "Meud a mheadhain a chaidh a luchdadh suas",
"Uploaded media total size": "Meud iomlan nam meadhanan a chaidh a luchdadh suas",
"Use my location": "Cleachd an t-ionad agam", "Use my location": "Cleachd an t-ionad agam",
"User": "Cleachdaiche", "User": "Cleachdaiche",
"User settings": "Roghainnean a chleachdaiche", "User settings": "Roghainnean a chleachdaiche",
@ -1010,6 +1060,8 @@
"View less": "Seall nas lugha", "View less": "Seall nas lugha",
"View more": "Seall barrachd", "View more": "Seall barrachd",
"View page on {hostname} (in a new window)": "Seall an duilleag air {hostname} (ann an uinneag ùr)", "View page on {hostname} (in a new window)": "Seall an duilleag air {hostname} (ann an uinneag ùr)",
"View past events": "Seall na tachartasan san àm a dhfhalbh",
"View the group profile on the original instance": "Faic pròifil a buidhinn air an ionstans tùsail",
"Visibility was set to an unknown value.": "Chaidh luach nach aithne dhuinn a shuidheachadh air an t-so-fhaicsinneachd.", "Visibility was set to an unknown value.": "Chaidh luach nach aithne dhuinn a shuidheachadh air an t-so-fhaicsinneachd.",
"Visibility was set to private.": "Chaidh so-fhaicsinneachd phrìobhaideach a shuidheachadh air.", "Visibility was set to private.": "Chaidh so-fhaicsinneachd phrìobhaideach a shuidheachadh air.",
"Visibility was set to public.": "Chaidh so-fhaicsinneachd phoblach a shuidheachadh air.", "Visibility was set to public.": "Chaidh so-fhaicsinneachd phoblach a shuidheachadh air.",
@ -1017,6 +1069,7 @@
"Visible everywhere on the web (public)": "Chithear air feadh an lìn e (poblach)", "Visible everywhere on the web (public)": "Chithear air feadh an lìn e (poblach)",
"Waiting for organization team approval.": "A feitheamh air aontachadh leis an sgioba eagrachaidh.", "Waiting for organization team approval.": "A feitheamh air aontachadh leis an sgioba eagrachaidh.",
"Warning": "Rabhadh", "Warning": "Rabhadh",
"We collect your feedback and the error information in order to improve this service.": "Cruinnichidh sinn do bheachdan agus am fiosrachadh mun mhearachd ach an doir sinn piseach air an t-seirbheis seo.",
"We couldn't save your participation inside this browser. Not to worry, you have successfully confirmed your participation, we just couldn't save it's status in this browser because of a technical issue.": "Cha b urrainn dhuinn an com-pàirteachadh agad a shàbhaladh sa bhrabhsair seo. Na gabh dragh, dhearbh thu gun gabh thu pàirt ann ach cha b urrainn dhuinn sin a shàbhaladh sa bhrabhsair seo ri linn duilgheadas teicnigeach.", "We couldn't save your participation inside this browser. Not to worry, you have successfully confirmed your participation, we just couldn't save it's status in this browser because of a technical issue.": "Cha b urrainn dhuinn an com-pàirteachadh agad a shàbhaladh sa bhrabhsair seo. Na gabh dragh, dhearbh thu gun gabh thu pàirt ann ach cha b urrainn dhuinn sin a shàbhaladh sa bhrabhsair seo ri linn duilgheadas teicnigeach.",
"We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):": "Bheir sinn piseach air a bhathar-bhog le taic do bheachdan. Tha dà dhòigh ann airson innse dhuinn mu dhèidhinn na trioblaide seo (gu mì-fhortanach, feumaidh tu cunntas a chruthachadh dhaibh):", "We improve this software thanks to your feedback. To let us know about this issue, two possibilities (both unfortunately require user account creation):": "Bheir sinn piseach air a bhathar-bhog le taic do bheachdan. Tha dà dhòigh ann airson innse dhuinn mu dhèidhinn na trioblaide seo (gu mì-fhortanach, feumaidh tu cunntas a chruthachadh dhaibh):",
"We just sent an email to {email}": "Tha sinn air post-d a chur gu {email}", "We just sent an email to {email}": "Tha sinn air post-d a chur gu {email}",
@ -1032,6 +1085,7 @@
"Welcome back!": "Fàilte air ais!", "Welcome back!": "Fàilte air ais!",
"Welcome to Mobilizon, {username}!": "Fàilte gu Mobilizon, {username}!", "Welcome to Mobilizon, {username}!": "Fàilte gu Mobilizon, {username}!",
"What can I do to help?": "Dè nì mi airson cuideachadh?", "What can I do to help?": "Dè nì mi airson cuideachadh?",
"What happened?": "Dè thachair?",
"Wheelchair accessibility": "Inntrigeadh cathrach-cuibhle", "Wheelchair accessibility": "Inntrigeadh cathrach-cuibhle",
"When a moderator from the group creates an event and attributes it to the group, it will show up here.": "Nuair a chruthaicheas maor a bhuidhinn tachartas le iomruineadh dhan bhuidheann, nochdaidh e an-seo.", "When a moderator from the group creates an event and attributes it to the group, it will show up here.": "Nuair a chruthaicheas maor a bhuidhinn tachartas le iomruineadh dhan bhuidheann, nochdaidh e an-seo.",
"When the event is private, you'll need to share the link around.": "Nuair a bhios an tachartas prìobhaideach, feumaidh tu fhèin an ceangal a cho-roinneadh.", "When the event is private, you'll need to share the link around.": "Nuair a bhios an tachartas prìobhaideach, feumaidh tu fhèin an ceangal a cho-roinneadh.",
@ -1046,7 +1100,6 @@
"Why create an account?": "Carson a chruthaichinn cunntas?", "Why create an account?": "Carson a chruthaichinn cunntas?",
"Will allow to display and manage your participation status on the event page when using this device. Uncheck if you're using a public device.": "Leigidh seo leat staid do chom-pàirteachaidh a shealltainn s a stiùireadh air duilleag an tachartais nuair a chleachdas tu an t-uidheam seo. Thoir a chromag air falbh ma tha thu a cleachdadh uidheam poblach.", "Will allow to display and manage your participation status on the event page when using this device. Uncheck if you're using a public device.": "Leigidh seo leat staid do chom-pàirteachaidh a shealltainn s a stiùireadh air duilleag an tachartais nuair a chleachdas tu an t-uidheam seo. Thoir a chromag air falbh ma tha thu a cleachdadh uidheam poblach.",
"Within {number} kilometers of {place}": "Am broinn {number} chilemeatair o {place}|Am broinn {number} chilemeatair o {place}|Am broinn {number} cilemeatairean o {place}|Am broinn {number} cilemeatair o {place}", "Within {number} kilometers of {place}": "Am broinn {number} chilemeatair o {place}|Am broinn {number} chilemeatair o {place}|Am broinn {number} cilemeatairean o {place}|Am broinn {number} cilemeatair o {place}",
"Write something…": "Sgrìobh rudeigin…",
"Yesterday": "An-dè", "Yesterday": "An-dè",
"You accepted the invitation to join the group.": "Ghabh thu ris a bhallrachd sa bhuidheann.", "You accepted the invitation to join the group.": "Ghabh thu ris a bhallrachd sa bhuidheann.",
"You added the member {member}.": "Chuir thu am ball {member} ris.", "You added the member {member}.": "Chuir thu am ball {member} ris.",
@ -1081,6 +1134,7 @@
"You don't follow any instances yet.": "Chan eil thu a leantainn air ionstans sam bith fhathast.", "You don't follow any instances yet.": "Chan eil thu a leantainn air ionstans sam bith fhathast.",
"You don't have any upcoming events. Maybe try another filter?": "Chan eil tachartas ri thighinn agad. Am feuch thu criathrag eile?", "You don't have any upcoming events. Maybe try another filter?": "Chan eil tachartas ri thighinn agad. Am feuch thu criathrag eile?",
"You excluded member {member}.": "Dhùin thu am ball {member} a-mach.", "You excluded member {member}.": "Dhùin thu am ball {member} a-mach.",
"You have attended {count} events in the past.": "",
"You have been disconnected": "Chaidh do cheangal a bhriseadh", "You have been disconnected": "Chaidh do cheangal a bhriseadh",
"You have been invited by {invitedBy} to the following group:": "Thug {invitedBy} cuireadh dhut dhan bhuidheann seo:", "You have been invited by {invitedBy} to the following group:": "Thug {invitedBy} cuireadh dhut dhan bhuidheann seo:",
"You have been removed from this group's members.": "Chaidh do thoirt air falbh o bhallrachd a bhuidhinn seo.", "You have been removed from this group's members.": "Chaidh do thoirt air falbh o bhallrachd a bhuidhinn seo.",
@ -1088,15 +1142,17 @@
"You have one event in {days} days.": "| Bidh {count} tachartas agad sna {days} là(ithean) ri thighinn| Bidh {count} thachartas agad sna {days} là(ithean) ri thighinn| Bidh {count} tachartasan agad sna {days} là(ithean) ri thighinn| Bidh {count} tachartas agad sna {days} là(ithean) ri thighinn", "You have one event in {days} days.": "| Bidh {count} tachartas agad sna {days} là(ithean) ri thighinn| Bidh {count} thachartas agad sna {days} là(ithean) ri thighinn| Bidh {count} tachartasan agad sna {days} là(ithean) ri thighinn| Bidh {count} tachartas agad sna {days} là(ithean) ri thighinn",
"You have one event today.": "Bidh {count} tachartas agad an-diugh| Bidh {count} thachartas agad an-diugh| Bidh {count} tachartasan agad an-diugh| Bidh {count} tachartas agad an-diugh", "You have one event today.": "Bidh {count} tachartas agad an-diugh| Bidh {count} thachartas agad an-diugh| Bidh {count} tachartasan agad an-diugh| Bidh {count} tachartas agad an-diugh",
"You have one event tomorrow.": "Bidh {count} tachartas agad a-màireach| Bidh {count} thachartas agad a-màireach| Bidh {count} tachartasan agad a-màireach| Bidh {count} tachartas agad a-màireach", "You have one event tomorrow.": "Bidh {count} tachartas agad a-màireach| Bidh {count} thachartas agad a-màireach| Bidh {count} tachartasan agad a-màireach| Bidh {count} tachartas agad a-màireach",
"You haven't interacted with other instances yet.": "Cha do rinn thu eadar-ghnìomh le ionstans sam bith eile fhathast.",
"You invited {member}.": "Thug thu cuireadh dha {member}.", "You invited {member}.": "Thug thu cuireadh dha {member}.",
"You may also:": "S urrainn dhut cuideachd:",
"You may clear all participation information for this device with the buttons below.": "S urrainn dhut gach fiosrachadh mun chom-pàirteachadh a shuathadh bàn on uidheam seo leis na putanan gu h-ìosal.", "You may clear all participation information for this device with the buttons below.": "S urrainn dhut gach fiosrachadh mun chom-pàirteachadh a shuathadh bàn on uidheam seo leis na putanan gu h-ìosal.",
"You may now close this page or {return_to_the_homepage}.": "S urrainn dhut an duilleag seo a dhùnadh a-nis no {return_to_the_homepage}.",
"You may now close this window, or {return_to_event}.": "S urrainn dhut an uinneag seo a dhùnadh a-nis no {return_to_event}.", "You may now close this window, or {return_to_event}.": "S urrainn dhut an uinneag seo a dhùnadh a-nis no {return_to_event}.",
"You may show some members as contacts.": "Faodaidh tu cuid a bhuill a shealltainn nan luchd-aithne.", "You may show some members as contacts.": "Faodaidh tu cuid a bhuill a shealltainn nan luchd-aithne.",
"You moved the folder {resource} into {new_path}.": "Ghluais thu am pasgan {resource} gu {new_path}.", "You moved the folder {resource} into {new_path}.": "Ghluais thu am pasgan {resource} gu {new_path}.",
"You moved the folder {resource} to the root folder.": "Ghluais thu am pasgan {resource} dhan phasgan freumhach.", "You moved the folder {resource} to the root folder.": "Ghluais thu am pasgan {resource} dhan phasgan freumhach.",
"You moved the resource {resource} into {new_path}.": "Ghluais thu an goireas {resource} gu {new_path}.", "You moved the resource {resource} into {new_path}.": "Ghluais thu an goireas {resource} gu {new_path}.",
"You moved the resource {resource} to the root folder.": "Ghluais thu an goireas {resource} dhan phasgan freumhach.", "You moved the resource {resource} to the root folder.": "Ghluais thu an goireas {resource} dhan phasgan freumhach.",
"You need to create the group before you create an event.": "Feumaidh buidheann a chruthachadh mus cruthaich thu tachartas.",
"You need to login.": "Feumaidh tu clàradh a-steach.", "You need to login.": "Feumaidh tu clàradh a-steach.",
"You posted a comment on the event {event}.": "Chuir thu beachd ris an tachartas {event}.", "You posted a comment on the event {event}.": "Chuir thu beachd ris an tachartas {event}.",
"You promoted the member {member} to an unknown role.": "Thug thu dreuchd nach aithnich sinn dha {member} (àrdachadh).", "You promoted the member {member} to an unknown role.": "Thug thu dreuchd nach aithnich sinn dha {member} (àrdachadh).",
@ -1121,7 +1177,7 @@
"You were promoted to moderator by {profile}.": "Rinn {profile} maor dhiot (àrdachadh).", "You were promoted to moderator by {profile}.": "Rinn {profile} maor dhiot (àrdachadh).",
"You will be able to add an avatar and set other options in your account settings.": "S urrainn dhut avatar a chur ris is roghainnean eile a thaghadh ann an roghainnean a chunntais agad.", "You will be able to add an avatar and set other options in your account settings.": "S urrainn dhut avatar a chur ris is roghainnean eile a thaghadh ann an roghainnean a chunntais agad.",
"You will be redirected to the original instance": "Thèid d ath-stiùireadh dhan ionstans tùsail", "You will be redirected to the original instance": "Thèid d ath-stiùireadh dhan ionstans tùsail",
"You will find here all the events you have created or of which you are a participant.": "Chì thu a h-uile tachartas a chruthaich thu no sa bheil thu a gabhail pàirt an-seo.", "You will find here all the events you have created or of which you are a participant, as well as events organized by groups you follow or are a member of.": "Chì thu a h-uile tachartas a chruthaich thu no sa bheil thu a gabhail pàirt an-seo agus na tachartasan a chaidh a chur air dòigh le buidhnean air a leanas tu no sa bheil thu nad bhall cuideachd.",
"You will receive notifications about this group's public activity depending on %{notification_settings}.": "Gheibh thu brathan mu ghnìomhachd phoblach a bhuidhinn seo a-rèir %{notification_settings} agad.", "You will receive notifications about this group's public activity depending on %{notification_settings}.": "Gheibh thu brathan mu ghnìomhachd phoblach a bhuidhinn seo a-rèir %{notification_settings} agad.",
"You wish to participate to the following event": "Tha thu airson pàirt a ghabhail san tachartas a leanas", "You wish to participate to the following event": "Tha thu airson pàirt a ghabhail san tachartas a leanas",
"You'll get a weekly recap every Monday for upcoming events, if you have any.": "Gheibh thu cuimhneachan gach madainn DiLuain mu na tachartasan ri thighinn ma tha gin agad.", "You'll get a weekly recap every Monday for upcoming events, if you have any.": "Gheibh thu cuimhneachan gach madainn DiLuain mu na tachartasan ri thighinn ma tha gin agad.",
@ -1142,6 +1198,7 @@
"Your email is being changed": "Tha am post-d agad ga atharrachadh", "Your email is being changed": "Tha am post-d agad ga atharrachadh",
"Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer.": "Cha dèid am post-d agad a chleachdadh ach airson dearbhadh gur e neach a th annad agus airson naidheachdan a chur thugad mun tachartas seo. S ann NACH DÈID a thar-chur gu ionstansan eile no gu eagraiche an tachartais.", "Your email will only be used to confirm that you're a real person and send you eventual updates for this event. It will NOT be transmitted to other instances or to the event organizer.": "Cha dèid am post-d agad a chleachdadh ach airson dearbhadh gur e neach a th annad agus airson naidheachdan a chur thugad mun tachartas seo. S ann NACH DÈID a thar-chur gu ionstansan eile no gu eagraiche an tachartais.",
"Your federated identity": "An dearbh-aithne co-naisgte agad", "Your federated identity": "An dearbh-aithne co-naisgte agad",
"Your membership is pending approval": "Tha do bhallrachd a feitheamh ri aontachadh",
"Your membership was approved by {profile}.": "Dhaontaich {profile} gum faigh thu ballrachd.", "Your membership was approved by {profile}.": "Dhaontaich {profile} gum faigh thu ballrachd.",
"Your participation has been confirmed": "Chaidh an com-pàirteachadh agad a dhearbhadh", "Your participation has been confirmed": "Chaidh an com-pàirteachadh agad a dhearbhadh",
"Your participation has been rejected": "Chaidh an com-pàirteachadh agad a dhiùltadh", "Your participation has been rejected": "Chaidh an com-pàirteachadh agad a dhiùltadh",
@ -1166,6 +1223,7 @@
"[This comment has been deleted]": "[Chaidh am beachd seo a sguabadh às]", "[This comment has been deleted]": "[Chaidh am beachd seo a sguabadh às]",
"[deleted]": "[air a sguabadh às]", "[deleted]": "[air a sguabadh às]",
"a non-existent report": "gearan nach eil ann", "a non-existent report": "gearan nach eil ann",
"access the corresponding account": "an cunntas cho-cheangailte inntrigeadh",
"access to the group's private content as well": "inntrigeadh do shusbaint phrìobhaideach a bhuidhinn cuideachd", "access to the group's private content as well": "inntrigeadh do shusbaint phrìobhaideach a bhuidhinn cuideachd",
"and {number} groups": "agus {number} buidheann/buidhnean", "and {number} groups": "agus {number} buidheann/buidhnean",
"any distance": "astar sam bith", "any distance": "astar sam bith",
@ -1185,10 +1243,13 @@
"https://mensuel.framapad.org/p/some-secret-token": "https://mensuel.framapad.org/p/some-secret-token", "https://mensuel.framapad.org/p/some-secret-token": "https://mensuel.framapad.org/p/some-secret-token",
"iCal Feed": "Inbhir iCal", "iCal Feed": "Inbhir iCal",
"instance rules": "riaghailtean an ionstans", "instance rules": "riaghailtean an ionstans",
"mobilizon-instance.tld": "ionstans-mobilizon.tld",
"more than 1360 contributors": "còrr is 1360 luchd-cuideachaidh", "more than 1360 contributors": "còrr is 1360 luchd-cuideachaidh",
"new@email.com": "ùr@post-d.com",
"profile@instance": "ainm@ionstans", "profile@instance": "ainm@ionstans",
"report #{report_number}": "gearan #{report_number}", "report #{report_number}": "gearan #{report_number}",
"return to the event's page": "till gu duilleag an tachartais", "return to the event's page": "till gu duilleag an tachartais",
"return to the homepage": "tilleadh dhan duilleag-dhachaigh",
"terms of service": "teirmichean na seirbheise", "terms of service": "teirmichean na seirbheise",
"with another identity…": "le dearbh-aithne eile…", "with another identity…": "le dearbh-aithne eile…",
"your notification settings": "roghainnean nam brathan", "your notification settings": "roghainnean nam brathan",
@ -1196,9 +1257,9 @@
"{available}/{capacity} available places": "{available}/{capacity} àite air fhàgail|{available}/{capacity} àite air fhàgail|{available}/{capacity} àiteachan air fhàgail|{available}/{capacity} àite air fhàgail", "{available}/{capacity} available places": "{available}/{capacity} àite air fhàgail|{available}/{capacity} àite air fhàgail|{available}/{capacity} àiteachan air fhàgail|{available}/{capacity} àite air fhàgail",
"{count} km": "{count} km", "{count} km": "{count} km",
"{count} members": "{count} bhall|{count} bhall|{count} buill|{count} ball", "{count} members": "{count} bhall|{count} bhall|{count} buill|{count} ball",
"{count} members or followers": "",
"{count} participants": "{count} chom-pàirtiche| {count} chom-pàirtiche| {count} com-pàirtichean| {count} com-pàirtiche", "{count} participants": "{count} chom-pàirtiche| {count} chom-pàirtiche| {count} com-pàirtichean| {count} com-pàirtiche",
"{count} requests waiting": "Tha {count} iarrtas(an) a feitheamh", "{count} requests waiting": "Tha {count} iarrtas(an) a feitheamh",
"{count} team members": "Buill an sgioba ({count})",
"{folder} - Resources": "{folder} Goireasan", "{folder} - Resources": "{folder} Goireasan",
"{group} activity timeline": "Loidhne-ama nan gnìomhachdan aig {group}", "{group} activity timeline": "Loidhne-ama nan gnìomhachdan aig {group}",
"{group} events": "Tachartasan {group}", "{group} events": "Tachartasan {group}",

File diff suppressed because it is too large Load Diff

341
js/src/i18n/he.json Normal file
View File

@ -0,0 +1,341 @@
{
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "כלי ידידותי למשתמש.ת, חופשי ואתי להתאספות, להתארגנות ולגיוס לפעולה משותפת.",
"A validation email was sent to {email}": "הודעת אימות נשלחה בדואר אלקטרוני לכתובת {email}",
"Abandon editing": "עזיבת עריכה",
"About": "אודות",
"About Mobilizon": "אודות מוביליזון",
"About this event": "אודות האירוע",
"About this instance": "אודות האתר",
"Accepted": "התקבל",
"Account": "חשבון",
"Add": "הוספה",
"Add a note": "הוספת הערה",
"Add an address": "הוספת כתובת",
"Add an instance": "הוספת אתר",
"Add some tags": "הוספת תגיות",
"Add to my calendar": "הוספה ללוח השנה שלי",
"Additional comments": "הערות נוספות",
"Admin": "מנהל.ת",
"Admin settings successfully saved.": "הגדרות מנהל.ת נשמרו בהצלחה.",
"Administration": "ניהול",
"All the places have already been taken": "כל המקומות כבר תפוסים",
"Allow registrations": "אפשור הרשמה",
"Anonymous participant": "משתתפ.ת בלתי מזוהה",
"Anonymous participants will be asked to confirm their participation through e-mail.": "משתתפים.ות בלתי מזוהים.ות יתבקשו לאשר השתתפות באמצעות דואר אלקטרוני.",
"Anonymous participations": "השתתפות בלתי מזוהה",
"Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.": "האם את.ה בטוח.ה שאת.ה רוצה למחוק לגמרי את החשבון שלך? תאבד.י הכל. זהויות, הגדרות, אירועים שנוצרו, הודעות והשתתפויות יימחקו לצמיתות.",
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "האם את.ה בטוח.ה שאת.ה רוצה <b>למחוק</b> את התגובה? לא ניתן יהיה לבטל את הפעולה.",
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the discussion with the event creator or edit its event instead.": "האם את.ה בטוח.ה שאת.ה רוצה <b>למחוק</b> את האירוע? לא ניתן יהיה לבטל את הפעולה. ייתכן שתרצ.י ליצור קשר עם יוצר.ת האירוע, או לערוך את האירוע, במקום למחוק.",
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "האם את.ה בטוח.ה שאת.ה רוצה לבטל את יצירת האירוע? כל השינויים יאבדו.",
"Are you sure you want to cancel the event edition? You'll lose all modifications.": "האם את.ה בטוח.ה שאת.ה רוצה לבטל את עריכת האירוע? כל השינויים יאבדו.",
"Are you sure you want to cancel your participation at event \"{title}\"?": "האם את.ה בטוח.ה שאת.ה רוצה לבטל את השתתפותך באירוע \"{title}\"?",
"Are you sure you want to delete this event? This action cannot be reverted.": "האם את.ה בטוח.ה שאת.ה רוצה למחוק את האירוע? לא ניתן יהיה לבטל את הפעולה.",
"Avatar": "תמונה",
"Back to previous page": "חזרה לדף הקודם",
"Before you can login, you need to click on the link inside it to validate your account.": "לפני שתוכל.י להתחבר, עלייך ללחוץ על הקישור שבתוך ההודעה כדי לאשר את החשבון שלך.",
"By {username}": "נכתב על־ידי {username}",
"Cancel": "ביטול",
"Cancel anonymous participation": "ביטול השתתפות בלתי מזוהה",
"Cancel creation": "ביטול יצירה",
"Cancel edition": "ביטול עריכה",
"Cancel my participation request…": "ביטול בקשת ההשתתפות שלי…",
"Cancel my participation…": "ביטול ההשתתפות שלי…",
"Cancelled: Won't happen": "מבוטל: לא יתקיים",
"Change": "שינוי",
"Change my email": "שינוי כתובת דואר אלקטרוני",
"Change my identity…": "שינוי הזהות שלי…",
"Change my password": "שינוי ססמה",
"Clear": "ניקוי",
"Click to upload": "לחצ.י להעלאה",
"Close": "סגירה",
"Close comments for all (except for admins)": "ביטול תגובות לכולןם (מלבד מנהלים.ות)",
"Closed": "סגור",
"Comment deleted": "התגובה נמחקה",
"Comment from @{username} reported": "התגובה של @{username} דווחה",
"Comments": "תגובות",
"Confirm my participation": "אישור ההשתתפות שלי",
"Confirm my particpation": "אישור ההשתתפות שלי",
"Confirmed: Will happen": "מאושר: יתקיים",
"Continue editing": "המשך עריכה",
"Country": "מדינה",
"Create": "יצירה",
"Create a new event": "יצירת אירוע חדש",
"Create a new group": "יצירת קבוצה חדשה",
"Create a new identity": "יצירת זהות חדשה",
"Create group": "יצירת קבוצה",
"Create my event": "יצירת אירוע",
"Create my group": "יצירת קבוצה",
"Create my profile": "יצירת הפרופיל שלי",
"Create token": "יצירת אסימון",
"Current identity has been changed to {identityName} in order to manage this event.": "הזהות הנוכחית שונתה ל־{identityName} כדי לנהל אירוע זה.",
"Current page": "הדף הנוכחי",
"Custom": "התאמה אישית",
"Custom URL": "קישור מותאם אישית",
"Custom text": "טקסט מותאם אישית",
"Dashboard": "לוח בקרה",
"Date": "תאריך",
"Date and time settings": "הגדרות זמן ותאריך",
"Date parameters": "מאפייני תאריך",
"Default": "ברירת מחדל",
"Delete": "מחיקה",
"Delete Comment": "מחיקת תגובה",
"Delete Event": "מחיקת אירוע",
"Delete account": "מחיקת חשבון",
"Delete event": "מחיקת אירוע",
"Delete everything": "מחיקת הכל",
"Delete my account": "מחיקת החשבון שלי",
"Delete this identity": "מחיקת זהות",
"Delete your identity": "מחיקת הזהות שלך",
"Delete {eventTitle}": "מחיקת {eventTitle}",
"Delete {preferredUsername}": "מחיקת {preferredUsername}",
"Deleting comment": "מוחק תגובה",
"Deleting event": "מוחק אירוע",
"Deleting my account will delete all of my identities.": "מחיקת החשבון שלי תמחק את כל הזהויות שלי.",
"Deleting your Mobilizon account": "מחיקת החשבון שלך במוביליזון",
"Description": "תיאור",
"Display name": "שם תצוגה",
"Display participation price": "הצגת מחיר השתתפות",
"Draft": "טיוטה",
"Drafts": "טיוטות",
"Edit": "עריכה",
"Eg: Stockholm, Dance, Chess…": "למשל: תל אביב, ריקוד, שחמט…",
"Either on the {instance} instance or on another instance.": "או באתר {instance} או באתר אחר.",
"Either the account is already validated, either the validation token is incorrect.": "החשבון כבר מאומת, או שקוד האימות שגוי.",
"Either the email has already been changed, either the validation token is incorrect.": "או שכתובת הדואר האלקטרוני כבר שונתה, או שקוד האימות שגוי.",
"Either the participation request has already been validated, either the validation token is incorrect.": "או שבקשת ההשתתפות כבר אומתה, או שקוד האימות שגוי.",
"Email": "דואר אלקטרוני",
"Ends on…": "נמשך עד…",
"Enter the link URL": "הזינ.י את הקישור",
"Error while changing email": "שגיאה בעת שינוי כתובת דואר אלקטרוני",
"Error while validating account": "שגיאה בעת אימות חשבון",
"Error while validating participation request": "שגיאה בעת אימות בקשת השתתפות",
"Event": "אירוע",
"Event already passed": "האירוע כבר חלף",
"Event cancelled": "האירוע בוטל",
"Event creation": "יצירת אירוע",
"Event edition": "עריכת אירוע",
"Event list": "רשימת אירועים",
"Event page settings": "הגדרות עמוד אירוע",
"Event to be confirmed": "אירוע לאישור",
"Event {eventTitle} deleted": "האירוע {eventTitle} נמחק",
"Event {eventTitle} reported": "האירוע {eventTitle} דווח",
"Events": "אירועים",
"Ex: mobilizon.fr": "למשל: mobilizon.fr",
"Explore": "סיור",
"Failed to save admin settings": "שמירת הגדרות ניהול נכשלה",
"Featured events": "אירועים מומלצים",
"Federation": "ביזור",
"Find an address": "מציאת כתובת",
"Find an instance": "מציאת אתר",
"Followers": "עוקבים.ות",
"Followings": "עוקב.ת אחרי",
"For instance: London, Taekwondo, Architecture…": "לדוגמה: תל אביב, אייקידו, ארכיטקטורה…",
"Forgot your password ?": "שכחת ססמה?",
"From the {startDate} at {startTime} to the {endDate}": "מתאריך {startDate} בשעה {startTime} עד התאריך {endDate}",
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "מתאריך {startDate} בשעה {startTime} עד התאריך {endDate} בשעה {endTime}",
"From the {startDate} to the {endDate}": "מהתאריך {startDate} עד התאריך {endDate}",
"Gather ⋅ Organize ⋅ Mobilize": "להתאסף ⋅ להתארגן ⋅ לפעול",
"General": "כללי",
"General information": "מידע כללי",
"Getting location": "מחפש מיקום",
"Go": "קדימה",
"Group name": "שם הקבוצה",
"Group {displayName} created": "הקבוצה {displayName} נוצרה",
"Groups": "קבוצות",
"Headline picture": "תמונת כותרת",
"Hide replies": "הסתרת תגובות",
"I create an identity": "אני יוצר.ת זהות",
"I don't have a Mobilizon account": "אין לי חשבון מוביליזון",
"I have a Mobilizon account": "יש לי חשבון מוביליזון",
"I have an account on another Mobilizon instance.": "יש לי חשבון באתר מוביליזון אחר.",
"I participate": "אני משתתפ.ת",
"I want to allow people to participate without an account.": "אני רוצה לאפשר לא.נשים להשתתף ללא חשבון.",
"I want to approve every participation request": "אני רוצה לאשר כל בקשת השתתפות",
"Identity {displayName} created": "הזהות {displayName} נוצרה",
"Identity {displayName} deleted": "הזהות {displayName} נמחקה",
"Identity {displayName} updated": "הזהות {displayName} עודכנה",
"If an account with this email exists, we just sent another confirmation email to {email}": "אם קיים חשבון עם כתובת הדואר האלקטרוני הזו, שלחנו עוד הודעת אימות לכתובת {email}",
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "אם הזהות הזו היא המנהלת היחידה של קבוצות כלשהן, עלייך למחוק אותן לפני שתוכלי למחוק את הזהות הזו.",
"If you want, you may send a message to the event organizer here.": "אם את.ה רוצה, באפשרותך לשלוח כאן הודעה למארגנ.ת האירוע.",
"Instance Name": "שם האתר",
"Instance Terms": "תנאי השימוש של האתר",
"Instance Terms Source": "תנאי השימוש של האתר",
"Instance Terms URL": "קישור לתנאי השימוש של האתר",
"Instance settings": "הגדרות האתר",
"Instances": "אתרים",
"Join <b>{instance}</b>, a Mobilizon instance": "הצטרפ.י ל־<b>{instance}</b>, אתר מוביליזון",
"Last published event": "האירוע האחרון שפורסם",
"Last week": "בשבוע שעבר",
"Learn more": "למדו עוד",
"Learn more about Mobilizon": "למדו עוד אודות מוביליזון",
"Leave event": "עזיבת האירוע",
"Leaving event \"{title}\"": "עוזב את האירוע \"{title}\"",
"License": "רישיון",
"Limited number of places": "מספר מוגבל של מקומות",
"Load more": "לטעון עוד",
"Locality": "מיקום",
"Log in": "כניסה",
"Log out": "יציאה",
"Login": "כניסה",
"Login on Mobilizon!": "כניסה למוביליזון!",
"Login on {instance}": "כניסה לאתר {instance}",
"Manage participations": "ניהול השתתפויות",
"Mark as resolved": "סימון כנפתר",
"Members": "חברים.ות",
"Message": "הודעה",
"Mobilizon is a federated network. You can interact with this event from a different server.": "מוביליזון היא רשת מבוזרת. ניתן לבצע פעולות בקשר לאירוע זה משרת אחר.",
"Moderated comments (shown after approval)": "תגובות שממתינות לאישור",
"Moderation": "אישור תגובות",
"Moderation log": "היסטוריית אישור תגובות",
"My account": "החשבון שלי",
"My events": "האירועים שלי",
"My identities": "הזהויות שלי",
"Name": "שם",
"New email": "הודעת דוא\"ל חדשה",
"New note": "הערה חדשה",
"New password": "ססמה חדשה",
"New profile": "פרופיל חדש",
"Next page": "הדף הבא",
"No address defined": "לא הוגדרה כתובת",
"No closed reports yet": "אין דיווחים סגורים עדיין",
"No comment": "אין תגובות",
"No comments yet": "אין תגובות עדיין",
"No end date": "אין תאריך סיום",
"No events found": "לא נמצאו אירועים",
"No group found": "לא נמצאה קבוצה",
"No groups found": "לא נמצאו קבוצות",
"No instance follows your instance yet.": "עדיין אין אתר שעוקב אחרי האתר שלך.",
"No instance to approve|Approve instance|Approve {number} instances": "אין אתרים לאשר|אישור האתר|אישור של {number} אתרים",
"No instance to reject|Reject instance|Reject {number} instances": "אין אתר לדחות|דחיית האתר|דחיית {number} אתרים",
"No instance to remove|Remove instance|Remove {number} instances": "אין אתרים להסיר|הסרת האתר|הסרת {number} אתרים",
"No message": "אין הודעות",
"No open reports yet": "אין דיווחים פתוחים עדיין",
"No participant to approve|Approve participant|Approve {number} participants": "אין משתתפים.ות לאשר|אישור המשתתפ.ת|אישור של {number} משתתפים.ות",
"No participant to reject|Reject participant|Reject {number} participants": "אין משתתפים.ות לדחות|דחיית המשתתפ.ת|דחיית {number} משתתפים.ות",
"No resolved reports yet": "אין דיווחים פתורים עדיין",
"No results for \"{queryText}\"": "אין תוצאות עבור \"{queryText}\"",
"Notes": "הערות",
"Number of places": "מספר מקומות",
"OK": "אישור",
"Old password": "ססמה ישנה",
"On {date}": "בתאריך {date}",
"On {date} ending at {endTime}": "בתאריך {date} מסתיים בשעה {endTime}",
"On {date} from {startTime} to {endTime}": "בתאריך {date} מהשעה {startTime} עד השעה {endTime}",
"On {date} starting at {startTime}": "בתאריך {date} החל מהשעה {startTime}",
"Only accessible through link (private)": "נגיש רק דרך קישור (פרטי)",
"Only alphanumeric lowercased characters and underscores are supported.": "רק אותיות קטנות באנגלית, ספרות וקו תחתי.",
"Open": "פתיחה",
"Opened reports": "דיווחים פתוחים",
"Or": "או",
"Organized": "מאורגן",
"Organized by {name}": "מאורגן על־ידי {name}",
"Organizer": "מארגנ.ת",
"Other software may also support this.": "תוכנות אחרות עשויות גם הן לתמוך בזה.",
"Otherwise this identity will just be removed from the group administrators.": "אחרת, זהות זו פשוט תוסר מניהול הקבוצה.",
"Page": "דף",
"Page limited to my group (asks for auth)": "הדף מוגבל לקבוצה שלי (מבקש להזדהות)",
"Participant": "משתתפ.ת",
"Participants": "משתתפים.ות",
"Participate": "השתתפות",
"Participate using your email address": "השתתפות בעזרת כתובת הדוא\"ל שלך",
"Participation approval": "אישור השתתפות",
"Participation confirmation": "אישור השתתפות",
"Participation requested!": "נשלחה בקשת השתתפות!",
"Password": "ססמה",
"Password (confirmation)": "ססמה (וידוא)",
"Password reset": "איפוס ססמה",
"Past events": "אירועים שחלפו",
"Pending": "ממתין",
"Pick an identity": "בחירת זהות",
"Please check your spam folder if you didn't receive the email.": "אנא בדק.י את תיקיית הספאם שלך אם לא קיבלת את הודעת הדוא\"ל.",
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "אנא צר.י קשר עם מנהל.ת האתר אם את.ה חושב.ת שזו טעות.",
"Please enter your password to confirm this action.": "אנא הזינ.י את הסממה שלך כדי לאשר את הפעולה.",
"Please make sure the address is correct and that the page hasn't been moved.": "אנא ודא.י שהכתובת נכונה ושהעמוד לא הוזז.",
"Post a comment": "פרסום תגובה",
"Post a reply": "פרסום תגובה",
"Postal Code": "מיקוד",
"Preferences": "העדפות",
"Previous page": "הדף הקודם",
"Privacy Policy": "מדיניות פרטיות",
"Private event": "אירוע פרטי",
"Private feeds": "היזנים פרטיים",
"Profiles": "פרופילים",
"Public RSS/Atom Feed": "ערוץ RSS/Atom פומבי",
"Public comment moderation": "בקרת תגובות פומביות",
"Public event": "אירוע פומבי",
"Public feeds": "היזנים פומביים",
"Public iCal Feed": "ערוץ iCal פומבי",
"Publish": "פרסום",
"Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations": "אירועים שפורסמו שיש להם <b>{comments}</b> תגובות ו־<b>{participations}</b> אישורי השתתפות",
"RSS/Atom Feed": "היזן רסס/אטום",
"Region": "אזור",
"Registration is allowed, anyone can register.": "ההרשמה מאופשרת, כל אחד.ת יכול.ה להירשם.",
"Registration is closed.": "ההרשמה סגורה.",
"Registration is currently closed.": "ההרשמה סגורה כעת.",
"Rejected": "נדחה",
"Reopen": "פתיחה מחדש",
"Reply": "תגובה",
"Report": "דיווח",
"Report this comment": "דיווח על תגובה זו",
"Report this event": "דיווח על אירוע זה",
"Reported": "מדווח",
"Reported by": "דווח על־ידי",
"Reported by someone on {domain}": "דווח על־ידי מישהו.י מהאתר {domain}",
"Reported by {reporter}": "דווח על־ידי {reporter}",
"Reported identity": "זהות מדווחת",
"Reports": "דיווחים",
"Reset my password": "איפוס הססמה שלי",
"Resolved": "נפתר",
"Resource provided is not an URL": "המשאב שהוזן אינו קישור תקין",
"Role": "תפקיד",
"Save": "שמירה",
"Save draft": "שמירת טיוטה",
"Search": "חיפוש",
"Search events, groups, etc.": "חיפוש אירועים, קבוצות וכו'",
"Searching…": "מחפש…",
"Send email": "שליחת דוא\"ל",
"Send the report": "שליחת הדיווח",
"Set an URL to a page with your own terms.": "הגדרת קישור לעמוד עם תנאים משלך.",
"Settings": "הגדרות",
"Share this event": "שיתוף אירוע זה",
"Show map": "הצגת מפה",
"Show remaining number of places": "הצגת מספר המקומות שנותרו",
"Show the time when the event begins": "הצגת זמן תחילת האירוע",
"Show the time when the event ends": "הצגת זמן סיום האירוע",
"Sign up": "הרשמה",
"Starts on…": "מתחיל ב…",
"Status": "מצב",
"Street": "רחוב",
"Tentative: Will be confirmed later": "לא סופי: יאושר בהמשך",
"Terms": "תנאים",
"The account's email address was changed. Check your emails to verify it.": "כתובת הדוא\"ל של החשבון שונתה. בדק.י את תיבת הדוא\"ל שלך כדי לאמת את השינוי.",
"The actual number of participants may differ, as this event is hosted on another instance.": "מספר המשתתפים.ות האמיתי עשוי להיות שונה, כי האירוע מתפרסם דרך אתר מוביליזון אחר.",
"The content came from another server. Transfer an anonymous copy of the report?": "התוכן הגיע משרת אחר. להעביר עותק אנונימי של הדיווח?",
"The draft event has been updated": "טיוטת האירוע עודכנה",
"The event has been created as a draft": "האירוע נוצר כטיוטה",
"The event has been published": "האירוע פורסם",
"The event has been updated": "האירוע עודכן",
"The event has been updated and published": "האירוע עודכן ופורסם",
"The event organiser has chosen to validate manually participations. Do you want to add a little note to explain why you want to participate to this event?": "בקשות השתתפות מאושרות ידנית על־ידי מארגנ.ת האירוע. האם ברצונך להוסיף הערה עם הסבר מדוע את.ה רוצה להשתתף באירוע זה?",
"The event organizer didn't add any description.": "מארגנ.ת האירוע לא הוסיפ.ה תיאור.",
"The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.": "בקשות השתתפות מאושרות ידנית על־ידי מארגנ.ת האירוע. היות ובחרת להשתתף ללא חשבון, אנא צרפ.י הסבר מדוע את.ה רוצה להשתתף באירוע זה.",
"The event title will be ellipsed.": "כותרת האירוע תוצג באופן מקוצר עם שלוש נקודות בסוף.",
"The page you're looking for doesn't exist.": "העמוד שחיפשת לא קיים.",
"The password was successfully changed": "הססמה שונתה בהצלחה",
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "הדיווח יישלח למנהלים.ות של השרת שלך. ניתן להסביר למטה את סיבת הדיווח של תוכן זה.",
"The {default_terms} will be used. They will be translated in the user's language.": "ייעשה שימוש ב{default_terms}. הם יתורגמו לשפה של המשתמש.ת.",
"There are {participants} participants.": "יש {participants} משתתפים.ות.",
"There will be no way to recover your data.": "לא תהיה אפשרות לשחזר את המידע שלך.",
"These events may interest you": "האירועים האלה עשויים לעניין אותך",
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "שרת מוביליזון זה ואירוע זה מאפשרים בקשת השתתפות ללא חשבון, אך נדרש אימות באמצעות דואר אלקטרוני.",
"This information is saved only on your computer. Click for details": "מידע זה נשמר רק על המחשב שלך. לפרטים לחצ.י",
"This instance isn't opened to registrations, but you can register on other instances.": "שרת זה אינו פתוח להרשמה, אך ניתן להירשם בשרתים אחרים.",
"This is a demonstration site to test Mobilizon.": "זהו אתר הדגמה לצורך בדיקה של מוביליזון.",
"This will delete / anonymize all content (events, comments, messages, participations…) created from this identity.": "הדבר ימחק / יהפוך לאנונימי את כל התוכן (אירועים, תגובות, הודעות, אישורי השתתפות…) שנוצר מתוך זהות זו.",
"Title": "כותרת",
"To confirm, type your event title \"{eventTitle}\"": "לאישור, נא להזין את כותרת האירוע \"{eventTitle}\"",
"To confirm, type your identity username \"{preferredUsername}\"": "לאישור, נא להזין את שם המשתמש.ת שלך \"{preferredUsername}\"",
"Transfer to {outsideDomain}": "העברה לשרת {outsideDomain}",
"Type": "סוג",
"URL": "קישור"
}

1263
js/src/i18n/hr.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More