Compare commits
253 Commits
chapril
...
3.0.0-beta
Author | SHA1 | Date |
---|---|---|
Thomas Citharel | e47904d17d | |
Thomas Citharel | 727a0175c6 | |
Thomas Citharel | a2289c4d03 | |
Thomas Citharel | 0ccac625d4 | |
Kate | 03e1a0ada8 | |
Kate | 23ace34149 | |
Kate | 0d019d40ec | |
Kate | 8ae0a929c7 | |
Thomas Citharel | d8103fb1a5 | |
Thomas Citharel | 5bce26980a | |
Thomas Citharel | 199add5743 | |
Thomas Citharel | 699465199e | |
Thomas Citharel | fb0238d1b7 | |
Thomas Citharel | e4ba0f21ee | |
Thomas Citharel | fca3720bdb | |
Thomas Citharel | c051e26380 | |
Thomas Citharel | 76ff11ceae | |
Thomas Citharel | ac7b994b6f | |
Jordi Brunet | ae5dab8433 | |
Thomas Citharel | b46ff4d458 | |
Thomas Citharel | 156490af62 | |
Thomas Citharel | 07a1118fab | |
Thomas Citharel | f91e728b91 | |
Thomas Citharel | 91e7bd8b68 | |
Thomas Citharel | ad4a9d4dff | |
Thomas Citharel | 8c36173027 | |
Thomas Citharel | 36932f601d | |
Thomas Citharel | 3400ee0ce0 | |
Thomas Citharel | 19a17494ae | |
Thomas Citharel | 06caab9d56 | |
Thomas Citharel | 34715d5f45 | |
Thomas Citharel | be2fa3a547 | |
Thomas Citharel | 68378c1860 | |
Thomas Citharel | c23c2bdba6 | |
Thomas Citharel | 74581912f3 | |
Thomas Citharel | 528744d765 | |
Thomas Citharel | 9fe816ebcf | |
Thomas Citharel | 557e12948b | |
Thomas Citharel | e22cb8c3bd | |
Thomas Citharel | ff1fbdfdba | |
Thomas Citharel | b514d417c0 | |
Thomas Citharel | 8eddb8b230 | |
Thomas Citharel | e94a537f30 | |
Thomas Citharel | 3928c2fb2a | |
Thomas Citharel | 475820472f | |
Thomas Citharel | 800e136aae | |
Thomas Citharel | 9b3674922e | |
Thomas Citharel | 3e8d5884a2 | |
Jiri Podhorecky | d23b3ad5f6 | |
Thomas Citharel | 15766c4e16 | |
Thomas Citharel | 6f9a941b18 | |
Ricardo Coelho | 11a4275667 | |
Ricardo Coelho | e01d7814a0 | |
Thomas Citharel | b769341944 | |
Ricardo Coelho | 6f6a379a06 | |
Thomas Citharel | 0d6626d55a | |
Thomas Citharel | 200e4d2c56 | |
Thomas Citharel | 7ccf42e313 | |
Thomas Citharel | 2acc1c3492 | |
Thomas Citharel | 1a31d9b54e | |
Thomas Citharel | 0758995ac7 | |
Thomas Citharel | c080795955 | |
Thomas Citharel | 0265e0220e | |
Milo Ivir | 04edb51a04 | |
Thomas Citharel | d665abcc5d | |
Thomas Citharel | 620008ee0d | |
Thomas Citharel | fee4f9add8 | |
Thomas Citharel | 1a9aef00e5 | |
Thomas Citharel | 456e987af8 | |
Thomas Citharel | ec9342744f | |
Milo Ivir | 893405b219 | |
Quentin PAGÈS | 10798f60c3 | |
Thomas Citharel | a6a49c27bf | |
Berto Te | d483b1824e | |
Thomas Citharel | 0cbe0b8fad | |
Thomas Citharel | b8942e7b4c | |
Milo Ivir | 2fdff72062 | |
Thomas Citharel | 5055bd4adc | |
Thomas Citharel | 1601153062 | |
Thomas Citharel | f0c8fa2525 | |
Thomas Citharel | 4f1465e84d | |
Thomas Citharel | ca9826e299 | |
Thomas Citharel | 4a16f572d8 | |
Thomas Citharel | 3e8029cf38 | |
Berto Te | ede03b9f54 | |
Thomas Citharel | fdc4c94465 | |
joenepraat | ea67a0b3ba | |
joenepraat | 0663708447 | |
Berto Te | 89e158e9f1 | |
Thomas Citharel | f6b0a4a55f | |
joenepraat | 86ec65acbe | |
Berto Te | 20dd02e633 | |
Thomas Citharel | 50c2d62af9 | |
Thomas Citharel | 63305d39f9 | |
Thomas Citharel | 661f7a15cf | |
joenepraat | e8bdd988ed | |
joenepraat | 8ba8961ad7 | |
Deleted User | 77d2f86e03 | |
joenepraat | ccb3aab7d7 | |
Thomas Citharel | 393438278f | |
Berto Te | 763a2cce43 | |
Thomas Citharel | 4b0375fd61 | |
Thomas Citharel | 1975ca7739 | |
Thomas Citharel | 9634b07910 | |
Thomas Citharel | 8915a5caad | |
Thomas Citharel | 895aad6ea7 | |
Thomas Citharel | 92dcc0780d | |
Thomas Citharel | fc6244bbcd | |
Thomas Citharel | 26c183e208 | |
Thomas Citharel | 0f05afc643 | |
Thomas Citharel | 600de5447d | |
Thomas Citharel | cbce14c9d8 | |
Thomas Citharel | 1087e19ee5 | |
Thomas Citharel | 680f812bdf | |
Thomas Citharel | 6f7d5f649b | |
Thomas Citharel | a37bab3b84 | |
Thomas Citharel | 530539c631 | |
778a69cd | 22dbfad98c | |
Thomas Citharel | 83d518579b | |
Thomas Citharel | c2c938c865 | |
Thomas Citharel | 280b255529 | |
Thomas Citharel | c35a066368 | |
Thomas Citharel | 88b4f98c41 | |
Thomas Citharel | 6675c62d94 | |
Thomas Citharel | d2372d5700 | |
Thomas Citharel | 009f90e0d7 | |
Thomas Citharel | bbbaa81a6e | |
Thomas Citharel | 655ae4f9a1 | |
Thomas Citharel | 6a9fba7db8 | |
Thomas Citharel | 151a7e54ae | |
Thomas Citharel | 86ca52c2cb | |
CHALLET Olivier | 35c5be3a32 | |
Ville Ranki | 8aa6dcc081 | |
Milo Ivir | 4cd6c3142e | |
CHALLET Olivier | 130f27b18a | |
CHALLET Olivier | 8ccb9050f0 | |
CHALLET Olivier | d0d33dbc96 | |
Thomas Citharel | d97927da13 | |
Thomas Citharel | 93921be722 | |
Thomas Citharel | eecb04516e | |
Thomas Citharel | b36ce27bbe | |
Thomas Citharel | d43e7f2c34 | |
Thomas Citharel | d12e8fe97d | |
Thomas Citharel | eb7c6d339f | |
Thomas Citharel | d2b59e6444 | |
Thomas Citharel | 07b2447a07 | |
Thomas Citharel | 0e7ff25333 | |
Thomas Citharel | 4db13046b7 | |
Thomas Citharel | 8812122168 | |
Thomas Citharel | ce38361d65 | |
Thomas Citharel | 45a477ee46 | |
Thomas Citharel | 48935e2168 | |
Norwin | 0041face54 | |
Thebigal Wisi | f36700c87a | |
Valentin Bachem | 551f53f849 | |
Thebigal Wisi | 7ef37c040c | |
fluxx | 2bf0dd482b | |
Valentin Bachem | ec812bf379 | |
Kate | d10aa4106f | |
Norwin | e787bf36ab | |
Valentin Bachem | f04d74295b | |
Milo Ivir | 073d99392f | |
Milo Ivir | 61acdc6a5b | |
Kate | 32cc3eb738 | |
Kate | 73e992f1b3 | |
Kate | 7b693dd4ae | |
Milo Ivir | 0c2f2df228 | |
Milo Ivir | efe9f1df66 | |
Milo Ivir | b0c3e8cce6 | |
Milo Ivir | fd1501af1c | |
Milo Ivir | cc335560c8 | |
Milo Ivir | f9c44124d1 | |
Thomas Citharel | bfc936f57c | |
Thomas Citharel | 2eee09bf4b | |
Thomas Citharel | ffac91a027 | |
Thomas Citharel | e07f88277b | |
Thomas Citharel | c72f050993 | |
Thomas Citharel | baac00f678 | |
Milo Ivir | b0f309d271 | |
Milo Ivir | 7868bdb803 | |
Milo Ivir | df7430a19b | |
Milo Ivir | 2503e10b7c | |
Milo Ivir | d11cba4ad4 | |
Thomas Citharel | 444e0d6a0c | |
Thomas Citharel | be0b3245bf | |
Thomas Citharel | d818557469 | |
Milo Ivir | 4f13934d43 | |
Milo Ivir | a8af226e1a | |
Milo Ivir | ff40aa5d65 | |
Thomas Citharel | 6e7359aebf | |
Thomas Citharel | 754e44f0a5 | |
Thomas Citharel | 4f9e0911e7 | |
Thomas Citharel | d8cf49e315 | |
Thomas Citharel | e893f3f089 | |
Thomas Citharel | 9d00aff619 | |
Thomas Citharel | 57c3df43ff | |
Thomas Citharel | ee20e03cc2 | |
Hermann San | 56b3df37ab | |
Hermann San | 1694ca2dc3 | |
Hermann San | 0f38dca6c6 | |
Thebigal Wisi | 5bb796270a | |
Thebigal Wisi | 032311e1c0 | |
Hermann San | de6958ebc8 | |
josé m | 3d17f31cef | |
josé m | 8879a8306b | |
Hermann San | ffc052aa83 | |
Hermann San | cddea8911a | |
Thebigal Wisi | 412a3ae822 | |
Hermann San | b4b97aeb30 | |
Thomas Citharel | 8f4099ee33 | |
Jiri Podhorecky | 38a9635a7e | |
J. Lavoie | e24446aef1 | |
J. Lavoie | 91b801d177 | |
J. Lavoie | f70dc4e61d | |
Thomas Citharel | 68973b91d9 | |
Thomas Citharel | 86925833ef | |
Thomas Citharel | 6aba0af3ec | |
Thomas Citharel | ed196d9b84 | |
Thomas Citharel | 5eadbbaae9 | |
Thomas Citharel | 8ae859e0c3 | |
DignifiedSilence | a46ce1352d | |
DignifiedSilence | 1457fd4b97 | |
Thomas Citharel | c2f7fbc78f | |
Jiri Podhorecky | 5e6dc16335 | |
Jiri Podhorecky | aa107f4a89 | |
Thomas Citharel | 0a9ff32443 | |
allilengyi | f8efcc1d63 | |
Thomas Citharel | 337adf83b2 | |
Thomas Citharel | fa7a10bd13 | |
Thomas Citharel | 212931b8a0 | |
allilengyi | 86fbcaff40 | |
allilengyi | 324cf3e5e2 | |
paficaf430 | 4917185b82 | |
paficaf430 | d7e2e82ea6 | |
paficaf430 | 958e7d5264 | |
Jiri Podhorecky | c01a4fdc6f | |
Jiri Podhorecky | 018f3f35d6 | |
Lucas Cimon | 18d4b73d91 | |
Lucas Cimon | 494e791a99 | |
Norwin | 06bc5fc4c3 | |
Thebigal Wisi | 2a052fddd6 | |
Thomas Frenzel | 9f34d1c538 | |
Thomas Citharel | 4e7284b486 | |
Jiri Podhorecky | 4728a7358c | |
Thomas Citharel | 44789189d7 | |
Kerstin | 59147af19b | |
Kerstin | 7a1b886f09 | |
Thomas Citharel | c6cc85e51e | |
josé m | 5299e10afe | |
Thomas Citharel | 829628b25f | |
DignifiedSilence | 9e1c8b475e | |
Thomas Frenzel | cf182c2aed | |
Thomas Citharel | 00424d64c9 |
|
@ -127,14 +127,14 @@ exunit:
|
|||
- test-junit-report.xml
|
||||
expire_in: 30 days
|
||||
|
||||
jest:
|
||||
vitest:
|
||||
stage: test
|
||||
needs:
|
||||
- lint-front
|
||||
before_script:
|
||||
- yarn --cwd "js" install --frozen-lockfile
|
||||
script:
|
||||
- yarn --cwd "js" run test:unit --no-color --ci --reporters=default --reporters=jest-junit
|
||||
- yarn --cwd "js" run coverage --reporter=default --reporter=junit --outputFile.junit=./junit.xml
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
|
@ -144,26 +144,32 @@ jest:
|
|||
- js/junit.xml
|
||||
expire_in: 30 days
|
||||
|
||||
# cypress:
|
||||
# stage: test
|
||||
# services:
|
||||
# - name: postgis/postgis:13.3
|
||||
# alias: postgres
|
||||
# variables:
|
||||
# MIX_ENV=e2e
|
||||
# script:
|
||||
# - mix ecto.create
|
||||
# - mix ecto.migrate
|
||||
# - mix run priv/repo/e2e.seed.exs
|
||||
# - mix phx.server &
|
||||
# - cd js
|
||||
# - npx wait-on http://localhost:4000
|
||||
# - if [ -z "$CYPRESS_KEY" ]; then npx cypress run; else npx cypress run --record --parallel --key $CYPRESS_KEY; fi
|
||||
# artifacts:
|
||||
# expire_in: 2 day
|
||||
# paths:
|
||||
# - js/tests/e2e/screenshots/**/*.png
|
||||
# - js/tests/e2e/videos/**/*.mp4
|
||||
e2e:
|
||||
stage: test
|
||||
services:
|
||||
- name: postgis/postgis:14-3.2
|
||||
alias: postgres
|
||||
variables:
|
||||
MIX_ENV: "e2e"
|
||||
before_script:
|
||||
- mix deps.get
|
||||
- mix ecto.create
|
||||
- mix ecto.migrate
|
||||
- mix run priv/repo/e2e.seed.exs
|
||||
- cd js && yarn install && yarn run build && npx playwright install && cd ../
|
||||
- mix phx.digest
|
||||
script:
|
||||
- mix phx.server &
|
||||
- cd js
|
||||
- npx wait-on http://localhost:4000
|
||||
- npx playwright test --project $BROWSER
|
||||
parallel:
|
||||
matrix:
|
||||
- BROWSER: ['firefox', 'chromium']
|
||||
artifacts:
|
||||
expire_in: 2 days
|
||||
paths:
|
||||
- js/playwright-report
|
||||
|
||||
pages:
|
||||
stage: deploy
|
||||
|
@ -183,7 +189,7 @@ pages:
|
|||
|
||||
.docker: &docker
|
||||
stage: docker
|
||||
image: docker:20.10.12
|
||||
image: docker:20.10.18
|
||||
variables:
|
||||
DOCKER_TLS_CERTDIR: "/certs"
|
||||
DOCKER_HOST: tcp://docker:2376
|
||||
|
@ -191,13 +197,13 @@ pages:
|
|||
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
|
||||
DOCKER_DRIVER: overlay2
|
||||
services:
|
||||
- docker:20.10.12-dind
|
||||
- docker:20.10.18-dind
|
||||
cache: {}
|
||||
before_script:
|
||||
# Install buildx
|
||||
- wget https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.linux-amd64
|
||||
- wget https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-amd64
|
||||
- mkdir -p ~/.docker/cli-plugins/
|
||||
- mv buildx-v0.8.1.linux-amd64 ~/.docker/cli-plugins/docker-buildx
|
||||
- mv buildx-v0.9.1.linux-amd64 ~/.docker/cli-plugins/docker-buildx
|
||||
- chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
# Create env
|
||||
- docker context create tls-environment
|
||||
|
@ -216,29 +222,47 @@ build-docker-main:
|
|||
rules:
|
||||
- if: '$CI_PROJECT_NAMESPACE != "framasoft"'
|
||||
when: never
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_TRIGGERED == "true"'
|
||||
script:
|
||||
- docker buildx build --push --platform linux/amd64 -t framasoft/mobilizon:main -f docker/production/Dockerfile .
|
||||
|
||||
build-docker-tag:
|
||||
# Don't push to latest when building beta/rc tags
|
||||
build-and-push-to-latest-docker-tag:
|
||||
<<: *docker
|
||||
rules: &tag-rules
|
||||
- if: '$CI_PROJECT_NAMESPACE != "framasoft"'
|
||||
when: never
|
||||
- if: $CI_COMMIT_TAG
|
||||
- if: $CI_COMMIT_TAG != null && $CI_COMMIT_TAG !~ /alpha|beta|rc/
|
||||
when: on_success
|
||||
timeout: 3 hours
|
||||
script:
|
||||
- >
|
||||
docker buildx build
|
||||
--push
|
||||
--platform linux/amd64,linux/arm64,linux/arm
|
||||
--platform linux/amd64,linux/arm
|
||||
-t framasoft/mobilizon:$CI_COMMIT_TAG
|
||||
-t framasoft/mobilizon:latest
|
||||
-f docker/production/Dockerfile .
|
||||
|
||||
build-and-push-docker-tag:
|
||||
<<: *docker
|
||||
rules: &tag-rules
|
||||
- if: '$CI_PROJECT_NAMESPACE != "framasoft"'
|
||||
when: never
|
||||
- if: $CI_COMMIT_TAG =~ /alpha|beta|rc/
|
||||
when: on_success
|
||||
timeout: 3 hours
|
||||
script:
|
||||
- >
|
||||
docker buildx build
|
||||
--push
|
||||
--platform linux/amd64,linux/arm
|
||||
-t framasoft/mobilizon:$CI_COMMIT_TAG
|
||||
-f docker/production/Dockerfile .
|
||||
|
||||
# Packaging app for amd64
|
||||
package-app:
|
||||
image: mobilizon/buildpack:1.13.4-erlang-24.3.3-debian-buster
|
||||
image: mobilizon/buildpack:1.14.1-erlang-25.1.1-debian-buster
|
||||
stage: package
|
||||
variables: &release-variables
|
||||
MIX_ENV: "prod"
|
||||
|
@ -250,7 +274,7 @@ package-app:
|
|||
- mix local.rebar --force
|
||||
- mix deps.get --only-prod
|
||||
- mix compile
|
||||
- mix phx.digest.clean --all && \
|
||||
- mix phx.digest.clean --all && mix phx.digest
|
||||
- mix release --path release/mobilizon
|
||||
- cd release/mobilizon && ln -s lib/mobilizon-*/priv priv && cd ../../
|
||||
- du -sh release/
|
||||
|
@ -317,11 +341,13 @@ multi-arch-release:
|
|||
matrix:
|
||||
- ARCH: ["arm", "arm64"]
|
||||
rules:
|
||||
- if: '$CI_PROJECT_NAMESPACE != "framasoft"'
|
||||
when: never
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
- if: $CI_COMMIT_TAG
|
||||
# arm64 is allowed to fail
|
||||
- if: '$CI_PROJECT_NAMESPACE == "framasoft" && ($CI_PIPELINE_SOURCE == "schedule" || $CI_COMMIT_TAG != null) && $ARCH == "arm64"'
|
||||
allow_failure: true
|
||||
- if: '$CI_PROJECT_NAMESPACE == "framasoft" && ($CI_PIPELINE_SOURCE == "schedule" || $CI_COMMIT_TAG != null) && $ARCH'
|
||||
allow_failure: false
|
||||
timeout: 3h
|
||||
allow_failure: true
|
||||
|
||||
# Release
|
||||
release-upload:
|
||||
|
@ -342,6 +368,7 @@ release-upload:
|
|||
parallel:
|
||||
matrix:
|
||||
- ARCH: ["amd64", "arm", "arm64"]
|
||||
allow_failure: true
|
||||
|
||||
release-create:
|
||||
stage: deploy
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
elixir 1.13.4-otp-24
|
||||
erlang 24.3.3
|
||||
elixir 1.14.1-otp-25
|
||||
erlang 25.1.1
|
||||
|
|
73
CHANGELOG.md
|
@ -5,6 +5,73 @@ 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/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 3.0.0-beta.3 - 2022-10-17
|
||||
|
||||
### Fixed
|
||||
- Don't add empty search parameters to global search engine
|
||||
- Fix getting categories from global search engine
|
||||
- Remove unused deps
|
||||
- Only show one pagination bar when searching in both events & groups
|
||||
- Run build multiarch release on tags too
|
||||
- Don't start mobilizon server when running migrations
|
||||
- Run phx.digest before mix release
|
||||
|
||||
|
||||
## 3.0.0-beta.2 - 2022-10-11
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved the pertinence of related events
|
||||
- Light front-end performance improvements
|
||||
- Various UI and A11Y fixes on the event page
|
||||
- Handle categories page being empty
|
||||
|
||||
### Fixed
|
||||
|
||||
- Address selector
|
||||
- Group location edition
|
||||
- Reconfigure plug at runtime with env
|
||||
- Fix global search term
|
||||
- Fix custom icons in metadata list
|
||||
- Handle unknown icon
|
||||
- Only preload svg pictures on homepage
|
||||
|
||||
## 3.0.0-beta.1 - 2022-09-27
|
||||
|
||||
### Added
|
||||
|
||||
- Add global search support, allowing to use https://search.joinmobilizon.org as a centralized event and group database
|
||||
- Add ability to filter search by categories and language
|
||||
- Add ability to explore search results on a map view
|
||||
- Add dark theme support
|
||||
- Add categories view
|
||||
- Support for Elixir 1.14 and Erlang OTP 25.
|
||||
|
||||
### Changed
|
||||
|
||||
- Homepage has been redesigned
|
||||
- Search view has been redesigned
|
||||
- Internal illustration pictures are now only served using WebP.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed deleting actor when participations association is not preloaded
|
||||
- Fixed rendering JSON-LD for an event with a single address (no online location)
|
||||
|
||||
### Internal
|
||||
- Build on Elixir 1.14 and Erlang OTP 25.
|
||||
- Migrate from Vue 2 and Vue Class Component to Vue 3 and the Composition API
|
||||
- Migrate from Bulma and Buefy to TailwindCSS and Oruga
|
||||
|
||||
### Tests
|
||||
|
||||
#### Unit Tests
|
||||
- Rewrote tests using Vitest
|
||||
|
||||
#### E2E Tests
|
||||
- Renabled E2E tests
|
||||
- Rewrote tests from Cypress to Playwright
|
||||
|
||||
## 2.1.0 - 2022-05-16
|
||||
|
||||
### Added
|
||||
|
@ -19,7 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Changed
|
||||
|
||||
- Changed mailer library from Bamboo to Swoosh, should fix emails being considered spam. **Some configuration changes are required, see below.**
|
||||
- Changed mailer library from Bamboo to Swoosh, should fix emails being considered spam. **Some configuration changes are required, see [UPGRADE.md](https://framagit.org/framasoft/mobilizon/-/blob/main/UPGRADE.md).**
|
||||
- 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
|
||||
|
@ -41,7 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- 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
|
||||
- Remove obsolete attribute `type="text/css"` from `<style>` tags
|
||||
- Improve actor cards integration
|
||||
- Use upstream dependencies for Ueberauth providers
|
||||
- Include ongoing events in search
|
||||
|
@ -81,7 +148,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- 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 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
|
||||
|
|
|
@ -54,7 +54,7 @@ config :mobilizon, Mobilizon.Web.Endpoint,
|
|||
secret_key_base: "1yOazsoE0Wqu4kXk3uC5gu3jDbShOimTCzyFL3OjCdBmOXMyHX87Qmf3+Tu9s0iM",
|
||||
render_errors: [view: Mobilizon.Web.ErrorView, accepts: ~w(html json)],
|
||||
pubsub_server: Mobilizon.PubSub,
|
||||
cache_static_manifest: "priv/static/manifest.json",
|
||||
cache_static_manifest: "priv/static/cache_manifest.json",
|
||||
has_reverse_proxy: true
|
||||
|
||||
config :mime, :types, %{
|
||||
|
@ -123,16 +123,24 @@ config :mobilizon, Mobilizon.Web.Email.Mailer,
|
|||
# can be `true`
|
||||
no_mx_lookups: false
|
||||
|
||||
config :vite_phx,
|
||||
release_app: :mobilizon,
|
||||
# to tell prod and dev env appart
|
||||
environment: config_env(),
|
||||
# this manifest is different from the Phoenix "cache_manifest.json"!
|
||||
# optional
|
||||
vite_manifest: "priv/static/manifest.json",
|
||||
# optional
|
||||
phx_manifest: "priv/static/cache_manifest.json",
|
||||
# optional
|
||||
dev_server_address: "http://localhost:5173"
|
||||
|
||||
# Configures Elixir's Logger
|
||||
config :logger, :console,
|
||||
backends: [:console],
|
||||
format: "$time $metadata[$level] $message\n",
|
||||
metadata: [:request_id]
|
||||
|
||||
config :logger, Sentry.LoggerBackend,
|
||||
level: :warn,
|
||||
capture_log_messages: true
|
||||
|
||||
config :mobilizon, Mobilizon.Web.Auth.Guardian,
|
||||
issuer: "mobilizon",
|
||||
token_ttl: %{
|
||||
|
@ -347,6 +355,23 @@ config :mobilizon, :exports,
|
|||
|
||||
config :mobilizon, :analytics, providers: []
|
||||
|
||||
config :mobilizon, Mobilizon.Service.Pictures, service: Mobilizon.Service.Pictures.Unsplash
|
||||
|
||||
config :mobilizon, Mobilizon.Service.Pictures.Unsplash,
|
||||
app_name: "Mobilizon",
|
||||
access_key: nil
|
||||
|
||||
config :mobilizon, :search, global: [is_default_search: false, is_enabled: true]
|
||||
|
||||
config :mobilizon, Mobilizon.Service.GlobalSearch,
|
||||
service: Mobilizon.Service.GlobalSearch.SearchMobilizon
|
||||
|
||||
config :mobilizon, Mobilizon.Service.GlobalSearch.SearchMobilizon,
|
||||
endpoint: "https://search.joinmobilizon.org",
|
||||
csp_policy: [
|
||||
img_src: "search.joinmobilizon.org"
|
||||
]
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{config_env()}.exs"
|
||||
|
|
|
@ -15,13 +15,7 @@ config :mobilizon, Mobilizon.Web.Endpoint,
|
|||
check_origin: false,
|
||||
watchers: [
|
||||
node: [
|
||||
"node_modules/webpack/bin/webpack.js",
|
||||
"--mode",
|
||||
"development",
|
||||
"--watch",
|
||||
"--watch-options-stdin",
|
||||
"--config",
|
||||
"node_modules/@vue/cli-service/webpack.config.js",
|
||||
"node_modules/.bin/vite",
|
||||
cd: Path.expand("../js", __DIR__)
|
||||
]
|
||||
]
|
||||
|
@ -102,3 +96,5 @@ config :mobilizon, :anonymous,
|
|||
reports: [
|
||||
allowed: true
|
||||
]
|
||||
|
||||
config :unplug, :init_mode, :runtime
|
||||
|
|
|
@ -19,19 +19,39 @@ config :mobilizon, Mobilizon.Web.Endpoint,
|
|||
yarn: [cd: Path.expand("../js", __DIR__)]
|
||||
]
|
||||
|
||||
require Logger
|
||||
config :vite_phx,
|
||||
release_app: :mobilizon,
|
||||
# Hard code :prod as an environment as :e2e will not be recongnized
|
||||
environment: :prod,
|
||||
vite_manifest: "priv/static/manifest.json",
|
||||
phx_manifest: "priv/static/cache_manifest.json",
|
||||
dev_server_address: "http://localhost:5173"
|
||||
|
||||
cond do
|
||||
System.get_env("INSTANCE_CONFIG") &&
|
||||
File.exists?("./config/#{System.get_env("INSTANCE_CONFIG")}") ->
|
||||
import_config System.get_env("INSTANCE_CONFIG")
|
||||
config :mobilizon, :instance,
|
||||
name: "E2E Testing instance",
|
||||
description: "E2E is safety",
|
||||
hostname: "mobilizon1.com",
|
||||
registrations_open: true,
|
||||
registration_email_denylist: ["gmail.com", "deny@tcit.fr"],
|
||||
demo: false,
|
||||
default_language: "en",
|
||||
allow_relay: true,
|
||||
federating: true,
|
||||
email_from: "mobilizon@mobilizon1.com",
|
||||
email_reply_to: nil,
|
||||
enable_instance_feeds: true,
|
||||
koena_connect_link: true,
|
||||
extra_categories: [
|
||||
%{
|
||||
id: :something_else,
|
||||
label: "Quelque chose d'autre"
|
||||
}
|
||||
]
|
||||
|
||||
System.get_env("DOCKER", "false") == "false" && File.exists?("./config/e2e.secret.exs") ->
|
||||
import_config "e2e.secret.exs"
|
||||
|
||||
System.get_env("DOCKER", "false") == "true" ->
|
||||
Logger.info("Using environment configuration for Docker")
|
||||
|
||||
true ->
|
||||
Logger.error("No configuration file found")
|
||||
end
|
||||
config :mobilizon, Mobilizon.Storage.Repo,
|
||||
adapter: Ecto.Adapters.Postgres,
|
||||
username: System.get_env("MOBILIZON_DATABASE_USERNAME", "mobilizon_e2e"),
|
||||
password: System.get_env("MOBILIZON_DATABASE_PASSWORD", "mobilizon_e2e"),
|
||||
database: System.get_env("MOBILIZON_DATABASE_DBNAME", "mobilizon_e2e"),
|
||||
hostname: System.get_env("MOBILIZON_DATABASE_HOST", "localhost"),
|
||||
port: System.get_env("MOBILIZON_DATABASE_PORT") || "5432"
|
||||
|
|
|
@ -35,6 +35,7 @@ RUN source /root/.bashrc && \
|
|||
mix deps.get --only prod && \
|
||||
mix compile && \
|
||||
mix phx.digest.clean --all && \
|
||||
mix phx.digest && \
|
||||
mix release --path release/mobilizon && \
|
||||
cd release/mobilizon && \
|
||||
ln -s lib/mobilizon-*/priv priv && \
|
||||
|
|
|
@ -5,14 +5,12 @@ RUN apk add --no-cache python3 build-base libwebp-tools bash imagemagick ncurses
|
|||
WORKDIR /build
|
||||
COPY js .
|
||||
|
||||
ENV CYPRESS_INSTALL_BINARY 0
|
||||
|
||||
# Network timeout because it's slow when cross-compiling
|
||||
RUN yarn install --network-timeout 100000 \
|
||||
&& yarn run build
|
||||
|
||||
# Then, build the application binary
|
||||
FROM elixir:1.13-alpine AS builder
|
||||
FROM elixir:1.14-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache build-base git cmake
|
||||
|
||||
|
@ -30,8 +28,7 @@ COPY rel ./rel
|
|||
COPY support ./support
|
||||
COPY --from=assets ./priv/static ./priv/static
|
||||
|
||||
RUN mix phx.digest.clean --all \
|
||||
&& mix release
|
||||
RUN mix phx.digest.clean --all && mix phx.digest && mix release
|
||||
|
||||
# Finally setup the app
|
||||
FROM alpine
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
FROM elixir:latest
|
||||
LABEL maintainer="Thomas Citharel <tcit@tcit.fr>"
|
||||
|
||||
ENV REFRESHED_AT=2022-04-06
|
||||
ENV REFRESHED_AT=2022-09-20
|
||||
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 npm install -g yarn wait-on
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/* eslint-env node */
|
||||
require("@rushstack/eslint-patch/modern-module-resolution");
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
|
||||
|
@ -6,10 +9,11 @@ module.exports = {
|
|||
},
|
||||
|
||||
extends: [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended",
|
||||
"plugin:vue/vue3-essential",
|
||||
"@vue/eslint-config-typescript/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"@vue/eslint-config-prettier",
|
||||
],
|
||||
|
||||
plugins: ["prettier"],
|
||||
|
@ -20,12 +24,11 @@ module.exports = {
|
|||
},
|
||||
|
||||
rules: {
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-underscore-dangle": [
|
||||
"error",
|
||||
{
|
||||
allow: ["__typename"],
|
||||
allow: ["__typename", "__schema"],
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
|
@ -50,4 +53,7 @@ module.exports = {
|
|||
},
|
||||
|
||||
ignorePatterns: ["src/typings/*.d.ts", "vue.config.js"],
|
||||
globals: {
|
||||
GeolocationPositionError: true,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
node_modules
|
||||
/dist
|
||||
|
||||
/tests/e2e/videos/
|
||||
/tests/e2e/screenshots/
|
||||
/coverage
|
||||
stats.html
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
|
@ -23,3 +22,6 @@ yarn-error.log*
|
|||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
|
|
41
js/README.md
|
@ -1,41 +0,0 @@
|
|||
# mobilizon
|
||||
|
||||
## Project setup
|
||||
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Run your unit tests
|
||||
|
||||
```
|
||||
yarn test:unit
|
||||
```
|
||||
|
||||
### Run your end-to-end tests
|
||||
|
||||
```
|
||||
yarn test:e2e
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"],
|
||||
};
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"pluginsFile": "tests/e2e/plugins/index.js",
|
||||
"projectId": "86dpkx",
|
||||
"baseUrl": "http://localhost:4000",
|
||||
"viewportWidth": 1920,
|
||||
"viewportHeight": 1080
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/// <reference types="histoire/vue" />
|
||||
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_SERVER_URL: string;
|
||||
readonly VITE_HISTOIRE_ENV: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
const fetch = require("node-fetch");
|
||||
const fs = require("fs");
|
||||
import fetch from "node-fetch";
|
||||
import fs from "fs";
|
||||
|
||||
fetch(`http://localhost:4000/api`, {
|
||||
method: "POST",
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/// <reference types="@histoire/plugin-vue/components" />
|
||||
|
||||
import { defineConfig } from "histoire";
|
||||
import { HstVue } from "@histoire/plugin-vue";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [HstVue()],
|
||||
setupFile: path.resolve(__dirname, "./src/histoire.setup.ts"),
|
||||
viteNodeInlineDeps: [/date-fns/],
|
||||
tree: {
|
||||
groups: [
|
||||
{
|
||||
title: "Actors",
|
||||
include: (file) => /^src\/components\/Account/.test(file.path),
|
||||
},
|
||||
{
|
||||
title: "Address",
|
||||
include: (file) => /^src\/components\/Address/.test(file.path),
|
||||
},
|
||||
{
|
||||
title: "Comments",
|
||||
include: (file) => /^src\/components\/Comment/.test(file.path),
|
||||
},
|
||||
{
|
||||
title: "Discussion",
|
||||
include: (file) => /^src\/components\/Discussion/.test(file.path),
|
||||
},
|
||||
{
|
||||
title: "Events",
|
||||
include: (file) => /^src\/components\/Event/.test(file.path),
|
||||
},
|
||||
{
|
||||
title: "Groups",
|
||||
include: (file) => /^src\/components\/Group/.test(file.path),
|
||||
},
|
||||
{
|
||||
title: "Home",
|
||||
include: (file) => /^src\/components\/Home/.test(file.path),
|
||||
},
|
||||
{
|
||||
title: "Posts",
|
||||
include: (file) => /^src\/components\/Post/.test(file.path),
|
||||
},
|
||||
{
|
||||
title: "Others",
|
||||
include: () => true,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
module.exports = {
|
||||
preset: "@vue/cli-plugin-unit-jest/presets/typescript-and-babel",
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: [
|
||||
"**/*.{vue,ts}",
|
||||
"!**/node_modules/**",
|
||||
"!get_union_json.ts",
|
||||
],
|
||||
coverageReporters: ["html", "text", "text-summary"],
|
||||
reporters: ["default", "jest-junit"],
|
||||
// The following should fix the issue with svgs and ?inline loader (see Logo.vue), but doesn't work
|
||||
//
|
||||
// transform: {
|
||||
// "^.+\\.svg$": "<rootDir>/tests/unit/svgTransform.js",
|
||||
// },
|
||||
// moduleNameMapper: {
|
||||
// "^@/(.*svg)(\\?inline)$": "<rootDir>/src/$1",
|
||||
// "^@/(.*)$": "<rootDir>/src/$1",
|
||||
// },
|
||||
};
|
111
js/package.json
|
@ -1,23 +1,28 @@
|
|||
{
|
||||
"name": "mobilizon",
|
||||
"version": "2.1.0",
|
||||
"version": "3.0.0-beta.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"dev": "vite",
|
||||
"preview": "vite preview",
|
||||
"build": "yarn run build:assets && yarn run build:pictures",
|
||||
"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",
|
||||
"lint": "vue-cli-service lint",
|
||||
"build:assets": "vue-cli-service build --report",
|
||||
"build:pictures": "bash ./scripts/build/pictures.sh"
|
||||
"lint": "eslint --ext .ts,.vue --ignore-path .gitignore --fix src",
|
||||
"format": "prettier . --write",
|
||||
"build:assets": "vite build",
|
||||
"build:pictures": "bash ./scripts/build/pictures.sh",
|
||||
"story:dev": "histoire dev",
|
||||
"story:build": "histoire build",
|
||||
"story:preview": "histoire preview",
|
||||
"test": "vitest",
|
||||
"coverage": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@absinthe/socket": "^0.2.1",
|
||||
"@absinthe/socket-apollo-link": "^0.2.1",
|
||||
"@apollo/client": "^3.3.16",
|
||||
"@mdi/font": "^6.1.95",
|
||||
"@sentry/tracing": "^6.16.1",
|
||||
"@sentry/vue": "^6.16.1",
|
||||
"@oruga-ui/oruga-next": "^0.5.5",
|
||||
"@sentry/tracing": "^7.1",
|
||||
"@sentry/vue": "^7.1",
|
||||
"@tailwindcss/line-clamp": "^0.4.0",
|
||||
"@tiptap/core": "^2.0.0-beta.41",
|
||||
"@tiptap/extension-blockquote": "^2.0.0-beta.25",
|
||||
|
@ -39,24 +44,30 @@
|
|||
"@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/vue-2": "^2.0.0-beta.21",
|
||||
"@tiptap/suggestion": "^2.0.0-beta.195",
|
||||
"@tiptap/vue-3": "^2.0.0-beta.96",
|
||||
"@vue-a11y/announcer": "^2.1.0",
|
||||
"@vue-a11y/skip-to": "^2.1.2",
|
||||
"@vue/apollo-option": "4.0.0-alpha.11",
|
||||
"@vue-leaflet/vue-leaflet": "^0.6.1",
|
||||
"@vue/apollo-composable": "^4.0.0-beta.1",
|
||||
"@vue/compiler-sfc": "^3.2.37",
|
||||
"@vueuse/core": "^9.1.0",
|
||||
"@vueuse/head": "^0.9.6",
|
||||
"@vueuse/router": "^9.0.2",
|
||||
"apollo-absinthe-upload-link": "^1.5.0",
|
||||
"autoprefixer": "^10",
|
||||
"blurhash": "^1.1.3",
|
||||
"buefy": "^0.9.0",
|
||||
"bulma-divider": "^0.2.0",
|
||||
"core-js": "^3.6.4",
|
||||
"blurhash": "^2.0.0",
|
||||
"date-fns": "^2.16.0",
|
||||
"date-fns-tz": "^1.1.6",
|
||||
"graphql": "^16.0.0",
|
||||
"floating-vue": "^2.0.0-beta.17",
|
||||
"graphql": "^15.8.0",
|
||||
"graphql-tag": "^2.10.3",
|
||||
"hammerjs": "^2.0.8",
|
||||
"intersection-observer": "^0.12.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"leaflet": "^1.4.0",
|
||||
"leaflet.locatecontrol": "^0.76.0",
|
||||
"leaflet.markercluster": "^1.5.3",
|
||||
"lodash": "^4.17.11",
|
||||
"ngeohash": "^0.6.3",
|
||||
"p-debounce": "^4.0.0",
|
||||
|
@ -67,24 +78,26 @@
|
|||
"tailwindcss": "^3",
|
||||
"tippy.js": "^6.2.3",
|
||||
"unfetch": "^4.2.0",
|
||||
"v-tooltip": "^2.1.3",
|
||||
"vue": "^2.6.11",
|
||||
"vue-class-component": "^7.2.3",
|
||||
"vue-i18n": "^8.14.0",
|
||||
"vue": "^3.2.37",
|
||||
"vue-i18n": "9",
|
||||
"vue-material-design-icons": "^5.1.2",
|
||||
"vue-matomo": "^4.1.0",
|
||||
"vue-meta": "^2.3.1",
|
||||
"vue-plausible": "^1.3.1",
|
||||
"vue-property-decorator": "^9.0.0",
|
||||
"vue-router": "^3.1.6",
|
||||
"vue-router": "4",
|
||||
"vue-scrollto": "^2.17.1",
|
||||
"vue2-leaflet": "^2.0.3",
|
||||
"vuedraggable": "^2.24.3"
|
||||
"vue-use-route-query": "^1.1.0",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.1.0",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@histoire/plugin-vue": "^0.11.0",
|
||||
"@playwright/test": "^1.25.1",
|
||||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"@types/hammerjs": "^2.0.41",
|
||||
"@types/leaflet": "^1.5.2",
|
||||
"@types/leaflet.locatecontrol": "^0.74",
|
||||
"@types/leaflet.markercluster": "^1.5.1",
|
||||
"@types/lodash": "^4.14.141",
|
||||
"@types/ngeohash": "^0.6.2",
|
||||
"@types/phoenix": "^1.5.2",
|
||||
|
@ -93,37 +106,29 @@
|
|||
"@types/prosemirror-state": "^1.2.4",
|
||||
"@types/prosemirror-view": "^1.11.4",
|
||||
"@types/sanitize-html": "^2.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
||||
"@typescript-eslint/parser": "^5.3.0",
|
||||
"@vue/cli-plugin-babel": "~5.0.4",
|
||||
"@vue/cli-plugin-eslint": "~5.0.4",
|
||||
"@vue/cli-plugin-pwa": "~5.0.4",
|
||||
"@vue/cli-plugin-router": "~5.0.4",
|
||||
"@vue/cli-plugin-typescript": "~5.0.4",
|
||||
"@vue/cli-plugin-unit-jest": "~5.0.4",
|
||||
"@vue/cli-service": "~5.0.4",
|
||||
"@vue/eslint-config-typescript": "^10.0.0",
|
||||
"@vue/test-utils": "^1.1.0",
|
||||
"@vue/vue2-jest": "^27.0.0-alpha.3",
|
||||
"@vue/vue3-jest": "^27.0.0-alpha.1",
|
||||
"eslint": "^8.2.0",
|
||||
"@vitejs/plugin-vue": "^3.0.3",
|
||||
"@vitest/coverage-c8": "^0.24.1",
|
||||
"@vitest/ui": "^0.24.1",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.0",
|
||||
"@vue/test-utils": "^2.0.2",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"eslint-plugin-vue": "^9.3.0",
|
||||
"flush-promises": "^1.0.2",
|
||||
"jest": "^27.1.0",
|
||||
"jest-junit": "^13.0.0",
|
||||
"histoire": "^0.11.0",
|
||||
"jsdom": "^20.0.0",
|
||||
"mock-apollo-client": "^1.1.0",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier-eslint": "^14.0.0",
|
||||
"prettier-eslint": "^15.0.1",
|
||||
"rollup-plugin-visualizer": "^5.7.1",
|
||||
"sass": "^1.34.1",
|
||||
"sass-loader": "^12.0.0",
|
||||
"ts-jest": "27",
|
||||
"typescript": "~4.5.5",
|
||||
"vue-cli-plugin-tailwind": "~3.0.0",
|
||||
"vue-i18n-extract": "^2.0.4",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack-cli": "^4.7.0"
|
||||
"typescript": "~4.8.3",
|
||||
"vite": "^3.0.9",
|
||||
"vite-plugin-pwa": "^0.13.0",
|
||||
"vitest": "^0.24.1",
|
||||
"vue-i18n-extract": "^2.0.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
import type { PlaywrightTestConfig } from "@playwright/test";
|
||||
import { devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: "./tests/e2e",
|
||||
/* Maximum time one test can run for. */
|
||||
timeout: 10 * 1000,
|
||||
expect: {
|
||||
/**
|
||||
* Maximum time expect() should wait for the condition to be met.
|
||||
* For example in `await expect(locator).toHaveText();`
|
||||
*/
|
||||
timeout: 5000,
|
||||
},
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||
actionTimeout: 0,
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://localhost:4000",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "firefox",
|
||||
use: {
|
||||
...devices["Desktop Firefox"],
|
||||
},
|
||||
},
|
||||
|
||||
// {
|
||||
// name: 'webkit',
|
||||
// use: {
|
||||
// ...devices['Desktop Safari'],
|
||||
// },
|
||||
// },
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: {
|
||||
// ...devices['Pixel 5'],
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: {
|
||||
// ...devices['iPhone 12'],
|
||||
// },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: {
|
||||
// channel: 'msedge',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: {
|
||||
// channel: 'chrome',
|
||||
// },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
||||
// outputDir: 'test-results/',
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// port: 3000,
|
||||
// },
|
||||
};
|
||||
|
||||
export default config;
|
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 776 B |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 920 B After Width: | Height: | Size: 920 B |
After Width: | Height: | Size: 29 KiB |
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 96 105"
|
||||
version="1.1"
|
||||
xml:space="preserve"
|
||||
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"
|
||||
class="svg-logo-solid"
|
||||
id="svg32"
|
||||
sodipodi:docname="owncast.svg"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"><metadata
|
||||
id="metadata38"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs36" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview34"
|
||||
showgrid="false"
|
||||
inkscape:zoom="7.5619048"
|
||||
inkscape:cx="48"
|
||||
inkscape:cy="52.367758"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg32" />
|
||||
<g
|
||||
transform="matrix(1.04457,0,0,1.04457,-0.742448,-0.0626735)"
|
||||
id="g30"
|
||||
style="fill:#000000">
|
||||
<g
|
||||
id="g12"
|
||||
style="fill:#000000">
|
||||
<path
|
||||
d="M91.5,75.35C92.533,72.55 92.583,70 91.65,67.7C90.783,65.567 89.117,63.767 86.65,62.3C84.35,60.967 81.567,60 78.3,59.4C75.333,58.867 72.1,58.633 68.6,58.7C65.233,58.8 61.967,59.167 58.8,59.8C55.767,60.433 53.1,61.233 50.8,62.2C48.533,63.167 46.767,64.217 45.5,65.35C44.233,66.55 43.567,67.783 43.5,69.05C43.4,70.55 44.167,72.167 45.8,73.9C47.3,75.5 49.4,77.067 52.1,78.6C54.8,80.133 57.783,81.45 61.05,82.55C64.55,83.717 68,84.467 71.4,84.8C73.6,85 75.65,85.033 77.55,84.9C79.617,84.7 81.533,84.267 83.3,83.6C85.2,82.867 86.817,81.85 88.15,80.55C89.65,79.117 90.767,77.383 91.5,75.35M70.6,67.5C71.733,68.1 72.567,68.833 73.1,69.7C73.633,70.667 73.75,71.767 73.45,73C73.217,73.867 72.833,74.617 72.3,75.25C71.8,75.817 71.133,76.267 70.3,76.6C69.6,76.9 68.75,77.117 67.75,77.25C66.783,77.35 65.817,77.367 64.85,77.3C63.15,77.2 61.283,76.867 59.25,76.3C57.483,75.767 55.783,75.1 54.15,74.3C52.65,73.567 51.417,72.8 50.45,72C49.517,71.167 49.067,70.433 49.1,69.8C49.167,69.267 49.55,68.75 50.25,68.25C50.95,67.783 51.917,67.367 53.15,67C54.383,66.6 55.75,66.3 57.25,66.1C58.95,65.9 60.567,65.8 62.1,65.8C63.8,65.833 65.333,65.967 66.7,66.2C68.167,66.5 69.467,66.933 70.6,67.5Z"
|
||||
style="fill:#000000;fill-rule:nonzero"
|
||||
id="path10" />
|
||||
</g>
|
||||
<g
|
||||
id="g16"
|
||||
style="fill:#000000">
|
||||
<path
|
||||
d="M66.6,15.05C66.467,11.45 65.567,8.45 63.9,6.05C62.133,3.417 59.533,1.617 56.1,0.65C55.333,0.417 54.517,0.25 53.65,0.15C52.883,0.05 52.1,0.033 51.3,0.1C50.567,0.1 49.833,0.183 49.1,0.35C48.467,0.483 47.767,0.7 47,1C44.533,1.967 42.3,3.667 40.3,6.1C38.433,8.3 36.833,11.033 35.5,14.3C34.333,17.067 33.4,20.1 32.7,23.4C32.033,26.5 31.583,29.65 31.35,32.85C31.15,35.75 31.133,38.533 31.3,41.2C31.5,43.833 31.867,46.217 32.4,48.35C33.467,52.717 35.1,55.4 37.3,56.4C37.5,56.5 37.7,56.583 37.9,56.65L39.2,56.85C39.367,56.85 39.617,56.833 39.95,56.8C41.35,56.667 42.933,56.083 44.7,55.05C46.4,54.017 48.183,52.6 50.05,50.8C52.05,48.867 53.983,46.617 55.85,44.05C57.817,41.383 59.567,38.567 61.1,35.6C62.9,32.1 64.283,28.667 65.25,25.3C66.25,21.6 66.7,18.183 66.6,15.05M47.55,23.15C47.883,23.217 48.167,23.3 48.4,23.4C51.1,24.333 52.483,26.483 52.55,29.85C52.583,32.617 51.733,35.8 50,39.4C48.567,42.4 46.85,45.033 44.85,47.3C42.983,49.433 41.417,50.567 40.15,50.7L39.9,50.75L39.45,50.7L39.2,50.6C38.267,50.167 37.617,48.75 37.25,46.35C36.883,43.917 36.9,41.133 37.3,38C37.733,34.5 38.55,31.433 39.75,28.8C41.183,25.667 42.95,23.817 45.05,23.25C45.417,23.15 45.683,23.1 45.85,23.1C46.117,23.067 46.383,23.05 46.65,23.05C46.917,23.05 47.217,23.083 47.55,23.15Z"
|
||||
style="fill:#000000;fill-rule:nonzero"
|
||||
id="path14" />
|
||||
</g>
|
||||
<g
|
||||
id="g20"
|
||||
style="fill:#000000">
|
||||
<path
|
||||
d="M2.7,33.6C2.3,34.133 1.967,34.717 1.7,35.35C1.4,36.117 1.183,36.9 1.05,37.7C0.35,40.967 0.733,44.133 2.2,47.2C3.4,49.733 5.333,52.117 8,54.35C10.367,56.317 13.033,57.917 16,59.15C19,60.383 21.617,60.95 23.85,60.85C24.283,60.85 24.75,60.8 25.25,60.7C25.75,60.6 26.167,60.467 26.5,60.3C26.833,60.133 27.15,59.917 27.45,59.65C27.75,59.383 27.983,59.083 28.15,58.75C28.95,57.217 28.733,54.85 27.5,51.65C26.233,48.55 24.317,45.367 21.75,42.1C19.083,38.7 16.3,36.017 13.4,34.05C10.267,31.95 7.617,31.167 5.45,31.7C4.917,31.833 4.417,32.067 3.95,32.4C3.483,32.7 3.067,33.1 2.7,33.6M10.1,43.55C10.267,43.25 10.433,43.017 10.6,42.85C10.767,42.683 10.967,42.533 11.2,42.4C11.467,42.3 11.7,42.233 11.9,42.2C12.967,42 14.317,42.467 15.95,43.6C17.417,44.567 18.883,45.933 20.35,47.7C21.683,49.3 22.75,50.867 23.55,52.4C24.317,53.967 24.55,55.067 24.25,55.7C24.183,55.833 24.1,55.933 24,56C23.9,56.133 23.783,56.217 23.65,56.25C23.583,56.317 23.45,56.367 23.25,56.4L22.7,56.5C21.633,56.567 20.25,56.267 18.55,55.6C16.883,54.933 15.317,54.05 13.85,52.95C12.283,51.783 11.117,50.517 10.35,49.15C9.483,47.583 9.283,46.017 9.75,44.45C9.85,44.117 9.967,43.817 10.1,43.55Z"
|
||||
style="fill:#000000;fill-rule:nonzero"
|
||||
id="path18" />
|
||||
</g>
|
||||
<g
|
||||
id="g24"
|
||||
style="fill:#000000">
|
||||
<path
|
||||
d="M34.95,74.2L34.75,74.2C33.717,74.167 32.767,74.517 31.9,75.25C31.1,75.95 30.417,76.95 29.85,78.25C29.35,79.417 29,80.733 28.8,82.2C28.6,83.667 28.567,85.15 28.7,86.65C28.967,89.817 29.9,92.5 31.5,94.7C33.367,97.233 35.967,98.9 39.3,99.7L39.4,99.7L39.7,99.8L39.85,99.8C43.483,100.5 45.917,99.817 47.15,97.75C47.717,96.783 48,95.55 48,94.05C47.967,92.617 47.7,91.05 47.2,89.35C46.7,87.617 46,85.883 45.1,84.15C44.2,82.383 43.183,80.783 42.05,79.35C40.85,77.85 39.65,76.65 38.45,75.75C37.183,74.817 36.017,74.3 34.95,74.2M33.55,80.4C34.083,78.933 34.767,78.233 35.6,78.3L35.65,78.3C36.483,78.4 37.467,79.267 38.6,80.9C39.733,82.533 40.583,84.25 41.15,86.05C41.783,88.017 41.917,89.583 41.55,90.75C41.117,91.983 40.05,92.483 38.35,92.25L38.3,92.25L38.25,92.2L38.1,92.2C36.433,91.867 35.15,91 34.25,89.6C33.483,88.333 33.05,86.8 32.95,85C32.85,83.233 33.05,81.7 33.55,80.4Z"
|
||||
style="fill:#000000;fill-rule:nonzero"
|
||||
id="path22" />
|
||||
</g>
|
||||
<g
|
||||
id="g28"
|
||||
style="fill:#000000">
|
||||
<path
|
||||
d="M22.7,69.65C22.4,69.417 22.033,69.217 21.6,69.05C21.167,68.883 20.717,68.767 20.25,68.7C19.817,68.6 19.35,68.533 18.85,68.5C17.417,68.467 16.017,68.683 14.65,69.15C13.317,69.583 12.233,70.233 11.4,71.1C10.567,72.033 10.167,73.067 10.2,74.2C10.233,75.433 10.817,76.767 11.95,78.2C12.25,78.567 12.617,78.967 13.05,79.4C13.383,79.733 13.767,80.033 14.2,80.3C14.533,80.5 14.9,80.683 15.3,80.85C15.767,81.017 16.133,81.1 16.4,81.1C17.6,81.267 18.767,81.017 19.9,80.35C21,79.717 21.95,78.817 22.75,77.65C23.583,76.45 24.1,75.217 24.3,73.95C24.5,72.55 24.25,71.4 23.55,70.5C23.283,70.167 23,69.883 22.7,69.65M21.7,71.7C22,72.1 22.067,72.633 21.9,73.3C21.767,73.933 21.467,74.583 21,75.25C20.533,75.883 20,76.383 19.4,76.75C18.767,77.15 18.15,77.317 17.55,77.25L17,77.15C16.8,77.083 16.617,76.983 16.45,76.85C16.317,76.783 16.133,76.65 15.9,76.45C15.767,76.317 15.6,76.133 15.4,75.9C14.8,75.133 14.567,74.433 14.7,73.8C14.767,73.233 15.117,72.733 15.75,72.3C16.317,71.9 17,71.6 17.8,71.4C18.6,71.2 19.367,71.117 20.1,71.15L20.65,71.2L21.1,71.3C21.233,71.367 21.35,71.433 21.45,71.5L21.7,71.7Z"
|
||||
style="fill:#000000;fill-rule:nonzero"
|
||||
id="path26" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 725 KiB |
After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 379 KiB |
After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 359 KiB |
After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 376 KiB |
After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 358 KiB |
After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 518 KiB |
After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 193 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 193 KiB |
Before Width: | Height: | Size: 1.8 MiB |
After Width: | Height: | Size: 317 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 1.5 MiB |
After Width: | Height: | Size: 222 KiB |
Before Width: | Height: | Size: 133 KiB |
After Width: | Height: | Size: 21 KiB |
|
@ -0,0 +1,10 @@
|
|||
<!--?xml version="1.0" standalone="no"?-->
|
||||
<svg id="sw-js-blob-svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<defs>
|
||||
<linearGradient id="sw-gradient" x1="0" x2="1" y1="1" y2="0">
|
||||
<stop id="stop1" stop-color="rgba(255, 231.287, 78.545, 0.3)" offset="0%"></stop>
|
||||
<stop id="stop2" stop-color="rgba(254.848, 165.324, 149.009, 0.25)" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#sw-gradient)" d="M18.2,-13.5C23.5,-7.8,27.8,-0.2,27.7,8.7C27.5,17.6,22.9,27.8,14.1,33.9C5.3,39.9,-7.8,41.6,-17.7,36.8C-27.6,32,-34.2,20.7,-37.1,8.4C-39.9,-3.9,-39,-17.2,-32.2,-23.2C-25.4,-29.3,-12.7,-28.3,-3.1,-25.8C6.4,-23.3,12.8,-19.3,18.2,-13.5Z" width="100%" height="100%" transform="translate(50 50)" style="transition: all 0.3s ease 0s;" stroke-width="0" stroke="url(#sw-gradient)"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1015 B |
|
@ -0,0 +1,10 @@
|
|||
<!--?xml version="1.0" standalone="no"?-->
|
||||
<svg id="sw-js-blob-svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<defs>
|
||||
<linearGradient id="sw-gradient" x1="0" x2="1" y1="1" y2="0">
|
||||
<stop id="stop1" stop-color="rgba(181.058, 255, 167.816, 0.2)" offset="0%"></stop>
|
||||
<stop id="stop2" stop-color="rgba(149.009, 254.848, 251.263, 0.25)" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#sw-gradient)" d="M20.2,-14.3C28.2,-6,38.3,2.5,37.6,9.8C36.9,17.1,25.5,23.1,15.5,25.2C5.6,27.3,-2.9,25.4,-11.2,21.9C-19.6,18.4,-27.9,13.3,-30.8,5.6C-33.7,-2.1,-31.2,-12.5,-25.2,-20.4C-19.1,-28.3,-9.6,-33.7,-1.8,-32.3C6.1,-30.9,12.1,-22.7,20.2,-14.3Z" width="100%" height="100%" transform="translate(50 50)" style="transition: all 0.3s ease 0s;" stroke-width="0" stroke="url(#sw-gradient)"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1016 B |
|
@ -0,0 +1,10 @@
|
|||
<!--?xml version="1.0" standalone="no"?-->
|
||||
<svg id="sw-js-blob-svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<defs>
|
||||
<linearGradient id="sw-gradient" x1="0" x2="1" y1="1" y2="0">
|
||||
<stop id="stop1" stop-color="rgba(172.198, 167.816, 255, 0.2)" offset="0%"></stop>
|
||||
<stop id="stop2" stop-color="rgba(236.8, 149.009, 254.848, 0.25)" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="url(#sw-gradient)" d="M25.3,-21.5C29.4,-15.2,26.8,-4.8,23.6,3.8C20.4,12.5,16.5,19.4,10.2,23.2C3.9,27,-4.8,27.6,-12.6,24.5C-20.3,21.4,-27,14.6,-30.1,5.6C-33.2,-3.4,-32.6,-14.4,-26.9,-21.1C-21.3,-27.8,-10.7,-30.1,0,-30.1C10.7,-30.1,21.3,-27.8,25.3,-21.5Z" width="100%" height="100%" transform="translate(50 50)" style="transition: all 0.3s ease 0s;" stroke-width="0" stroke="url(#sw-gradient)"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1013 B |
|
@ -1,22 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" dir="auto">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
|
||||
<meta name="server-injected-data" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong
|
||||
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
|
||||
properly without JavaScript enabled. Please enable it to
|
||||
continue.</strong
|
||||
>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
|
@ -30,11 +30,6 @@ convert_image () {
|
|||
convert -geometry "$resolution"x $file $output
|
||||
}
|
||||
|
||||
produce_webp () {
|
||||
name=$(file_name)
|
||||
output="$output_dir/$name.webp"
|
||||
cwebp $file -quiet -o $output
|
||||
}
|
||||
|
||||
progress() {
|
||||
local w=80 p=$1; shift
|
||||
|
@ -68,23 +63,3 @@ do
|
|||
fi
|
||||
done
|
||||
echo -e "\nDone!"
|
||||
|
||||
echo "Generating optimized versions of the pictures…"
|
||||
|
||||
if ! command -v cwebp &> /dev/null
|
||||
then
|
||||
echo "$(tput setaf 1)ERROR: The cwebp command could not be found. You need to install webp.$(tput sgr 0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
nb_files=$( shopt -s nullglob ; set -- $output_dir/* ; echo $#)
|
||||
i=1
|
||||
for file in $output_dir/*
|
||||
do
|
||||
if [[ -f $file ]]; then
|
||||
produce_webp
|
||||
progress $(($i*100/$nb_files)) still working...
|
||||
i=$((i+1))
|
||||
fi
|
||||
done
|
||||
echo -e "\nDone!"
|
482
js/src/App.vue
|
@ -1,267 +1,297 @@
|
|||
<template>
|
||||
<div id="mobilizon">
|
||||
<VueAnnouncer />
|
||||
<VueSkipTo to="#main" :label="$t('Skip to main content')" />
|
||||
<!-- <VueAnnouncer />
|
||||
<VueSkipTo to="#main" :label="t('Skip to main content')" /> -->
|
||||
<NavBar />
|
||||
<div v-if="config && config.demoMode">
|
||||
<b-message
|
||||
class="container"
|
||||
type="is-danger"
|
||||
:title="$t('Warning').toLocaleUpperCase()"
|
||||
<div v-if="isDemoMode">
|
||||
<o-notification
|
||||
class="container mx-auto"
|
||||
variant="danger"
|
||||
:title="t('Warning').toLocaleUpperCase()"
|
||||
closable
|
||||
:aria-close-label="$t('Close')"
|
||||
:aria-close-label="t('Close')"
|
||||
>
|
||||
<p>
|
||||
{{ $t("This is a demonstration site to test Mobilizon.") }}
|
||||
<b>{{ $t("Please do not use it in any real way.") }}</b>
|
||||
{{ t("This is a demonstration site to test Mobilizon.") }}
|
||||
<b>{{ t("Please do not use it in any real way.") }}</b>
|
||||
{{
|
||||
$t(
|
||||
t(
|
||||
"This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone)."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</b-message>
|
||||
</o-notification>
|
||||
</div>
|
||||
<error v-if="error" :error="error" />
|
||||
<ErrorComponent v-if="error" :error="error" />
|
||||
|
||||
<main id="main" v-else>
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view ref="routerView" />
|
||||
</transition>
|
||||
<main id="main" class="pt-4" v-else>
|
||||
<router-view></router-view>
|
||||
</main>
|
||||
<mobilizon-footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Vue, Watch } from "vue-property-decorator";
|
||||
import NavBar from "./components/NavBar.vue";
|
||||
<script lang="ts" setup>
|
||||
import NavBar from "@/components/NavBar.vue";
|
||||
import {
|
||||
AUTH_ACCESS_TOKEN,
|
||||
AUTH_USER_EMAIL,
|
||||
AUTH_USER_ID,
|
||||
AUTH_USER_ROLE,
|
||||
} from "./constants";
|
||||
import {
|
||||
CURRENT_USER_CLIENT,
|
||||
UPDATE_CURRENT_USER_CLIENT,
|
||||
} from "./graphql/user";
|
||||
import Footer from "./components/Footer.vue";
|
||||
import Logo from "./components/Logo.vue";
|
||||
import { initializeCurrentActor } from "./utils/auth";
|
||||
import { CONFIG } from "./graphql/config";
|
||||
import { IConfig } from "./types/config.model";
|
||||
import { ICurrentUser } from "./types/current-user.model";
|
||||
} from "@/constants";
|
||||
import { UPDATE_CURRENT_USER_CLIENT } from "@/graphql/user";
|
||||
import MobilizonFooter from "@/components/PageFooter.vue";
|
||||
import jwt_decode, { JwtPayload } from "jwt-decode";
|
||||
import { refreshAccessToken } from "./apollo/utils";
|
||||
import { Route } from "vue-router";
|
||||
import { refreshAccessToken } from "@/apollo/utils";
|
||||
import {
|
||||
reactive,
|
||||
ref,
|
||||
provide,
|
||||
onUnmounted,
|
||||
onMounted,
|
||||
onBeforeMount,
|
||||
inject,
|
||||
defineAsyncComponent,
|
||||
computed,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { LocationType } from "@/types/user-location.model";
|
||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||
import {
|
||||
initializeCurrentActor,
|
||||
NoIdentitiesException,
|
||||
} from "@/utils/identity";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Snackbar } from "@/plugins/snackbar";
|
||||
import { Notifier } from "@/plugins/notifier";
|
||||
import { CONFIG } from "@/graphql/config";
|
||||
import { IConfig } from "@/types/config.model";
|
||||
import { useRouter } from "vue-router";
|
||||
import RouteName from "@/router/name";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
currentUser: CURRENT_USER_CLIENT,
|
||||
config: CONFIG,
|
||||
},
|
||||
components: {
|
||||
Logo,
|
||||
NavBar,
|
||||
error: () =>
|
||||
import(/* webpackChunkName: "editor" */ "./components/Error.vue"),
|
||||
"mobilizon-footer": Footer,
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
titleTemplate: "%s | Mobilizon",
|
||||
const { result: configResult } = useQuery<{ config: IConfig }>(
|
||||
CONFIG,
|
||||
undefined,
|
||||
{ fetchPolicy: "cache-only" }
|
||||
);
|
||||
|
||||
const config = computed(() => configResult.value?.config);
|
||||
|
||||
const ErrorComponent = defineAsyncComponent(
|
||||
() => import("@/components/ErrorComponent.vue")
|
||||
);
|
||||
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
|
||||
const location = computed(() => config.value?.location);
|
||||
|
||||
const userLocation = reactive<LocationType>({
|
||||
lon: undefined,
|
||||
lat: undefined,
|
||||
name: undefined,
|
||||
picture: undefined,
|
||||
isIPLocation: true,
|
||||
accuracy: 100,
|
||||
});
|
||||
|
||||
const updateUserLocation = (newLocation: LocationType) => {
|
||||
userLocation.lat = newLocation.lat;
|
||||
userLocation.lon = newLocation.lon;
|
||||
userLocation.name = newLocation.name;
|
||||
userLocation.picture = newLocation.picture;
|
||||
userLocation.isIPLocation = newLocation.isIPLocation;
|
||||
userLocation.accuracy = newLocation.accuracy;
|
||||
};
|
||||
|
||||
updateUserLocation({
|
||||
lat: location.value?.latitude,
|
||||
lon: location.value?.longitude,
|
||||
name: "", // config.ipLocation.country.name,
|
||||
isIPLocation: true,
|
||||
accuracy: 150, // config.ipLocation.location.accuracy_radius * 1.5 || 150,
|
||||
});
|
||||
|
||||
provide("userLocation", {
|
||||
userLocation,
|
||||
updateUserLocation,
|
||||
});
|
||||
|
||||
// const routerView = ref("routerView");
|
||||
const error = ref<Error | null>(null);
|
||||
const online = ref(true);
|
||||
const interval = ref<number>(0);
|
||||
|
||||
const notifier = inject<Notifier>("notifier");
|
||||
|
||||
interval.value = setInterval(async () => {
|
||||
const accessToken = localStorage.getItem(AUTH_ACCESS_TOKEN);
|
||||
if (accessToken) {
|
||||
const token = jwt_decode<JwtPayload>(accessToken);
|
||||
if (
|
||||
token?.exp !== undefined &&
|
||||
new Date(token.exp * 1000 - 60000) < new Date()
|
||||
) {
|
||||
refreshAccessToken();
|
||||
}
|
||||
}
|
||||
}, 60000) as unknown as number;
|
||||
|
||||
onBeforeMount(async () => {
|
||||
if (initializeCurrentUser()) {
|
||||
try {
|
||||
await initializeCurrentActor();
|
||||
} catch (err) {
|
||||
if (err instanceof NoIdentitiesException) {
|
||||
await router.push({
|
||||
name: RouteName.REGISTER_PROFILE,
|
||||
params: {
|
||||
email: localStorage.getItem(AUTH_USER_EMAIL),
|
||||
userAlreadyActivated: "true",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const snackbar = inject<Snackbar>("snackbar");
|
||||
|
||||
onMounted(() => {
|
||||
online.value = window.navigator.onLine;
|
||||
window.addEventListener("offline", () => {
|
||||
online.value = false;
|
||||
showOfflineNetworkWarning();
|
||||
console.debug("offline");
|
||||
});
|
||||
window.addEventListener("online", () => {
|
||||
online.value = true;
|
||||
console.debug("online");
|
||||
});
|
||||
document.addEventListener("refreshApp", (event: Event) => {
|
||||
snackbar?.open({
|
||||
queue: false,
|
||||
indefinite: true,
|
||||
variant: "dark",
|
||||
actionText: t("Update app"),
|
||||
cancelText: t("Ignore"),
|
||||
message: t("A new version is available."),
|
||||
onAction: async () => {
|
||||
const registration = event.detail as ServiceWorkerRegistration;
|
||||
try {
|
||||
await refreshApp(registration);
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
notifier?.error(t("An error has occured while refreshing the page."));
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(interval.value);
|
||||
interval.value = 0;
|
||||
});
|
||||
|
||||
const { mutate: updateCurrentUser } = useMutation(UPDATE_CURRENT_USER_CLIENT);
|
||||
|
||||
const initializeCurrentUser = () => {
|
||||
const userId = localStorage.getItem(AUTH_USER_ID);
|
||||
const userEmail = localStorage.getItem(AUTH_USER_EMAIL);
|
||||
const accessToken = localStorage.getItem(AUTH_ACCESS_TOKEN);
|
||||
const role = localStorage.getItem(AUTH_USER_ROLE);
|
||||
|
||||
if (userId && userEmail && accessToken && role) {
|
||||
updateCurrentUser({
|
||||
id: userId,
|
||||
email: userEmail,
|
||||
isLoggedIn: true,
|
||||
role,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const refreshApp = async (
|
||||
registration: ServiceWorkerRegistration
|
||||
): Promise<any> => {
|
||||
const worker = registration.waiting;
|
||||
if (!worker) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const channel = new MessageChannel();
|
||||
|
||||
channel.port1.onmessage = (event) => {
|
||||
if (event.data.error) {
|
||||
reject(event.data);
|
||||
} else {
|
||||
resolve(event.data);
|
||||
}
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class App extends Vue {
|
||||
config!: IConfig;
|
||||
worker?.postMessage({ type: "skip-waiting" }, [channel.port2]);
|
||||
});
|
||||
};
|
||||
|
||||
currentUser!: ICurrentUser;
|
||||
const showOfflineNetworkWarning = (): void => {
|
||||
notifier?.error(t("You are offline"));
|
||||
};
|
||||
// const extractPageTitleFromRoute = (routeWatched: RouteLocation): string => {
|
||||
// if (routeWatched.meta?.announcer?.message) {
|
||||
// return routeWatched.meta?.announcer?.message();
|
||||
// }
|
||||
// return document.title;
|
||||
// };
|
||||
|
||||
error: Error | null = null;
|
||||
// watch(route, (routeWatched) => {
|
||||
// const pageTitle = extractPageTitleFromRoute(routeWatched);
|
||||
// if (pageTitle) {
|
||||
// // this.$announcer.polite(
|
||||
// // t("Navigated to {pageTitle}", {
|
||||
// // pageTitle,
|
||||
// // }) as string
|
||||
// // );
|
||||
// }
|
||||
// // Set the focus to the router view
|
||||
// // https://marcus.io/blog/accessible-routing-vuejs
|
||||
// setTimeout(() => {
|
||||
// const focusTarget = (
|
||||
// routerView.value?.$refs?.componentFocusTarget !== undefined
|
||||
// ? routerView.value?.$refs?.componentFocusTarget
|
||||
// : routerView.value?.$el
|
||||
// ) as HTMLElement;
|
||||
// if (focusTarget && focusTarget instanceof Element) {
|
||||
// // Make focustarget programmatically focussable
|
||||
// focusTarget.setAttribute("tabindex", "-1");
|
||||
|
||||
online = true;
|
||||
// // Focus element
|
||||
// focusTarget.focus();
|
||||
|
||||
interval: number | undefined = undefined;
|
||||
// // Remove tabindex from focustarget.
|
||||
// // Reason: https://axesslab.com/skip-links/#update-3-a-comment-from-gov-uk
|
||||
// focusTarget.removeAttribute("tabindex");
|
||||
// }
|
||||
// }, 0);
|
||||
// });
|
||||
|
||||
@Ref("routerView") routerView!: Vue;
|
||||
const router = useRouter();
|
||||
|
||||
async created(): Promise<void> {
|
||||
if (await this.initializeCurrentUser()) {
|
||||
await initializeCurrentActor(this.$apollo.provider.defaultClient);
|
||||
}
|
||||
}
|
||||
|
||||
errorCaptured(error: Error): void {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
private async initializeCurrentUser() {
|
||||
const userId = localStorage.getItem(AUTH_USER_ID);
|
||||
const userEmail = localStorage.getItem(AUTH_USER_EMAIL);
|
||||
const accessToken = localStorage.getItem(AUTH_ACCESS_TOKEN);
|
||||
const role = localStorage.getItem(AUTH_USER_ROLE);
|
||||
|
||||
if (userId && userEmail && accessToken && role) {
|
||||
return this.$apollo.mutate({
|
||||
mutation: UPDATE_CURRENT_USER_CLIENT,
|
||||
variables: {
|
||||
id: userId,
|
||||
email: userEmail,
|
||||
isLoggedIn: true,
|
||||
role,
|
||||
},
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
mounted(): void {
|
||||
this.online = window.navigator.onLine;
|
||||
window.addEventListener("offline", () => {
|
||||
this.online = false;
|
||||
this.showOfflineNetworkWarning();
|
||||
console.debug("offline");
|
||||
});
|
||||
window.addEventListener("online", () => {
|
||||
this.online = true;
|
||||
console.debug("online");
|
||||
});
|
||||
document.addEventListener("refreshApp", (event: Event) => {
|
||||
this.$buefy.snackbar.open({
|
||||
queue: false,
|
||||
indefinite: true,
|
||||
type: "is-secondary",
|
||||
actionText: this.$t("Update app") as string,
|
||||
cancelText: this.$t("Ignore") as string,
|
||||
message: this.$t("A new version is available.") as string,
|
||||
onAction: async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const detail = event.detail;
|
||||
const registration = detail as ServiceWorkerRegistration;
|
||||
try {
|
||||
await this.refreshApp(registration);
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.$notifier.error(
|
||||
this.$t(
|
||||
"An error has occured while refreshing the page."
|
||||
) as string
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
this.interval = setInterval(async () => {
|
||||
const accessToken = localStorage.getItem(AUTH_ACCESS_TOKEN);
|
||||
if (accessToken) {
|
||||
const token = jwt_decode<JwtPayload>(accessToken);
|
||||
if (
|
||||
token?.exp !== undefined &&
|
||||
new Date(token.exp * 1000 - 60000) < new Date()
|
||||
) {
|
||||
refreshAccessToken(this.$apollo.getClient());
|
||||
}
|
||||
}
|
||||
}, 60000);
|
||||
}
|
||||
|
||||
private async refreshApp(
|
||||
registration: ServiceWorkerRegistration
|
||||
): Promise<any> {
|
||||
const worker = registration.waiting;
|
||||
if (!worker) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
console.debug("Doing worker.skipWaiting().");
|
||||
return new Promise((resolve, reject) => {
|
||||
const channel = new MessageChannel();
|
||||
|
||||
channel.port1.onmessage = (event) => {
|
||||
console.debug("Done worker.skipWaiting().");
|
||||
if (event.data.error) {
|
||||
reject(event.data);
|
||||
} else {
|
||||
resolve(event.data);
|
||||
}
|
||||
};
|
||||
console.debug("calling skip waiting");
|
||||
worker?.postMessage({ type: "skip-waiting" }, [channel.port2]);
|
||||
watch(config, async (configWatched: IConfig | undefined) => {
|
||||
if (configWatched) {
|
||||
const { statistics } = await import("@/services/statistics");
|
||||
statistics(configWatched?.analytics, {
|
||||
router,
|
||||
version: configWatched.version,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
showOfflineNetworkWarning(): void {
|
||||
this.$notifier.error(this.$t("You are offline") as string);
|
||||
}
|
||||
|
||||
unmounted(): void {
|
||||
clearInterval(this.interval);
|
||||
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 })
|
||||
updateAnnouncement(route: Route): void {
|
||||
const pageTitle = this.extractPageTitleFromRoute(route);
|
||||
if (pageTitle) {
|
||||
this.$announcer.polite(
|
||||
this.$t("Navigated to {pageTitle}", {
|
||||
pageTitle,
|
||||
}) as string
|
||||
);
|
||||
}
|
||||
// Set the focus to the router view
|
||||
// https://marcus.io/blog/accessible-routing-vuejs
|
||||
setTimeout(() => {
|
||||
const focusTarget = (
|
||||
this.routerView?.$refs?.componentFocusTarget !== undefined
|
||||
? this.routerView?.$refs?.componentFocusTarget
|
||||
: this.routerView?.$el
|
||||
) as HTMLElement;
|
||||
if (focusTarget && focusTarget instanceof Element) {
|
||||
// Make focustarget programmatically focussable
|
||||
focusTarget.setAttribute("tabindex", "-1");
|
||||
|
||||
// Focus element
|
||||
focusTarget.focus();
|
||||
|
||||
// Remove tabindex from focustarget.
|
||||
// Reason: https://axesslab.com/skip-links/#update-3-a-comment-from-gov-uk
|
||||
focusTarget.removeAttribute("tabindex");
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
extractPageTitleFromRoute(route: Route): string {
|
||||
if (route.meta?.announcer?.message) {
|
||||
return route.meta?.announcer?.message();
|
||||
}
|
||||
return document.title;
|
||||
}
|
||||
}
|
||||
const isDemoMode = computed(() => config.value?.demoMode);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "variables";
|
||||
|
||||
/* Icons */
|
||||
$mdi-font-path: "~@mdi/font/fonts";
|
||||
@import "~@mdi/font/scss/materialdesignicons";
|
||||
@import "common";
|
||||
|
||||
#mobilizon {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
|
|
|
@ -14,7 +14,8 @@ export const MOBILIZON_INSTANCE_HOST = window.location.hostname;
|
|||
*
|
||||
* Example: https://framameet.org
|
||||
*/
|
||||
export const GRAPHQL_API_ENDPOINT = window.location.origin;
|
||||
export const GRAPHQL_API_ENDPOINT =
|
||||
import.meta.env.VITE_SERVER_URL ?? window.location.origin;
|
||||
|
||||
/**
|
||||
* URL with path on which the API is. Replaces GRAPHQL_API_ENDPOINT if used
|
||||
|
@ -23,4 +24,4 @@ export const GRAPHQL_API_ENDPOINT = window.location.origin;
|
|||
*
|
||||
* Example: https://framameet.org/api
|
||||
*/
|
||||
export const GRAPHQL_API_FULL_PATH = `${window.location.origin}/api`;
|
||||
export const GRAPHQL_API_FULL_PATH = `${GRAPHQL_API_ENDPOINT}/api`;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { Socket as PhoenixSocket } from "phoenix";
|
||||
import { create } from "@absinthe/socket";
|
||||
import { createAbsintheSocketLink } from "@absinthe/socket-apollo-link";
|
||||
import { AUTH_ACCESS_TOKEN } from "@/constants";
|
||||
import { GRAPHQL_API_ENDPOINT } from "@/api/_entrypoint";
|
||||
|
||||
const httpServer = GRAPHQL_API_ENDPOINT || "http://localhost:4000";
|
||||
|
||||
const webSocketPrefix = import.meta.env.PROD ? "wss" : "ws";
|
||||
const wsEndpoint = `${webSocketPrefix}${httpServer.substring(
|
||||
httpServer.indexOf(":")
|
||||
)}/graphql_socket`;
|
||||
|
||||
const phoenixSocket = new PhoenixSocket(wsEndpoint, {
|
||||
params: () => {
|
||||
const token = localStorage.getItem(AUTH_ACCESS_TOKEN);
|
||||
if (token) {
|
||||
return { token };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
});
|
||||
|
||||
const absintheSocket = create(phoenixSocket);
|
||||
export default createAbsintheSocketLink(absintheSocket);
|
|
@ -0,0 +1,20 @@
|
|||
import fetch from "unfetch";
|
||||
import { createLink } from "apollo-absinthe-upload-link";
|
||||
import { GRAPHQL_API_ENDPOINT, GRAPHQL_API_FULL_PATH } from "@/api/_entrypoint";
|
||||
|
||||
// Endpoints
|
||||
const httpServer = GRAPHQL_API_ENDPOINT || "http://localhost:4000";
|
||||
const httpEndpoint = GRAPHQL_API_FULL_PATH || `${httpServer}/api`;
|
||||
|
||||
const customFetch = async (uri: string, options: any) => {
|
||||
const response = await fetch(uri, options);
|
||||
if (response.status >= 400) {
|
||||
return Promise.reject(response.status);
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
export const uploadLink = createLink({
|
||||
uri: httpEndpoint,
|
||||
fetch: customFetch,
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
import { AUTH_ACCESS_TOKEN } from "@/constants";
|
||||
import { ApolloLink } from "@apollo/client/core";
|
||||
|
||||
export function generateTokenHeader() {
|
||||
const token = localStorage.getItem(AUTH_ACCESS_TOKEN);
|
||||
|
||||
return token ? `Bearer ${token}` : null;
|
||||
}
|
||||
|
||||
const authMiddleware = new ApolloLink((operation, forward) => {
|
||||
// add the authorization to the headers
|
||||
operation.setContext({
|
||||
headers: {
|
||||
authorization: generateTokenHeader(),
|
||||
},
|
||||
});
|
||||
|
||||
if (forward) return forward(operation);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
export { authMiddleware };
|
|
@ -0,0 +1,101 @@
|
|||
import { logout } from "@/utils/auth";
|
||||
import { onError } from "@apollo/client/link/error";
|
||||
import { fromPromise } from "@apollo/client/core";
|
||||
import { refreshAccessToken } from "./utils";
|
||||
import { GraphQLError } from "graphql";
|
||||
import { generateTokenHeader } from "./auth";
|
||||
|
||||
let isRefreshing = false;
|
||||
let pendingRequests: any[] = [];
|
||||
|
||||
const resolvePendingRequests = () => {
|
||||
pendingRequests.map((callback) => callback());
|
||||
pendingRequests = [];
|
||||
};
|
||||
|
||||
const isAuthError = (graphQLError: GraphQLError | undefined) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return graphQLError && [403, 401].includes(graphQLError.status_code);
|
||||
};
|
||||
|
||||
const errorLink = onError(
|
||||
({ graphQLErrors, networkError, forward, operation }) => {
|
||||
console.debug("We have an apollo error", [graphQLErrors, networkError]);
|
||||
if (
|
||||
graphQLErrors?.some((graphQLError) => isAuthError(graphQLError)) ||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
networkError === 401
|
||||
) {
|
||||
console.debug("It's a authorization error (statusCode 401)");
|
||||
let forwardOperation;
|
||||
|
||||
if (!isRefreshing) {
|
||||
console.debug("Setting isRefreshing to true");
|
||||
isRefreshing = true;
|
||||
|
||||
forwardOperation = fromPromise(
|
||||
refreshAccessToken()
|
||||
.then((res) => {
|
||||
if (res !== true) {
|
||||
// failed to refresh the token
|
||||
throw "Failed to refresh the token";
|
||||
}
|
||||
resolvePendingRequests();
|
||||
|
||||
const context = operation.getContext();
|
||||
const oldHeaders = context.headers;
|
||||
|
||||
operation.setContext({
|
||||
headers: {
|
||||
...oldHeaders,
|
||||
authorization: generateTokenHeader(),
|
||||
},
|
||||
});
|
||||
return true;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.debug("Something failed, let's logout", e);
|
||||
pendingRequests = [];
|
||||
// don't perform a logout since we don't have any working access/refresh tokens
|
||||
logout(false);
|
||||
return;
|
||||
})
|
||||
.finally(() => {
|
||||
isRefreshing = false;
|
||||
})
|
||||
).filter((value) => Boolean(value));
|
||||
} else {
|
||||
forwardOperation = fromPromise(
|
||||
new Promise((resolve) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
pendingRequests.push(() => resolve());
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return forwardOperation.flatMap(() => forward(operation));
|
||||
}
|
||||
|
||||
if (graphQLErrors) {
|
||||
graphQLErrors.map(
|
||||
(graphQLError: GraphQLError & { status_code?: number }) => {
|
||||
if (graphQLError?.status_code !== 401) {
|
||||
console.debug(
|
||||
`[GraphQL error]: Message: ${graphQLError.message}, Location: ${graphQLError.locations}, Path: ${graphQLError.path}`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (networkError) {
|
||||
console.error(`[Network error]: ${networkError}`);
|
||||
console.debug(JSON.stringify(networkError));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default errorLink;
|
|
@ -0,0 +1,40 @@
|
|||
import { split } from "@apollo/client/core";
|
||||
import { RetryLink } from "@apollo/client/link/retry";
|
||||
import { getMainDefinition } from "@apollo/client/utilities";
|
||||
import absintheSocketLink from "./absinthe-socket-link";
|
||||
import { authMiddleware } from "./auth";
|
||||
import errorLink from "./error-link";
|
||||
import { uploadLink } from "./absinthe-upload-socket-link";
|
||||
|
||||
let link;
|
||||
|
||||
// The Absinthe socket Apollo link relies on an old library
|
||||
// (@jumpn/utils-composite) which itself relies on an old
|
||||
// Babel version, which is incompatible with Histoire.
|
||||
// We just don't use the absinthe apollo socket link
|
||||
// in this case.
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (!import.meta.env.VITE_HISTOIRE_ENV) {
|
||||
// const absintheSocketLink = await import("./absinthe-socket-link");
|
||||
|
||||
link = split(
|
||||
// split based on operation type
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query);
|
||||
return (
|
||||
definition.kind === "OperationDefinition" &&
|
||||
definition.operation === "subscription"
|
||||
);
|
||||
},
|
||||
absintheSocketLink,
|
||||
uploadLink
|
||||
);
|
||||
}
|
||||
|
||||
const retryLink = new RetryLink();
|
||||
|
||||
export const fullLink = authMiddleware
|
||||
.concat(retryLink)
|
||||
.concat(errorLink)
|
||||
.concat(link ?? uploadLink);
|
|
@ -0,0 +1,14 @@
|
|||
import { defaultDataIdFromObject, InMemoryCache } from "@apollo/client/core";
|
||||
import { possibleTypes, typePolicies } from "./utils";
|
||||
|
||||
export const cache = new InMemoryCache({
|
||||
addTypename: true,
|
||||
typePolicies,
|
||||
possibleTypes,
|
||||
dataIdFromObject: (object: any) => {
|
||||
if (object.__typename === "Address") {
|
||||
return object.origin_id;
|
||||
}
|
||||
return defaultDataIdFromObject(object);
|
||||
},
|
||||
});
|