Merge branch 'upstream_1.0.3' into chapril
@ -39,9 +39,9 @@ lint:
|
||||
- mix format --check-formatted --dry-run || export EXITVALUE=1
|
||||
- cd js
|
||||
- yarn install
|
||||
#- yarn run lint || export EXITVALUE=1
|
||||
- yarn run prettier --ignore-path="src/i18n/*" -c . || export EXITVALUE=1
|
||||
- yarn run build
|
||||
- yarn run lint || export EXITVALUE=1
|
||||
- yarn run prettier -c . || export EXITVALUE=1
|
||||
- yarn run build:assets
|
||||
- cd ../
|
||||
- exit $EXITVALUE
|
||||
artifacts:
|
||||
@ -69,7 +69,7 @@ exunit:
|
||||
before_script:
|
||||
- cd js
|
||||
- yarn install
|
||||
- yarn run build
|
||||
- yarn run build:assets
|
||||
- cd ../
|
||||
- mix deps.get
|
||||
- MIX_ENV=test mix ecto.create
|
||||
@ -78,6 +78,21 @@ exunit:
|
||||
- lint
|
||||
script:
|
||||
- mix coveralls
|
||||
|
||||
jest:
|
||||
stage: test
|
||||
before_script:
|
||||
- cd js
|
||||
- yarn install
|
||||
dependencies:
|
||||
- lint
|
||||
script:
|
||||
- yarn run test:unit --no-color
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
- js/coverage
|
||||
expire_in: 30 days
|
||||
# cypress:
|
||||
# stage: test
|
||||
# services:
|
||||
|
@ -1,8 +0,0 @@
|
||||
projects:
|
||||
Mobilizon:
|
||||
schemaPath: schema.graphql
|
||||
extensions:
|
||||
endpoints:
|
||||
dev:
|
||||
url: 'http://localhost:4000/api'
|
||||
introspect: true
|
91
CHANGELOG.md
@ -4,7 +4,96 @@ 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).
|
||||
|
||||
## Unreleased
|
||||
## 1.0.3 - 18-12-2020
|
||||
|
||||
**This release adds new migrations, be sure to run them before restarting Mobilizon**
|
||||
|
||||
**This release has repair steps, be sure to execute them right after restarting Mobilizon**
|
||||
|
||||
### Special operations
|
||||
|
||||
* **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.
|
||||
|
||||
* Source install
|
||||
`MIX_ENV=prod mix mobilizon.maintenance.fix_unattached_media_in_body`
|
||||
* Docker
|
||||
`docker-compose exec mobilizon mobilizon_ctl maintenance.fix_unattached_media_in_body`
|
||||
|
||||
* **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.
|
||||
|
||||
* Source install
|
||||
`MIX_ENV=prod mix mobilizon.actors.refresh --all`
|
||||
* Docker
|
||||
`docker-compose exec mobilizon mobilizon_ctl actors.refresh --all`
|
||||
|
||||
* **imagemagick and webp are now a required dependency** to build Mobilizon.
|
||||
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.
|
||||
|
||||
### Added
|
||||
|
||||
- **Add a command to clean orphan media files**. There's a `--dry-run` option to see what files would have been deleted.
|
||||
**Make sure all media files have been reattached properly (see above) before running this command.**
|
||||
In 1.1.0 a scheduled job will be enabled to clear orphan media files automatically after a while.
|
||||
- Added user and actors media usage information in administration
|
||||
- Added a scheduled job to clean unconfirmed users (and their eventual initial profile) after a 48 hour grace period
|
||||
- Added a mix task to manually clean unconfirmed users
|
||||
- Added OpenStreetMap (OSRM) or GoogleMaps routing pages on the event map modal
|
||||
- Added PWA support, Mobilizon can now be installed on Android (Firefox and Chrome), iOS (Safari) and desktop (Chrome)
|
||||
- Added possibility to pick language through a setting on the footer for unlogged users
|
||||
|
||||
### Changed
|
||||
|
||||
- Save remote avatars and banners instead of proxifying them
|
||||
- Forbid creating usernames with uppercase characters
|
||||
- Allow LDAP admin to use a fully qualified DN (different than the one for the users)
|
||||
- Allow LDAP users to be filtered by LDAP attribute `memberOf`.
|
||||
- Improve the "My events" and "My groups" page when there's nothing here yet
|
||||
- Show identity concerned when listing event participations (in "My events") and group membership (in "My groups")
|
||||
- The datetime picker on the event's edition page has been changed and allows directly editing the text
|
||||
- Allow to clear and remove pictures from events and posts
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed inline media that weren't being tracked, so that they are not considered orphans media files.
|
||||
- Fixed permissions on the Docker volume
|
||||
- Fixed emails not using user timezone
|
||||
- Fixed draft status not being shown on group events & posts inside admin section
|
||||
- Fixed cancelled status not being shown on cancelled events cards
|
||||
- Fixed membership notification emails not being sent with the user's language
|
||||
- Fixed group posts ActivityPub endpoint
|
||||
- Fixed unlisted groups being available in search
|
||||
- Fixed inline media pictures being unattached when editing an event or a post
|
||||
- Fixed adding an instance to follow with spaces
|
||||
- Fixed past groups showing up on group's page
|
||||
- Fixed error message not showing up when you are already an anonymous participant for an event
|
||||
- Fixed error message not showing up when you pick an username already in user for a new profile or a group
|
||||
- Fixed translations not fallbacking properly to english when not found
|
||||
-
|
||||
|
||||
### Security
|
||||
|
||||
- Stop logging user JWT tokens in Websocket Mobilizon logs
|
||||
|
||||
### Translations
|
||||
|
||||
Updated translations:
|
||||
- Catalan
|
||||
- Dutch
|
||||
- English
|
||||
- Finnish
|
||||
- French
|
||||
- Galician
|
||||
- German
|
||||
- Hungarian
|
||||
- Italian
|
||||
- Norwegian
|
||||
- Occitan
|
||||
- Polish
|
||||
- Spanish
|
||||
- Swedish
|
||||
|
||||
## 1.0.2 - 2020-11-15
|
||||
|
||||
|
@ -52,7 +52,7 @@ We appreciate any contribution to Mobilizon. Check our [CONTRIBUTING](CONTRIBUTI
|
||||
* 📜 Documentation [https://docs.joinmobilizon.org](https://docs.joinmobilizon.org)
|
||||
|
||||
### Discuss
|
||||
* 💬 Riot/Matrix: [https://riot.im/app/#/room/#Mobilizon:matrix.org](https://riot.im/app/#/room/#Mobilizon:matrix.org)
|
||||
* 💬 Element/Matrix: [https://matrix.to/#/#Mobilizon:matrix.org](https://matrix.to/#/#Mobilizon:matrix.org)
|
||||
* 🗣️ Forum: [https://framacolibri.org/c/mobilizon](https://framacolibri.org/c/mobilizon)
|
||||
|
||||
### Follow
|
||||
|
@ -28,6 +28,10 @@ config :mobilizon, :instance,
|
||||
upload_limit: 10_000_000,
|
||||
avatar_upload_limit: 2_000_000,
|
||||
banner_upload_limit: 4_000_000,
|
||||
remove_orphan_uploads: true,
|
||||
orphan_upload_grace_period_hours: 48,
|
||||
remove_unconfirmed_users: true,
|
||||
unconfirmed_user_grace_period_hours: 48,
|
||||
email_from: "noreply@localhost",
|
||||
email_reply_to: "noreply@localhost"
|
||||
|
||||
@ -77,17 +81,6 @@ config :mobilizon, Mobilizon.Web.Upload,
|
||||
|
||||
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "uploads"
|
||||
|
||||
config :mobilizon, :media_proxy,
|
||||
enabled: true,
|
||||
proxy_opts: [
|
||||
redirect_on_failure: false,
|
||||
max_body_length: 25 * 1_048_576,
|
||||
http: [
|
||||
follow_redirect: true,
|
||||
pool: :media
|
||||
]
|
||||
]
|
||||
|
||||
config :mobilizon, Mobilizon.Web.Email.Mailer,
|
||||
adapter: Bamboo.SMTPAdapter,
|
||||
server: "localhost",
|
||||
@ -142,6 +135,10 @@ config :mobilizon, :ldap,
|
||||
base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
|
||||
uid: System.get_env("LDAP_UID") || "cn",
|
||||
require_bind_for_search: !(System.get_env("LDAP_REQUIRE_BIND_FOR_SEARCH") == "false"),
|
||||
# The full CN to filter by `memberOf`, or `false` if disabled
|
||||
group: false,
|
||||
# Either the admin UID matching the field in `uid`,
|
||||
# Either a tuple with the fully qualified DN: {:full, uid=admin,dc=example.com,dc=local}
|
||||
bind_uid: System.get_env("LDAP_BIND_UID"),
|
||||
bind_password: System.get_env("LDAP_BIND_PASSWORD")
|
||||
|
||||
@ -154,22 +151,20 @@ config :geolix,
|
||||
}
|
||||
]
|
||||
|
||||
config :auto_linker,
|
||||
opts: [
|
||||
scheme: true,
|
||||
extra: true,
|
||||
# TODO: Set to :no_scheme when it works properly
|
||||
validate_tld: true,
|
||||
class: false,
|
||||
strip_prefix: false,
|
||||
new_window: true,
|
||||
rel: "noopener noreferrer ugc"
|
||||
]
|
||||
config :mobilizon, Mobilizon.Service.Formatter,
|
||||
class: false,
|
||||
rel: "noopener noreferrer ugc",
|
||||
new_window: true,
|
||||
truncate: false,
|
||||
strip_prefix: false,
|
||||
extra: true,
|
||||
validate_tld: :no_scheme
|
||||
|
||||
config :tesla, adapter: Tesla.Adapter.Hackney
|
||||
|
||||
config :phoenix, :format_encoders, json: Jason, "activity-json": Jason
|
||||
config :phoenix, :json_library, Jason
|
||||
config :phoenix, :filter_parameters, ["password", "token"]
|
||||
|
||||
config :ex_cldr,
|
||||
default_locale: "en",
|
||||
@ -180,26 +175,8 @@ config :http_signatures,
|
||||
|
||||
config :mobilizon, :cldr,
|
||||
locales: [
|
||||
"ar",
|
||||
"be",
|
||||
"ca",
|
||||
"cs",
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"fi",
|
||||
"fr",
|
||||
"gl",
|
||||
"hu",
|
||||
"it",
|
||||
"ja",
|
||||
"nl",
|
||||
"nn",
|
||||
"oc",
|
||||
"pl",
|
||||
"pt",
|
||||
"ru",
|
||||
"sv"
|
||||
"en"
|
||||
]
|
||||
|
||||
config :mobilizon, :activitypub,
|
||||
@ -233,6 +210,9 @@ config :mobilizon, :maps,
|
||||
tiles: [
|
||||
endpoint: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
attribution: "© The OpenStreetMap Contributors"
|
||||
],
|
||||
routing: [
|
||||
type: :openstreetmap
|
||||
]
|
||||
|
||||
config :mobilizon, :anonymous,
|
||||
@ -266,7 +246,10 @@ config :mobilizon, Oban,
|
||||
queues: [default: 10, search: 5, mailers: 10, background: 5],
|
||||
crontab: [
|
||||
{"@hourly", Mobilizon.Service.Workers.BuildSiteMap, queue: :background},
|
||||
{"17 * * * *", Mobilizon.Service.Workers.RefreshGroups, queue: :background}
|
||||
{"17 * * * *", Mobilizon.Service.Workers.RefreshGroups, queue: :background},
|
||||
# To be activated in Mobilizon 1.2
|
||||
# {"@hourly", Mobilizon.Service.Workers.CleanOrphanMediaWorker, queue: :background},
|
||||
{"@hourly", Mobilizon.Service.Workers.CleanUnconfirmedUsersWorker, queue: :background}
|
||||
]
|
||||
|
||||
config :mobilizon, :rich_media,
|
||||
|
@ -19,7 +19,6 @@ config :mobilizon, Mobilizon.Web.Endpoint,
|
||||
code_reloader: true,
|
||||
check_origin: false,
|
||||
watchers: [
|
||||
# yarn: ["run", "dev", cd: Path.expand("../js", __DIR__)]
|
||||
node: [
|
||||
"node_modules/webpack/bin/webpack.js",
|
||||
"--mode",
|
||||
@ -53,8 +52,8 @@ config :mobilizon, Mobilizon.Web.Endpoint,
|
||||
patterns: [
|
||||
~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
|
||||
~r{priv/gettext/.*(po)$},
|
||||
~r{lib/mobilizon_web/views/.*(ex)$},
|
||||
~r{lib/mobilizon_web/templates/.*(eex)$}
|
||||
~r{lib/web/(live|views)/.*(ex)$},
|
||||
~r{lib/web/templates/.*(eex)$}
|
||||
]
|
||||
]
|
||||
|
||||
@ -92,13 +91,6 @@ config :mobilizon, :instance,
|
||||
|
||||
# config :mobilizon, :activitypub, sign_object_fetches: false
|
||||
|
||||
# No need to compile every locale in development environment
|
||||
config :mobilizon, :cldr,
|
||||
locales: [
|
||||
"fr",
|
||||
"en"
|
||||
]
|
||||
|
||||
config :mobilizon, :anonymous,
|
||||
reports: [
|
||||
allowed: true
|
||||
|
@ -13,6 +13,31 @@ config :mobilizon, Mobilizon.Web.Endpoint,
|
||||
# Do not print debug messages in production
|
||||
config :logger, level: :info
|
||||
|
||||
# Load all locales in production
|
||||
config :mobilizon, :cldr,
|
||||
locales: [
|
||||
"ar",
|
||||
"be",
|
||||
"ca",
|
||||
"cs",
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"fi",
|
||||
"fr",
|
||||
"gl",
|
||||
"hu",
|
||||
"it",
|
||||
"ja",
|
||||
"nl",
|
||||
"nn",
|
||||
"oc",
|
||||
"pl",
|
||||
"pt",
|
||||
"ru",
|
||||
"sv"
|
||||
]
|
||||
|
||||
cond do
|
||||
System.get_env("INSTANCE_CONFIG") &&
|
||||
File.exists?("./config/#{System.get_env("INSTANCE_CONFIG")}") ->
|
||||
|
@ -1,7 +1,7 @@
|
||||
# First build the application assets
|
||||
FROM node:alpine as assets
|
||||
|
||||
RUN apk add --no-cache python build-base
|
||||
RUN apk add --no-cache python build-base libwebp-tools bash imagemagick ncurses
|
||||
|
||||
COPY js .
|
||||
RUN yarn install \
|
||||
@ -33,6 +33,8 @@ FROM alpine
|
||||
|
||||
RUN apk add --no-cache openssl ncurses-libs file postgresql-client
|
||||
|
||||
RUN mkdir -p /app/uploads && chown nobody:nobody /app/uploads
|
||||
|
||||
USER nobody
|
||||
EXPOSE 4000
|
||||
|
||||
|
@ -4,4 +4,4 @@ indent_size = 2
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
||||
max_line_length = 80
|
||||
|
@ -7,13 +7,9 @@ module.exports = {
|
||||
|
||||
extends: [
|
||||
"plugin:vue/essential",
|
||||
"@vue/airbnb",
|
||||
"@vue/typescript/recommended",
|
||||
"plugin:cypress/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"prettier",
|
||||
"eslint:recommended",
|
||||
"@vue/prettier",
|
||||
"@vue/typescript/recommended",
|
||||
"@vue/prettier/@typescript-eslint",
|
||||
],
|
||||
|
||||
@ -21,6 +17,7 @@ module.exports = {
|
||||
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
parser: "@typescript-eslint/parser",
|
||||
},
|
||||
|
||||
rules: {
|
||||
@ -35,29 +32,24 @@ module.exports = {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"cypress/no-unnecessary-waiting": "off",
|
||||
"vue/max-len": [
|
||||
"error",
|
||||
"off",
|
||||
{
|
||||
ignoreStrings: true,
|
||||
ignoreHTMLTextContents: true,
|
||||
ignoreTemplateLiterals: true,
|
||||
ignoreComments: true,
|
||||
template: 170,
|
||||
code: 100,
|
||||
code: 80,
|
||||
},
|
||||
],
|
||||
"prettier/prettier": "error",
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"import/prefer-default-export": "off",
|
||||
"import/extensions": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"no-shadow": "off",
|
||||
"@typescript-eslint/no-shadow": ["error"],
|
||||
},
|
||||
|
||||
ignorePatterns: ["src/typings/*.d.ts", "vue.config.js"],
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: ["**/__tests__/*.{j,t}s?(x)", "**/tests/unit/**/*.spec.{j,t}s?(x)"],
|
||||
env: {
|
||||
mocha: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
1
js/.gitignore
vendored
@ -4,6 +4,7 @@ node_modules
|
||||
|
||||
/tests/e2e/videos/
|
||||
/tests/e2e/screenshots/
|
||||
/coverage
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
|
@ -1 +1,2 @@
|
||||
src/i18n/*.json
|
||||
src/i18n/*.json
|
||||
coverage/
|
@ -24,7 +24,9 @@ fetch(`http://localhost:4000/api`, {
|
||||
.then((result) => result.json())
|
||||
.then((result) => {
|
||||
// here we're filtering out any type information unrelated to unions or interfaces
|
||||
const filteredData = result.data.__schema.types.filter((type) => type.possibleTypes !== null);
|
||||
const filteredData = result.data.__schema.types.filter(
|
||||
(type) => type.possibleTypes !== null
|
||||
);
|
||||
result.data.__schema.types = filteredData;
|
||||
fs.writeFile("./fragmentTypes.json", JSON.stringify(result.data), (err) => {
|
||||
if (err) {
|
||||
|
19
js/jest.config.js
Normal file
@ -0,0 +1,19 @@
|
||||
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"],
|
||||
// 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",
|
||||
// },
|
||||
};
|
@ -1,11 +1,13 @@
|
||||
{
|
||||
"name": "mobilizon",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build --modern",
|
||||
"test:unit": "vue-cli-service test:unit",
|
||||
"build:assets": "vue-cli-service build --modern",
|
||||
"build:pictures": "bash ./scripts/build/pictures.sh",
|
||||
"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 vue-cli-service test:unit",
|
||||
"test:e2e": "vue-cli-service test:e2e",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
@ -29,7 +31,7 @@
|
||||
"eslint-plugin-cypress": "^2.10.3",
|
||||
"graphql": "^15.0.0",
|
||||
"graphql-tag": "^2.10.3",
|
||||
"intersection-observer": "^0.11.0",
|
||||
"intersection-observer": "^0.12.0",
|
||||
"leaflet": "^1.4.0",
|
||||
"leaflet.locatecontrol": "^0.72.0",
|
||||
"lodash": "^4.17.11",
|
||||
@ -39,6 +41,7 @@
|
||||
"tippy.js": "^6.2.3",
|
||||
"tiptap": "^1.26.0",
|
||||
"tiptap-extensions": "^1.29.1",
|
||||
"unfetch": "^4.2.0",
|
||||
"v-tooltip": "2.0.2",
|
||||
"vue": "^2.6.11",
|
||||
"vue-apollo": "^3.0.3",
|
||||
@ -52,6 +55,7 @@
|
||||
"vuedraggable": "2.23.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.18",
|
||||
"@types/leaflet": "^1.5.2",
|
||||
"@types/leaflet.locatecontrol": "^0.60.7",
|
||||
"@types/lodash": "^4.14.141",
|
||||
@ -63,27 +67,27 @@
|
||||
"@types/vuedraggable": "^2.23.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.0.1",
|
||||
"@typescript-eslint/parser": "^4.0.1",
|
||||
"@vue/cli-plugin-babel": "~4.5.8",
|
||||
"@vue/cli-plugin-e2e-cypress": "~4.5.8",
|
||||
"@vue/cli-plugin-eslint": "~4.5.8",
|
||||
"@vue/cli-plugin-pwa": "~4.5.8",
|
||||
"@vue/cli-plugin-router": "~4.5.8",
|
||||
"@vue/cli-plugin-typescript": "~4.5.8",
|
||||
"@vue/cli-service": "~4.5.8",
|
||||
"@vue/eslint-config-airbnb": "^5.0.2",
|
||||
"@vue/cli-plugin-babel": "~4.5.9",
|
||||
"@vue/cli-plugin-e2e-cypress": "~4.5.9",
|
||||
"@vue/cli-plugin-eslint": "~4.5.9",
|
||||
"@vue/cli-plugin-pwa": "~4.5.9",
|
||||
"@vue/cli-plugin-router": "~4.5.9",
|
||||
"@vue/cli-plugin-typescript": "~4.5.9",
|
||||
"@vue/cli-plugin-unit-jest": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.9",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"@vue/test-utils": "^1.1.0",
|
||||
"eslint": "^7.7.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-config-prettier": "^7.0.0",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
"prettier": "2.1.2",
|
||||
"prettier-eslint": "^11.0.0",
|
||||
"mock-apollo-client": "^0.4",
|
||||
"prettier": "2.2.1",
|
||||
"prettier-eslint": "^12.0.0",
|
||||
"sass": "^1.29.0",
|
||||
"sass-loader": "^10.0.1",
|
||||
"typescript": "~4.0.2",
|
||||
"typescript": "~4.1.2",
|
||||
"vue-cli-plugin-svg": "~0.1.3",
|
||||
"vue-i18n-extract": "^1.0.2",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
|
Before Width: | Height: | Size: 725 KiB After Width: | Height: | Size: 725 KiB |
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
3291
js/schema.graphql
Normal file
90
js/scripts/build/pictures.sh
Executable file
@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
output_dir="../priv/static/img/pics"
|
||||
resolutions=(
|
||||
480
|
||||
1024
|
||||
1920
|
||||
)
|
||||
ignore=(
|
||||
homepage_background.png
|
||||
)
|
||||
|
||||
file_extension () {
|
||||
filename=$(basename -- "$file")
|
||||
echo "${filename##*.}"
|
||||
}
|
||||
|
||||
file_name () {
|
||||
filename=$(basename -- "$file")
|
||||
echo "${filename%.*}"
|
||||
}
|
||||
|
||||
convert_image () {
|
||||
name=$(file_name)
|
||||
extension=$(file_extension)
|
||||
res="$1w"
|
||||
output="$output_dir/$name-$res.$extension"
|
||||
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
|
||||
# create a string of spaces, then change them to dots
|
||||
printf -v dots "%*s" "$(( $p*$w/100 ))" ""; dots=${dots// /.};
|
||||
# print those dots on a fixed-width space plus the percentage etc.
|
||||
printf "\r\e[K|%-*s| %3d %% %s" "$w" "$dots" "$p" "$*";
|
||||
}
|
||||
|
||||
|
||||
echo "Generating responsive versions of the pictures…"
|
||||
|
||||
if ! command -v convert &> /dev/null
|
||||
then
|
||||
echo "$(tput setaf 1)ERROR: The convert command could not be found. You need to install ImageMagick.$(tput sgr 0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
nb_files=$( shopt -s nullglob ; set -- $output_dir/* ; echo $#)
|
||||
|
||||
tasks=$((${#resolutions[@]}*$nb_files))
|
||||
i=1
|
||||
for file in $output_dir/*
|
||||
do
|
||||
if [[ -f $file ]]; then
|
||||
for resolution in "${resolutions[@]}"; do
|
||||
convert_image $resolution
|
||||
progress $(($i*100/$tasks)) still working...
|
||||
i=$((i+1))
|
||||
done
|
||||
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!"
|
18
js/src/@types/dom.d.ts
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
declare global {
|
||||
interface GeolocationCoordinates {
|
||||
readonly accuracy: number;
|
||||
readonly altitude: number | null;
|
||||
readonly altitudeAccuracy: number | null;
|
||||
readonly heading: number | null;
|
||||
readonly latitude: number;
|
||||
readonly longitude: number;
|
||||
readonly speed: number | null;
|
||||
}
|
||||
|
||||
interface GeolocationPosition {
|
||||
readonly coords: GeolocationCoordinates;
|
||||
readonly timestamp: number;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
@ -32,8 +32,16 @@
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
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 {
|
||||
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";
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { ICurrentUserRole } from "@/types/enums";
|
||||
import { ApolloCache } from "apollo-cache";
|
||||
import { NormalizedCacheObject } from "apollo-cache-inmemory";
|
||||
import { ICurrentUserRole } from "@/types/current-user.model";
|
||||
import { Resolvers } from "apollo-client/core/types";
|
||||
|
||||
export default function buildCurrentUserResolver(cache: ApolloCache<NormalizedCacheObject>) {
|
||||
export default function buildCurrentUserResolver(
|
||||
cache: ApolloCache<NormalizedCacheObject>
|
||||
): Resolvers {
|
||||
cache.writeData({
|
||||
data: {
|
||||
currentUser: {
|
||||
@ -53,7 +56,12 @@ export default function buildCurrentUserResolver(cache: ApolloCache<NormalizedCa
|
||||
preferredUsername,
|
||||
avatar,
|
||||
name,
|
||||
}: { id: string; preferredUsername: string; avatar: string; name: string },
|
||||
}: {
|
||||
id: string;
|
||||
preferredUsername: string;
|
||||
avatar: string;
|
||||
name: string;
|
||||
},
|
||||
{ cache: localCache }: { cache: ApolloCache<NormalizedCacheObject> }
|
||||
) => {
|
||||
const data = {
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { IntrospectionFragmentMatcher, NormalizedCacheObject } from "apollo-cache-inmemory";
|
||||
import {
|
||||
IntrospectionFragmentMatcher,
|
||||
NormalizedCacheObject,
|
||||
} from "apollo-cache-inmemory";
|
||||
import { AUTH_ACCESS_TOKEN, AUTH_REFRESH_TOKEN } from "@/constants";
|
||||
import { REFRESH_TOKEN } from "@/graphql/auth";
|
||||
import { saveTokenData } from "@/utils/auth";
|
||||
@ -11,7 +14,11 @@ export const fragmentMatcher = new IntrospectionFragmentMatcher({
|
||||
{
|
||||
kind: "UNION",
|
||||
name: "SearchResult",
|
||||
possibleTypes: [{ name: "Event" }, { name: "Person" }, { name: "Group" }],
|
||||
possibleTypes: [
|
||||
{ name: "Event" },
|
||||
{ name: "Person" },
|
||||
{ name: "Group" },
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "INTERFACE",
|
||||
|
@ -1,11 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 248.16 46.78">
|
||||
<title>Mobilizon Logo</title>
|
||||
<g data-name="header">
|
||||
<path d="M0 45.82l3.18-40.8a29.88 29.88 0 015.07-.36 27.74 27.74 0 014.95.36l4.86 17.16a92.19 92.19 0 012.34 10.08h.36a92.19 92.19 0 012.34-10.08L28 5.02a29.23 29.23 0 015-.36 29.23 29.23 0 015 .36l3.18 40.8a13.61 13.61 0 01-3.63.42 23.41 23.41 0 01-3.63-.24l-1.2-19.92q-.36-5.52-.48-12.84h-.44l-7.32 26.51a25.62 25.62 0 01-4 .3 23.36 23.36 0 01-3.84-.3L9.36 13.24H9q-.3 8.94-.48 12.84L7.26 46a22.47 22.47 0 01-3.6.24A13.75 13.75 0 010 45.82zM74 31.06q0 8-4.26 12.3a12.21 12.21 0 01-9 3.42 12.21 12.21 0 01-9-3.42q-4.26-4.26-4.26-12.3t4.24-12.31a12.21 12.21 0 019-3.42 12.21 12.21 0 019 3.42Q74 23.02 74 31.06zM60.75 20.98q-5.67 0-5.67 10.08t5.67 10.08q5.67 0 5.67-10.08t-5.67-10.08zM103.2 19.75q2.7 4.11 2.7 11.28T102 42.31a13.18 13.18 0 01-10 4.11 31.41 31.41 0 01-11.34-2V2.2l.4-.45h2.76A4 4 0 0187 2.83a5.38 5.38 0 01.93 3.57v11.94a12.08 12.08 0 017.56-2.7 8.71 8.71 0 017.71 4.11zm-9.72 2a7.28 7.28 0 00-5.58 2.82v16a15 15 0 004.08.54 5.25 5.25 0 004.68-2.67q1.68-2.67 1.68-7.59 0-9.03-4.86-9.1zM121 22v23.94a20.85 20.85 0 01-3.66.3 23 23 0 01-3.78-.3V24.75q0-3.24-2.7-3.24h-.72a9.32 9.32 0 01-.3-2.58 10.7 10.7 0 01.3-2.7 39.63 39.63 0 014.38-.24h1a5.19 5.19 0 014 1.62A6.27 6.27 0 01121 22z" />
|
||||
<path d="M119.82.84a7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.93 7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.93z" fill="#fff" />
|
||||
<path d="M139.08 40.42h2a10.23 10.23 0 01.6 3.18 9.24 9.24 0 01-.18 2.1 38.47 38.47 0 01-5.64.54q-6.48 0-6.48-7v-37l.36-.42h2.88a3.94 3.94 0 013.12 1.05 5.52 5.52 0 01.9 3.57v31.31q-.02 2.67 2.44 2.67zM155.94 22v23.94a20.85 20.85 0 01-3.66.3 23 23 0 01-3.78-.3V24.75q0-3.24-2.7-3.24h-.72a9.32 9.32 0 01-.3-2.58 10.7 10.7 0 01.3-2.7 39.63 39.63 0 014.38-.24h1a5.19 5.19 0 014.05 1.62 6.27 6.27 0 011.43 4.39z" />
|
||||
<path d="M154.8 2.84a7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.93 7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.93z" fill="#fff" />
|
||||
<path d="M163.08 39.22l8.76-11.82q1.32-1.8 4.8-5.7l-.18-.3a63.09 63.09 0 01-7.74.42H163a9.79 9.79 0 01-.24-2.34 15.8 15.8 0 01.42-3.3h20.4a16.31 16.31 0 011 4.26 4.1 4.1 0 01-.78 2.34L175 34.66a64.65 64.65 0 01-4.56 5.7l.18.24q3.12-.3 5.22-.3h2.58a15.35 15.35 0 006.12-.9 9.4 9.4 0 01.72 3.12q0 3.42-4.32 3.42h-18a14.27 14.27 0 01-.9-3.93 5.08 5.08 0 011.04-2.79zM215.88 31.06q0 8-4.26 12.3a13.63 13.63 0 01-18.06 0q-4.26-4.26-4.26-12.3t4.26-12.31a13.63 13.63 0 0118.06 0q4.26 4.27 4.26 12.31zm-13.29-10.08q-5.67 0-5.67 10.08t5.67 10.08q5.67 0 5.67-10.08t-5.67-10.08zM247 25.84v13.32a11 11 0 001.2 5.64 7 7 0 01-4.41 1.56q-2.43 0-3.33-1.14a5.69 5.69 0 01-.9-3.54V27.4a7.74 7.74 0 00-.72-3.87 2.78 2.78 0 00-2.58-1.17 8.62 8.62 0 00-6.3 3v20.58a20.85 20.85 0 01-3.66.3 23 23 0 01-3.78-.3v-29.7l.42-.36h2.76q3.42 0 4.08 3.6 4.38-3.84 8.73-3.84t6.42 2.82a12.17 12.17 0 012.07 7.38z" />
|
||||
<path d="M57.26 10.75a7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.84 7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.84zM198.26 10.75a7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.84 7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.84z" fill="#fff" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.4 KiB |
@ -13,7 +13,12 @@
|
||||
<template slot-scope="props">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<img width="32" :src="props.option.avatar.url" v-if="props.option.avatar" alt="" />
|
||||
<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">
|
||||
@ -21,7 +26,9 @@
|
||||
{{ props.option.name }}
|
||||
<br />
|
||||
<small>{{ `@${props.option.preferredUsername}` }}</small>
|
||||
<small v-if="props.option.domain">{{ `@${props.option.domain}` }}</small>
|
||||
<small v-if="props.option.domain">{{
|
||||
`@${props.option.domain}`
|
||||
}}</small>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ `@${props.option.preferredUsername}` }}
|
||||
@ -53,7 +60,9 @@ export default class ActorAutoComplete extends Vue {
|
||||
|
||||
selected: IPerson | null = this.defaultSelected;
|
||||
|
||||
name: string = this.defaultSelected ? this.defaultSelected.preferredUsername : "";
|
||||
name: string = this.defaultSelected
|
||||
? this.defaultSelected.preferredUsername
|
||||
: "";
|
||||
|
||||
page = 1;
|
||||
|
||||
|
@ -12,8 +12,15 @@
|
||||
<p>
|
||||
{{ actor.name || `@${usernameWithDomain(actor)}` }}
|
||||
</p>
|
||||
<p class="has-text-grey" v-if="actor.name">@{{ usernameWithDomain(actor) }}</p>
|
||||
<div v-if="full" class="summary" :class="{ limit: limit }" v-html="actor.summary" />
|
||||
<p class="has-text-grey" v-if="actor.name">
|
||||
@{{ usernameWithDomain(actor) }}
|
||||
</p>
|
||||
<div
|
||||
v-if="full"
|
||||
class="summary"
|
||||
:class="{ limit: limit }"
|
||||
v-html="actor.summary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,10 @@
|
||||
<ul class="identities">
|
||||
<li v-for="identity in identities" :key="identity.id">
|
||||
<router-link
|
||||
:to="{ name: 'UpdateIdentity', params: { identityName: identity.preferredUsername } }"
|
||||
:to="{
|
||||
name: 'UpdateIdentity',
|
||||
params: { identityName: identity.preferredUsername },
|
||||
}"
|
||||
class="media identity"
|
||||
v-bind:class="{ 'is-current-identity': isCurrentIdentity(identity) }"
|
||||
>
|
||||
@ -24,7 +27,10 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<router-link :to="{ name: 'CreateIdentity' }" class="button create-identity is-primary">
|
||||
<router-link
|
||||
:to="{ name: 'CreateIdentity' }"
|
||||
class="button create-identity is-primary"
|
||||
>
|
||||
{{ $t("Create a new identity") }}
|
||||
</router-link>
|
||||
</section>
|
||||
@ -53,7 +59,7 @@ export default class Identities extends Vue {
|
||||
|
||||
errors: string[] = [];
|
||||
|
||||
isCurrentIdentity(identity: IPerson) {
|
||||
isCurrentIdentity(identity: IPerson): boolean {
|
||||
return identity.preferredUsername === this.currentIdentityName;
|
||||
}
|
||||
}
|
||||
|
@ -1,94 +0,0 @@
|
||||
<docs>
|
||||
```vue
|
||||
<participant-card :participant="{ actor: { preferredUsername: 'user1', name: 'someoneIDontLike' }, role: 'REJECTED' }" />
|
||||
```
|
||||
|
||||
```vue
|
||||
<participant-card :participant="{ actor: { preferredUsername: 'user2', name: 'someoneWhoWillWait' }, role: 'NOT_APPROVED' }" />
|
||||
```
|
||||
|
||||
```vue
|
||||
<participant-card :participant="{ actor: { preferredUsername: 'user3', name: 'a_participant' }, role: 'PARTICIPANT' }" />
|
||||
```
|
||||
|
||||
```vue
|
||||
<participant-card :participant="{ actor: { preferredUsername: 'me', name: 'myself' }, role: 'CREATOR' }" />
|
||||
```
|
||||
</docs>
|
||||
<template>
|
||||
<article class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left" v-if="participant.actor.avatar">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="participant.actor.avatar.url" />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<span ref="title">{{ actorDisplayName }}</span
|
||||
><br />
|
||||
<small class="has-text-grey" v-if="participant.actor.domain"
|
||||
>@{{ participant.actor.preferredUsername }}@{{ participant.actor.domain }}</small
|
||||
>
|
||||
<small class="has-text-grey" v-else>@{{ participant.actor.preferredUsername }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<b-button
|
||||
v-if="[ParticipantRole.NOT_APPROVED, ParticipantRole.REJECTED].includes(participant.role)"
|
||||
@click="accept(participant)"
|
||||
type="is-success"
|
||||
class="card-footer-item"
|
||||
>{{ $t("Approve") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
v-if="participant.role === ParticipantRole.NOT_APPROVED"
|
||||
@click="reject(participant)"
|
||||
type="is-danger"
|
||||
class="card-footer-item"
|
||||
>{{ $t("Reject") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
v-if="participant.role === ParticipantRole.PARTICIPANT"
|
||||
@click="exclude(participant)"
|
||||
type="is-danger"
|
||||
class="card-footer-item"
|
||||
>{{ $t("Exclude") }}</b-button
|
||||
>
|
||||
<span v-if="participant.role === ParticipantRole.CREATOR" class="card-footer-item">{{
|
||||
$t("Creator")
|
||||
}}</span>
|
||||
</footer>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { IParticipant, ParticipantRole } from "../../types/participant.model";
|
||||
import { IPerson, Person } from "../../types/actor";
|
||||
|
||||
@Component
|
||||
export default class ParticipantCard extends Vue {
|
||||
@Prop({ required: true }) participant!: IParticipant;
|
||||
|
||||
@Prop({ type: Function }) accept!: Function;
|
||||
|
||||
@Prop({ type: Function }) reject!: Function;
|
||||
|
||||
@Prop({ type: Function }) exclude!: Function;
|
||||
|
||||
ParticipantRole = ParticipantRole;
|
||||
|
||||
get actorDisplayName(): string {
|
||||
const actor = new Person(this.participant.actor as IPerson);
|
||||
return actor.displayName();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.card-footer-item {
|
||||
height: $control-height;
|
||||
}
|
||||
</style>
|
@ -12,8 +12,9 @@
|
||||
</v-popover>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ActorType } from "@/types/enums";
|
||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
||||
import { IActor, ActorType } from "../../types/actor";
|
||||
import { IActor } from "../../types/actor";
|
||||
import ActorCard from "./ActorCard.vue";
|
||||
|
||||
@Component({
|
||||
|
@ -16,11 +16,21 @@
|
||||
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.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-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>
|
||||
@ -33,26 +43,39 @@
|
||||
centered
|
||||
v-slot="props"
|
||||
>
|
||||
<span :class="`tag ${props.row.approved ? 'is-success' : 'is-danger'}`">{{
|
||||
props.row.approved ? $t("Accepted") : $t("Pending")
|
||||
}}</span>
|
||||
<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-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
|
||||
<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 slot="detail" slot-scope="props">
|
||||
@ -143,7 +166,11 @@ export default class Followers extends Mixins(RelayMixin) {
|
||||
await this.$apollo.queries.relayFollowers.refetch();
|
||||
this.checkedRows = [];
|
||||
} catch (e) {
|
||||
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +185,11 @@ export default class Followers extends Mixins(RelayMixin) {
|
||||
await this.$apollo.queries.relayFollowers.refetch();
|
||||
this.checkedRows = [];
|
||||
} catch (e) {
|
||||
Snackbar.open({ message: e.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<form @submit="followRelay">
|
||||
<b-field :label="$t('Add an instance')" custom-class="add-relay" horizontal>
|
||||
<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')" />
|
||||
<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>
|
||||
<b-button type="is-primary" native-type="submit">{{
|
||||
$t("Add an instance")
|
||||
}}</b-button>
|
||||
</p>
|
||||
</b-field>
|
||||
</b-field>
|
||||
@ -29,12 +38,25 @@
|
||||
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.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-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>
|
||||
|
||||
@ -46,26 +68,39 @@
|
||||
centered
|
||||
v-slot="props"
|
||||
>
|
||||
<span :class="`tag ${props.row.approved ? 'is-success' : 'is-danger'}`">{{
|
||||
props.row.approved ? $t("Accepted") : $t("Pending")
|
||||
}}</span>
|
||||
<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-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
|
||||
<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 slot="detail" slot-scope="props">
|
||||
@ -103,7 +138,6 @@ 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 { Paginate } from "../../types/paginate";
|
||||
import RelayMixin from "../../mixins/relay";
|
||||
|
||||
@Component({
|
||||
@ -127,19 +161,25 @@ export default class Followings extends Mixins(RelayMixin) {
|
||||
await this.$apollo.mutate({
|
||||
mutation: ADD_RELAY,
|
||||
variables: {
|
||||
address: this.newRelayAddress,
|
||||
address: this.newRelayAddress.trim(), // trim to fix copy and paste domain name spaces and tabs
|
||||
},
|
||||
});
|
||||
await this.$apollo.queries.relayFollowings.refetch();
|
||||
this.newRelayAddress = "";
|
||||
} catch (err) {
|
||||
Snackbar.open({ message: err.message, type: "is-danger", position: "is-bottom" });
|
||||
Snackbar.open({
|
||||
message: err.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||