Various UI stuff (mainly implement mookup)

Fix lint

Disable modern mode

Fixes

UI fixes

Fixes

Ignore .po~ files

Fixes

Fix homepage

Fixes

Fixes

Mix format

Fix tests

Fix tests (yeah…)

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2019-04-03 17:29:03 +02:00
parent 2dcd65ea78
commit da2a0593ca
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
66 changed files with 14247 additions and 15872 deletions

2
.gitignore vendored
View File

@ -31,3 +31,5 @@ cover/
uploads/* uploads/*
!uploads/.gitkeep !uploads/.gitkeep
.idea .idea
*.mo
*.po~

View File

@ -11,6 +11,7 @@ config :mobilizon,
config :mobilizon, :instance, config :mobilizon, :instance,
name: System.get_env("MOBILIZON_INSTANCE_NAME") || "Localhost", name: System.get_env("MOBILIZON_INSTANCE_NAME") || "Localhost",
description: System.get_env("MOBILIZON_INSTANCE_DESCRIPTION") || "This is a Mobilizon instance",
version: "1.0.0-dev", version: "1.0.0-dev",
registrations_open: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_OPEN") || false registrations_open: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_OPEN") || false

21
js/fragmentTypes.json Normal file
View File

@ -0,0 +1,21 @@
{"__schema":
{"types":[
{
"possibleTypes":[
{"name":"Person"},
{"name":"Group"}
],
"name":"Actor",
"kind":"INTERFACE"
},
{
"possibleTypes":[
{"name":"Event"},
{"name":"Person"},
{"name":"Group"}
],
"name":"SearchResult",
"kind":"UNION"}
]
}
}

View File

@ -1,7 +1,7 @@
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const fs = require('fs'); const fs = require('fs');
fetch(`http://localhost:4000/graphiql`, { fetch(`http://localhost:4001`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({

15025
js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "vue-cli-service build --modern", "build": "vue-cli-service build",
"lint": "vue-cli-service lint", "lint": "vue-cli-service lint",
"analyze-bundle": "npm run build -- --report-json && webpack-bundle-analyzer ../priv/static/report.json", "analyze-bundle": "npm run build -- --report-json && webpack-bundle-analyzer ../priv/static/report.json",
"dev": "vue-cli-service serve", "dev": "vue-cli-service serve",
@ -23,19 +23,21 @@
"graphql": "^14.2.1", "graphql": "^14.2.1",
"graphql-tag": "^2.10.1", "graphql-tag": "^2.10.1",
"leaflet": "^1.4.0", "leaflet": "^1.4.0",
"line-clamp": "0.0.9",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"material-design-icons": "^3.0.1", "material-design-icons": "^3.0.1",
"ngeohash": "^0.6.3", "ngeohash": "^0.6.3",
"npm-check-updates": "^3.1.3", "npm-check-updates": "^3.1.7",
"register-service-worker": "^1.6.2", "register-service-worker": "^1.6.2",
"typeface-signika": "0.0.72",
"vue": "^2.6.10", "vue": "^2.6.10",
"vue-apollo": "^3.0.0-beta.28", "vue-apollo": "^3.0.0-beta.28",
"vue-class-component": "^7.0.2", "vue-class-component": "^7.0.2",
"vue-gettext": "^2.1.2", "vue-gettext": "^2.1.3",
"vue-property-decorator": "^8.1.0", "vue-property-decorator": "^8.1.0",
"vue-router": "^3.0.2", "vue-router": "^3.0.3",
"vue-simple-markdown": "^1.0.9", "vue-simple-markdown": "^1.0.9",
"vue2-leaflet": "^2.0.2", "vue2-leaflet": "^2.0.3",
"vuex": "^3.1.0" "vuex": "^3.1.0"
}, },
"devDependencies": { "devDependencies": {
@ -53,13 +55,16 @@
"@vue/test-utils": "^1.0.0-beta.29", "@vue/test-utils": "^1.0.0-beta.29",
"chai": "^4.2.0", "chai": "^4.2.0",
"dotenv-webpack": "^1.7.0", "dotenv-webpack": "^1.7.0",
"eslint": "^5.16.0",
"node-sass": "^4.11.0", "node-sass": "^4.11.0",
"patch-package": "^6.1.0", "patch-package": "^6.1.1",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"tslint": "^5.15.0",
"tslint-config-airbnb": "^5.11.1", "tslint-config-airbnb": "^5.11.1",
"typescript": "^3.4.1", "typescript": "^3.4.3",
"vue-template-compiler": "^2.6.10", "vue-template-compiler": "^2.6.10",
"webpack-bundle-analyzer": "^3.1.0" "webpack": "^4.29.6",
"webpack-bundle-analyzer": "^3.3.0"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",

View File

@ -6,7 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" href="//cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css"> <link rel="stylesheet" href="//cdn.materialdesignicons.com/3.5.95/css/materialdesignicons.min.css">
<title>mobilizon</title> <title>mobilizon</title>
<!--server-generated-meta--> <!--server-generated-meta-->
</head> </head>
@ -19,4 +19,4 @@
<!-- built files will be auto injected --> <!-- built files will be auto injected -->
</body> </body>
</html> </html>

View File

@ -1,18 +1,10 @@
<template> <template>
<div id="mobilizon"> <div id="mobilizon">
<NavBar></NavBar> <NavBar />
<main class="container"> <main>
<router-view></router-view> <router-view />
</main> </main>
<footer class="footer"> <mobilizon-footer />
<div class="content has-text-centered">
<span
v-translate="{
date: new Date().getFullYear(),
}"
>© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks</span>
</div>
</footer>
</div> </div>
</template> </template>
@ -22,6 +14,8 @@ import { Component, Vue } from 'vue-property-decorator';
import { AUTH_TOKEN, AUTH_USER_ACTOR, AUTH_USER_EMAIL, AUTH_USER_ID } from '@/constants'; import { AUTH_TOKEN, AUTH_USER_ACTOR, AUTH_USER_EMAIL, AUTH_USER_ID } from '@/constants';
import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user'; import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user';
import { ICurrentUser } from '@/types/current-user.model'; import { ICurrentUser } from '@/types/current-user.model';
import Footer from '@/components/Footer.vue';
import Logo from '@/components/Logo.vue';
@Component({ @Component({
apollo: { apollo: {
@ -30,63 +24,24 @@ import { ICurrentUser } from '@/types/current-user.model';
}, },
}, },
components: { components: {
Logo,
NavBar, NavBar,
'mobilizon-footer': Footer,
}, },
}) })
export default class App extends Vue { export default class App extends Vue {
drawer = false;
fab = false;
items = [
{
icon: 'poll',
text: 'Events',
route: 'EventList',
role: null,
},
{
icon: 'group',
text: 'Groups',
route: 'GroupList',
role: null,
},
{ icon: 'settings', text: 'Settings', role: 'ROLE_USER' },
{ icon: 'chat_bubble', text: 'Send feedback', role: 'ROLE_USER' },
{ icon: 'help', text: 'Help', role: null },
{ icon: 'phonelink', text: 'App downloads', role: null },
];
error = {
timeout: 3000,
show: false,
text: '',
};
currentUser!: ICurrentUser; currentUser!: ICurrentUser;
actor = localStorage.getItem(AUTH_USER_ACTOR); actor = localStorage.getItem(AUTH_USER_ACTOR);
mounted () { async mounted () {
this.initializeCurrentUser(); await this.initializeCurrentUser();
}
get displayed_name () {
// FIXME: load actor
return 'no implemented';
// return this.actor.display_name === null ? this.actor.username : this.actor.display_name
}
showMenuItem(elem) {
// FIXME: load actor
return false;
// return elem !== null && this.user && this.user.roles !== undefined ? this.user.roles.includes(elem) : true
} }
getUser (): ICurrentUser|false { getUser (): ICurrentUser|false {
return this.currentUser.id ? this.currentUser : false; return this.currentUser.id ? this.currentUser : false;
} }
toggleDrawer() {
this.drawer = !this.drawer;
}
private initializeCurrentUser() { private initializeCurrentUser() {
const userId = localStorage.getItem(AUTH_USER_ID); const userId = localStorage.getItem(AUTH_USER_ID);
const userEmail = localStorage.getItem(AUTH_USER_EMAIL); const userEmail = localStorage.getItem(AUTH_USER_EMAIL);
@ -106,7 +61,32 @@ export default class App extends Vue {
} }
</script> </script>
<style> <style lang="scss">
@import "variables";
// Import Bulma and Buefy styles
@import "~bulma/sass/utilities/_all";
@import "~bulma/sass/base/_all.sass";
@import "~bulma/sass/elements/button.sass";
@import "~bulma/sass/elements/container.sass";
@import "~bulma/sass/components/card.sass";
@import "~bulma/sass/components/pagination.sass";
@import "~bulma/sass/elements/form.sass";
@import "~bulma/sass/layout/hero.sass";
@import "~bulma/sass/elements/title.sass";
@import "~bulma/sass/elements/image.sass";
@import "~bulma/sass/elements/box.sass";
@import "~bulma/sass/components/navbar.sass";
@import "~bulma/sass/components/modal.sass";
@import "~bulma/sass/grid/_all.sass";
@import "~bulma/sass/layout/section.sass";
@import "~bulma/sass/layout/footer.sass";
@import "~buefy/src/scss/utils/_all";
@import "~buefy/src/scss/components/datepicker";
@import "~buefy/src/scss/components/modal";
@import "~buefy/src/scss/components/form";
@import "~buefy/src/scss/components/dropdown";
.router-enter-active, .router-enter-active,
.router-leave-active { .router-leave-active {
transition-property: opacity; transition-property: opacity;
@ -121,4 +101,8 @@ export default class App extends Vue {
.router-leave-active { .router-leave-active {
opacity: 0; opacity: 0;
} }
body {
background: #f6f7f8;
}
</style> </style>

View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<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"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 64 64"
style="enable-background:new 0 0 64 64;"
xml:space="preserve"
id="svg4965"
sodipodi:docname="Circle-icons-calendar.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14"><metadata
id="metadata4971"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs4969" /><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="1018"
id="namedview4967"
showgrid="false"
inkscape:zoom="10.429825"
inkscape:cx="30.416304"
inkscape:cy="28.788016"
inkscape:window-x="0"
inkscape:window-y="34"
inkscape:window-maximized="1"
inkscape:current-layer="g4961" />
<style
type="text/css"
id="style4919">
.st0{fill:#77B3D4;}
.st1{opacity:0.2;}
.st2{fill:#231F20;}
.st3{fill:#FFFFFF;}
.st4{fill:#C75C5C;}
.st5{fill:#4F5D73;}
.st6{fill:#E0E0D1;}
</style>
<g
id="Layer_1">
<g
id="g4923">
<circle
class="st0"
cx="32"
cy="32"
r="32"
id="circle4921" />
</g>
<g
id="g4961">
<g
class="st1"
id="g4927">
<path
class="st2"
d="M12,25v25c0,2.2,1.8,4,4,4h32c2.2,0,4-1.8,4-4V25H12z"
id="path4925" />
</g>
<g
id="g4931">
<path
class="st3"
d="M12,23v25c0,2.2,1.8,4,4,4h32c2.2,0,4-1.8,4-4V23H12z"
id="path4929" />
</g>
<g
class="st1"
id="g4935">
<path
class="st2"
d="M48,14H16c-2.2,0-4,1.8-4,4v7h40v-7C52,15.8,50.2,14,48,14z"
id="path4933" />
</g>
<g
id="g4939">
<path
class="st4"
d="M48,12H16c-2.2,0-4,1.8-4,4v7h40v-7C52,13.8,50.2,12,48,12z"
id="path4937" />
</g>
<g
class="st1"
id="g4947">
<path
class="st2"
d="M20,21c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2l0,0c1.1,0,2,0.9,2,2v7C22,20.1,21.1,21,20,21L20,21z"
id="path4945" />
</g>
<g
class="st1"
id="g4951">
<path
class="st2"
d="M45,21c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2l0,0c1.1,0,2,0.9,2,2v7C47,20.1,46.1,21,45,21L45,21z"
id="path4949" />
</g>
<g
id="g4955">
<path
class="st6"
d="M20,19c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2l0,0c1.1,0,2,0.9,2,2v7C22,18.1,21.1,19,20,19L20,19z"
id="path4953" />
</g>
<g
id="g4959">
<path
class="st6"
d="M45,19c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2l0,0c1.1,0,2,0.9,2,2v7C47,18.1,46.1,19,45,19L45,19z"
id="path4957" />
</g>
</g>
</g>
<g
id="Layer_2">
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
js/src/assets/footer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

748
js/src/assets/texting.svg Normal file
View File

@ -0,0 +1,748 @@
<?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"
id="abd2d575-db47-4a01-ab9c-7ced98725eec"
data-name="Layer 1"
width="1162"
height="716.89"
viewBox="0 0 1162 716.89"
version="1.1"
sodipodi:docname="texting.svg"
inkscape:version="0.92.4 5da689c313, 2019-01-14">
<metadata
id="metadata4844">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4842" />
<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="1018"
id="namedview4840"
showgrid="false"
inkscape:zoom="2.2625507"
inkscape:cx="839.50648"
inkscape:cy="437.82829"
inkscape:window-x="0"
inkscape:window-y="34"
inkscape:window-maximized="1"
inkscape:current-layer="abd2d575-db47-4a01-ab9c-7ced98725eec" />
<title
id="title4701">texting</title>
<path
d="M19,808.44H1142S1240.45,681,1124.57,550c-43-48.58-46.27-108.65-33.39-165C1126,232.68,986.65,97.88,835.46,137.44c-2.88.76-5.79,1.53-8.72,2.33-227.89,62.15-414.47-.31-486.28-31.23a206.52,206.52,0,0,0-90.8-16.78c-80.59,3.6-202.72,34.64-89.28,205.62C320.31,538.42,19,808.44,19,808.44Z"
transform="translate(-19 -91.56)"
fill="#6c63ff"
opacity="0.1"
id="path4703" />
<ellipse
cx="270.5"
cy="666.44"
rx="22.5"
ry="29.45"
fill="#3f3d56"
id="ellipse4705" />
<ellipse
cx="270.5"
cy="630.44"
rx="22.5"
ry="29.45"
fill="#3f3d56"
id="ellipse4707" />
<ellipse
cx="270.5"
cy="594.44"
rx="22.5"
ry="29.45"
fill="#3f3d56"
id="ellipse4709" />
<ellipse
cx="270.5"
cy="558.44"
rx="22.5"
ry="29.45"
fill="#3f3d56"
id="ellipse4711" />
<ellipse
cx="270.5"
cy="522.44"
rx="22.5"
ry="29.45"
fill="#3f3d56"
id="ellipse4713" />
<ellipse
cx="270.5"
cy="486.44"
rx="22.5"
ry="29.45"
fill="#3f3d56"
id="ellipse4715" />
<ellipse
cx="270.5"
cy="450.44"
rx="22.5"
ry="29.45"
fill="#3f3d56"
id="ellipse4717" />
<ellipse
cx="457.5"
cy="675.44"
rx="12.5"
ry="16.36"
fill="#3f3d56"
id="ellipse4719" />
<ellipse
cx="457.5"
cy="655.44"
rx="12.5"
ry="16.36"
fill="#3f3d56"
id="ellipse4721" />
<ellipse
cx="457.5"
cy="635.44"
rx="12.5"
ry="16.36"
fill="#3f3d56"
id="ellipse4723" />
<ellipse
cx="457.5"
cy="615.44"
rx="12.5"
ry="16.36"
fill="#3f3d56"
id="ellipse4725" />
<ellipse
cx="457.5"
cy="595.44"
rx="12.5"
ry="16.36"
fill="#3f3d56"
id="ellipse4727" />
<ellipse
cx="457.5"
cy="575.44"
rx="12.5"
ry="16.36"
fill="#3f3d56"
id="ellipse4729" />
<ellipse
cx="457.5"
cy="555.44"
rx="12.5"
ry="16.36"
fill="#3f3d56"
id="ellipse4731" />
<path
d="M373.73,295.64a109.12,109.12,0,0,0,8.38-12.33L323,273.6l63.94.47A108,108,0,0,0,389,188.68l-85.79,44.5L382.31,175a107.78,107.78,0,1,0-178,120.62A107.91,107.91,0,0,0,192,315.3l76.74,39.87L186.93,327.7a107.81,107.81,0,0,0,17.38,101.21,107.77,107.77,0,1,0,169.42,0,107.81,107.81,0,0,0,0-133.27Z"
transform="translate(-19 -91.56)"
fill="#6c63ff"
id="path4733" />
<path
d="M181.24,362.28a107.29,107.29,0,0,0,23.07,66.63,107.77,107.77,0,1,0,169.42,0C388.17,410.57,181.24,350.18,181.24,362.28Z"
transform="translate(-19 -91.56)"
opacity="0.1"
id="path4735" />
<path
d="M420.28,465.61a70.68,70.68,0,0,1-5.58-8.22l39.41-6.48-42.63.32a72,72,0,0,1-1.37-56.93L467.3,424,414.56,385.2a71.85,71.85,0,1,1,118.67,80.41,71.62,71.62,0,0,1,8.19,13.1l-51.16,26.58L544.81,487a71.85,71.85,0,0,1-11.58,67.48,71.85,71.85,0,1,1-113,0,71.89,71.89,0,0,1,0-88.85Z"
transform="translate(-19 -91.56)"
fill="#6c63ff"
id="path4737" />
<path
d="M548.61,510a71.57,71.57,0,0,1-15.38,44.43,71.85,71.85,0,1,1-113,0C410.65,542.23,548.61,502,548.61,510Z"
transform="translate(-19 -91.56)"
opacity="0.1"
id="path4739" />
<path
d="M19,808.44s120.52-117,162.24-77.64,217.87,49.83,234.1,47.51,111.25-6.95,164.56-1.16,542.35-16.22,562.05,31.29Z"
transform="translate(-19 -91.56)"
fill="#3f3d56"
id="path4741" />
<path
d="M800.08,236h-54.2a9.23,9.23,0,0,0,2-5.8,9.33,9.33,0,0,0-.68-3.47h25a9.27,9.27,0,0,0,0-18.54H622.77a9.3,9.3,0,0,0-9.27,9.27,9.1,9.1,0,0,0,.69,3.47h-25a9.28,9.28,0,0,0,0,18.55h54.2a9.2,9.2,0,0,0-2,5.79,9.3,9.3,0,0,0,9.27,9.27H800.08a9.27,9.27,0,0,0,0-18.54Z"
transform="translate(-19 -91.56)"
fill="#6c63ff"
opacity="0.1"
id="path4743" />
<path
d="M1044.6,369.23H990.4a9.23,9.23,0,0,0,2.05-5.8,9.13,9.13,0,0,0-.68-3.47h25a9.27,9.27,0,0,0,0-18.54H867.3a9.3,9.3,0,0,0-9.27,9.27,9.09,9.09,0,0,0,.68,3.47h-25a9.28,9.28,0,0,0,0,18.55h54.2a9.2,9.2,0,0,0-2,5.79,9.3,9.3,0,0,0,9.27,9.27H1044.6a9.27,9.27,0,1,0,0-18.54Z"
transform="translate(-19 -91.56)"
fill="#6c63ff"
opacity="0.1"
id="path4745" />
<path
d="M555.92,312.84H524.17a5.39,5.39,0,0,0,1.2-3.39,5.53,5.53,0,0,0-.4-2h14.66a5.43,5.43,0,0,0,0-10.86H452a5.45,5.45,0,0,0-5.43,5.43,5.34,5.34,0,0,0,.41,2H432.36a5.43,5.43,0,1,0,0,10.86h31.75a5.39,5.39,0,0,0-1.2,3.39,5.45,5.45,0,0,0,5.43,5.44h87.58a5.44,5.44,0,0,0,0-10.87Z"
transform="translate(-19 -91.56)"
fill="#6c63ff"
opacity="0.1"
id="path4747" />
<path
d="M708.47,755.55a23.78,23.78,0,0,1-1.74,6A43,43,0,0,0,717.34,764a1,1,0,0,0,.68-.1c.27-.21.23-.63.19-1a13.29,13.29,0,0,1,1.1-5.76q3-8.35,5.92-16.72c-3.46-1.57-10.62-.62-13.54,2C709,744.84,709.12,752.17,708.47,755.55Z"
transform="translate(-19 -91.56)"
fill="#a1616a"
id="path4749" />
<path
d="M758.09,764.88a1.92,1.92,0,0,0-.18.74c0,.89,1.18,1.2,2.07,1.28a56.67,56.67,0,0,1,12.62,2.63c.5-1.48-.2-3.07-.22-4.62,0-2.16,1.27-4.08,2.06-6.09,1.41-3.55,1.3-7.49,1.17-11.31-3.42.23-11.58-1.35-13.49,2.19-1.06,2-1,5-1.48,7.2A55.7,55.7,0,0,1,758.09,764.88Z"
transform="translate(-19 -91.56)"
fill="#a1616a"
id="path4751" />
<path
d="M737.75,611.19l-9,.53a2.58,2.58,0,0,0-1.64.48,2.46,2.46,0,0,0-.61,1.57c-1.2,9,.91,18.19-.21,27.19-.45,3.54-1.39,7-2.12,10.48-1.23,5.81-1.9,11.72-2.57,17.62a40.19,40.19,0,0,0-.34,8.83c.26,2.48,1,4.9,1.09,7.39a25.7,25.7,0,0,1-1.52,8.64,94,94,0,0,1-5.6,13.87c-1.93,3.87-4.14,7.64-5.31,11.8-1.86,6.62-1,13.83-3.21,20.34-.55,1.6-1.29,3.25-1,4.91.39,2.15,2.48,3.68,4.63,4.09a16.44,16.44,0,0,0,6.49-.49c3.51-.75,7.24-1.62,9.71-4.22a17.78,17.78,0,0,0,3.24-5.82c1.63-4,3.25-8.06,4.74-12.14a46.12,46.12,0,0,1,2.3-5.65c.82-1.56,1.84-3,2.71-4.56a46.34,46.34,0,0,0,3.48-8.4c6.42-19.7,8.38-41,18.08-59.3.46-.87.52-2,1-2.82a29.14,29.14,0,0,1,1.34,9.21,193.59,193.59,0,0,1-.51,21.27c-.4,5.16-1,10.31-1.12,15.49,0,2.36,0,4.73.05,7.1l0,1.93c.08,3.37.16,6.74.49,10.09,1.41,13.84-2.95,27.3-1.38,41.12a2.39,2.39,0,0,0,.46,1.36c.67.72,1.86.33,2.77-.06a29.76,29.76,0,0,1,9.46-2.36,6.38,6.38,0,0,0,2.87-.64c1.44-.86,1.85-2.73,2.11-4.39q3.27-20.19,6.53-40.38a96.54,96.54,0,0,0,1.24-9.73c.25-4.24-.06-8.5.35-12.72.57-6,3-11.7,4.82-17.4.94-3,1.89-5.95,2.75-8.95s1.65-6.11,2.35-9.19c1.2-5.36,2.14-10.89,1.31-16.32s-3.69-10.79-8.51-13.41c-3.53-1.93-7.67-2.22-11.66-2.66-11.39-1.25-22.74-4-34.15-3C741.68,612.07,739.79,611.07,737.75,611.19Z"
transform="translate(-19 -91.56)"
fill="#3f3d56"
id="path4753" />
<path
d="M712.51,794.74a12.83,12.83,0,0,0,1.42-3l3.69-10a86.92,86.92,0,0,0,3-9.13,42.77,42.77,0,0,0,1.22-11.32,42.15,42.15,0,0,1-11.11-1.56,17.17,17.17,0,0,0-4.22-.89,7.34,7.34,0,0,0-7.12,7.77,11.19,11.19,0,0,1,.16,1.82,5.69,5.69,0,0,1-.52,1.75l-3.94,9.6a21.07,21.07,0,0,0-2,7.4C692.81,796.16,706.84,802.91,712.51,794.74Z"
transform="translate(-19 -91.56)"
fill="#3f3d56"
id="path4755" />
<path
d="M750.51,793.87a17.17,17.17,0,0,0,0,4.53c.78,4.23,4.87,7.44,9.16,7.74a13.13,13.13,0,0,0,11.11-5.47,11.8,11.8,0,0,0,1.85-3.55,18.43,18.43,0,0,0,.62-3.77l1.59-16.83c.23-2.36,1.19-5.77,0-8-1-1.89-3.43-2.51-5.3-3.22a20.85,20.85,0,0,0-9.82-1.47c-5.83.49-5.77,6.44-6.65,11.19Q751.35,784.4,750.51,793.87Z"
transform="translate(-19 -91.56)"
fill="#3f3d56"
id="path4757" />
<path
d="M752.24,452.06c-2.69,3.77-6.61,6.54-9.25,10.35a19.45,19.45,0,0,0-3.41,11.85c.09,2.16.54,4.37,0,6.46A11.49,11.49,0,0,1,737,485a35.94,35.94,0,0,1-16.43,10.92c-.77.24-1.73.76-1.54,1.54a1.59,1.59,0,0,0,.71.81l13,9.31c7.63,5.47,15.3,11,23.74,15.09a84.34,84.34,0,0,0,48.5,7.63,25.29,25.29,0,0,1-3.85-11c-.5-4.7.34-9.44.25-14.17a36.62,36.62,0,0,0-3-14c-1.12-2.57-2.54-5-3.4-7.67a39.68,39.68,0,0,1-1.44-10l-.67-11.19a22.58,22.58,0,0,0-.93-6.13,15.19,15.19,0,0,0-2.38-4.22c-4.76-6.22-12-8.88-19.58-9.53-3.31-.28-7.36-.55-10.41.89C755.92,445.07,754.44,449,752.24,452.06Z"
transform="translate(-19 -91.56)"
fill="#3f3d56"
id="path4759" />
<path
d="M751,492.42a7.43,7.43,0,0,1-4.69,4.36,22.12,22.12,0,0,1-6.54,1L735,498a2.08,2.08,0,0,0-1.47.44,1.72,1.72,0,0,0-.42,1.07c-.3,2.54,1.18,4.91,2.66,7,4.74,6.64,10.42,12.95,17.83,16.34a29.41,29.41,0,0,0,33.9-6.84,3,3,0,0,0,.94-1.67A2.81,2.81,0,0,0,787,512c-1.58-1.17-3.44-1.93-5-3.09a15,15,0,0,1-5.43-8.21,24.7,24.7,0,0,1-.45-9.95c-5.93-1.69-12.39-.35-18.33-2-1.39-.38-4-1.84-5.46-1S751.46,491,751,492.42Z"
transform="translate(-19 -91.56)"
fill="#a1616a"
id="path4761" />
<path
d="M751,492.42a7.43,7.43,0,0,1-4.69,4.36,22.12,22.12,0,0,1-6.54,1L735,498a2.08,2.08,0,0,0-1.47.44,1.72,1.72,0,0,0-.42,1.07c-.3,2.54,1.18,4.91,2.66,7,4.74,6.64,10.42,12.95,17.83,16.34a29.41,29.41,0,0,0,33.9-6.84,3,3,0,0,0,.94-1.67A2.81,2.81,0,0,0,787,512c-1.58-1.17-3.44-1.93-5-3.09a15,15,0,0,1-5.43-8.21,24.7,24.7,0,0,1-.45-9.95c-5.93-1.69-12.39-.35-18.33-2-1.39-.38-4-1.84-5.46-1S751.46,491,751,492.42Z"
transform="translate(-19 -91.56)"
opacity="0.1"
id="path4763" />
<circle
cx="747.07"
cy="390.79"
r="19.97"
fill="#a1616a"
id="circle4765" />
<path
d="M780.6,502.7a65.52,65.52,0,0,1-11.46,3.72,22.06,22.06,0,0,1-11.9-.65,41.43,41.43,0,0,1-7.37-4.2c-4.14-2.65-8.66-5-13.56-5.48-2.29-.24-4.79,0-6.6,1.42a9,9,0,0,0-2.91,6c-.66,4.76.47,9.56,1.48,14.26,3.83,17.85,6,36.52,1.71,54.28-2.25,9.43.18,19.24.51,28.93a34.62,34.62,0,0,0,1.31,9.85c1.05,3.15,3.17,6.1,6.23,7.39,2.85,1.2,6.08.83,9.16.58,3.67-.29,7.5-.34,10.83,1.2,2.36,1.1,4.57,3,7.15,2.77,1.32-.12,2.52-.8,3.83-1,2.63-.44,5.15,1,7.54,2.18a46.66,46.66,0,0,0,19,4.81c3.37-7.93,1.63-16.85,2-25.46.15-3.22.63-6.42.82-9.64a107.82,107.82,0,0,0-.15-11.65L797,555.81c-.35-7.13,3.29-14.11,3-21.23a64.26,64.26,0,0,0-1.37-9.86c-.22-1.1-.45-2.19-.76-3.26-2.2-7.51-8.53-13-14.58-17.93A2.93,2.93,0,0,0,780.6,502.7Z"
transform="translate(-19 -91.56)"
fill="#6c63ff"
id="path4767" />
<path
d="M747.22,619.63c.08,7-2.44,13.77-4.91,20.34-.76,2-1.69,4.22-3.63,5.15s-4.11.33-6-.41a40.37,40.37,0,0,1-5.13-2.37c-1.74-1-3.7-2.07-5.57-1.43-2.29.78-3,3.63-3.31,6a63.45,63.45,0,0,1-9.35-.57,1.76,1.76,0,0,1-.82-.26,1.73,1.73,0,0,1-.52-1.12c-.32-1.67-.65-3.33-1-5-.1-.52-.48-1.21-1-1,.42-6.48-.93-12.93-1.42-19.4a51.45,51.45,0,0,1,.1-9.85,95.42,95.42,0,0,1,1.88-9.73c1.54-6.86,4.5-13.83,4.67-20.79a23.49,23.49,0,0,0-.27-4.18q-2.12-13.09-4.22-26.2c-.36-2.18-.71-4.38-1.22-6.53-.38-1.6-.74-3.15-1.15-4.69-.25-.94-.52-1.87-.83-2.81q4-11,8-22a40.43,40.43,0,0,1,2.2-5.27,20.62,20.62,0,0,1,8.95-8.73,8.07,8.07,0,0,0,3-2c.39-.51.63-1.13,1-1.66,1.41-2,4.3-2.16,6.71-1.72,2.21.39,4.5,1.23,5.84,3A11.77,11.77,0,0,1,740.5,499q2.4,5.82,4.82,11.63c1.37.66,2,2.45,2,4,0,2.88-.41,5.75-.29,8.63,0,.21,0,.41.06.62.36,2.59,2.19,4.77,2.81,7.37,2.38,9.95-1.09,20.19-3.13,30.25-.08.4-.16.79-.23,1.19a90.88,90.88,0,0,0-1.6,17.73,50.68,50.68,0,0,0,.52,7.38c.33,2.15.86,4.27,1.1,6.44a59.58,59.58,0,0,1,0,9.06C746.28,608.71,747.16,614.16,747.22,619.63Z"
transform="translate(-19 -91.56)"
fill="#454b69"
id="path4769" />
<path
d="M755.46,533.92a8.61,8.61,0,0,1,1.14,4,2.66,2.66,0,0,1-.6,1.66,2.5,2.5,0,0,1-2.29.63,6.29,6.29,0,0,1-2.23-1,5.17,5.17,0,0,1-2.13-2.14,5.57,5.57,0,0,1-.32-1.34l-.53-3.38a4.31,4.31,0,0,1,0-2c.45-1.39,2.47-3.85,4-2.65S754.71,532.32,755.46,533.92Z"
transform="translate(-19 -91.56)"
fill="#454b69"
id="path4771" />
<path
d="M814,539.48c-.59-1.54-2.35-2.19-3.79-3-3-1.67-5.08-4.59-6.66-7.63s-2.76-6.28-4.47-9.24a3.6,3.6,0,0,0-1.14-1.36,1.38,1.38,0,0,0-1.66.1,1.8,1.8,0,0,0-.37.95c-.36,2.21.27,4.44.61,6.65a25.92,25.92,0,0,1-6.34,21c-2.07,2.29-4.64,4.3-5.75,7.18a16.91,16.91,0,0,0-.6,7l.69,12.76a120.6,120.6,0,0,1-4.35,38.49c-2,7.2-5.38,14.47-4,21.81.52,2.81,1.74,5.6,1.24,8.42a2.87,2.87,0,0,0,0,1.64,2.39,2.39,0,0,0,1,.93l6.65,4.34,6-17.77a32.86,32.86,0,0,0,1.33-4.65,35.54,35.54,0,0,0,.39-5.46,230.54,230.54,0,0,1,6.17-47.44,36.41,36.41,0,0,1,2-6.56c1.73-3.89,4.64-7.11,7.51-10.27.07-1.47-.12-2.94,0-4.42a28.29,28.29,0,0,1,1.09-7.62C810.33,542.9,811.67,540.56,814,539.48Z"
transform="translate(-19 -91.56)"
fill="#454b69"
id="path4773" />
<path
d="M803.91,556.08a90.29,90.29,0,0,0,.19,12.63l1,16.74a24.07,24.07,0,0,0,1,6.61c.8,2.28,2.28,4.28,3.05,6.58.26.78.44,1.6.7,2.39.33,1,.78,1.87,1.2,2.8,2.7,6.11,3.44,12.89,3.63,19.56s-.15,13.36.53,20a12.07,12.07,0,0,1-5.31.26,7.43,7.43,0,0,0-2-.27,6.31,6.31,0,0,0-1.68.48,17.31,17.31,0,0,1-14.53-1.3,29,29,0,0,1-3.71-2.72,11.37,11.37,0,0,1-2.54-2.69,10,10,0,0,1-1.15-4.86c-.23-6.79,2-13.39,3.51-20a185.9,185.9,0,0,0,3-19.39c1.11-9.53.81-19.33,3-28.68a7.9,7.9,0,0,1,1.6-3.71,8,8,0,0,1,2.58-1.66C799.93,557.9,801.92,557,803.91,556.08Z"
transform="translate(-19 -91.56)"
fill="#454b69"
id="path4775" />
<path
d="M814,539.48c-.59-1.54-2.35-2.19-3.79-3-3-1.67-5.08-4.59-6.66-7.63s-2.76-6.28-4.47-9.24a3.6,3.6,0,0,0-1.14-1.36,1.38,1.38,0,0,0-1.66.1,1.8,1.8,0,0,0-.37.95c-.36,2.21.27,4.44.61,6.65a25.92,25.92,0,0,1-6.34,21c-2.07,2.29-4.64,4.3-5.75,7.18a16.91,16.91,0,0,0-.6,7l.69,12.76a120.6,120.6,0,0,1-4.35,38.49c-2,7.2-5.38,14.47-4,21.81.52,2.81,1.74,5.6,1.24,8.42a2.87,2.87,0,0,0,0,1.64,2.39,2.39,0,0,0,1,.93l6.65,4.34,6-17.77a32.86,32.86,0,0,0,1.33-4.65,35.54,35.54,0,0,0,.39-5.46,230.54,230.54,0,0,1,6.17-47.44,36.41,36.41,0,0,1,2-6.56c1.73-3.89,4.64-7.11,7.51-10.27.07-1.47-.12-2.94,0-4.42a28.29,28.29,0,0,1,1.09-7.62C810.33,542.9,811.67,540.56,814,539.48Z"
transform="translate(-19 -91.56)"
opacity="0.1"
id="path4777" />
<path
d="M806.59,644.53a3.57,3.57,0,0,1-1.3,1.88c-.67.38-1.76.06-1.82-.71-1.12-.15-2,1.17-3.08,1.26s-2.08-1-3.14-.76c-.68.18-1.32.9-1.94.58-.26-.13-.42-.41-.69-.51-.43-.14-.84.26-1.28.36-.93.21-1.61-.93-1.64-1.88s.25-2-.19-2.82a.6.6,0,0,0-.29-.32c-.3-.14-.63.14-.83.41-.74,1-.95,2.39-1.7,3.43s-2.54,1.53-3.22.45a2.55,2.55,0,0,1-.14-1.81c.45-2.56,1.45-5.31,3.75-6.51,2-1,4.4-.62,6.6-.16q4.59,1,9.17,2.07c.81.19,2.75.29,3.1,1.23S806.91,643.73,806.59,644.53Z"
transform="translate(-19 -91.56)"
fill="#a1616a"
id="path4779" />
<path
d="M770.57,468.25c1,5.39,4.08,10.14,6.55,15.05s4.44,10.49,3.1,15.81a55,55,0,0,1-2,5.39,50.92,50.92,0,0,0-2.06,9.58c-.66,4.42-1.32,8.91-.79,13.35s2.42,8.9,6,11.6l-.28-2a22.23,22.23,0,0,1,4.23,2.91,26.86,26.86,0,0,0,3-8.15l6.79,5.27c3,2.3,6.36,4.72,10.08,4.26l-3-6.91c-.83-1.92-1.66-3.83-2.64-5.68a17.87,17.87,0,0,1-1.62-3.46,15.61,15.61,0,0,1-.44-3.2,223.13,223.13,0,0,0-9.82-52,43.35,43.35,0,0,0-4.73-11A13.78,13.78,0,0,0,779,455c-2.49-1.59-5.55-1.93-8.49-2.23a26.19,26.19,0,0,0-6.19-.14,19,19,0,0,0-6,2.06,32,32,0,0,0-13.84,13.73,27.2,27.2,0,0,0-3.13,13.89c.39,5.1,5.25,2.78,8.53,1.51C758,480.65,765.8,475.73,770.57,468.25Z"
transform="translate(-19 -91.56)"
fill="#3f3d56"
id="path4781" />
<path
d="M755.46,533.92a8.61,8.61,0,0,1,1.14,4,2.66,2.66,0,0,1-.6,1.66,2.5,2.5,0,0,1-2.29.63,6.29,6.29,0,0,1-2.23-1,5.17,5.17,0,0,1-2.13-2.14,5.57,5.57,0,0,1-.32-1.34l-.53-3.38a4.31,4.31,0,0,1,0-2c.45-1.39,2.47-3.85,4-2.65S754.71,532.32,755.46,533.92Z"
transform="translate(-19 -91.56)"
opacity="0.1"
id="path4783" />
<polygon
points="742.71 407.39 738.96 435.48 755.44 434.48 760.8 407.39 742.71 407.39"
fill="#454b69"
id="polygon4785" />
<circle
cx="750.82"
cy="415.75"
r="1.25"
fill="#fff"
id="circle4787" />
<g
opacity="0.1"
id="g4793">
<path
d="M780.65,495.73c.17-.49.32-1,.45-1.48,1.33-5.33-.64-10.91-3.11-15.82s-5.51-9.65-6.54-15.05c-4.77,7.48-12.55,12.4-20.68,15.56-3.28,1.27-8.15,3.58-8.54-1.51a20.47,20.47,0,0,1,0-3.41,23.69,23.69,0,0,0-.9,8.15c.39,5.1,5.25,2.78,8.53,1.51,8.14-3.15,15.91-8.07,20.68-15.55,1,5.39,4.08,10.14,6.55,15C779.09,487.09,780.74,491.43,780.65,495.73Z"
transform="translate(-19 -91.56)"
id="path4789" />
<path
d="M796,532.2q-3.4-2.62-6.79-5.27a26.78,26.78,0,0,1-3,8.16,22,22,0,0,0-4.23-2.92l.28,2c-3.56-2.7-5.45-7.16-6-11.6s.12-8.93.78-13.35c.07-.48.15-1,.22-1.44-.47,2-.78,4.11-1.09,6.18-.66,4.43-1.32,8.91-.79,13.35s2.42,8.9,6,11.6l-.28-2a22.75,22.75,0,0,1,4.23,2.92,27,27,0,0,0,3-8.15q3.4,2.63,6.79,5.27c3,2.29,6.36,4.72,10.08,4.26l-2.16-5C800.49,535.58,798.13,533.86,796,532.2Z"
transform="translate(-19 -91.56)"
id="path4791" />
</g>
<path
d="M752.67,513.9a23.59,23.59,0,0,1,5.59-5.34,1.68,1.68,0,0,1,1.15-.32c.32.07.59.31.92.37a1.83,1.83,0,0,0,1.08-.26l4.62-2.22a4.9,4.9,0,0,1,2.21-.64,1.62,1.62,0,0,1,1.61,1.39c0,.86-.85,1.44-1.61,1.87a43.21,43.21,0,0,0-6.48,4.52c-1,.89-2.14,2.36-1.36,3.5a3,3,0,0,0,2.54.85,20.39,20.39,0,0,0,5-.38c1.32-.33,2.56-.92,3.89-1.23a19.13,19.13,0,0,1,3.71-.37,3,3,0,0,1,2.09.45c.55.47.5,1.6-.21,1.74,1,1.33.12,3.29-1.19,4.29s-3,1.41-4.37,2.26c1.6-.57,3.49-1.12,4.91-.18a1,1,0,0,1,.45.56c.09.47-.41.83-.85,1-6.56,3.06-14.33,2-21.2,4.27a22.55,22.55,0,0,0-7.88,4.55c1.07-1-5.52-8-4.95-10,.46-1.64,3.77-3.73,5-5Q750.1,516.89,752.67,513.9Z"
transform="translate(-19 -91.56)"
fill="#a1616a"
id="path4795" />
<path
d="M752.42,545.69a5.34,5.34,0,0,0,.35,2.68c.54,2.26-.25,4.63-1.34,6.69a29,29,0,0,1-4.68,6.39,61.7,61.7,0,0,1-7.06,6.26,152,152,0,0,1-15.4,10.62,12.82,12.82,0,0,1-4.45,1.95,13.33,13.33,0,0,1-3.73-.06,21.53,21.53,0,0,1-4.89-1,23.49,23.49,0,0,0-.27-4.18q-2.12-13.09-4.22-26.2c-.36-2.18-.71-4.38-1.22-6.53-.38-1.6-.74-3.15-1.15-4.69,0,0,0-.08,0-.11,1-2.52,3.93-4.12,4.22-6.8,4.23-.4,8.2-.87,12.44-1.26a28,28,0,0,1-.81,11.84,1.55,1.55,0,0,0,1.55-.95c.31-.57.41-1.23.67-1.83,1-2.28,3.93-2.82,6-4.14,3.64-2.26,5.21-7.16,9.13-8.86a1.43,1.43,0,0,1,.92-.16c.86.23,1,1.74,1.93,1.68a1.32,1.32,0,0,0,.74-.42c1.6-1.45,3.6-2.65,5.19-4.09.22.43.43.86.64,1.29q3.34,6.76,6.67,13.53c.87,1.76,1.84,3.63,3.57,4.56C756.55,543.9,753.05,543.66,752.42,545.69Z"
transform="translate(-19 -91.56)"
opacity="0.1"
id="path4797" />
<path
d="M707.37,528.6c-.28,2.69-3.23,4.29-4.22,6.81a12.2,12.2,0,0,0-.49,4.14c-.1,4-1,7.82-1.5,11.73s-.6,8,.84,11.68c.86,2.18,2.24,4.16,2.83,6.43.4,1.52.43,3.16,1.14,4.56,1.55,3,5.5,3.78,8.89,4.15a13.33,13.33,0,0,0,3.73.06,13,13,0,0,0,4.46-1.95,152.94,152.94,0,0,0,15.39-10.62c4.57-3.57,9-7.53,11.74-12.65,1.09-2.06,1.88-4.43,1.35-6.69a5.13,5.13,0,0,1-.35-2.68c.63-2,4.12-1.79,4.88-3.78-1.73-.92-2.7-2.79-3.56-4.55l-7.31-14.83c-1.6,1.45-3.6,2.65-5.19,4.1a1.38,1.38,0,0,1-.75.42c-.89.06-1.07-1.45-1.93-1.68a1.46,1.46,0,0,0-.91.15c-3.93,1.71-5.5,6.61-9.14,8.87-2.12,1.32-5,1.86-6,4.14-.26.59-.36,1.26-.67,1.83a1.55,1.55,0,0,1-1.55,1,27.88,27.88,0,0,0,.81-11.84C715.58,527.74,711.61,528.21,707.37,528.6Z"
transform="translate(-19 -91.56)"
fill="#454b69"
id="path4799" />
<path
d="M806.24,643.86c.27-.1.54-.21.81-.29a3.32,3.32,0,0,0,.9-3.6c-.35-.94-2.29-1-3.1-1.23q-4.58-1.1-9.17-2.06c-2.2-.47-4.61-.88-6.6.15-.74.39-1.35,1.69-1.85,2.35l.77.66c.83.69,1.68,1.37,2.56,2a.63.63,0,0,1,.66-.21.6.6,0,0,1,.29.32,2,2,0,0,1,.21.63A17.26,17.26,0,0,0,806.24,643.86Z"
transform="translate(-19 -91.56)"
fill="#454b69"
id="path4801" />
<path
d="M1157,459.81v85.44c0,2.54-4.76,4.61-10.65,4.61H920.81c-5.89,0-10.66-2.07-10.66-4.61V459.81c0-2.55,4.77-5.14,10.66-5.14h225.55C1152.25,454.67,1157,457.26,1157,459.81Z"
transform="translate(-19 -91.56)"
fill="#3f3d56"
id="path4803" />
<rect
x="999.49"
y="393.65"
width="117.48"
height="4.15"
fill="#6c63ff"
id="rect4805" />
<rect
x="999.49"
y="403.75"
width="117.48"
height="4.15"
fill="#6c63ff"
opacity="0.5"
id="rect4807" />
<rect
x="999.49"
y="413.86"
width="117.48"
height="4.15"
fill="#6c63ff"
opacity="0.5"
id="rect4809" />
<rect
x="999.49"
y="423.97"
width="117.48"
height="4.15"
fill="#6c63ff"
opacity="0.5"
id="rect4811" />
<path
d="M1157,220.13v85.45c0,2.54-4.76,4.61-10.65,4.61H920.81c-5.89,0-10.66-2.07-10.66-4.61V220.13c0-2.54,4.77-5.14,10.66-5.14h225.55C1152.25,215,1157,217.59,1157,220.13Z"
transform="translate(-19 -91.56)"
fill="#3f3d56"
id="path4815" />
<rect
x="999.49"
y="153.97"
width="117.48"
height="4.15"
fill="#6c63ff"
id="rect4817" />
<rect
x="999.49"
y="164.08"
width="117.48"
height="4.15"
fill="#6c63ff"
opacity="0.5"
id="rect4819" />
<rect
x="999.49"
y="174.19"
width="117.48"
height="4.15"
fill="#6c63ff"
opacity="0.5"
id="rect4821" />
<rect
x="999.49"
y="184.29"
width="117.48"
height="4.15"
fill="#6c63ff"
opacity="0.5"
id="rect4823" />
<path
d="M1045,339.47v85.45c0,2.54-4.77,4.6-10.66,4.6H808.76c-5.88,0-10.65-2.06-10.65-4.6V339.47c0-2.55,4.77-5.14,10.65-5.14h225.55C1040.2,334.33,1045,336.92,1045,339.47Z"
transform="translate(-19 -91.56)"
fill="#3f3d56"
id="path4827" />
<rect
x="887.44"
y="273.31"
width="117.48"
height="4.15"
fill="#6c63ff"
id="rect4829" />
<rect
x="887.44"
y="283.42"
width="117.48"
height="4.15"
fill="#6c63ff"
opacity="0.5"
id="rect4833" />
<rect
x="887.44"
y="293.52"
width="117.48"
height="4.15"
fill="#6c63ff"
opacity="0.5"
id="rect4835" />
<rect
x="887.44"
y="303.63"
width="117.48"
height="4.15"
fill="#6c63ff"
opacity="0.5"
id="rect4837" />
<style
id="style4919"
type="text/css">
.st0{fill:#77B3D4;}
.st1{opacity:0.2;}
.st2{fill:#231F20;}
.st3{fill:#FFFFFF;}
.st4{fill:#C75C5C;}
.st5{fill:#4F5D73;}
.st6{fill:#E0E0D1;}
</style>
<g
id="g5924"
transform="translate(911.18328,138.60391)">
<g
id="Layer_1">
<g
id="g4961">
<g
id="g4927"
class="st1"
style="opacity:0.2">
<path
id="path4925"
d="m 12,25 v 25 c 0,2.2 1.8,4 4,4 h 32 c 2.2,0 4,-1.8 4,-4 V 25 Z"
class="st2"
inkscape:connector-curvature="0"
style="fill:#231f20" />
</g>
<g
id="g4931">
<path
id="path4929"
d="m 12,23 v 25 c 0,2.2 1.8,4 4,4 h 32 c 2.2,0 4,-1.8 4,-4 V 23 Z"
class="st3"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
</g>
<g
id="g4935"
class="st1"
style="opacity:0.2">
<path
id="path4933"
d="M 48,14 H 16 c -2.2,0 -4,1.8 -4,4 v 7 h 40 v -7 c 0,-2.2 -1.8,-4 -4,-4 z"
class="st2"
inkscape:connector-curvature="0"
style="fill:#231f20" />
</g>
<g
id="g4939">
<path
id="path4937"
d="M 48,12 H 16 c -2.2,0 -4,1.8 -4,4 v 7 h 40 v -7 c 0,-2.2 -1.8,-4 -4,-4 z"
class="st4"
inkscape:connector-curvature="0"
style="fill:#c75c5c" />
</g>
<g
id="g4947"
class="st1"
style="opacity:0.2">
<path
id="path4945"
d="m 20,21 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
class="st2"
inkscape:connector-curvature="0"
style="fill:#231f20" />
</g>
<g
id="g4951"
class="st1"
style="opacity:0.2">
<path
id="path4949"
d="m 45,21 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
class="st2"
inkscape:connector-curvature="0"
style="fill:#231f20" />
</g>
<g
id="g4955">
<path
id="path4953"
d="m 20,19 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
class="st6"
inkscape:connector-curvature="0"
style="fill:#e0e0d1" />
</g>
<g
id="g4959">
<path
id="path4957"
d="m 45,19 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
class="st6"
inkscape:connector-curvature="0"
style="fill:#e0e0d1" />
</g>
</g>
</g>
<g
id="Layer_2">
</g>
</g>
<g
id="g5924-4"
transform="translate(794.71,254.6)">
<g
id="Layer_1-5">
<g
id="g4961-4">
<g
id="g4927-7"
class="st1"
style="opacity:0.2">
<path
id="path4925-4"
d="m 12,25 v 25 c 0,2.2 1.8,4 4,4 h 32 c 2.2,0 4,-1.8 4,-4 V 25 Z"
class="st2"
inkscape:connector-curvature="0"
style="fill:#231f20" />
</g>
<g
id="g4931-4">
<path
id="path4929-3"
d="m 12,23 v 25 c 0,2.2 1.8,4 4,4 h 32 c 2.2,0 4,-1.8 4,-4 V 23 Z"
class="st3"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
</g>
<g
id="g4935-0"
class="st1"
style="opacity:0.2">
<path
id="path4933-7"
d="M 48,14 H 16 c -2.2,0 -4,1.8 -4,4 v 7 h 40 v -7 c 0,-2.2 -1.8,-4 -4,-4 z"
class="st2"
inkscape:connector-curvature="0"
style="fill:#231f20" />
</g>
<g
id="g4939-8">
<path
id="path4937-6"
d="M 48,12 H 16 c -2.2,0 -4,1.8 -4,4 v 7 h 40 v -7 c 0,-2.2 -1.8,-4 -4,-4 z"
class="st4"
inkscape:connector-curvature="0"
style="fill:#c75c5c" />
</g>
<g
id="g4947-8"
class="st1"
style="opacity:0.2">
<path
id="path4945-8"
d="m 20,21 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
class="st2"
inkscape:connector-curvature="0"
style="fill:#231f20" />
</g>
<g
id="g4951-4"
class="st1"
style="opacity:0.2">
<path
id="path4949-3"
d="m 45,21 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
class="st2"
inkscape:connector-curvature="0"
style="fill:#231f20" />
</g>
<g
id="g4955-1">
<path
id="path4953-4"
d="m 20,19 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
class="st6"
inkscape:connector-curvature="0"
style="fill:#e0e0d1" />
</g>
<g
id="g4959-9">
<path
id="path4957-2"
d="m 45,19 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
class="st6"
inkscape:connector-curvature="0"
style="fill:#e0e0d1" />
</g>
</g>
</g>
<g
id="Layer_2-0" />
</g>
<g
id="g5924-6"
transform="translate(913.83516,374.62072)">
<g
id="Layer_1-8">
<g
id="g4961-6">
<g
id="g4927-6"
class="st1"
style="opacity:0.2">
<path
id="path4925-49"
d="m 12,25 v 25 c 0,2.2 1.8,4 4,4 h 32 c 2.2,0 4,-1.8 4,-4 V 25 Z"
class="st2"
inkscape:connector-curvature="0"
style="fill:#231f20" />
</g>
<g
id="g4931-5">
<path
id="path4929-0"
d="m 12,23 v 25 c 0,2.2 1.8,4 4,4 h 32 c 2.2,0 4,-1.8 4,-4 V 23 Z"
class="st3"
inkscape:connector-curvature="0"
style="fill:#ffffff" />
</g>
<g
id="g4935-4"
class="st1"
style="opacity:0.2">
<path
id="path4933-8"
d="M 48,14 H 16 c -2.2,0 -4,1.8 -4,4 v 7 h 40 v -7 c 0,-2.2 -1.8,-4 -4,-4 z"
class="st2"
inkscape:connector-curvature="0"
style="fill:#231f20" />
</g>
<g
id="g4939-7">
<path
id="path4937-1"
d="M 48,12 H 16 c -2.2,0 -4,1.8 -4,4 v 7 h 40 v -7 c 0,-2.2 -1.8,-4 -4,-4 z"
class="st4"
inkscape:connector-curvature="0"
style="fill:#c75c5c" />
</g>
<g
id="g4947-7"
class="st1"
style="opacity:0.2">
<path
id="path4945-2"
d="m 20,21 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
class="st2"
inkscape:connector-curvature="0"
style="fill:#231f20" />
</g>
<g
id="g4951-7"
class="st1"
style="opacity:0.2">
<path
id="path4949-2"
d="m 45,21 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
class="st2"
inkscape:connector-curvature="0"
style="fill:#231f20" />
</g>
<g
id="g4955-2">
<path
id="path4953-6"
d="m 20,19 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
class="st6"
inkscape:connector-curvature="0"
style="fill:#e0e0d1" />
</g>
<g
id="g4959-1">
<path
id="path4957-0"
d="m 45,19 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
class="st6"
inkscape:connector-curvature="0"
style="fill:#e0e0d1" />
</g>
</g>
</g>
<g
id="Layer_2-6" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,18 +1,18 @@
<template> <template>
<span class="container"> <time class="container" :datetime="dateObj.getUTCSeconds()">
<span class="month">{{ month }}</span> <span class="month">{{ month }}</span>
<span class="day">{{ day }}</span> <span class="day">{{ day }}</span>
</span> </time>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
@Component @Component
export default class DateComponent extends Vue { export default class DateCalendarIcon extends Vue {
@Prop({ required: true }) date!: string; @Prop({ required: true }) date!: string;
get dateObj() { get dateObj() {
return new Date(this.$props.date); return new Date(this.$props.date);
} }
get month() { get month() {
@ -26,22 +26,27 @@ export default class DateComponent extends Vue {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { time.container {
display: inline-flex; background: #f6f7f8;
padding: 2px 0; border: 1px solid rgba(46,62,72,.12);
width: 40px; border-radius: 8px;
background: #fff; display: flex;
flex-direction: column;
justify-content: center;
/*height: 50px;*/
width: 48px;
padding: 8px;
text-align: center;
span { span {
flex: 0; display: block;
flex-direction: column;
text-align: center;
&.month { &.month {
color: #fa3e3e; color: #fa3e3e;
padding: 2px 0; padding: 2px 0;
font-size: 12px; font-size: 12px;
line-height: 12px; line-height: 12px;
text-transform: uppercase;
} }
&.day { &.day {

View File

@ -1,61 +1,124 @@
<template> <template>
<div class="card"> <router-link class="card" :to="{ name: 'Event', params: { uuid: event.uuid } }">
<div class="card-image" v-if="!event.image"> <div class="card-image" v-if="!event.image">
<figure class="image is-16by9"> <figure class="image is-16by9">
<img src="https://picsum.photos/g/400/225/?random"> <img src="https://picsum.photos/g/400/225/?random">
</figure> </figure>
</div> </div>
<div class="card-content"> <div class="content">
<div class="content"> <div class="title-wrapper">
<router-link :to="{ name: 'Event', params:{ uuid: event.uuid } }"> <div class="date-component">
<h2 class="title">{{ event.title }}</h2> <date-calendar-icon v-if="!mergedOptions.hideDate" :date="event.beginsOn" />
</router-link> </div>
<DateComponent v-if="!options.hideDate" :date="event.beginsOn" /> <h2 class="title" ref="title">{{ event.title }}</h2>
</div>
<div v-if="!options.hideDetails">
<div v-if="event.participants.length > 0 &&
options.loggedPerson &&
event.participants[0].actor.id === options.loggedPerson.id">
<b-tag type="is-info"><translate>Organizer</translate></b-tag>
</div>
<div v-else-if="event.participants.length === 1">
<translate
:translate-params="{name: event.participants[0].actor.preferredUsername}"
>%{name} organizes this event</translate>
</div>
<div v-else>
<span v-for="participant in event.participants" :key="participant.actor.uuid">
{{ participant.actor.preferredUsername }}
<span v-if="participant.role === ParticipantRole.CREATOR">(organizer)</span>,
<!-- <translate
:translate-params="{name: participant.actor.preferredUsername}"
>&nbsp;%{name} is in,</translate>-->
</span>
</div>
</div> </div>
<span>
<span v-if="event.physicalAddress && event.physicalAddress.locality">{{ event.physicalAddress.locality }} - </span>
<span v-if="actorDisplayName && actorDisplayName !== '@'">{{ actorDisplayName }}</span>
</span>
</div> </div>
</div> <!-- <div v-if="!mergedOptions.hideDetails" class="details">-->
<!-- <div v-if="event.participants.length > 0 &&-->
<!-- mergedOptions.loggedPerson &&-->
<!-- event.participants[0].actor.id === mergedOptions.loggedPerson.id">-->
<!-- <b-tag type="is-info"><translate>Organizer</translate></b-tag>-->
<!-- </div>-->
<!-- <div v-else-if="event.participants.length === 1">-->
<!-- <translate-->
<!-- :translate-params="{name: event.participants[0].actor.preferredUsername}"-->
<!-- >%{name} organizes this event</translate>-->
<!-- </div>-->
<!-- <div v-else>-->
<!-- <span v-for="participant in event.participants" :key="participant.actor.uuid">-->
<!-- {{ participant.actor.preferredUsername }}-->
<!-- <span v-if="participant.role === ParticipantRole.CREATOR">(organizer)</span>,-->
<!-- &lt;!&ndash; <translate-->
<!-- :translate-params="{name: participant.actor.preferredUsername}"-->
<!-- >&nbsp;%{name} is in,</translate>&ndash;&gt;-->
<!-- </span>-->
<!-- </div>-->
</router-link>
</template> </template>
<script lang="ts"> <script lang="ts">
import { IEvent, ParticipantRole } from '@/types/event.model'; import { IEvent, ParticipantRole } from '@/types/event.model';
import { Component, Prop, Vue } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import DateComponent from '@/components/Event/Date.vue'; import DateCalendarIcon from '@/components/Event/DateCalendarIcon.vue';
import { IActor, IPerson, Person } from '@/types/actor.model';
const lineClamp = require('line-clamp');
export interface IEventCardOptions {
hideDate: boolean;
loggedPerson: IPerson | boolean;
hideDetails: boolean;
organizerActor: IActor | null;
}
@Component({ @Component({
components: { components: {
DateComponent, DateCalendarIcon,
EventCard, EventCard,
}, },
mounted() {
lineClamp(this.$refs.title, 3);
},
}) })
export default class EventCard extends Vue { export default class EventCard extends Vue {
@Prop({ required: true }) event!: IEvent; @Prop({ required: true }) event!: IEvent;
@Prop({ default() { return { hideDate: false, loggedPerson: false, hideDetails: false }; } }) options!: object; @Prop({ required: false }) options!: IEventCardOptions;
data() { ParticipantRole = ParticipantRole;
return {
ParticipantRole, defaultOptions: IEventCardOptions = {
}; hideDate: false,
loggedPerson: false,
hideDetails: false,
organizerActor: null,
};
get mergedOptions(): IEventCardOptions {
return { ...this.defaultOptions, ...this.options };
} }
get actorDisplayName() {
const actor = Object.assign(new Person(), this.event.organizerActor || this.mergedOptions.organizerActor);
return actor.displayName();
}
} }
</script> </script>
<style lang="scss">
@import "../../variables";
a.card {
border: none;
background: $secondary;
div.card-image {
background: $secondary;
}
div.content {
padding: 5px;
background: $secondary;
div.title-wrapper {
display: flex;
div.date-component {
flex: 0;
margin-right: 16px;
}
.title {
font-weight: 400;
line-height: 1em;
font-size: 1.6em;
padding-bottom: 5px;
}
}
}
}
</style>

View File

@ -0,0 +1,36 @@
<template>
<translate
v-if="!endsOn"
:translate-params="{date: formatDate(beginsOn), time: formatTime(beginsOn)}"
>The %{ date } at %{ time }</translate>
<translate
v-else-if="isSameDay()"
:translate-params="{date: formatDate(beginsOn), startTime: formatTime(beginsOn), endTime: formatTime(endsOn)}"
>The %{ date } from %{ startTime } to %{ endTime }</translate>
<translate
v-else-if="endsOn"
:translate-params="{startDate: formatDate(beginsOn), startTime: formatTime(beginsOn), endDate: formatDate(endsOn), endTime: formatTime(endsOn)}"
>From the %{ startDate } at %{ startTime } to the %{ endDate } at %{ endTime }</translate>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class EventFullDate extends Vue {
@Prop({ required: true }) beginsOn!: string;
@Prop({ required: false }) endsOn!: string;
formatDate(value) {
return value ? new Date(value).toLocaleString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }) : null;
}
formatTime(value) {
return value ? new Date(value).toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric' }) : null;
}
isSameDay() {
const sameDay = ((new Date(this.beginsOn)).toDateString()) === ((new Date(this.endsOn)).toDateString());
return this.endsOn && sameDay;
}
}
</script>

View File

@ -0,0 +1,60 @@
<template>
<footer class="footer">
<mobilizon-logo :invert="true" class="logo" />
<img src="../assets/footer.png" :alt="$gettext('World map')" />
<ul>
<li><router-link :to="{ name: 'About'}"><translate>About</translate></router-link></li>
<li><router-link :to="{ name: 'Licence'}"><translate>License</translate></router-link></li>
<li><router-link :to="{ name: 'Legal'}"><translate>Legal</translate></router-link></li>
</ul>
<div class="content has-text-centered">
<span
v-translate="{
date: new Date().getFullYear(),
}"
>© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks</span>
</div>
</footer>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Logo from './Logo.vue';
@Component({
components: {
'mobilizon-logo': Logo,
},
})
export default class Footer extends Vue {
}
</script>
<style lang="scss" scoped>
@import "../variables.scss";
footer.footer {
color: $secondary;
display: flex;
flex-direction: column;
align-items: center;
.logo {
flex: 1;
}
div.content {
flex: 1;
}
ul li {
display: inline-flex;
margin: auto 5px;
a {
color: #eee;
font-size: 1.5rem;
text-decoration: underline;
text-decoration-color: $secondary;
}
}
}
</style>

View File

@ -7,11 +7,11 @@
</div> </div>
<div class="card-content"> <div class="card-content">
<div class="content"> <div class="content">
<router-link :to="{ name: 'Group', params:{ uuid: group.uuid } }"> <router-link :to="{ name: RouteName.GROUP, params:{ preferredUsername: group.preferredUsername } }">
<h2 class="title">{{ group.name ? group.name : group.preferredUsername }}</h2> <h2 class="title">{{ group.displayName() }}</h2>
</router-link> </router-link>
</div> </div>
<div v-if="!hideDetails"> <div>
<p>{{ group.summary }}</p> <p>{{ group.summary }}</p>
</div> </div>
</div> </div>
@ -20,11 +20,13 @@
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import { IGroup } from '../../types/actor.model'; import { Group } from '@/types/actor.model';
import { RouteName } from '@/router';
@Component @Component
export default class GroupCard extends Vue { export default class GroupCard extends Vue {
@Prop({ required: true }) group!: IGroup; @Prop({ required: true }) group!: Group;
@Prop({ default: false }) hideDetails!: boolean;
RouteName = RouteName;
} }
</script> </script>

View File

@ -0,0 +1,32 @@
<template>
<span class="logo" v-bind:class="{ invert }">M<span class="accent">o</span>b<span class="accent">ı</span>l<span class="accent">ı</span>z<span class="accent">o</span>n</span>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class Logo extends Vue {
@Prop({ type: Boolean, required: false, default: false }) invert!: boolean;
}
</script>
<style lang="scss" scoped>
@import "../variables.scss";
@import "~typeface-signika/index.css";
.logo {
font-size: 3.5em;
color: $primary;
font-weight: 400;
font-family: Signika,serif;
&.invert {
color: $secondary;
}
span.accent::after {
content: "̇"; // U+0307
color: #fff;
}
}
</style>

View File

@ -1,8 +1,9 @@
<template> <template>
<div style="height: 100%; width: 100%"> <div class="map-container">
<l-map <l-map
:zoom="16" :zoom="mergedOptions.zoom"
style="height: 80%; width: 100%" :style="`height: ${mergedOptions.height}; width: ${mergedOptions.width}`"
class="leaflet-map"
:center="[lat, lon]" :center="[lat, lon]"
> >
<l-tile-layer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"></l-tile-layer> <l-tile-layer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png"></l-tile-layer>
@ -22,9 +23,16 @@ import { LMap, LTileLayer, LMarker, LPopup } from 'vue2-leaflet';
@Component({ @Component({
components: { LTileLayer, LMap, LMarker, LPopup }, components: { LTileLayer, LMap, LMarker, LPopup },
}) })
export default class Event extends Vue { export default class Map extends Vue {
@Prop({ type: String, required: true }) coords!: string; @Prop({ type: String, required: true }) coords!: string;
@Prop({ type: String, required: false }) popup!: string; @Prop({ type: String, required: false }) popup!: string;
@Prop({ type: Object, required: false }) options!: object;
defaultOptions: object = {
zoom: 15,
height: '100%',
width: '100%',
};
mounted() { mounted() {
// this part resolve an issue where the markers would not appear // this part resolve an issue where the markers would not appear
@ -38,7 +46,21 @@ export default class Event extends Vue {
}); });
} }
get mergedOptions(): object {
return { ...this.defaultOptions, ...this.options };
}
get lat() { return this.$props.coords.split(';')[0]; } get lat() { return this.$props.coords.split(';')[0]; }
get lon() { return this.$props.coords.split(';')[1]; } get lon() { return this.$props.coords.split(';')[1]; }
} }
</script> </script>
<style lang="scss" scoped>
div.map-container {
height: 100%;
width: 100%;
.leaflet-map {
z-index: 20;
}
}
</style>

View File

@ -1,43 +1,56 @@
<template> <template>
<nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation"> <nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">
<div class="navbar-brand"> <div class="container">
<router-link class="navbar-item" :to="{ name: 'Home' }">Mobilizon</router-link> <div class="navbar-brand">
<router-link class="navbar-item" :to="{ name: 'Home' }"><logo /></router-link>
<a <a
role="button" role="button"
class="navbar-burger burger" class="navbar-burger burger"
aria-label="menu" aria-label="menu"
aria-expanded="false" aria-expanded="false"
data-target="navbarBasicExample" data-target="navbarBasicExample"
> @click="showNavbar = !showNavbar" :class="{ 'is-active': showNavbar }"
<span aria-hidden="true"></span> >
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
</a> <span aria-hidden="true"></span>
</div> </a>
<div class="navbar-end"> </div>
<div class="navbar-item"> <div class="navbar-menu" :class="{ 'is-active': showNavbar }">
<div class="buttons"> <div class="navbar-end">
<router-link class="button is-primary" v-if="!currentUser.isLoggedIn && config && config.registrationsOpen" :to="{ name: 'Register' }"> <div class="navbar-item">
<strong> <search-field />
<translate>Sign up</translate> </div>
</strong> <div class="navbar-item" v-if="!currentUser.isLoggedIn">
</router-link> <div class="buttons">
<router-link class="button is-light" v-if="!currentUser.isLoggedIn" :to="{ name: 'Login' }"> <router-link class="button is-primary" v-if="config && config.registrationsOpen" :to="{ name: 'Register' }">
<translate>Log in</translate> <strong>
</router-link> <translate>Sign up</translate>
<router-link </strong>
class="button is-light" </router-link>
v-if="currentUser.isLoggedIn && loggedPerson" <router-link class="button is-primary" :to="{ name: 'Login' }">
:to="{ name: 'Profile', params: { name: loggedPerson.preferredUsername} }" <translate>Log in</translate>
> </router-link>
<figure class="image is-24x24"> </div>
<img :src="loggedPerson.avatarUrl"> </div>
</figure> <div class="navbar-item has-dropdown is-hoverable" v-else>
<span>{{ loggedPerson.preferredUsername }}</span> <router-link
</router-link> class="navbar-link"
v-if="currentUser.isLoggedIn && loggedPerson"
:to="{ name: 'Profile', params: { name: loggedPerson.preferredUsername} }"
>
<figure class="image is-24x24">
<img :src="loggedPerson.avatarUrl">
</figure>
<span>{{ loggedPerson.preferredUsername }}</span>
</router-link>
<span v-if="currentUser.isLoggedIn" class="button" v-on:click="logout()">Log out</span> <div class="navbar-dropdown">
<a class="navbar-item"><translate>My account</translate></a>
<a class="navbar-item" v-on:click="logout()"><translate>Log out</translate></a>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -46,30 +59,19 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator'; import { Component, Vue, Watch } from 'vue-property-decorator';
import { SEARCH } from '@/graphql/search';
import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user'; import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user';
import { onLogout } from '@/vue-apollo'; import { onLogout } from '@/vue-apollo';
import { deleteUserData } from '@/utils/auth'; import { deleteUserData } from '@/utils/auth';
import { LOGGED_PERSON } from '@/graphql/actor'; import { LOGGED_PERSON } from '@/graphql/actor';
import { IActor, IPerson } from '@/types/actor.model'; import { IPerson } from '@/types/actor.model';
import { RouteName } from '@/router';
import { CONFIG } from '@/graphql/config'; import { CONFIG } from '@/graphql/config';
import { IConfig } from '@/types/config.model'; import { IConfig } from '@/types/config.model';
import { ICurrentUser } from '@/types/current-user.model' import { ICurrentUser } from '@/types/current-user.model';
import Logo from '@/components/Logo.vue';
import SearchField from '@/components/SearchField.vue';
@Component({ @Component({
apollo: { apollo: {
search: {
query: SEARCH,
variables() {
return {
searchText: this.searchText,
};
},
skip() {
return !this.searchText;
},
},
currentUser: { currentUser: {
query: CURRENT_USER_CLIENT, query: CURRENT_USER_CLIENT,
}, },
@ -77,35 +79,20 @@ import { ICurrentUser } from '@/types/current-user.model'
query: CONFIG, query: CONFIG,
}, },
}, },
components: {
Logo,
SearchField,
},
}) })
export default class NavBar extends Vue { export default class NavBar extends Vue {
notifications = [ notifications = [
{ header: 'Coucou' }, { header: 'Coucou' },
{ title: "T'as une notification", subtitle: 'Et elle est cool' }, { title: "T'as une notification", subtitle: 'Et elle est cool' },
]; ];
model = null;
search: any[] = [];
searchText: string | null = null;
searchSelect = null;
loggedPerson: IPerson | null = null; loggedPerson: IPerson | null = null;
config!: IConfig; config!: IConfig;
currentUser!: ICurrentUser; currentUser!: ICurrentUser;
showNavbar: boolean = false;
get items() {
return this.search.map(searchEntry => {
switch (searchEntry.__typename) {
case 'Actor':
searchEntry.label =
searchEntry.preferredUsername +
(searchEntry.domain === null ? '' : `@${searchEntry.domain}`);
break;
case 'Event':
searchEntry.label = searchEntry.title;
break;
}
return searchEntry;
});
}
@Watch('currentUser') @Watch('currentUser')
async onCurrentUserChanged() { async onCurrentUserChanged() {
@ -121,34 +108,6 @@ export default class NavBar extends Vue {
} }
} }
@Watch('model')
onModelChanged(val) {
switch (val.__typename) {
case 'Event':
this.$router.push({ name: RouteName.EVENT, params: { uuid: val.uuid } });
break;
case 'Actor':
this.$router.push({
name: RouteName.PROFILE,
params: { name: this.usernameWithDomain(val) },
});
break;
}
}
usernameWithDomain(actor: IActor) {
return (
actor.preferredUsername +
(actor.domain === null ? '' : `@${actor.domain}`)
);
}
enter() {
console.log('enter');
this.$apollo.queries['search'].refetch();
}
async logout() { async logout() {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: UPDATE_CURRENT_USER_CLIENT, mutation: UPDATE_CURRENT_USER_CLIENT,
@ -161,9 +120,16 @@ export default class NavBar extends Vue {
deleteUserData(); deleteUserData();
onLogout(this.$apollo) onLogout(this.$apollo);
return this.$router.push({ path: '/' }) return this.$router.push({ path: '/' });
} }
} }
</script> </script>
<style lang="scss" scoped>
@import "../variables.scss";
nav {
border-bottom: solid 1px #0a0a0a;
}
</style>

View File

@ -0,0 +1,22 @@
<template>
<b-input icon="magnify" type="search" rounded :placeholder="defaultPlaceHolder" v-model="searchText" @keyup.native.enter="enter" />
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { RouteName } from '@/router';
@Component
export default class SearchField extends Vue {
@Prop({ type: String, required: false }) placeholder!: string;
searchText: string = '';
enter() {
this.$router.push({ name: RouteName.SEARCH, params: { searchTerm: this.searchText } });
}
get defaultPlaceHolder(): string {
// We can't use "this" inside @Prop's default value.
return this.placeholder || this.$gettext('Search');
}
}
</script>

View File

@ -95,3 +95,33 @@ mutation ($preferredUsername: String!, $name: String!, $summary: String!, $email
} }
} }
`; `;
export const FETCH_GROUP = gql`
query($name:String!) {
group(preferredUsername: $name) {
id,
url,
name,
domain,
summary,
preferredUsername,
suspended,
avatarUrl,
bannerUrl,
organizedEvents {
uuid,
title,
beginsOn
},
members {
role,
actor {
id,
name,
domain,
preferredUsername
}
}
}
}
`;

View File

@ -4,6 +4,7 @@ export const CONFIG = gql`
query { query {
config { config {
name, name,
description,
registrationsOpen registrationsOpen
} }
} }

View File

@ -51,6 +51,10 @@ export const FETCH_EVENT = gql`
# }, # },
participants { participants {
${participantQuery} ${participantQuery}
},
tags {
slug,
title
} }
} }
} }
@ -75,7 +79,8 @@ export const FETCH_EVENTS = gql`
# online_address, # online_address,
# phone_address, # phone_address,
physicalAddress { physicalAddress {
description description,
locality
} }
organizerActor { organizerActor {
avatarUrl, avatarUrl,

View File

@ -6,12 +6,14 @@ query SearchEvents($searchText: String!) {
...on Event { ...on Event {
title, title,
uuid, uuid,
beginsOn,
__typename __typename
}, },
...on Actor { ...on Actor {
avatarUrl, avatarUrl,
domain, domain,
preferredUsername, preferredUsername,
name,
__typename __typename
} }
} }

View File

@ -1,14 +1,14 @@
# English translations for mobilizon package. # English translations for mobilizon package.
# Copyright (C) 2018 THE mobilizon'S COPYRIGHT HOLDER # Copyright (C) 2019 THE mobilizon'S COPYRIGHT HOLDER
# This file is distributed under the same license as the mobilizon package. # This file is distributed under the same license as the mobilizon package.
# Automatically generated, 2018. # Automatically generated, 2019.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: mobilizon 0.1.0\n" "Project-Id-Version: mobilizon 0.1.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-17 16:08+0100\n" "POT-Creation-Date: 2019-04-10 16:31+0200\n"
"PO-Revision-Date: 2018-10-24 16:25+0200\n" "PO-Revision-Date: 2019-04-08 20:58+0200\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: en_US\n" "Language: en_US\n"
@ -17,178 +17,423 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/App.vue:8 #: src/components/Footer.vue:10
msgid "© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks" msgid "© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks"
msgstr "" msgstr "© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks"
#: src/components/Account/Register.vue:89 #: src/views/Account/Register.vue:57
msgid "A validation email was sent to %{email}" msgid "A validation email was sent to %{email}"
msgstr "A validation email was sent to %{email}" msgstr "A validation email was sent to %{email}"
#: src/components/Account/Register.vue:26 #: src/components/Footer.vue:5
msgid "About this instance" msgid "About"
msgstr "" msgstr "About"
#: src/components/Account/Register.vue:92 #: src/views/Event/Event.vue:138
msgid "About this event"
msgstr "About this event"
#: src/views/User/Register.vue:26
msgid "About this instance"
msgstr "About this instance"
#: src/views/Account/Identities.vue:7
msgid "Add a new profile"
msgstr "Add a new profile"
#: src/views/Event/Event.vue:44 src/views/Event/Event.vue:217
msgid "Add to my calendar"
msgstr "Add to my calendar"
#: src/views/Event/Event.vue:2
msgid "Are you going to this event?"
msgstr "Are you going to this event?"
#: src/views/Account/Register.vue:60
msgid "Before you can login, you need to click on the link inside it to validate your account" msgid "Before you can login, you need to click on the link inside it to validate your account"
msgstr "Before you can login, you need to click on the link inside it to validate your account" msgstr "Before you can login, you need to click on the link inside it to validate your account"
#: src/components/Category/Create.vue:7 #: src/views/Event/Event.vue:101
msgid "Create a new category" msgid "By %{ name }"
msgstr "" msgstr "By %{ name }"
#: src/components/Category/Create.vue:34 #: src/views/Event/Create.vue:3
msgid "Create category" msgid "Create a new event"
msgstr "" msgstr "Create a new event"
#: src/components/Account/Register.vue:16 #: src/views/Group/Create.vue:3
msgid "Create a new group"
msgstr "Create a new group"
#: src/views/Group/GroupList.vue:15
msgid "Create group"
msgstr "Create group"
#: src/views/Event/Create.vue:25
msgid "Create my event"
msgstr "Create my event"
#: src/views/Group/Create.vue:20
msgid "Create my group"
msgstr "Create my group"
#: src/views/Account/Register.vue:43
msgid "Create my profile"
msgstr "Create my profile"
#: src/views/Account/Profile.vue:61
msgid "Create token"
msgstr "Create token"
#: src/views/User/Register.vue:16
msgid "Create your communities and your events" msgid "Create your communities and your events"
msgstr "" msgstr "Create your communities and your events"
#: src/components/Account/Profile.vue:48 src/components/Category/List.vue:21 #: src/views/Account/Identities.vue:36
#: src/components/Event/Event.vue:41 msgid "Current"
msgstr "Current"
#: src/views/Account/Profile.vue:93 src/views/Event/Event.vue:64
msgid "Delete" msgid "Delete"
msgstr "" msgstr "Delete"
#: src/components/Account/Register.vue:80 #: src/views/User/Register.vue:82
msgid "Didn't receive the instructions ?" msgid "Didn't receive the instructions ?"
msgstr "" msgstr "Didn't receive the instructions ?"
#: src/components/Event/Event.vue:36 #: src/views/Event/Event.vue:59
msgid "Download"
msgstr ""
#: src/components/Event/Event.vue:31
msgid "Edit" msgid "Edit"
msgstr "" msgstr "Edit"
#: src/components/Account/Validate.vue:8 #: src/views/User/Validate.vue:8
msgid "Error while validating account" msgid "Either the account is already validated, either the validation token is incorrect."
msgstr "" msgstr "Either the account is already validated, either the validation token is incorrect."
#: src/components/Category/List.vue:18 #: src/views/Event/EventList.vue:3
msgid "Explore" msgid "Event list"
msgstr "" msgstr "Event list"
#: src/components/Account/Register.vue:14 #: src/views/Search.vue:10
msgid "Events"
msgstr "Events"
#: src/views/Home.vue:68
msgid "Events nearby you"
msgstr "Events nearby you"
#: src/views/Home.vue:24
msgid "Events you're going at"
msgstr "Events you're going at"
#: src/views/User/Register.vue:14
msgid "Features" msgid "Features"
msgstr "" msgstr "Features"
#: src/components/Account/Login.vue:46 #: src/views/User/Login.vue:41
msgid "Forgot your password ?" msgid "Forgot your password ?"
msgstr "" msgstr "Forgot your password ?"
#: src/components/Account/Register.vue:20 #: src/components/Event/EventFullDate.vue:9
msgid "From the %{ startDate } at %{ startTime } to the %{ endDate } at %{ endTime }"
msgstr "From the %{ startDate } at %{ startTime } to the %{ endDate } at %{ endTime }"
#: src/views/Group/GroupList.vue:3
msgid "Group List"
msgstr "Group List"
#: src/views/Search.vue:28
msgid "Groups"
msgstr "Groups"
#: src/views/Account/Profile.vue:56
msgid "iCal Feed"
msgstr "iCal Feed"
#: src/views/Account/Identities.vue:4
msgid "Identities"
msgstr "Identities"
#: src/views/User/ResendConfirmation.vue:16
msgid "If an account with this email exists, we just sent another confirmation email to %{email}"
msgstr "If an account with this email exists, we just sent another confirmation email to %{email}"
#: src/views/Event/Event.vue:20
msgid "Join"
msgstr "Join"
#: src/views/User/Register.vue:20
msgid "" msgid ""
"Learn more on\n" "Learn more on\n"
" <a target=\"_blank\" href=\"https://joinmobilizon.org\">joinmobilizon.org</a>" " <a target=\"_blank\" href=\"https://joinmobilizon.org\">joinmobilizon.org</a>"
msgstr "" msgstr ""
"Learn more on\n"
" <a target=\"_blank\" href=\"https://joinmobilizon.org\">joinmobilizon.org</a>"
#: src/components/NavBar.vue:26 #: src/views/Event/Event.vue:24
msgid "Leave"
msgstr "Leave"
#: src/components/Footer.vue:7
msgid "Legal"
msgstr "Legal"
#: src/components/Footer.vue:6
msgid "License"
msgstr "License"
#: src/components/NavBar.vue:32
msgid "Log in" msgid "Log in"
msgstr "" msgstr "Log in"
#: src/components/Account/Login.vue:38 #: src/components/NavBar.vue:50
msgid "Log out"
msgstr "Log out"
#: src/views/User/Login.vue:33 src/views/User/Register.vue:91
msgid "Login" msgid "Login"
msgstr "" msgstr "Login"
#: src/components/Account/Register.vue:32 #: src/views/User/Register.vue:32
msgid "meditate a bit" msgid "meditate a bit"
msgstr "" msgstr "meditate a bit"
#: src/components/Home.vue:33 #: src/views/Group/Group.vue:41
msgid "Members"
msgstr "Members"
#: src/components/NavBar.vue:49
msgid "My account"
msgstr "My account"
#: src/views/Event/Event.vue:70
msgid "No address defined"
msgstr "No address defined"
#: src/views/Event/EventList.vue:15 src/views/Home.vue:78
#: src/views/Search.vue:22
msgid "No events found" msgid "No events found"
msgstr "" msgstr "No events found"
#: src/components/Account/Profile.vue:29 #: src/views/Group/Group.vue:52
msgid "No group found"
msgstr "No group found"
#: src/views/Search.vue:38
msgid "No groups found"
msgstr "No groups found"
#: src/views/Account/Profile.vue:66 src/views/Group/Group.vue:27
msgid "Organized" msgid "Organized"
msgstr "" msgstr "Organized"
#: src/components/Account/Register.vue:17 #: src/components/Event/EventCard.vue:1
msgid "Organizer"
msgstr "Organizer"
#: src/views/User/Register.vue:17
msgid "Other stuff…" msgid "Other stuff…"
msgstr "" msgstr "Other stuff…"
#: src/components/Account/SendPasswordReset.vue:4 #: src/views/User/PasswordReset.vue:4 src/views/User/SendPasswordReset.vue:4
msgid "Password reset" msgid "Password reset"
msgstr "" msgstr "Password reset"
#: src/components/Account/Register.vue:31 #: src/views/User/Register.vue:31
msgid "Please be nice to each other" msgid "Please be nice to each other"
msgstr "" msgstr "Please be nice to each other"
#: src/components/Account/ResendConfirmation.vue:21 #: src/views/User/ResendConfirmation.vue:21
#: src/components/Account/SendPasswordReset.vue:22 #: src/views/User/SendPasswordReset.vue:22
msgid "Please check you spam folder if you didn't receive the email." msgid "Please check you spam folder if you didn't receive the email."
msgstr "Please check you spam folder if you didn't receive the email."
#: src/views/PageNotFound.vue:12
msgid "Please contact this instance's Mobilizon admin if you think this is a mistake."
msgstr "" msgstr ""
#: src/components/Account/Register.vue:35 #: src/views/PageNotFound.vue:9
msgid "Please make sure the address is correct and that the page hasn't been moved."
msgstr ""
#: src/views/User/Register.vue:35
msgid "Please read the full rules" msgid "Please read the full rules"
msgstr "" msgstr "Please read the full rules"
#: src/components/Account/Register.vue:72 src/components/Home.vue:9 #: src/views/Account/Profile.vue:45
msgid "Private feeds"
msgstr "Private feeds"
#: src/views/Event/Event.vue:34
msgid "public event"
msgstr "public event"
#: src/views/Account/Profile.vue:27
msgid "Public feeds"
msgstr "Public feeds"
#: src/views/Account/Profile.vue:38
msgid "Public iCal Feed"
msgstr "Public iCal Feed"
#: src/views/Account/Profile.vue:33
msgid "Public RSS/Atom Feed"
msgstr "Public RSS/Atom Feed"
#: src/views/Account/Identities.vue:16 src/views/Home.vue:8
#: src/views/User/Login.vue:49 src/views/User/Register.vue:74
msgid "Register" msgid "Register"
msgstr "Register" msgstr "Register"
#: src/components/Account/Register.vue:5 #: src/views/Account/Register.vue:5 src/views/User/Register.vue:5
msgid "Register an account on Mobilizon!" msgid "Register an account on Mobilizon!"
msgstr "" msgstr "Register an account on Mobilizon!"
#: src/components/Account/ResendConfirmation.vue:4 #: src/views/Error.vue:2
msgid "Registration is currently closed."
msgstr "Registration is currently closed."
#: src/views/User/ResendConfirmation.vue:4
msgid "Resend confirmation email" msgid "Resend confirmation email"
msgstr "" msgstr "Resend confirmation email"
#: src/components/Account/PasswordReset.vue:26 #: src/views/User/PasswordReset.vue:29
msgid "Reset my password" msgid "Reset my password"
msgstr "" msgstr "Reset my password"
#: src/components/Account/ResendConfirmation.vue:11 #: src/views/Account/Profile.vue:51
msgid "RSS/Atom Feed"
msgstr "RSS/Atom Feed"
#: src/views/PageNotFound.vue:18 src/components/SearchField.vue:19
msgid "Search"
msgstr "Search"
#: src/views/Search.vue:3
msgid "Search results: « %{ search } »"
msgstr "Search results: « %{ search } »"
#: src/views/User/ResendConfirmation.vue:11
msgid "Send confirmation email again" msgid "Send confirmation email again"
msgstr "" msgstr "Send confirmation email again"
#: src/components/Account/SendPasswordReset.vue:12 #: src/views/User/SendPasswordReset.vue:12
msgid "Send email to reset my password" msgid "Send email to reset my password"
msgstr "" msgstr "Send email to reset my password"
#: src/components/NavBar.vue:22 #: src/views/Event/Event.vue:206
msgid "Share this event"
msgstr "Share this event"
#: src/views/Event/Event.vue:79
msgid "Show map"
msgstr "Show map"
#: src/components/NavBar.vue:28
msgid "Sign up" msgid "Sign up"
msgstr "Sign up"
#: src/components/Event/EventFullDate.vue:1
msgid "The %{ date } at %{ time }"
msgstr "The %{ date } at %{ time }"
#: src/components/Event/EventFullDate.vue:5
msgid "The %{ date } from %{ startTime } to %{ endTime }"
msgstr "The %{ date } from %{ startTime } to %{ endTime }"
#: src/views/Event/Event.vue:141
msgid "The event organizer didn't add any description."
msgstr "The event organizer didn't add any description."
#: src/views/PageNotFound.vue:6
msgid "The page you're looking for doesn't exist."
msgstr "" msgstr ""
#: src/components/Account/Profile.vue:43 #: src/views/Event/Event.vue:224
msgid "These events may interest you"
msgstr "These events may interest you"
#: src/views/Home.vue:11
msgid "This instance isn't opened to registrations, but you can register on other instances."
msgstr "This instance isn't opened to registrations, but you can register on other instances."
#: src/views/Error.vue:6
msgid "Unknown error."
msgstr "Unknown error."
#: src/views/Account/Profile.vue:84
msgid "User logout" msgid "User logout"
msgstr "" msgstr "User logout"
#: src/components/Event/Event.vue:50 #: src/views/User/SendPasswordReset.vue:17
msgid "Vous avez annoncé aller à cet événement."
msgstr ""
#: src/components/Event/Event.vue:46
msgid "Vous êtes organisateur de cet événement."
msgstr ""
#: src/components/Account/SendPasswordReset.vue:17
msgid "We just sent an email to %{email}" msgid "We just sent an email to %{email}"
msgstr "" msgstr "We just sent an email to %{email}"
#: src/components/Account/ResendConfirmation.vue:16 #: src/views/Home.vue:18
msgid "We just sent another confirmation email to %{email}"
msgstr ""
#: src/components/Home.vue:16
msgid "Welcome back %{username}" msgid "Welcome back %{username}"
msgstr "" msgstr "Welcome back %{username}"
#: src/components/Account/Login.vue:4 #: src/views/User/Login.vue:4
msgid "Welcome back!" msgid "Welcome back!"
msgstr "" msgstr "Welcome back!"
#: src/components/Account/Validate.vue:12 #: src/views/Event/Event.vue:2
msgid "You announced that you're going to this event."
msgstr "You announced that you're going to this event."
#: src/views/User/Login.vue:58
msgid "You are already logged-in."
msgstr "You are already logged-in."
#: src/views/Event/Event.vue:2
msgid "You are an organizer."
msgstr "You are an organizer."
#: src/views/Home.vue:45
msgid "You have one event in %{ days } days."
msgid_plural "You have %{ count } events in %{ days } days"
msgstr[0] "You have one event in %{ days } days."
msgstr[1] "You have %{ count } events in %{ days } days"
#: src/views/Home.vue:29
msgid "You have one event today."
msgid_plural "You have %{ count } events today"
msgstr[0] "You have one event today."
msgstr[1] "You have %{ count } events today"
#: src/views/Home.vue:37
msgid "You have one event tomorrow."
msgid_plural "You have %{ count } events tomorrow"
msgstr[0] "You have one event tomorrow."
msgstr[1] "You have %{ count } events tomorrow"
#: src/views/User/Login.vue:9
msgid "You need to login."
msgstr "You need to login."
#: src/views/Home.vue:64
msgid "You're not going to any event yet"
msgstr "You're not going to any event yet"
#: src/views/User/Validate.vue:12
msgid "Your account has been validated" msgid "Your account has been validated"
msgstr "" msgstr "Your account has been validated"
#: src/components/Account/Validate.vue:3 #: src/views/User/Validate.vue:3
msgid "Your account is being validated" msgid "Your account is being validated"
msgstr "" msgstr "Your account is being validated"
#: src/components/Account/Register.vue:28 #: src/views/Account/Register.vue:52
msgid "Your account is nearly ready, %{username}"
msgstr "Your account is nearly ready, %{username}"
#: src/views/User/Register.vue:28
msgid "Your local administrator resumed it's policy:" msgid "Your local administrator resumed it's policy:"
msgstr "Your local administrator resumed it's policy:"
#: src/components/Footer.vue:4
msgid "World map"
msgstr "World map"
#: src/views/PageNotFound.vue:40
msgid "Search events, groups, etc."
msgstr "" msgstr ""

View File

@ -1,30 +0,0 @@
# English translations for mobilizon package.
# Copyright (C) 2018 THE mobilizon'S COPYRIGHT HOLDER
# This file is distributed under the same license as the mobilizon package.
# Automatically generated, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: mobilizon 0.1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-24 16:25+0200\n"
"PO-Revision-Date: 2018-10-24 16:25+0200\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: en_US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/components/Account/Register.vue:70
msgid "A validation email was sent to %{email}"
msgstr "A validation email was sent to %{email}"
#: src/components/Account/Register.vue:71
msgid "Before you can login, you need to click on the link inside it to validate your account"
msgstr "Before you can login, you need to click on the link inside it to validate your account"
#: src/components/Home.vue:14
msgid "Register"
msgstr "Register"

View File

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: mobilizon 0.1.0\n" "Project-Id-Version: mobilizon 0.1.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-17 16:08+0100\n" "POT-Creation-Date: 2019-04-10 16:31+0200\n"
"PO-Revision-Date: 2018-10-24 16:25+0200\n" "PO-Revision-Date: 2019-04-10 16:33+0200\n"
"Last-Translator: Automatically generated\n" "Last-Translator: Automatically generated\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: fr_FR\n" "Language: fr_FR\n"
@ -16,179 +16,425 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Poedit 2.2.1\n"
#: src/App.vue:8 #: src/components/Footer.vue:10
msgid "© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks" msgid "© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix, VueJS & with some love and some weeks"
msgstr "" msgstr "© Les contributeurs de Mobilizon %{date} - Fait avec Elixir, Phoenix, VueJS & et de l'amour et des semaines"
#: src/components/Account/Register.vue:89 #: src/views/Account/Register.vue:57
msgid "A validation email was sent to %{email}" msgid "A validation email was sent to %{email}"
msgstr "" msgstr "Un email de validation a été envoyé à %{email}"
#: src/components/Account/Register.vue:26 #: src/components/Footer.vue:5
msgid "About"
msgstr "À propos"
#: src/views/Event/Event.vue:138
msgid "About this event"
msgstr "À propos de cet événement"
#: src/views/User/Register.vue:26
msgid "About this instance" msgid "About this instance"
msgstr "" msgstr "À propos de cette instance"
#: src/components/Account/Register.vue:92 #: src/views/Account/Identities.vue:7
msgid "Add a new profile"
msgstr "Ajouter un nouveau profil"
#: src/views/Event/Event.vue:44 src/views/Event/Event.vue:217
msgid "Add to my calendar"
msgstr "Ajouter à mon agenda"
#: src/views/Event/Event.vue:2
msgid "Are you going to this event?"
msgstr "Allez-vous à cet événement ?"
#: src/views/Account/Register.vue:60
msgid "Before you can login, you need to click on the link inside it to validate your account" msgid "Before you can login, you need to click on the link inside it to validate your account"
msgstr "" msgstr "Avant que vous puissiez vous enregistrer, vous devez cliquer sur le lien à l'intérieur pour valider votre compte"
#: src/components/Category/Create.vue:7 #: src/views/Event/Event.vue:101
msgid "Create a new category" msgid "By %{ name }"
msgstr "" msgstr "Par %{name}"
#: src/components/Category/Create.vue:34 #: src/views/Event/Create.vue:3
msgid "Create category" msgid "Create a new event"
msgstr "" msgstr "Créer un nouvel événement"
#: src/components/Account/Register.vue:16 #: src/views/Group/Create.vue:3
msgid "Create a new group"
msgstr "Créer un nouveau groupe"
#: src/views/Group/GroupList.vue:15
msgid "Create group"
msgstr "Créer un groupe"
#: src/views/Event/Create.vue:25
msgid "Create my event"
msgstr "Créer mon événement"
#: src/views/Group/Create.vue:20
msgid "Create my group"
msgstr "Créer mon groupe"
#: src/views/Account/Register.vue:43
msgid "Create my profile"
msgstr "Créer mon profil"
#: src/views/Account/Profile.vue:61
msgid "Create token"
msgstr "Créer un jeton"
#: src/views/User/Register.vue:16
msgid "Create your communities and your events" msgid "Create your communities and your events"
msgstr "" msgstr "Créer vos communautés et vos événements"
#: src/components/Account/Profile.vue:48 src/components/Category/List.vue:21 #: src/views/Account/Identities.vue:36
#: src/components/Event/Event.vue:41 msgid "Current"
msgstr "Actuel"
#: src/views/Account/Profile.vue:93 src/views/Event/Event.vue:64
msgid "Delete" msgid "Delete"
msgstr "" msgstr "Supprimer"
#: src/components/Account/Register.vue:80 #: src/views/User/Register.vue:82
msgid "Didn't receive the instructions ?" msgid "Didn't receive the instructions ?"
msgstr "" msgstr "Vous n'avez pas reçu les instructions ?"
#: src/components/Event/Event.vue:36 #: src/views/Event/Event.vue:59
msgid "Download"
msgstr ""
#: src/components/Event/Event.vue:31
msgid "Edit" msgid "Edit"
msgstr "" msgstr "Éditer"
#: src/components/Account/Validate.vue:8 #: src/views/User/Validate.vue:8
msgid "Error while validating account" msgid "Either the account is already validated, either the validation token is incorrect."
msgstr "" msgstr "Soit le compte est déjà validé, soit le jeton de validation est incorrect."
#: src/components/Category/List.vue:18 #: src/views/Event/EventList.vue:3
msgid "Explore" msgid "Event list"
msgstr "" msgstr "Liste d'événements"
#: src/components/Account/Register.vue:14 #: src/views/Search.vue:10
msgid "Events"
msgstr "Événements"
#: src/views/Home.vue:68
msgid "Events nearby you"
msgstr "Événements près de chez vous"
#: src/views/Home.vue:24
msgid "Events you're going at"
msgstr "Événements auxquels vous vous rendez"
#: src/views/User/Register.vue:14
msgid "Features" msgid "Features"
msgstr "" msgstr "Fonctionnalités"
#: src/components/Account/Login.vue:46 #: src/views/User/Login.vue:41
msgid "Forgot your password ?" msgid "Forgot your password ?"
msgstr "" msgstr "Mot de passe oublié ?"
#: src/components/Account/Register.vue:20 #: src/components/Event/EventFullDate.vue:9
msgid "From the %{ startDate } at %{ startTime } to the %{ endDate } at %{ endTime }"
msgstr "Du %{ startDate } à %{ startTime } au %{ endDate } à %{ endTime }"
#: src/views/Group/GroupList.vue:3
msgid "Group List"
msgstr "Liste de groupes"
#: src/views/Search.vue:28
msgid "Groups"
msgstr "Groupes"
#: src/views/Account/Profile.vue:56
msgid "iCal Feed"
msgstr "Flux iCal"
#: src/views/Account/Identities.vue:4
msgid "Identities"
msgstr "Identités"
#: src/views/User/ResendConfirmation.vue:16
msgid "If an account with this email exists, we just sent another confirmation email to %{email}"
msgstr "Si un compte avec un tel email existe, nous venons juste d'envoyer un nouvel email de confirmation à %{email}"
#: src/views/Event/Event.vue:20
msgid "Join"
msgstr "Rejoindre"
#: src/views/User/Register.vue:20
msgid "" msgid ""
"Learn more on\n" "Learn more on\n"
" <a target=\"_blank\" href=\"https://joinmobilizon.org\">joinmobilizon.org</a>" " <a target=\"_blank\" href=\"https://joinmobilizon.org\">joinmobilizon.org</a>"
msgstr "" msgstr ""
"En apprendre plus sur\n"
" <a target=\"_blank\" href=\"https://joinmobilizon.org\">joinmobilizon.org</a>"
#: src/components/NavBar.vue:26 #: src/views/Event/Event.vue:24
msgid "Leave"
msgstr "Quitter"
#: src/components/Footer.vue:7
msgid "Legal"
msgstr "Mentions légales"
#: src/components/Footer.vue:6
msgid "License"
msgstr "License"
#: src/components/NavBar.vue:32
msgid "Log in" msgid "Log in"
msgstr "" msgstr "Se connecter"
#: src/components/Account/Login.vue:38 #: src/components/NavBar.vue:50
msgid "Log out"
msgstr "Se déconnecter"
#: src/views/User/Login.vue:33 src/views/User/Register.vue:91
msgid "Login" msgid "Login"
msgstr "" msgstr "Se connecter"
#: src/components/Account/Register.vue:32 #: src/views/User/Register.vue:32
msgid "meditate a bit" msgid "meditate a bit"
msgstr "" msgstr "méditez un peu"
#: src/components/Home.vue:33 #: src/views/Group/Group.vue:41
msgid "Members"
msgstr "Membres"
#: src/components/NavBar.vue:49
msgid "My account"
msgstr "Mon compte"
#: src/views/Event/Event.vue:70
msgid "No address defined"
msgstr "Aucune adresse définie"
#: src/views/Event/EventList.vue:15 src/views/Home.vue:78
#: src/views/Search.vue:22
msgid "No events found" msgid "No events found"
msgstr "" msgstr "Aucun événement trouvé"
#: src/components/Account/Profile.vue:29 #: src/views/Group/Group.vue:52
msgid "No group found"
msgstr "Aucun groupe trouvé"
#: src/views/Search.vue:38
msgid "No groups found"
msgstr "Aucun groupe trouvé"
#: src/views/Account/Profile.vue:66 src/views/Group/Group.vue:27
msgid "Organized" msgid "Organized"
msgstr "" msgstr "Organisés"
#: src/components/Account/Register.vue:17 #: src/components/Event/EventCard.vue:1
msgid "Organizer"
msgstr "Organisateur"
#: src/views/User/Register.vue:17
msgid "Other stuff…" msgid "Other stuff…"
msgstr "" msgstr "Autres trucs…"
#: src/components/Account/SendPasswordReset.vue:4 #: src/views/User/PasswordReset.vue:4 src/views/User/SendPasswordReset.vue:4
msgid "Password reset" msgid "Password reset"
msgstr "" msgstr "Réinitialisation du mot de passe"
#: src/components/Account/Register.vue:31 #: src/views/User/Register.vue:31
msgid "Please be nice to each other" msgid "Please be nice to each other"
msgstr "" msgstr "Soyez sympas entre vous"
#: src/components/Account/ResendConfirmation.vue:21 #: src/views/User/ResendConfirmation.vue:21
#: src/components/Account/SendPasswordReset.vue:22 #: src/views/User/SendPasswordReset.vue:22
msgid "Please check you spam folder if you didn't receive the email." msgid "Please check you spam folder if you didn't receive the email."
msgstr "" msgstr "Merci de vérifier votre dossier des indésirables si vous n'avez pas reçu l'email."
#: src/components/Account/Register.vue:35 #: src/views/PageNotFound.vue:12
msgid "Please contact this instance's Mobilizon admin if you think this is a mistake."
msgstr "Veuillez contacter l'administrateur de cette instance Mobilizon si vous pensez quil sagit dune erreur."
#: src/views/PageNotFound.vue:9
msgid "Please make sure the address is correct and that the page hasn't been moved."
msgstr "Assurezvous que ladresse est correcte et que la page na pas été déplacée."
#: src/views/User/Register.vue:35
msgid "Please read the full rules" msgid "Please read the full rules"
msgstr "" msgstr "Merci de lire les règles complètes"
#: src/components/Account/Register.vue:72 src/components/Home.vue:9 #: src/views/Account/Profile.vue:45
msgid "Private feeds"
msgstr "Flux privés"
#: src/views/Event/Event.vue:34
msgid "public event"
msgstr "événement public"
#: src/views/Account/Profile.vue:27
msgid "Public feeds"
msgstr "Flux publics"
#: src/views/Account/Profile.vue:38
msgid "Public iCal Feed"
msgstr "Flux iCal public"
#: src/views/Account/Profile.vue:33
msgid "Public RSS/Atom Feed"
msgstr "Flux RSS/Atom public"
#: src/views/Account/Identities.vue:16 src/views/Home.vue:8
#: src/views/User/Login.vue:49 src/views/User/Register.vue:74
msgid "Register" msgid "Register"
msgstr "S'inscrire" msgstr "S'inscrire"
#: src/components/Account/Register.vue:5 #: src/views/Account/Register.vue:5 src/views/User/Register.vue:5
msgid "Register an account on Mobilizon!" msgid "Register an account on Mobilizon!"
msgstr "" msgstr "S'inscrire sur Mobilizon !"
#: src/components/Account/ResendConfirmation.vue:4 #: src/views/Error.vue:2
msgid "Registration is currently closed."
msgstr "Les inscriptions sont actuellement fermées."
#: src/views/User/ResendConfirmation.vue:4
msgid "Resend confirmation email" msgid "Resend confirmation email"
msgstr "" msgstr "Envoyer à nouveau l'email de confirmation"
#: src/components/Account/PasswordReset.vue:26 #: src/views/User/PasswordReset.vue:29
msgid "Reset my password" msgid "Reset my password"
msgstr "" msgstr "Réinitialiser mon mot de passe"
#: src/components/Account/ResendConfirmation.vue:11 #: src/views/Account/Profile.vue:51
msgid "RSS/Atom Feed"
msgstr "Flux RSS/Atom"
#: src/views/PageNotFound.vue:18 src/components/SearchField.vue:19
msgid "Search"
msgstr "Rechercher"
#: src/views/Search.vue:3
msgid "Search results: « %{ search } »"
msgstr "Résultats de recherche : « %{ search } »"
#: src/views/User/ResendConfirmation.vue:11
msgid "Send confirmation email again" msgid "Send confirmation email again"
msgstr "" msgstr "Envoyer l'email de confirmation à nouveau"
#: src/components/Account/SendPasswordReset.vue:12 #: src/views/User/SendPasswordReset.vue:12
msgid "Send email to reset my password" msgid "Send email to reset my password"
msgstr "" msgstr "Envoyer un email pour réinitialiser mon mot de passe"
#: src/components/NavBar.vue:22 #: src/views/Event/Event.vue:206
msgid "Share this event"
msgstr "Partager cet événement"
#: src/views/Event/Event.vue:79
msgid "Show map"
msgstr "Afficher la carte"
#: src/components/NavBar.vue:28
msgid "Sign up" msgid "Sign up"
msgstr "" msgstr "S'enregistrer"
#: src/components/Account/Profile.vue:43 #: src/components/Event/EventFullDate.vue:1
msgid "The %{ date } at %{ time }"
msgstr "Le %{ date } à %{ time }"
#: src/components/Event/EventFullDate.vue:5
msgid "The %{ date } from %{ startTime } to %{ endTime }"
msgstr "Le %{ date } de %{ startTime } à %{ endTime }"
#: src/views/Event/Event.vue:141
msgid "The event organizer didn't add any description."
msgstr "L'organisateur de l'événement n'a pas ajouté de description."
#: src/views/PageNotFound.vue:6
msgid "The page you're looking for doesn't exist."
msgstr "La page que vous recherchez n'existe pas."
#: src/views/Event/Event.vue:224
msgid "These events may interest you"
msgstr "Ces événements peuvent vous intéresser"
#: src/views/Home.vue:11
msgid "This instance isn't opened to registrations, but you can register on other instances."
msgstr "Cette instance n'autorise pas les inscriptions, mais vous pouvez vous enregistrer sur d'autres instances."
#: src/views/Error.vue:6
msgid "Unknown error."
msgstr "Erreur inconnue."
#: src/views/Account/Profile.vue:84
msgid "User logout" msgid "User logout"
msgstr "" msgstr "Déconnexion"
#: src/components/Event/Event.vue:50 #: src/views/User/SendPasswordReset.vue:17
msgid "Vous avez annoncé aller à cet événement."
msgstr ""
#: src/components/Event/Event.vue:46
msgid "Vous êtes organisateur de cet événement."
msgstr ""
#: src/components/Account/SendPasswordReset.vue:17
msgid "We just sent an email to %{email}" msgid "We just sent an email to %{email}"
msgstr "" msgstr "Nous venons d'envoyer un email à %{email}"
#: src/components/Account/ResendConfirmation.vue:16 #: src/views/Home.vue:18
msgid "We just sent another confirmation email to %{email}"
msgstr ""
#: src/components/Home.vue:16
msgid "Welcome back %{username}" msgid "Welcome back %{username}"
msgstr "" msgstr "Bon retour %{username}"
#: src/components/Account/Login.vue:4 #: src/views/User/Login.vue:4
msgid "Welcome back!" msgid "Welcome back!"
msgstr "" msgstr "Bon retour !"
#: src/components/Account/Validate.vue:12 #: src/views/Event/Event.vue:2
msgid "You announced that you're going to this event."
msgstr "Vous avez annoncé vous rendre à cet événement."
#: src/views/User/Login.vue:58
msgid "You are already logged-in."
msgstr "Vous êtes déjà connecté."
#: src/views/Event/Event.vue:2
msgid "You are an organizer."
msgstr "Vous êtes un organisateur."
#: src/views/Home.vue:45
msgid "You have one event in %{ days } days."
msgid_plural "You have %{ count } events in %{ days } days"
msgstr[0] "Vous avez un événement dans %{ days } jours."
msgstr[1] "Vous avez %{ count } événements dans %{ days } jours"
#: src/views/Home.vue:29
msgid "You have one event today."
msgid_plural "You have %{ count } events today"
msgstr[0] "Vous avez un événement aujourd'hui."
msgstr[1] "Vous avez %{ count } événements aujourd'hui"
#: src/views/Home.vue:37
msgid "You have one event tomorrow."
msgid_plural "You have %{ count } events tomorrow"
msgstr[0] "Vous avez un événement demain."
msgstr[1] "Vous avez %{ count } événements demain"
#: src/views/User/Login.vue:9
msgid "You need to login."
msgstr "Vous devez vous connecter."
#: src/views/Home.vue:64
msgid "You're not going to any event yet"
msgstr "Vous n'allez à aucun événement pour le moment"
#: src/views/User/Validate.vue:12
msgid "Your account has been validated" msgid "Your account has been validated"
msgstr "" msgstr "Votre compte a été validé"
#: src/components/Account/Validate.vue:3 #: src/views/User/Validate.vue:3
msgid "Your account is being validated" msgid "Your account is being validated"
msgstr "" msgstr "Votre compte est en cours de validation"
#: src/components/Account/Register.vue:28 #: src/views/Account/Register.vue:52
msgid "Your account is nearly ready, %{username}"
msgstr "Votre compte est presque prêt, %{ username }"
#: src/views/User/Register.vue:28
msgid "Your local administrator resumed it's policy:" msgid "Your local administrator resumed it's policy:"
msgstr "" msgstr "Votre administrateur local a résumé sa politique ainsi :"
#: src/components/Footer.vue:4
msgid "World map"
msgstr "Carte mondiale"
#: src/views/PageNotFound.vue:40
msgid "Search events, groups, etc."
msgstr "Rechercher des événements, des groupes, etc."

View File

@ -1,30 +0,0 @@
# French translations for mobilizon package.
# Copyright (C) 2018 THE mobilizon'S COPYRIGHT HOLDER
# This file is distributed under the same license as the mobilizon package.
# Automatically generated, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: mobilizon 0.1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-10-24 16:25+0200\n"
"PO-Revision-Date: 2018-10-24 16:25+0200\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: src/components/Account/Register.vue:70
msgid "A validation email was sent to %{email}"
msgstr ""
#: src/components/Account/Register.vue:71
msgid "Before you can login, you need to click on the link inside it to validate your account"
msgstr ""
#: src/components/Home.vue:14
msgid "Register"
msgstr "S'inscrire"

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,6 @@
import Vue from 'vue'; import Vue from 'vue';
import VueSimpleMarkdown from 'vue-simple-markdown'; import VueSimpleMarkdown from 'vue-simple-markdown';
import Buefy from 'buefy'; import Buefy from 'buefy';
import 'buefy/dist/buefy.css';
import GetTextPlugin from 'vue-gettext'; import GetTextPlugin from 'vue-gettext';
import App from '@/App.vue'; import App from '@/App.vue';
import router from '@/router'; import router from '@/router';
@ -20,9 +19,6 @@ Vue.use(Buefy, {
const language = (window.navigator as any).userLanguage || window.navigator.language; const language = (window.navigator as any).userLanguage || window.navigator.language;
Vue.filter('formatDate', value => value ? new Date(value).toLocaleString() : null);
Vue.filter('formatDay', value => value ? new Date(value).toLocaleDateString() : null);
Vue.use(GetTextPlugin, { Vue.use(GetTextPlugin, {
translations, translations,
defaultLanguage: 'en_US', defaultLanguage: 'en_US',

View File

@ -33,7 +33,7 @@ export const actorRoutes: RouteConfig[] = [
meta: { requiredAuth: true }, meta: { requiredAuth: true },
}, },
{ {
path: '/~:name', path: '/~:preferredUsername',
name: ActorRouteName.GROUP, name: ActorRouteName.GROUP,
component: Group, component: Group,
props: true, props: true,

View File

@ -12,7 +12,7 @@ export const beforeRegisterGuard: NavigationGuard = async function (to, from, ne
const config: IConfig = data.config; const config: IConfig = data.config;
if (config.registrationsOpen === false) { if (!config.registrationsOpen) {
return next({ return next({
name: ErrorRouteName.ERROR, name: ErrorRouteName.ERROR,
query: { code: ErrorCode.REGISTRATION_CLOSED }, query: { code: ErrorCode.REGISTRATION_CLOSED },

View File

@ -7,12 +7,24 @@ import { EventRouteName, eventRoutes } from '@/router/event';
import { ActorRouteName, actorRoutes } from '@/router/actor'; import { ActorRouteName, actorRoutes } from '@/router/actor';
import { ErrorRouteName, errorRoutes } from '@/router/error'; import { ErrorRouteName, errorRoutes } from '@/router/error';
import { authGuardIfNeeded } from '@/router/guards/auth-guard'; import { authGuardIfNeeded } from '@/router/guards/auth-guard';
import Search from '@/views/Search.vue';
Vue.use(Router); Vue.use(Router);
enum GlobalRouteName { enum GlobalRouteName {
HOME = 'Home', HOME = 'Home',
PAGE_NOT_FOUND = 'PageNotFound', PAGE_NOT_FOUND = 'PageNotFound',
SEARCH = 'Search',
}
function scrollBehavior(to) {
if (to.hash) {
return {
selector: to.hash,
// , offset: { x: 0, y: 10 }
};
}
return { x: 0, y: 0 };
} }
// Hack to merge enums // Hack to merge enums
@ -26,6 +38,7 @@ export const RouteName = {
}; };
const router = new Router({ const router = new Router({
scrollBehavior,
mode: 'history', mode: 'history',
base: '/', base: '/',
routes: [ routes: [
@ -33,7 +46,13 @@ const router = new Router({
...eventRoutes, ...eventRoutes,
...actorRoutes, ...actorRoutes,
...errorRoutes, ...errorRoutes,
{
path: '/search/:searchTerm/:searchType?',
name: RouteName.SEARCH,
component: Search,
props: true,
meta: { requiredAuth: false },
},
{ {
path: '/', path: '/',
name: RouteName.HOME, name: RouteName.HOME,

View File

@ -22,6 +22,19 @@ export class Actor implements IActor {
summary: string = ''; summary: string = '';
suspended: boolean = false; suspended: boolean = false;
url: string = ''; url: string = '';
get displayNameAndUsername(): string {
return `${this.name} (${this.usernameWithDomain})`;
}
public usernameWithDomain(): string {
const domain = this.domain ? `@${this.domain}` : '';
return `@${this.preferredUsername}${domain}`;
}
public displayName(): string {
return this.name != null && this.name !== '' ? this.name : this.usernameWithDomain();
}
} }
export interface IPerson extends IActor { export interface IPerson extends IActor {
@ -38,6 +51,10 @@ export class Person extends Actor implements IPerson {
goingToEvents: IEvent[] = []; goingToEvents: IEvent[] = [];
} }
export class Group extends Actor implements IGroup {
members: IMember[] = [];
}
export interface IFeedToken { export interface IFeedToken {
token: string; token: string;
actor?: IPerson; actor?: IPerson;

View File

@ -1,5 +1,6 @@
export interface IConfig { export interface IConfig {
name: string; name: string;
description: string;
registrationsOpen: boolean; registrationsOpen: boolean;
} }

View File

@ -8,10 +8,10 @@ export enum EventStatus {
} }
export enum EventVisibility { export enum EventVisibility {
PUBLIC, PUBLIC = 'PUBLIC',
UNLISTED, UNLISTED = 'UNLISTED',
RESTRICTED, RESTRICTED = 'RESTRICTED',
PRIVATE, PRIVATE = 'PRIVATE',
} }
export enum EventJoinOptions { export enum EventJoinOptions {

View File

@ -0,0 +1,3 @@
export interface ISearch {
__typename: string;
}

15
js/src/variables.scss Normal file
View File

@ -0,0 +1,15 @@
$primary: #424056;
$secondary: #FAB12D;
// Navbar
$navbar-background-color: $secondary;
$navbar-item-color: $primary;
$navbar-height: 7rem;
// Footer
$footer-padding: 3rem 1.5rem 4rem;
$footer-background-color: $primary;
// Card
$card-background-color: $secondary;

View File

@ -1,105 +1,101 @@
<template> <template>
<section> <section class="container">
<div class="columns"> <div v-if="person">
<div class="column"> <div class="card-image" v-if="person.bannerUrl">
<div class="card" v-if="person"> <figure class="image">
<div class="card-image" v-if="person.bannerUrl"> <img :src="person.bannerUrl">
<figure class="image"> </figure>
<img :src="person.bannerUrl"> </div>
<div class="card-content">
<div class="media">
<div class="media-left">
<figure class="image is-48x48">
<img :src="person.avatarUrl">
</figure> </figure>
</div> </div>
<div class="card-content"> <div class="media-content">
<div class="media"> <p class="title">{{ person.name }}</p>
<div class="media-left"> <p class="subtitle">@{{ person.preferredUsername }}</p>
<figure class="image is-48x48">
<img :src="person.avatarUrl">
</figure>
</div>
<div class="media-content">
<p class="title">{{ person.name }}</p>
<p class="subtitle">@{{ person.preferredUsername }}</p>
</div>
</div>
<div class="content">
<p v-html="person.summary"></p>
</div>
<b-dropdown hoverable has-link aria-role="list">
<button class="button is-info" slot="trigger">
<translate>Public feeds</translate>
<b-icon icon="menu-down"></b-icon>
</button>
<b-dropdown-item aria-role="listitem">
<a :href="feedUrls('atom', true)">
<translate>Public RSS/Atom Feed</translate>
</a>
</b-dropdown-item>
<b-dropdown-item aria-role="listitem">
<a :href="feedUrls('ics', true)">
<translate>Public iCal Feed</translate>
</a>
</b-dropdown-item>
</b-dropdown>
<b-dropdown hoverable has-link aria-role="list" v-if="person.feedTokens.length > 0">
<button class="button is-info" slot="trigger">
<translate>Private feeds</translate>
<b-icon icon="menu-down"></b-icon>
</button>
<b-dropdown-item aria-role="listitem">
<a :href="feedUrls('atom', false)">
<translate>RSS/Atom Feed</translate>
</a>
</b-dropdown-item>
<b-dropdown-item aria-role="listitem">
<a :href="feedUrls('ics', false)">
<translate>iCal Feed</translate>
</a>
</b-dropdown-item>
</b-dropdown>
<a class="button" v-else @click="createToken">
<translate>Create token</translate>
</a>
</div> </div>
<section v-if="person.organizedEvents.length > 0">
<h2 class="subtitle">
<translate>Organized</translate>
</h2>
<div class="columns">
<EventCard
v-for="event in person.organizedEvents"
:event="event"
:options="{ hideDetails: true }"
:key="event.uuid"
class="column is-one-third"
/>
</div>
<div class="field is-grouped">
<p class="control">
<a
class="button"
@click="logoutUser()"
v-if="loggedPerson && loggedPerson.id === person.id"
>
<translate>User logout</translate>
</a>
</p>
<p class="control">
<a
class="button"
@click="deleteProfile()"
v-if="loggedPerson && loggedPerson.id === person.id"
>
<translate>Delete</translate>
</a>
</p>
</div>
</section>
</div> </div>
<div class="content">
<vue-simple-markdown :source="person.summary"></vue-simple-markdown>
</div>
<b-dropdown hoverable has-link aria-role="list">
<button class="button is-primary" slot="trigger">
<translate>Public feeds</translate>
<b-icon icon="menu-down"></b-icon>
</button>
<b-dropdown-item aria-role="listitem">
<a :href="feedUrls('atom', true)">
<translate>Public RSS/Atom Feed</translate>
</a>
</b-dropdown-item>
<b-dropdown-item aria-role="listitem">
<a :href="feedUrls('ics', true)">
<translate>Public iCal Feed</translate>
</a>
</b-dropdown-item>
</b-dropdown>
<b-dropdown hoverable has-link aria-role="list" v-if="person.feedTokens.length > 0">
<button class="button is-info" slot="trigger">
<translate>Private feeds</translate>
<b-icon icon="menu-down"></b-icon>
</button>
<b-dropdown-item aria-role="listitem">
<a :href="feedUrls('atom', false)">
<translate>RSS/Atom Feed</translate>
</a>
</b-dropdown-item>
<b-dropdown-item aria-role="listitem">
<a :href="feedUrls('ics', false)">
<translate>iCal Feed</translate>
</a>
</b-dropdown-item>
</b-dropdown>
<a class="button" v-else-if="loggedPerson" @click="createToken">
<translate>Create token</translate>
</a>
</div> </div>
<section v-if="person.organizedEvents.length > 0">
<h2 class="subtitle">
<translate>Organized</translate>
</h2>
<div class="columns">
<EventCard
v-for="event in person.organizedEvents"
:event="event"
:options="{ hideDetails: true, organizerActor: person }"
:key="event.uuid"
class="column is-one-third"
/>
</div>
<div class="field is-grouped">
<p class="control">
<a
class="button"
@click="logoutUser()"
v-if="loggedPerson && loggedPerson.id === person.id"
>
<translate>User logout</translate>
</a>
</p>
<p class="control">
<a
class="button"
@click="deleteProfile()"
v-if="loggedPerson && loggedPerson.id === person.id"
>
<translate>Delete</translate>
</a>
</p>
</div>
</section>
</div> </div>
</section> </section>
</template> </template>
@ -172,3 +168,8 @@ export default class Profile extends Vue {
} }
} }
</script> </script>
<style lang="scss">
@import "../../variables";
@import "~bulma/sass/utilities/_all";
@import "~bulma/sass/components/dropdown.sass";
</style>

View File

@ -9,17 +9,17 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import { ErrorCode } from '@/types/error-code.model'; import { ErrorCode } from '@/types/error-code.model';
@Component @Component
export default class ErrorPage extends Vue { export default class ErrorPage extends Vue {
code: ErrorCode | null = null; code: ErrorCode | null = null;
ErrorCode = ErrorCode; ErrorCode = ErrorCode;
mounted() { mounted() {
this.code = this.$route.query[ 'code' ] as ErrorCode; this.code = this.$route.query['code'] as ErrorCode;
}
} }
}
</script> </script>

View File

@ -95,7 +95,7 @@ export default class CreateEvent extends Vue {
}); });
}) })
.catch(error => { .catch(error => {
console.log(error); console.error(error);
}); });
} }
} }

View File

@ -1,131 +1,258 @@
<template> <template>
<div class="columns is-centered"> <div>
<div class="column is-three-quarters"> <b-loading :active.sync="$apollo.loading"></b-loading>
<b-loading :active.sync="$apollo.loading"></b-loading> <div v-if="event">
<div class="card" v-if="event"> <div class="header-picture container">
<div class="card-image"> <figure class="image is-3by1">
<figure class="image is-4by3"> <img src="https://picsum.photos/600/200/">
<img src="https://picsum.photos/600/400/"> </figure>
</figure> </div>
</div> <section class="container">
<div class="card-content"> <div class="title-and-participate-button">
<span>{{ event.beginsOn | formatDay }}</span> <div class="title-wrapper">
<span class="tag is-primary">{{ event.category }}</span> <div class="date-component">
<h1 class="title">{{ event.title }}</h1> <date-calendar-icon :date="event.beginsOn"></date-calendar-icon>
<router-link </div>
:to="{name: 'Profile', params: { name: event.organizerActor.preferredUsername } }" <h1 class="title">{{ event.title }}</h1>
> </div>
<figure v-if="event.organizerActor.avatarUrl"> <div v-if="!actorIsOrganizer()" class="participate-button has-text-centered">
<img :src="event.organizerActor.avatarUrl"> <a v-if="!actorIsParticipant()" @click="joinEvent" class="button is-large is-primary is-rounded">
</figure> <b-icon icon="circle-outline"></b-icon>
</router-link> <translate>Join</translate>
<span
v-if="event.organizerActor"
>Organisé par {{ event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername }}</span>
<div class="field has-addons">
<p class="control">
<router-link
v-if="actorIsOrganizer()"
class="button"
:to="{ name: 'EditEvent', params: {uuid: event.uuid}}"
>
<translate>Edit</translate>
</router-link>
</p>
<p class="control">
<a class="button" @click="downloadIcsEvent()">
<translate>Download</translate>
</a> </a>
</p> <a v-if="actorIsParticipant()" @click="leaveEvent" class="button is-large is-primary is-rounded">
<p class="control"> <b-icon icon="check-circle"></b-icon>
<a class="button is-danger" v-if="actorIsOrganizer()" @click="deleteEvent()"> <translate>Leave</translate>
<translate>Delete</translate>
</a> </a>
</p>
</div>
<div>
<span>{{ event.beginsOn | formatDate }} - {{ event.endsOn | formatDate }}</span>
</div>
<div class="address" v-if="event.physicalAddress">
<h3 class="subtitle">Adresse</h3>
<address>
<span>{{ event.physicalAddress.description }}</span><br>
<span>{{ event.physicalAddress.floor }} {{ event.physicalAddress.street }}</span><br>
<span>{{ event.physicalAddress.postal_code }} {{ event.physicalAddress.locality }}</span><br>
<span>{{ event.physicalAddress.region }} {{ event.physicalAddress.country }}</span>
</address>
<div class="map">
<map-leaflet
:coords="event.physicalAddress.geom"
:popup="event.physicalAddress.description"
/>
</div> </div>
</div> </div>
<p v-if="actorIsOrganizer()"> <div class="metadata columns">
<translate>You are an organizer.</translate> <div class="column is-three-quarters-desktop">
</p> <p class="tags" v-if="event.category || event.tags.length > 0">
<div v-else> <span class="tag" v-if="event.category">{{ event.category }}</span>
<p v-if="actorIsParticipant()"> <span class="tag" v-if="event.tags" v-for="tag in event.tags">{{ tag.title }}</span>
<translate>You announced that you're going to this event.</translate> <span class="visibility">
</p> <translate v-if="event.visibility === EventVisibility.PUBLIC">public event</translate>
<p v-else> </span>
<translate>Are you going to this event?</translate><br /> </p>
<span> <div class="date-and-add-to-calendar">
<translate <div class="date-and-privacy" v-if="event.beginsOn">
:translate-n="event.participants.length" <b-icon icon="calendar-clock" />
translate-plural="%{event.participants.length} persons are going" <event-full-date :beginsOn="event.beginsOn" :endsOn="event.endsOn" />
> </div>
One person is going. <a class="add-to-calendar" @click="downloadIcsEvent()">
</translate> <b-icon icon="calendar-plus" />
</span> <translate>Add to my calendar</translate>
</p> </a>
</div>
<div v-if="!actorIsOrganizer()">
<a v-if="!actorIsParticipant()" @click="joinEvent" class="button">
<translate>Join</translate>
</a>
<a v-if="actorIsParticipant()" @click="leaveEvent" class="button">Leave</a>
</div>
<h2 class="subtitle">Details</h2>
<p v-if="event.description">
<vue-simple-markdown :source="event.description"></vue-simple-markdown>
</p>
<h2 class="subtitle">Participants</h2>
<span v-if="event.participants.length === 0">No participants yet.</span>
<div class="columns">
<router-link
class="card column"
v-for="participant in event.participants"
:key="participant.preferredUsername"
:to="{name: 'Profile', params: { name: participant.actor.preferredUsername }}"
>
<div>
<figure>
<img v-if="!participant.actor.avatarUrl" src="https://picsum.photos/125/125/">
<img v-else :src="participant.actor.avatarUrl">
</figure>
<span>{{ participant.actor.preferredUsername }}</span>
</div> </div>
</router-link> <p class="slug">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
In aliquam libero quam, ut ultricies velit porttitor a. Maecenas mollis vestibulum dolor.
</p>
</div>
<div class="column sidebar">
<div class="field has-addons" v-if="actorIsOrganizer()">
<p class="control">
<router-link
class="button"
:to="{ name: 'EditEvent', params: {uuid: event.uuid}}"
>
<translate>Edit</translate>
</router-link>
</p>
<p class="control">
<a class="button is-danger" @click="deleteEvent()">
<translate>Delete</translate>
</a>
</p>
</div>
<div class="address-wrapper">
<b-icon icon="map" />
<translate v-if="!event.physicalAddress">No address defined</translate>
<div class="address" v-if="event.physicalAddress">
<address>
<span class="addressDescription">{{ event.physicalAddress.description }}</span>
<span>{{ event.physicalAddress.floor }} {{ event.physicalAddress.street }}</span>
<span>{{ event.physicalAddress.postal_code }} {{ event.physicalAddress.locality }}</span>
<!-- <span>{{ event.physicalAddress.region }} {{ event.physicalAddress.country }}</span>-->
</address>
<span class="map-show-button" @click="showMap = !showMap">
<translate>Show map</translate>
</span>
</div>
<!-- <div class="map" v-if="showMap">-->
<!-- <map-leaflet-->
<!-- :coords="event.physicalAddress.geom"-->
<!-- :popup="event.physicalAddress.description"-->
<!-- />-->
<!-- </div>-->
<b-modal v-if="event.physicalAddress" :active.sync="showMap" :width="800" scroll="keep">
<div class="map">
<map-leaflet
:coords="event.physicalAddress.geom"
:popup="event.physicalAddress.description"
/>
</div>
</b-modal>
</div>
<div class="organizer">
<router-link
:to="{name: 'Profile', params: { name: event.organizerActor.preferredUsername } }"
>
<translate
:translate-params="{name: event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername}"
v-if="event.organizerActor">By %{ name }</translate>
<figure v-if="event.organizerActor.avatarUrl" class="image is-48x48">
<img
class="is-rounded"
:src="event.organizerActor.avatarUrl"
:alt="$gettextInterpolate('%{actor}\'s avatar', {actor: event.organizerActor.preferredUsername})" />
</figure>
</router-link>
</div>
</div>
</div>
</section>
<!-- <p v-if="actorIsOrganizer()">-->
<!-- <translate>You are an organizer.</translate>-->
<!-- </p>-->
<!-- <div v-else>-->
<!-- <p v-if="actorIsParticipant()">-->
<!-- <translate>You announced that you're going to this event.</translate>-->
<!-- </p>-->
<!-- <p v-else>-->
<!-- <translate>Are you going to this event?</translate><br />-->
<!-- <span>-->
<!-- <translate-->
<!-- :translate-n="event.participants.length"-->
<!-- translate-plural="%{event.participants.length} persons are going"-->
<!-- >-->
<!-- One person is going.-->
<!-- </translate>-->
<!-- </span>-->
<!-- </p>-->
<!-- </div>-->
<div class="description">
<div class="description-container container">
<h3 class="title">
<translate>About this event</translate>
</h3>
<p v-if="!event.description">
<translate>The event organizer didn't add any description.</translate>
</p>
<div class="columns" v-else="event.description">
<div class="column is-half">
<!-- <vue-simple-markdown :source="event.description" />-->
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Suspendisse vehicula ex dapibus augue volutpat, ultrices cursus mi rutrum.
Nunc ante nunc, facilisis a tellus quis, tempor mollis diam. Aenean consectetur quis est a ultrices.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
<p><a href="https://framasoft.org">https://framasoft.org</a>
<p>
Nam sit amet est eget velit tristique commodo. Etiam sollicitudin dignissim diam, ut ultricies tortor.
Sed quis blandit diam, a tincidunt nunc. Donec tincidunt tristique neque at rhoncus. Ut eget vulputate felis.
Pellentesque nibh purus, viverra ac augue sed, iaculis feugiat velit. Nulla ut hendrerit elit.
Etiam at justo eu nunc tempus sagittis. Sed ac tincidunt tellus, sit amet luctus velit.
Nam ullamcorper eros eleifend, eleifend diam vitae, lobortis risus.
</p>
<p>
<em>
Curabitur rhoncus sapien tortor, vitae imperdiet massa scelerisque non.
Aliquam eu augue mi. Donec hendrerit lorem orci.
</em>
</p>
<p>
Donec volutpat, enim eu laoreet dictum, urna quam varius enim, eu convallis urna est vitae massa.
Morbi porttitor lacus a sem efficitur blandit. Mauris in est in quam tincidunt iaculis non vitae ipsum.
Phasellus eget velit tellus. Curabitur ac neque pharetra velit viverra mollis.
</p>
<img src="https://framasoft.org/img/biglogo-notxt.png" alt="logo Framasoft"/>
<p>Aenean gravida, ante vitae aliquet aliquet, elit quam tristique orci, sit amet dictum lorem ipsum nec tortor.
Vestibulum est eros, faucibus et semper vel, dapibus ac est. Suspendisse potenti. Suspendisse potenti.
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
Nulla molestie nisi ac risus hendrerit, dapibus mattis sapien scelerisque.
</p>
<p>Maecenas id pretium justo, nec dignissim sapien. Mauris in venenatis odio, in congue augue. </p>
</div>
</div>
</div> </div>
</div> </div>
<!-- <section class="container">-->
<!-- <h2 class="title">Participants</h2>-->
<!-- <span v-if="event.participants.length === 0">No participants yet.</span>-->
<!-- <div class="columns">-->
<!-- <router-link-->
<!-- class="column"-->
<!-- v-for="participant in event.participants"-->
<!-- :key="participant.preferredUsername"-->
<!-- :to="{name: 'Profile', params: { name: participant.actor.preferredUsername }}"-->
<!-- >-->
<!-- <div>-->
<!-- <figure>-->
<!-- <img v-if="!participant.actor.avatarUrl" src="https://picsum.photos/125/125/">-->
<!-- <img v-else :src="participant.actor.avatarUrl">-->
<!-- </figure>-->
<!-- <span>{{ participant.actor.preferredUsername }}</span>-->
<!-- </div>-->
<!-- </router-link>-->
<!-- </div>-->
<!-- </section>-->
<section class="share">
<div class="container">
<div class="columns">
<div class="column is-half has-text-centered">
<h3 class="title"><translate>Share this event</translate></h3>
<b-icon icon="mastodon" size="is-large" type="is-primary" />
<a :href="facebookShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="facebook" size="is-large" type="is-primary" /></a>
<a :href="twitterShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="twitter" size="is-large" type="is-primary" /></a>
<a :href="emailShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="email" size="is-large" type="is-primary" /></a>
<!-- TODO: mailto: links are not used anymore, we should provide a popup to redact a message instead -->
<a :href="linkedInShareUrl" target="_blank" rel="nofollow noopener"><b-icon icon="linkedin" size="is-large" type="is-primary" /></a>
</div>
<hr />
<div class="column is-half has-text-right add-to-calendar">
<h3 @click="downloadIcsEvent()">
<translate>Add to my calendar</translate>
</h3>
</div>
</div>
</div>
</section>
<section class="more-events container">
<h3 class="title has-text-centered"><translate>These events may interest you</translate></h3>
<div class="columns">
<div class="column" v-for="index in 3" :key="index">
<EventCard :event="event" />
</div>
</div>
</section>
</div> </div>
</div> </div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { DELETE_EVENT, FETCH_EVENT, JOIN_EVENT, LEAVE_EVENT } from '@/graphql/event'; import { DELETE_EVENT, FETCH_EVENT, JOIN_EVENT, LEAVE_EVENT } from '@/graphql/event';
import { Component, Prop, Vue } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import { LOGGED_PERSON } from '@/graphql/actor'; import { LOGGED_PERSON } from '@/graphql/actor';
import { IEvent, IParticipant } from '@/types/event.model'; import { EventVisibility, IEvent, IParticipant } from '@/types/event.model';
import { IPerson } from '@/types/actor.model'; import { IPerson } from '@/types/actor.model';
import { RouteName } from '@/router'; import { RouteName } from '@/router';
import 'vue-simple-markdown/dist/vue-simple-markdown.css'; import 'vue-simple-markdown/dist/vue-simple-markdown.css';
import { GRAPHQL_API_ENDPOINT } from '@/api/_entrypoint'; import { GRAPHQL_API_ENDPOINT } from '@/api/_entrypoint';
import DateCalendarIcon from '@/components/Event/DateCalendarIcon.vue';
import BIcon from 'buefy/src/components/icon/Icon.vue';
import EventCard from '@/components/Event/EventCard.vue';
import EventFullDate from '@/components/Event/EventFullDate.vue';
@Component({ @Component({
components: { components: {
EventFullDate,
EventCard,
BIcon,
DateCalendarIcon,
'map-leaflet': () => import('@/components/Map.vue'), 'map-leaflet': () => import('@/components/Map.vue'),
}, },
apollo: { apollo: {
@ -148,6 +275,9 @@ export default class Event extends Vue {
event!: IEvent; event!: IEvent;
loggedPerson!: IPerson; loggedPerson!: IPerson;
validationSent: boolean = false; validationSent: boolean = false;
showMap: boolean = false;
EventVisibility = EventVisibility;
async deleteEvent() { async deleteEvent() {
const router = this.$router; const router = this.$router;
@ -241,12 +371,255 @@ export default class Event extends Vue {
return this.loggedPerson && return this.loggedPerson &&
this.loggedPerson.id === this.event.organizerActor.id; this.loggedPerson.id === this.event.organizerActor.id;
} }
get twitterShareUrl(): string {
return `https://twitter.com/intent/tweet?url=${encodeURIComponent(this.event.url)}&text=${this.event.title}`;
}
get facebookShareUrl(): string {
return `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(this.event.url)}`;
}
get linkedInShareUrl(): string {
return `https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(this.event.url)}&title=${this.event.title}`;
}
get emailShareUrl(): string {
return `mailto:?to=&body=${this.event.url}${encodeURIComponent('\n\n')}${this.event.description}&subject=${this.event.title}`;
}
} }
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.address div.map { @import "../../variables";
height: 400px;
width: 400px; div.sidebar {
padding: 25px 35px; display: flex;
flex-wrap: wrap;
flex-direction: column;
position: relative;
&::before {
content: "";
background: #B3B3B2;
position: absolute;
bottom: 30px;
top: 30px;
left: 0;
height: calc(100% - 60px);
width: 1px;
}
div.address-wrapper {
display: flex;
flex: 1;
flex-wrap: wrap;
div.address {
flex: 1;
.map-show-button {
cursor: pointer;
}
address {
font-style: normal;
flex-wrap: wrap;
display: flex;
justify-content: flex-start;
span.addressDescription {
text-overflow: ellipsis;
white-space: nowrap;
flex: 1 0 auto;
min-width: 100%;
}
:not(.addressDescription) {
color: rgba(46, 62, 72, .6);
flex: 1;
min-width: 100%;
}
}
}
div.map {
height: 900px;
width: 100%;
padding: 25px 5px 0;
}
}
div.organizer {
display: inline-flex;
padding-top: 10px;
a {
color: #4a4a4a;
span {
line-height: 2.7rem;
padding-right: 6px;
}
}
}
}
div.title-and-participate-button {
display: flex;
flex-wrap: wrap;
/*flex-flow: row wrap;*/
justify-content: space-between;
/*align-self: center;*/
align-items: stretch;
/*align-content: space-around;*/
padding: 15px 10px 0;
div.title-wrapper {
display: flex;
flex: 1 1 auto;
div.date-component {
margin-right: 16px;
}
h1.title {
font-weight: normal;
word-break: break-word;
font-size: 1.7em;
}
}
.participate-button {
flex: 0 1 auto;
display: inline-flex;
a.button {
margin: 0 auto;
}
}
}
div.metadata {
padding: 0 10px;
div.date-and-add-to-calendar {
display: flex;
flex-wrap: wrap;
span.icon {
margin-right: 5px;
}
div.date-and-privacy {
color: $primary;
padding: 0.3rem;
background: $secondary;
font-weight: bold;
}
a.add-to-calendar {
flex: 0 0 auto;
margin-left: 10px;
color: #484849;
&:hover {
text-decoration: underline;
}
}
}
}
p.tags {
span {
&.tag {
&::before {
content: '#';
}
text-transform: uppercase;
}
&.visibility::before {
content: "⋅"
}
margin: auto 5px;
}
margin-bottom: 1rem;
}
h3.title {
font-size: 3rem;
font-weight: 300;
}
.description {
padding-top: 10px;
min-height: 40rem;
background-repeat: no-repeat;
background-size: 800px;
background-position: 95% 101%;
background-image: url('../../assets/texting.svg');
border-top: solid 1px #111;
border-bottom: solid 1px #111;
p {
margin: 10px auto;
a {
display: inline-block;
padding: 0.3rem;
background: $secondary;
color: #111;
}
}
}
.share {
border-bottom: solid 1px #111;
.columns {
& > * {
padding: 10rem 0;
}
.add-to-calendar {
background-repeat: no-repeat;
background-size: 400px;
background-position: 10% 50%;
background-image: url('../../assets/undraw_events.svg');
position: relative;
&::before {
content:"";
background: #B3B3B2;
position: absolute;
bottom: 25%;
left: 0;
height: 40%;
width: 1px;
}
h3 {
display: block;
color: $primary;
font-size: 3rem;
text-decoration: underline;
text-decoration-color: $secondary;
cursor: pointer;
max-width: 20rem;
margin-right: 0;
margin-left: auto;
}
}
}
}
.more-events {
margin: 50px auto;
} }
</style> </style>

View File

@ -1,76 +1,73 @@
<template> <template>
<section> <section class="container">
<div class="columns"> <div v-if="group">
<div class="column"> <div class="card-image" v-if="group.bannerUrl">
<div class="card" v-if="group"> <figure class="image">
<div class="card-image" v-if="group.bannerUrl"> <img :src="group.bannerUrl">
<figure class="image"> </figure>
<img :src="group.bannerUrl"> </div>
<div class="box">
<div class="media">
<div class="media-left">
<figure class="image is-48x48">
<img :src="group.avatarUrl">
</figure> </figure>
</div> </div>
<div class="card-content"> <div class="media-content">
<div class="media"> <p class="title">{{ group.name }}</p>
<div class="media-left"> <p class="subtitle">@{{ group.preferredUsername }}</p>
<figure class="image is-48x48">
<img :src="group.avatarUrl">
</figure>
</div>
<div class="media-content">
<p class="title">{{ group.name }}</p>
<p class="subtitle">@{{ group.preferredUsername }}</p>
</div>
</div>
<div class="content">
<p v-html="group.summary"></p>
</div>
</div> </div>
<section v-if="group.organizedEvents.length > 0">
<h2 class="subtitle">
<translate>Organized</translate>
</h2>
<div class="columns">
<EventCard
v-for="event in group.organizedEvents"
:event="event"
:options="{ hideDetails: true }"
:key="event.uuid"
class="column is-one-third"
/>
</div>
</section>
<section v-if="group.members.length > 0">
<h2 class="subtitle">
<translate>Members</translate>
</h2>
<div class="columns">
<span
v-for="member in group.members"
:key="member"
>{{ member.actor.preferredUsername }}</span>
</div>
</section>
</div> </div>
<b-message v-if-else="!group && $apollo.loading === false" type="is-danger">
<translate>No group found</translate> <div class="content">
</b-message> <p v-html="group.summary"></p>
</div>
</div> </div>
<section class="box" v-if="group.organizedEvents.length > 0">
<h2 class="subtitle">
<translate>Organized</translate>
</h2>
<div class="columns">
<EventCard
v-for="event in group.organizedEvents"
:event="event"
:options="{ hideDetails: true }"
:key="event.uuid"
class="column is-one-third"
/>
</div>
</section>
<section v-if="group.members.length > 0">
<h2 class="subtitle">
<translate>Members</translate>
</h2>
<div class="columns">
<span
v-for="member in group.members"
:key="member.actor.preferredUsername"
>{{ member.actor.preferredUsername }}</span>
</div>
</section>
</div> </div>
<b-message v-else-if="!group && $apollo.loading === false" type="is-danger">
<translate>No group found</translate>
</b-message>
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'; import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import EventCard from '@/components/Event/EventCard.vue'; import EventCard from '@/components/Event/EventCard.vue';
import { FETCH_PERSON, LOGGED_PERSON } from '@/graphql/actor'; import { FETCH_GROUP, LOGGED_PERSON } from '@/graphql/actor';
import { IGroup } from '@/types/actor.model';
@Component({ @Component({
apollo: { apollo: {
person: { group: {
query: FETCH_PERSON, query: FETCH_GROUP,
variables() { variables() {
return { return {
name: this.$route.params.name, name: this.$route.params.preferredUsername,
}; };
}, },
}, },
@ -83,9 +80,9 @@ import { FETCH_PERSON, LOGGED_PERSON } from '@/graphql/actor';
}, },
}) })
export default class Group extends Vue { export default class Group extends Vue {
@Prop({ type: String, required: true }) name!: string; @Prop({ type: String, required: true }) preferredUsername!: string;
group = null; group!: IGroup;
loading = true; loading = true;
created() { created() {
@ -110,3 +107,8 @@ export default class Group extends Vue {
} }
} }
</script> </script>
<style lang="scss">
section.container {
min-height: 30em;
}
</style>

View File

@ -12,7 +12,7 @@
class="column is-one-quarter-desktop is-half-mobile" class="column is-one-quarter-desktop is-half-mobile"
/> />
</div> </div>
<router-link class="button" :to="{ name: 'CreateGroup' }"> <router-link class="button" :to="{ name: RouteName.CREATE_GROUP }">
<translate>Create group</translate> <translate>Create group</translate>
</router-link> </router-link>
</section> </section>
@ -27,6 +27,8 @@ export default class GroupList extends Vue {
groups = []; groups = [];
loading = true; loading = true;
RouteName = RouteName;
created() { created() {
this.fetchData(); this.fetchData();
} }

View File

@ -3,11 +3,14 @@
<section class="hero is-link" v-if="!currentUser.id || !loggedPerson"> <section class="hero is-link" v-if="!currentUser.id || !loggedPerson">
<div class="hero-body"> <div class="hero-body">
<div class="container"> <div class="container">
<h1 class="title">Find events you like</h1> <h1 class="title">{{ config.name }}</h1>
<h2 class="subtitle">Share them with Mobilizon</h2> <h2 class="subtitle">{{ config.description }}</h2>
<router-link class="button" :to="{ name: 'Register' }"> <router-link class="button" :to="{ name: 'Register' }" v-if="config.registrationsOpen">
<translate>Register</translate> <translate>Register</translate>
</router-link> </router-link>
<p v-else>
<translate>This instance isn't opened to registrations, but you can register on other instances.</translate>
</p>
</div> </div>
</div> </div>
</section> </section>
@ -18,8 +21,8 @@
>Welcome back %{username}</translate> >Welcome back %{username}</translate>
</h1> </h1>
</section> </section>
<section v-if="loggedPerson"> <section v-if="loggedPerson" class="container">
<span class="events-nearby title">Events you're going at</span> <span class="events-nearby title"><translate>Events you're going at</translate></span>
<b-loading :active.sync="$apollo.loading"></b-loading> <b-loading :active.sync="$apollo.loading"></b-loading>
<div v-if="goingToEvents.size > 0" v-for="row in Array.from(goingToEvents.entries())"> <div v-if="goingToEvents.size > 0" v-for="row in Array.from(goingToEvents.entries())">
<!-- Iterators will be supported in v-for with VueJS 3 --> <!-- Iterators will be supported in v-for with VueJS 3 -->
@ -62,16 +65,15 @@
<translate>You're not going to any event yet</translate> <translate>You're not going to any event yet</translate>
</b-message> </b-message>
</section> </section>
<section> <section class="container">
<span class="events-nearby title">Events nearby you</span> <h3 class="events-nearby title"><translate>Events nearby you</translate></h3>
<b-loading :active.sync="$apollo.loading"></b-loading> <b-loading :active.sync="$apollo.loading"></b-loading>
<div v-if="events.length > 0" class="columns is-multiline"> <div v-if="events.length > 0" class="columns is-multiline">
<EventCard <div class="column is-one-third-desktop" v-for="event in events.slice(0, 6)" :key="event.uuid">
v-for="event in events" <EventCard
:key="event.uuid" :event="event"
:event="event" />
class="column is-one-quarter-desktop is-half-mobile" </div>
/>
</div> </div>
<b-message v-else type="is-danger"> <b-message v-else type="is-danger">
<translate>No events found</translate> <translate>No events found</translate>
@ -91,7 +93,9 @@ import { ICurrentUser } from '@/types/current-user.model';
import { CURRENT_USER_CLIENT } from '@/graphql/user'; import { CURRENT_USER_CLIENT } from '@/graphql/user';
import { RouteName } from '@/router'; import { RouteName } from '@/router';
import { IEvent } from '@/types/event.model'; import { IEvent } from '@/types/event.model';
import DateComponent from '@/components/Event/Date.vue'; import DateComponent from '@/components/Event/DateCalendarIcon.vue';
import { CONFIG } from '@/graphql/config';
import { IConfig } from '@/types/config.model';
@Component({ @Component({
apollo: { apollo: {
@ -105,6 +109,9 @@ import DateComponent from '@/components/Event/Date.vue';
currentUser: { currentUser: {
query: CURRENT_USER_CLIENT, query: CURRENT_USER_CLIENT,
}, },
config: {
query: CONFIG,
},
}, },
components: { components: {
DateComponent, DateComponent,
@ -112,18 +119,19 @@ import DateComponent from '@/components/Event/Date.vue';
}, },
}) })
export default class Home extends Vue { export default class Home extends Vue {
events = []; events: Event[] = [];
locations = []; locations = [];
city = { name: null }; city = { name: null };
country = { name: null }; country = { name: null };
loggedPerson: IPerson = new Person(); loggedPerson: IPerson = new Person();
currentUser!: ICurrentUser; currentUser!: ICurrentUser;
config: IConfig = { description: '', name: '', registrationsOpen: false };
get displayed_name() { // get displayed_name() {
return this.loggedPerson.name === null // return this.loggedPerson && this.loggedPerson.name === null
? this.loggedPerson.preferredUsername // ? this.loggedPerson.preferredUsername
: this.loggedPerson.name; // : this.loggedPerson.name;
} // }
isToday(date: string) { isToday(date: string) {
return (new Date(date)).toDateString() === (new Date()).toDateString(); return (new Date(date)).toDateString() === (new Date()).toDateString();
@ -153,19 +161,18 @@ export default class Home extends Vue {
get goingToEvents(): Map<string, IEvent[]> { get goingToEvents(): Map<string, IEvent[]> {
const res = this.$data.loggedPerson.goingToEvents.filter((event) => { const res = this.$data.loggedPerson.goingToEvents.filter((event) => {
return event.beginsOn != null && this.isBefore(event.beginsOn, 0) return event.beginsOn != null && this.isBefore(event.beginsOn, 0);
}); });
res.sort( res.sort(
(a: IEvent, b: IEvent) => new Date(a.beginsOn) > new Date(b.beginsOn), (a: IEvent, b: IEvent) => new Date(a.beginsOn) > new Date(b.beginsOn),
); );
const groups = res.reduce((acc: Map<string, IEvent[]>, event: IEvent) => { return res.reduce((acc: Map<string, IEvent[]>, event: IEvent) => {
const day = (new Date(event.beginsOn)).toDateString(); const day = (new Date(event.beginsOn)).toDateString();
const events: IEvent[] = acc.get(day) || []; const events: IEvent[] = acc.get(day) || [];
events.push(event); events.push(event);
acc.set(day, events); acc.set(day, events);
return acc; return acc;
}, new Map()); }, new Map());
return groups;
} }
geoLocalize() { geoLocalize() {
@ -210,9 +217,9 @@ export default class Home extends Vue {
this.$router.push({ name: RouteName.EVENT, params: { uuid: event.uuid } }); this.$router.push({ name: RouteName.EVENT, params: { uuid: event.uuid } });
} }
ipLocation() { // ipLocation() {
return this.city.name ? this.city.name : this.country.name; // return this.city.name ? this.city.name : this.country.name;
} // }
} }
</script> </script>

View File

@ -1,8 +1,63 @@
<template> <template>
<section> <section class="container has-text-centered not-found">
<h1> <div class="columns is-vertical">
<translate>Page not found!</translate> <div class="column is-centered">
<img src="../assets/oh_no.jpg"> <img src="../assets/oh_no.jpg" alt="Not found 'oh no' picture">
</h1> <h1 class="title">
<translate>The page you're looking for doesn't exist.</translate>
</h1>
<p>
<translate>Please make sure the address is correct and that the page hasn't been moved.</translate>
</p>
<p>
<translate>Please contact this instance's Mobilizon admin if you think this is a mistake.</translate>
</p>
<!-- The following should just be replaced with the SearchField component but it fails for some reason -->
<form @submit="enter">
<b-field class="search">
<b-input expanded icon="magnify" type="search" :placeholder="searchPlaceHolder" v-model="searchText" />
<p class="control">
<button type="submit" class="button is-primary"><translate>Search</translate></button>
</p>
</b-field>
</form>
</div>
</div>
</section> </section>
</template> </template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteName } from '@/router';
import BField from 'buefy/src/components/field/Field.vue';
@Component({
components: {
BField,
},
})
export default class PageNotFound extends Vue {
searchText: string = '';
get searchPlaceHolder(): string {
return this.$gettext('Search events, groups, etc.');
}
enter() {
this.$router.push({ name: RouteName.SEARCH, params: { searchTerm: this.searchText } });
}
}
</script>
<style lang="scss">
.container.not-found {
margin: auto;
max-width: 600px;
img {
margin-top: 3rem;
}
p {
margin-bottom: 1em;
}
}
</style>

140
js/src/views/Search.vue Normal file
View File

@ -0,0 +1,140 @@
<template>
<section class="container">
<h1>
<translate :translate-params="{ search: this.searchTerm }">Search results: « %{ search } »</translate>
</h1>
<b-loading :active.sync="$apollo.loading" />
<b-tabs v-model="activeTab" type="is-boxed" class="searchTabs" @change="changeTab">
<b-tab-item>
<template slot="header">
<b-icon icon="calendar"></b-icon>
<span><translate>Events</translate> <b-tag rounded>{{ events.length }}</b-tag> </span>
</template>
<div v-if="search.length > 0" class="columns is-multiline">
<div class="column is-one-quarter-desktop is-half-mobile"
v-for="event in events"
:key="event.uuid">
<EventCard
:event="event"
/>
</div>
</div>
<b-message v-else-if="$apollo.loading === false" type="is-danger">
<translate>No events found</translate>
</b-message>
</b-tab-item>
<b-tab-item>
<template slot="header">
<b-icon icon="account-multiple"></b-icon>
<span><translate>Groups</translate> <b-tag rounded>{{ groups.length }}</b-tag> </span>
</template>
<div v-if="groups.length > 0" class="columns is-multiline">
<div class="column is-one-quarter-desktop is-half-mobile"
v-for="group in groups"
:key="group.uuid">
<group-card :group="group" />
</div>
</div>
<b-message v-else-if="$apollo.loading === false" type="is-danger">
<translate>No groups found</translate>
</b-message>
</b-tab-item>
</b-tabs>
</section>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import { SEARCH } from '@/graphql/search';
import { RouteName } from '@/router';
import { IEvent } from '@/types/event.model';
import { ISearch } from '@/types/search.model';
import EventCard from '@/components/Event/EventCard.vue';
import { IGroup, Group } from '@/types/actor.model';
import GroupCard from '@/components/Group/GroupCard.vue';
enum SearchTabs {
EVENTS = 0,
GROUPS = 1,
PERSONS = 2, // not used right now
}
const tabsName = {
events: SearchTabs.EVENTS,
groups: SearchTabs.GROUPS,
};
@Component({
apollo: {
search: {
query: SEARCH,
variables() {
return {
searchText: this.searchTerm,
};
},
skip() {
return !this.searchTerm;
},
},
},
components: {
GroupCard,
EventCard,
},
})
export default class Search extends Vue {
@Prop({ type: String, required: true }) searchTerm!: string;
@Prop({ type: String, required: false, default: 'events' }) searchType!: string;
search = [];
activeTab: SearchTabs = tabsName[this.searchType];
changeTab(index: number) {
switch (index) {
case SearchTabs.EVENTS:
this.$router.push({ name: RouteName.SEARCH, params: { searchTerm: this.searchTerm, searchType: 'events' } });
break;
case SearchTabs.GROUPS:
this.$router.push({ name: RouteName.SEARCH, params: { searchTerm: this.searchTerm, searchType: 'groups' } });
break;
}
}
@Watch('search')
changeTabForResult() {
if (this.events.length === 0 && this.groups.length > 0) {
this.activeTab = SearchTabs.GROUPS;
}
if (this.groups.length === 0 && this.events.length > 0) {
this.activeTab = SearchTabs.EVENTS;
}
}
@Watch('search')
@Watch('$route')
async loadSearch() {
await this.$apollo.queries['search'].refetch();
}
get events(): IEvent[] {
return this.search.filter((value: ISearch) => { return value.__typename === 'Event'; }) as IEvent[];
}
get groups(): IGroup[] {
const groups = this.search.filter((value: ISearch) => { return value.__typename === 'Group'; }) as IGroup[];
return groups.map(group => Object.assign(new Group(), group));
}
}
</script>
<style lang="scss">
@import "~bulma/sass/utilities/_all";
@import "~bulma/sass/components/tabs";
@import "~buefy/src/scss/components/tabs";
@import "~bulma/sass/elements/tag";
.searchTabs .tab-content {
background: #fff;
min-height: 10em;
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <div class="container">
<section class="hero"> <section class="hero">
<h1 class="title"> <h1 class="title">
<translate>Welcome back!</translate> <translate>Welcome back!</translate>
@ -12,14 +12,14 @@
<section v-if="!currentUser.isLoggedIn"> <section v-if="!currentUser.isLoggedIn">
<div class="columns is-mobile is-centered"> <div class="columns is-mobile is-centered">
<div class="column is-half card"> <div class="column is-half">
<b-message title="Error" type="is-danger" v-for="error in errors" :key="error">{{ error }}</b-message> <b-message title="Error" type="is-danger" v-for="error in errors" :key="error">{{ error }}</b-message>
<form @submit="loginAction"> <form @submit="loginAction">
<b-field label="Email"> <b-field :label="$gettext('Email')">
<b-input aria-required="true" required type="email" v-model="credentials.email"/> <b-input aria-required="true" required type="email" v-model="credentials.email"/>
</b-field> </b-field>
<b-field label="Password"> <b-field :label="$gettext('Password')">
<b-input <b-input
aria-required="true" aria-required="true"
required required
@ -70,20 +70,20 @@ import { ILogin } from '@/types/login.model';
import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user'; import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user';
import { onLogin } from '@/vue-apollo'; import { onLogin } from '@/vue-apollo';
import { RouteName } from '@/router'; import { RouteName } from '@/router';
import { LoginErrorCode } from '@/types/login-error-code.model' import { LoginErrorCode } from '@/types/login-error-code.model';
import { ICurrentUser } from '@/types/current-user.model' import { ICurrentUser } from '@/types/current-user.model';
import { CONFIG } from '@/graphql/config' import { CONFIG } from '@/graphql/config';
import { IConfig } from '@/types/config.model' import { IConfig } from '@/types/config.model';
@Component({ @Component({
apollo: { apollo: {
config: { config: {
query: CONFIG query: CONFIG,
}, },
currentUser: { currentUser: {
query: CURRENT_USER_CLIENT query: CURRENT_USER_CLIENT,
} },
} },
}) })
export default class Login extends Vue { export default class Login extends Vue {
@Prop({ type: String, required: false, default: '' }) email!: string; @Prop({ type: String, required: false, default: '' }) email!: string;
@ -113,9 +113,9 @@ export default class Login extends Vue {
this.credentials.email = this.email; this.credentials.email = this.email;
this.credentials.password = this.password; this.credentials.password = this.password;
let query = this.$route.query; const query = this.$route.query;
this.errorCode = query[ 'code' ] as LoginErrorCode; this.errorCode = query['code'] as LoginErrorCode;
this.redirect = query[ 'redirect' ] as string; this.redirect = query['redirect'] as string;
} }
async loginAction(e: Event) { async loginAction(e: Event) {
@ -146,7 +146,7 @@ export default class Login extends Vue {
onLogin(this.$apollo); onLogin(this.$apollo);
if (this.redirect) { if (this.redirect) {
this.$router.push(this.redirect) this.$router.push(this.redirect);
} else { } else {
this.$router.push({ name: RouteName.HOME }); this.$router.push({ name: RouteName.HOME });
} }

View File

@ -12,7 +12,7 @@
<div class="columns is-mobile"> <div class="columns is-mobile">
<div class="column"> <div class="column">
<div class="content"> <div class="content">
<h2 class="subtitle" v-translate>Features</h2> <h3 class="title" v-translate>Features</h3>
<ul> <ul>
<li v-translate>Create your communities and your events</li> <li v-translate>Create your communities and your events</li>
<li v-translate>Other stuff</li> <li v-translate>Other stuff</li>
@ -24,7 +24,7 @@
</p> </p>
<hr> <hr>
<div class="content"> <div class="content">
<h2 class="subtitle" v-translate>About this instance</h2> <h3 class="title" v-translate>About this instance</h3>
<p> <p>
<translate>Your local administrator resumed it's policy:</translate> <translate>Your local administrator resumed it's policy:</translate>
</p> </p>
@ -96,9 +96,7 @@
</form> </form>
<div v-if="errors.length > 0"> <div v-if="errors.length > 0">
<b-message type="is-danger" v-for="error in errors" :key="error"> <b-message type="is-danger" v-for="error in errors" :key="error">{{ error }}</b-message>
<translate>{{ error }}</translate>
</b-message>
</div> </div>
</div> </div>
</div> </div>
@ -154,6 +152,8 @@ export default class Register extends Vue {
</script> </script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.avatar-enter-active { .avatar-enter-active {
transition: opacity 1s ease; transition: opacity 1s ease;
} }
@ -166,4 +166,9 @@ export default class Register extends Vue {
.avatar-leave { .avatar-leave {
display: none; display: none;
} }
h3.title {
background: $secondary;
display: inline;
}
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<section class="columns"> <section class="container">
<div class="column card"> <div class="column">
<h1 class="title"> <h1 class="title">
<translate>Resend confirmation email</translate> <translate>Resend confirmation email</translate>
</h1> </h1>

View File

@ -1,6 +1,6 @@
<template> <template>
<section class="columns"> <section class="container">
<div class="card column"> <div class="column">
<h1 class="title"> <h1 class="title">
<translate>Password reset</translate> <translate>Password reset</translate>
</h1> </h1>

View File

@ -27,9 +27,18 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({
name: 'SearchResult', name: 'SearchResult',
possibleTypes: [ possibleTypes: [
{ name: 'Event' }, { name: 'Event' },
{ name: 'Actor' }, { name: 'Person' },
{ name: 'Group' },
], ],
}, // this is an example, put your INTERFACE and UNION kinds here! },
{
kind: 'INTERFACE',
name: 'Actor',
possibleTypes: [
{ name: 'Person' },
{ name: 'Group' },
],
},
], ],
}, },
}, },

11150
js/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,11 @@ defmodule Mobilizon.CommonConfig do
|> get_in([:name]) |> get_in([:name])
end end
def instance_description() do
instance_config()
|> get_in([:description])
end
defp instance_config(), do: Application.get_env(:mobilizon, :instance) defp instance_config(), do: Application.get_env(:mobilizon, :instance)
defp to_bool(v), do: v == true or v == "true" or v == "True" defp to_bool(v), do: v == true or v == "true" or v == "True"

View File

@ -9,6 +9,11 @@ defmodule MobilizonWeb.Resolvers.Config do
Get config Get config
""" """
def get_config(_parent, _params, _context) do def get_config(_parent, _params, _context) do
{:ok, %{name: instance_name(), registrations_open: registrations_open?()}} {:ok,
%{
name: instance_name(),
registrations_open: registrations_open?(),
description: instance_description()
}}
end end
end end

View File

@ -141,7 +141,7 @@ defmodule MobilizonWeb.Resolvers.Group do
actor_id: actor.id, actor_id: actor.id,
role: role role: role
}) do }) do
{:ok, %{parent: group, person: actor, role: role}} {:ok, %{parent: group, actor: actor, role: role}}
else else
{:is_owned, false} -> {:is_owned, false} ->
{:error, "Actor id is not owned by authenticated user"} {:error, "Actor id is not owned by authenticated user"}
@ -185,7 +185,7 @@ defmodule MobilizonWeb.Resolvers.Group do
parent: %{ parent: %{
id: group_id id: group_id
}, },
person: %{ actor: %{
id: actor_id id: actor_id
} }
} }

View File

@ -0,0 +1,15 @@
defmodule MobilizonWeb.Resolvers.Member do
@moduledoc """
Handles the member-related GraphQL calls
"""
alias Mobilizon.Actors
alias Mobilizon.Actors.{Actor}
@doc """
Find members for group
"""
def find_members_for_group(%Actor{} = actor, _args, _resolution) do
members = Actors.memberships_for_group(actor)
{:ok, members}
end
end

View File

@ -31,6 +31,7 @@ defmodule MobilizonWeb.Router do
end end
pipeline :browser do pipeline :browser do
plug(Plug.Static, at: "/", from: "priv/static")
plug(:accepts, ["html"]) plug(:accepts, ["html"])
plug(:fetch_session) plug(:fetch_session)
plug(:fetch_flash) plug(:fetch_flash)

View File

@ -6,6 +6,7 @@ defmodule MobilizonWeb.Schema.Actors.GroupType do
import Absinthe.Resolution.Helpers, only: [dataloader: 1] import Absinthe.Resolution.Helpers, only: [dataloader: 1]
import_types(MobilizonWeb.Schema.Actors.MemberType) import_types(MobilizonWeb.Schema.Actors.MemberType)
alias MobilizonWeb.Resolvers alias MobilizonWeb.Resolvers
alias Mobilizon.Events
@desc """ @desc """
Represents a group of actors Represents a group of actors
@ -49,7 +50,10 @@ defmodule MobilizonWeb.Schema.Actors.GroupType do
description: "Whether the group is opened to all or has restricted access" description: "Whether the group is opened to all or has restricted access"
) )
field(:members, non_null(list_of(:member)), description: "List of group members") field(:members, non_null(list_of(:member)),
resolve: &Resolvers.Member.find_members_for_group/3,
description: "List of group members"
)
end end
@desc """ @desc """

View File

@ -11,14 +11,14 @@ defmodule MobilizonWeb.Schema.Actors.MemberType do
""" """
object :member do object :member do
field(:parent, :group, description: "Of which the profile is member") field(:parent, :group, description: "Of which the profile is member")
field(:person, :person, description: "Which profile is member of") field(:actor, :person, description: "Which profile is member of")
field(:role, :integer, description: "The role of this membership") field(:role, :integer, description: "The role of this membership")
end end
@desc "Represents a deleted member" @desc "Represents a deleted member"
object :deleted_member do object :deleted_member do
field(:parent, :deleted_object) field(:parent, :deleted_object)
field(:person, :deleted_object) field(:actor, :deleted_object)
end end
object :member_mutations do object :member_mutations do

View File

@ -10,6 +10,7 @@ defmodule MobilizonWeb.Schema.ConfigType do
object :config do object :config do
# Instance name # Instance name
field(:name, :string) field(:name, :string)
field(:description, :string)
field(:registrations_open, :boolean) field(:registrations_open, :boolean)
end end

View File

@ -18,7 +18,7 @@ defmodule Mobilizon.Service.Export.ICalendar do
@spec export_public_event(Event.t()) :: {:ok, String.t()} @spec export_public_event(Event.t()) :: {:ok, String.t()}
def export_public_event(%Event{visibility: visibility} = event) def export_public_event(%Event{visibility: visibility} = event)
when visibility in [:public, :unlisted] do when visibility in [:public, :unlisted] do
{:ok, %ICalendar{events: [do_export_event(event)]} |> ICalendar.to_ics()} {:ok, %ICalendar{events: [do_export_event(event)]} |> ICalendar.to_ics(vendor: "Mobilizon")}
end end
@spec export_public_event(Event.t()) :: {:error, :event_not_public} @spec export_public_event(Event.t()) :: {:error, :event_not_public}
@ -29,6 +29,7 @@ defmodule Mobilizon.Service.Export.ICalendar do
%ICalendar.Event{ %ICalendar.Event{
summary: event.title, summary: event.title,
dtstart: event.begins_on, dtstart: event.begins_on,
dtstamp: event.publish_at || DateTime.utc_now(),
dtend: event.ends_on, dtend: event.ends_on,
description: event.description, description: event.description,
uid: event.uuid, uid: event.uuid,

View File

@ -67,7 +67,7 @@ defmodule Mobilizon.Mixfile do
{:geo, "~> 3.0"}, {:geo, "~> 3.0"},
{:geo_postgis, "~> 3.1"}, {:geo_postgis, "~> 3.1"},
{:timex, "~> 3.0"}, {:timex, "~> 3.0"},
{:icalendar, "~> 0.7"}, {:icalendar, github: "tcitworld/icalendar"},
{:exgravatar, "~> 2.0.1"}, {:exgravatar, "~> 2.0.1"},
{:httpoison, "~> 1.0"}, {:httpoison, "~> 1.0"},
{:json_ld, "~> 0.3"}, {:json_ld, "~> 0.3"},

View File

@ -56,7 +56,7 @@
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"http_sign": {:hex, :http_sign, "0.1.1", "b16edb83aa282892f3271f9a048c155e772bf36e15700ab93901484c55f8dd10", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "http_sign": {:hex, :http_sign, "0.1.1", "b16edb83aa282892f3271f9a048c155e772bf36e15700ab93901484c55f8dd10", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "1.5.0", "71ae9f304bdf7f00e9cd1823f275c955bdfc68282bc5eb5c85c3a9ade865d68e", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "httpoison": {:hex, :httpoison, "1.5.0", "71ae9f304bdf7f00e9cd1823f275c955bdfc68282bc5eb5c85c3a9ade865d68e", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"icalendar": {:hex, :icalendar, "0.7.1", "323c67afc82b9dc373778c8df13d786abab28585833a0656406a858ecb539b58", [:mix], [{:timex, "~> 3.4", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"}, "icalendar": {:git, "https://github.com/tcitworld/icalendar.git", "bd08e872c125f70a87c3ac7d87ea2f22a5577059", []},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, "jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},

View File

@ -23,7 +23,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
group_id: #{group.id} group_id: #{group.id}
) { ) {
role, role,
person { actor {
id id
}, },
parent { parent {
@ -41,7 +41,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
assert json_response(res, 200)["errors"] == nil assert json_response(res, 200)["errors"] == nil
assert json_response(res, 200)["data"]["joinGroup"]["role"] == "not_approved" assert json_response(res, 200)["data"]["joinGroup"]["role"] == "not_approved"
assert json_response(res, 200)["data"]["joinGroup"]["parent"]["id"] == group.id assert json_response(res, 200)["data"]["joinGroup"]["parent"]["id"] == group.id
assert json_response(res, 200)["data"]["joinGroup"]["person"]["id"] == actor.id assert json_response(res, 200)["data"]["joinGroup"]["actor"]["id"] == actor.id
mutation = """ mutation = """
mutation { mutation {
@ -151,7 +151,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
actor_id: #{actor.id}, actor_id: #{actor.id},
group_id: #{group.id} group_id: #{group.id}
) { ) {
person { actor {
id id
}, },
parent { parent {
@ -168,7 +168,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
assert json_response(res, 200)["errors"] == nil assert json_response(res, 200)["errors"] == nil
assert json_response(res, 200)["data"]["leaveGroup"]["parent"]["id"] == group.id assert json_response(res, 200)["data"]["leaveGroup"]["parent"]["id"] == group.id
assert json_response(res, 200)["data"]["leaveGroup"]["person"]["id"] == actor.id assert json_response(res, 200)["data"]["leaveGroup"]["actor"]["id"] == actor.id
end end
test "leave_group/3 should check if the member is the only administrator", %{ test "leave_group/3 should check if the member is the only administrator", %{
@ -186,7 +186,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
actor_id: #{actor.id}, actor_id: #{actor.id},
group_id: #{group.id} group_id: #{group.id}
) { ) {
person { actor {
id id
}, },
parent { parent {
@ -214,7 +214,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
actor_id: #{actor.id}, actor_id: #{actor.id},
group_id: #{group.id} group_id: #{group.id}
) { ) {
person { actor {
id id
} }
} }
@ -242,7 +242,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
actor_id: 1042, actor_id: 1042,
group_id: #{group.id} group_id: #{group.id}
) { ) {
person { actor {
id id
} }
} }
@ -271,7 +271,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
actor_id: #{actor.id}, actor_id: #{actor.id},
group_id: 1042 group_id: 1042
) { ) {
person { actor {
id id
} }
} }