Merge branch 'various/UI' into 'master'

Various/ui

See merge request framasoft/mobilizon!108
This commit is contained in:
Thomas Citharel 2019-04-11 12:09:30 +02:00
commit 16a9df48a7
73 changed files with 13947 additions and 15895 deletions

2
.gitignore vendored
View file

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

View file

@ -35,7 +35,7 @@ setup_js_deps:
before_script:
- cd js
script:
- npm install
- yarn install
after_script:
- cd ../
cache:
@ -46,9 +46,9 @@ js:
stage: front
before_script:
- cd js
- npm install
- yarn install
script:
- npm run build
- yarn run build
after_script:
- cd ../
cache:
@ -65,10 +65,9 @@ js_deps:
stage: front
before_script:
- cd js
- npm install
- npm install -g npm-check-updates
- yarn install
script:
- ncu --error-level 2
- yarn outdated
after_script:
- cd ../
cache:
@ -80,9 +79,9 @@ js_check:
stage: front
before_script:
- cd js
- npm install
- yarn install
script:
- npm run lint
- yarn run lint
after_script:
- cd ../
cache:

View file

@ -11,6 +11,7 @@ config :mobilizon,
config :mobilizon, :instance,
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",
registrations_open: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_OPEN") || false

View file

@ -1,9 +1,10 @@
FROM elixir:latest
LABEL maintainer="Thomas Citharel <tcit@tcit.fr>"
ENV REFRESHED_AT=2019-02-21
ENV REFRESHED_AT=2019-04-10
RUN apt-get update -yq && apt-get install -yq build-essential inotify-tools postgresql-client git curl gnupg
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash && apt-get install nodejs -yq
RUN npm install -g yarn
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN mix local.hex --force && mix local.rebar --force
RUN curl http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz --output GeoLite2-City.tar.gz -s && tar zxf GeoLite2-City.tar.gz && mkdir -p /usr/share/GeoIP && mv GeoLite2-City_*/GeoLite2-City.mmdb /usr/share/GeoIP/GeoLite2-City.mmdb

View file

@ -1,5 +1,5 @@
#!/bin/bash
npm install
npm rebuild node-sass
npm run dev
yan install
yarn rebuild node-sass
yarn run dev

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 fs = require('fs');
fetch(`http://localhost:4000/graphiql`, {
fetch(`http://localhost:4001`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({

15025
js/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

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

View file

@ -6,7 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<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>
<!--server-generated-meta-->
</head>

View file

@ -1,18 +1,10 @@
<template>
<div id="mobilizon">
<NavBar></NavBar>
<main class="container">
<router-view></router-view>
<NavBar />
<main>
<router-view />
</main>
<footer class="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>
<mobilizon-footer />
</div>
</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 { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user';
import { ICurrentUser } from '@/types/current-user.model';
import Footer from '@/components/Footer.vue';
import Logo from '@/components/Logo.vue';
@Component({
apollo: {
@ -30,63 +24,24 @@ import { ICurrentUser } from '@/types/current-user.model';
},
},
components: {
Logo,
NavBar,
'mobilizon-footer': Footer,
},
})
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;
actor = localStorage.getItem(AUTH_USER_ACTOR);
mounted () {
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
async mounted () {
await this.initializeCurrentUser();
}
getUser (): ICurrentUser|false {
return this.currentUser.id ? this.currentUser : false;
}
toggleDrawer() {
this.drawer = !this.drawer;
}
private initializeCurrentUser() {
const userId = localStorage.getItem(AUTH_USER_ID);
const userEmail = localStorage.getItem(AUTH_USER_EMAIL);
@ -106,7 +61,32 @@ export default class App extends Vue {
}
</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-leave-active {
transition-property: opacity;
@ -121,4 +101,8 @@ export default class App extends Vue {
.router-leave-active {
opacity: 0;
}
body {
background: #f6f7f8;
}
</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,14 +1,14 @@
<template>
<span class="container">
<time class="container" :datetime="dateObj.getUTCSeconds()">
<span class="month">{{ month }}</span>
<span class="day">{{ day }}</span>
</span>
</time>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class DateComponent extends Vue {
export default class DateCalendarIcon extends Vue {
@Prop({ required: true }) date!: string;
get dateObj() {
@ -26,22 +26,27 @@ export default class DateComponent extends Vue {
</script>
<style lang="scss" scoped>
.container {
display: inline-flex;
padding: 2px 0;
width: 40px;
background: #fff;
time.container {
background: #f6f7f8;
border: 1px solid rgba(46,62,72,.12);
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
/*height: 50px;*/
width: 48px;
padding: 8px;
text-align: center;
span {
flex: 0;
flex-direction: column;
text-align: center;
display: block;
&.month {
color: #fa3e3e;
padding: 2px 0;
font-size: 12px;
line-height: 12px;
text-transform: uppercase;
}
&.day {

View file

@ -1,61 +1,124 @@
<template>
<div class="card">
<router-link class="card" :to="{ name: 'Event', params: { uuid: event.uuid } }">
<div class="card-image" v-if="!event.image">
<figure class="image is-16by9">
<img src="https://picsum.photos/g/400/225/?random">
</figure>
</div>
<div class="card-content">
<div class="content">
<router-link :to="{ name: 'Event', params:{ uuid: event.uuid } }">
<h2 class="title">{{ event.title }}</h2>
</router-link>
<DateComponent v-if="!options.hideDate" :date="event.beginsOn" />
<div class="title-wrapper">
<div class="date-component">
<date-calendar-icon v-if="!mergedOptions.hideDate" :date="event.beginsOn" />
</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>
<h2 class="title" ref="title">{{ event.title }}</h2>
</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>
<span v-if="event.physicalAddress && event.physicalAddress.locality">{{ event.physicalAddress.locality }} - </span>
<span v-if="actorDisplayName && actorDisplayName !== '@'">{{ actorDisplayName }}</span>
</span>
</div>
</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>
<script lang="ts">
import { IEvent, ParticipantRole } from '@/types/event.model';
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({
components: {
DateComponent,
DateCalendarIcon,
EventCard,
},
mounted() {
lineClamp(this.$refs.title, 3);
},
})
export default class EventCard extends Vue {
@Prop({ required: true }) event!: IEvent;
@Prop({ default() { return { hideDate: false, loggedPerson: false, hideDetails: false }; } }) options!: object;
@Prop({ required: false }) options!: IEventCardOptions;
data() {
return {
ParticipantRole,
ParticipantRole = 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>
<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 class="card-content">
<div class="content">
<router-link :to="{ name: 'Group', params:{ uuid: group.uuid } }">
<h2 class="title">{{ group.name ? group.name : group.preferredUsername }}</h2>
<router-link :to="{ name: RouteName.GROUP, params:{ preferredUsername: group.preferredUsername } }">
<h2 class="title">{{ group.displayName() }}</h2>
</router-link>
</div>
<div v-if="!hideDetails">
<div>
<p>{{ group.summary }}</p>
</div>
</div>
@ -20,11 +20,13 @@
<script lang="ts">
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
export default class GroupCard extends Vue {
@Prop({ required: true }) group!: IGroup;
@Prop({ default: false }) hideDetails!: boolean;
@Prop({ required: true }) group!: Group;
RouteName = RouteName;
}
</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>
<div style="height: 100%; width: 100%">
<div class="map-container">
<l-map
:zoom="16"
style="height: 80%; width: 100%"
:zoom="mergedOptions.zoom"
:style="`height: ${mergedOptions.height}; width: ${mergedOptions.width}`"
class="leaflet-map"
:center="[lat, lon]"
>
<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({
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: false }) popup!: string;
@Prop({ type: Object, required: false }) options!: object;
defaultOptions: object = {
zoom: 15,
height: '100%',
width: '100%',
};
mounted() {
// 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 lon() { return this.$props.coords.split(';')[1]; }
}
</script>
<style lang="scss" scoped>
div.map-container {
height: 100%;
width: 100%;
.leaflet-map {
z-index: 20;
}
}
</style>

View file

@ -1,7 +1,8 @@
<template>
<nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">
<div class="container">
<div class="navbar-brand">
<router-link class="navbar-item" :to="{ name: 'Home' }">Mobilizon</router-link>
<router-link class="navbar-item" :to="{ name: 'Home' }"><logo /></router-link>
<a
role="button"
@ -9,25 +10,33 @@
aria-label="menu"
aria-expanded="false"
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>
</a>
</div>
<div class="navbar-menu" :class="{ 'is-active': showNavbar }">
<div class="navbar-end">
<div class="navbar-item">
<search-field />
</div>
<div class="navbar-item" v-if="!currentUser.isLoggedIn">
<div class="buttons">
<router-link class="button is-primary" v-if="!currentUser.isLoggedIn && config && config.registrationsOpen" :to="{ name: 'Register' }">
<router-link class="button is-primary" v-if="config && config.registrationsOpen" :to="{ name: 'Register' }">
<strong>
<translate>Sign up</translate>
</strong>
</router-link>
<router-link class="button is-light" v-if="!currentUser.isLoggedIn" :to="{ name: 'Login' }">
<router-link class="button is-primary" :to="{ name: 'Login' }">
<translate>Log in</translate>
</router-link>
</div>
</div>
<div class="navbar-item has-dropdown is-hoverable" v-else>
<router-link
class="button is-light"
class="navbar-link"
v-if="currentUser.isLoggedIn && loggedPerson"
:to="{ name: 'Profile', params: { name: loggedPerson.preferredUsername} }"
>
@ -37,7 +46,11 @@
<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>
@ -46,30 +59,19 @@
<script lang="ts">
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 { onLogout } from '@/vue-apollo';
import { deleteUserData } from '@/utils/auth';
import { LOGGED_PERSON } from '@/graphql/actor';
import { IActor, IPerson } from '@/types/actor.model';
import { RouteName } from '@/router';
import { IPerson } from '@/types/actor.model';
import { CONFIG } from '@/graphql/config';
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({
apollo: {
search: {
query: SEARCH,
variables() {
return {
searchText: this.searchText,
};
},
skip() {
return !this.searchText;
},
},
currentUser: {
query: CURRENT_USER_CLIENT,
},
@ -77,35 +79,20 @@ import { ICurrentUser } from '@/types/current-user.model'
query: CONFIG,
},
},
components: {
Logo,
SearchField,
},
})
export default class NavBar extends Vue {
notifications = [
{ header: 'Coucou' },
{ title: "T'as une notification", subtitle: 'Et elle est cool' },
];
model = null;
search: any[] = [];
searchText: string | null = null;
searchSelect = null;
loggedPerson: IPerson | null = null;
config!: IConfig;
currentUser!: ICurrentUser;
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;
});
}
showNavbar: boolean = false;
@Watch('currentUser')
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() {
await this.$apollo.mutate({
mutation: UPDATE_CURRENT_USER_CLIENT,
@ -161,9 +120,16 @@ export default class NavBar extends Vue {
deleteUserData();
onLogout(this.$apollo)
onLogout(this.$apollo);
return this.$router.push({ path: '/' })
return this.$router.push({ path: '/' });
}
}
</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 {
config {
name,
description,
registrationsOpen
}
}

View file

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

View file

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

View file

@ -1,14 +1,14 @@
# 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.
# Automatically generated, 2018.
# Automatically generated, 2019.
#
msgid ""
msgstr ""
"Project-Id-Version: mobilizon 0.1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-17 16:08+0100\n"
"PO-Revision-Date: 2018-10-24 16:25+0200\n"
"POT-Creation-Date: 2019-04-10 16:31+0200\n"
"PO-Revision-Date: 2019-04-08 20:58+0200\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: en_US\n"
@ -17,178 +17,423 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\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"
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}"
msgstr "A validation email was sent to %{email}"
#: src/components/Account/Register.vue:26
msgid "About this instance"
msgstr ""
#: src/components/Footer.vue:5
msgid "About"
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"
msgstr "Before you can login, you need to click on the link inside it to validate your account"
#: src/components/Category/Create.vue:7
msgid "Create a new category"
msgstr ""
#: src/views/Event/Event.vue:101
msgid "By %{ name }"
msgstr "By %{ name }"
#: src/components/Category/Create.vue:34
msgid "Create category"
msgstr ""
#: src/views/Event/Create.vue:3
msgid "Create a new event"
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"
msgstr ""
msgstr "Create your communities and your events"
#: src/components/Account/Profile.vue:48 src/components/Category/List.vue:21
#: src/components/Event/Event.vue:41
#: src/views/Account/Identities.vue:36
msgid "Current"
msgstr "Current"
#: src/views/Account/Profile.vue:93 src/views/Event/Event.vue:64
msgid "Delete"
msgstr ""
msgstr "Delete"
#: src/components/Account/Register.vue:80
#: src/views/User/Register.vue:82
msgid "Didn't receive the instructions ?"
msgstr ""
msgstr "Didn't receive the instructions ?"
#: src/components/Event/Event.vue:36
msgid "Download"
msgstr ""
#: src/components/Event/Event.vue:31
#: src/views/Event/Event.vue:59
msgid "Edit"
msgstr ""
msgstr "Edit"
#: src/components/Account/Validate.vue:8
msgid "Error while validating account"
msgstr ""
#: src/views/User/Validate.vue:8
msgid "Either the account is already validated, either the validation token is incorrect."
msgstr "Either the account is already validated, either the validation token is incorrect."
#: src/components/Category/List.vue:18
msgid "Explore"
msgstr ""
#: src/views/Event/EventList.vue:3
msgid "Event list"
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"
msgstr ""
msgstr "Features"
#: src/components/Account/Login.vue:46
#: src/views/User/Login.vue:41
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 ""
"Learn more on\n"
" <a target=\"_blank\" href=\"https://joinmobilizon.org\">joinmobilizon.org</a>"
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"
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"
msgstr ""
msgstr "Login"
#: src/components/Account/Register.vue:32
#: src/views/User/Register.vue:32
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"
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"
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…"
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"
msgstr ""
msgstr "Password reset"
#: src/components/Account/Register.vue:31
#: src/views/User/Register.vue:31
msgid "Please be nice to each other"
msgstr ""
msgstr "Please be nice to each other"
#: src/components/Account/ResendConfirmation.vue:21
#: src/components/Account/SendPasswordReset.vue:22
#: src/views/User/ResendConfirmation.vue:21
#: src/views/User/SendPasswordReset.vue:22
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 ""
#: 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"
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"
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!"
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"
msgstr ""
msgstr "Resend confirmation email"
#: src/components/Account/PasswordReset.vue:26
#: src/views/User/PasswordReset.vue:29
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"
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"
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"
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 ""
#: 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"
msgstr ""
msgstr "User logout"
#: src/components/Event/Event.vue:50
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
#: src/views/User/SendPasswordReset.vue:17
msgid "We just sent an email to %{email}"
msgstr ""
msgstr "We just sent an email to %{email}"
#: src/components/Account/ResendConfirmation.vue:16
msgid "We just sent another confirmation email to %{email}"
msgstr ""
#: src/components/Home.vue:16
#: src/views/Home.vue:18
msgid "Welcome back %{username}"
msgstr ""
msgstr "Welcome back %{username}"
#: src/components/Account/Login.vue:4
#: src/views/User/Login.vue:4
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"
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"
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:"
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 ""

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 ""
"Project-Id-Version: mobilizon 0.1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-17 16:08+0100\n"
"PO-Revision-Date: 2018-10-24 16:25+0200\n"
"POT-Creation-Date: 2019-04-10 16:31+0200\n"
"PO-Revision-Date: 2019-04-10 16:33+0200\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: fr_FR\n"
@ -16,179 +16,425 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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"
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}"
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"
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"
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
msgid "Create a new category"
msgstr ""
#: src/views/Event/Event.vue:101
msgid "By %{ name }"
msgstr "Par %{name}"
#: src/components/Category/Create.vue:34
msgid "Create category"
msgstr ""
#: src/views/Event/Create.vue:3
msgid "Create a new event"
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"
msgstr ""
msgstr "Créer vos communautés et vos événements"
#: src/components/Account/Profile.vue:48 src/components/Category/List.vue:21
#: src/components/Event/Event.vue:41
#: src/views/Account/Identities.vue:36
msgid "Current"
msgstr "Actuel"
#: src/views/Account/Profile.vue:93 src/views/Event/Event.vue:64
msgid "Delete"
msgstr ""
msgstr "Supprimer"
#: src/components/Account/Register.vue:80
#: src/views/User/Register.vue:82
msgid "Didn't receive the instructions ?"
msgstr ""
msgstr "Vous n'avez pas reçu les instructions ?"
#: src/components/Event/Event.vue:36
msgid "Download"
msgstr ""
#: src/components/Event/Event.vue:31
#: src/views/Event/Event.vue:59
msgid "Edit"
msgstr ""
msgstr "Éditer"
#: src/components/Account/Validate.vue:8
msgid "Error while validating account"
msgstr ""
#: src/views/User/Validate.vue:8
msgid "Either the account is already validated, either the validation token is incorrect."
msgstr "Soit le compte est déjà validé, soit le jeton de validation est incorrect."
#: src/components/Category/List.vue:18
msgid "Explore"
msgstr ""
#: src/views/Event/EventList.vue:3
msgid "Event list"
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"
msgstr ""
msgstr "Fonctionnalités"
#: src/components/Account/Login.vue:46
#: src/views/User/Login.vue:41
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 ""
"Learn more on\n"
" <a target=\"_blank\" href=\"https://joinmobilizon.org\">joinmobilizon.org</a>"
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"
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"
msgstr ""
msgstr "Se connecter"
#: src/components/Account/Register.vue:32
#: src/views/User/Register.vue:32
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"
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"
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…"
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"
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"
msgstr ""
msgstr "Soyez sympas entre vous"
#: src/components/Account/ResendConfirmation.vue:21
#: src/components/Account/SendPasswordReset.vue:22
#: src/views/User/ResendConfirmation.vue:21
#: src/views/User/SendPasswordReset.vue:22
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"
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"
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!"
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"
msgstr ""
msgstr "Envoyer à nouveau l'email de confirmation"
#: src/components/Account/PasswordReset.vue:26
#: src/views/User/PasswordReset.vue:29
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"
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"
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"
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"
msgstr ""
msgstr "Déconnexion"
#: src/components/Event/Event.vue:50
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
#: src/views/User/SendPasswordReset.vue:17
msgid "We just sent an email to %{email}"
msgstr ""
msgstr "Nous venons d'envoyer un email à %{email}"
#: src/components/Account/ResendConfirmation.vue:16
msgid "We just sent another confirmation email to %{email}"
msgstr ""
#: src/components/Home.vue:16
#: src/views/Home.vue:18
msgid "Welcome back %{username}"
msgstr ""
msgstr "Bon retour %{username}"
#: src/components/Account/Login.vue:4
#: src/views/User/Login.vue:4
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"
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"
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:"
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 VueSimpleMarkdown from 'vue-simple-markdown';
import Buefy from 'buefy';
import 'buefy/dist/buefy.css';
import GetTextPlugin from 'vue-gettext';
import App from '@/App.vue';
import router from '@/router';
@ -20,9 +19,6 @@ Vue.use(Buefy, {
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, {
translations,
defaultLanguage: 'en_US',

View file

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

View file

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

View file

@ -7,12 +7,24 @@ import { EventRouteName, eventRoutes } from '@/router/event';
import { ActorRouteName, actorRoutes } from '@/router/actor';
import { ErrorRouteName, errorRoutes } from '@/router/error';
import { authGuardIfNeeded } from '@/router/guards/auth-guard';
import Search from '@/views/Search.vue';
Vue.use(Router);
enum GlobalRouteName {
HOME = 'Home',
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
@ -26,6 +38,7 @@ export const RouteName = {
};
const router = new Router({
scrollBehavior,
mode: 'history',
base: '/',
routes: [
@ -33,7 +46,13 @@ const router = new Router({
...eventRoutes,
...actorRoutes,
...errorRoutes,
{
path: '/search/:searchTerm/:searchType?',
name: RouteName.SEARCH,
component: Search,
props: true,
meta: { requiredAuth: false },
},
{
path: '/',
name: RouteName.HOME,

View file

@ -22,6 +22,19 @@ export class Actor implements IActor {
summary: string = '';
suspended: boolean = false;
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 {
@ -38,6 +51,10 @@ export class Person extends Actor implements IPerson {
goingToEvents: IEvent[] = [];
}
export class Group extends Actor implements IGroup {
members: IMember[] = [];
}
export interface IFeedToken {
token: string;
actor?: IPerson;

View file

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

View file

@ -8,10 +8,10 @@ export enum EventStatus {
}
export enum EventVisibility {
PUBLIC,
UNLISTED,
RESTRICTED,
PRIVATE,
PUBLIC = 'PUBLIC',
UNLISTED = 'UNLISTED',
RESTRICTED = 'RESTRICTED',
PRIVATE = 'PRIVATE',
}
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,8 +1,6 @@
<template>
<section>
<div class="columns">
<div class="column">
<div class="card" v-if="person">
<section class="container">
<div v-if="person">
<div class="card-image" v-if="person.bannerUrl">
<figure class="image">
<img :src="person.bannerUrl">
@ -22,11 +20,11 @@
</div>
<div class="content">
<p v-html="person.summary"></p>
<vue-simple-markdown :source="person.summary"></vue-simple-markdown>
</div>
<b-dropdown hoverable has-link aria-role="list">
<button class="button is-info" slot="trigger">
<button class="button is-primary" slot="trigger">
<translate>Public feeds</translate>
<b-icon icon="menu-down"></b-icon>
</button>
@ -60,7 +58,7 @@
</a>
</b-dropdown-item>
</b-dropdown>
<a class="button" v-else @click="createToken">
<a class="button" v-else-if="loggedPerson" @click="createToken">
<translate>Create token</translate>
</a>
</div>
@ -72,7 +70,7 @@
<EventCard
v-for="event in person.organizedEvents"
:event="event"
:options="{ hideDetails: true }"
:options="{ hideDetails: true, organizerActor: person }"
:key="event.uuid"
class="column is-one-third"
/>
@ -99,8 +97,6 @@
</div>
</section>
</div>
</div>
</div>
</section>
</template>
@ -172,3 +168,8 @@ export default class Profile extends Vue {
}
}
</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>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { ErrorCode } from '@/types/error-code.model';
import { Component, Vue } from 'vue-property-decorator';
import { ErrorCode } from '@/types/error-code.model';
@Component
export default class ErrorPage extends Vue {
@Component
export default class ErrorPage extends Vue {
code: ErrorCode | null = null;
ErrorCode = ErrorCode;
mounted() {
this.code = this.$route.query[ 'code' ] as ErrorCode;
}
this.code = this.$route.query['code'] as ErrorCode;
}
}
</script>

View file

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

View file

@ -1,31 +1,59 @@
<template>
<div class="columns is-centered">
<div class="column is-three-quarters">
<div>
<b-loading :active.sync="$apollo.loading"></b-loading>
<div class="card" v-if="event">
<div class="card-image">
<figure class="image is-4by3">
<img src="https://picsum.photos/600/400/">
<div v-if="event">
<div class="header-picture container">
<figure class="image is-3by1">
<img src="https://picsum.photos/600/200/">
</figure>
</div>
<div class="card-content">
<span>{{ event.beginsOn | formatDay }}</span>
<span class="tag is-primary">{{ event.category }}</span>
<section class="container">
<div class="title-and-participate-button">
<div class="title-wrapper">
<div class="date-component">
<date-calendar-icon :date="event.beginsOn"></date-calendar-icon>
</div>
<h1 class="title">{{ event.title }}</h1>
<router-link
:to="{name: 'Profile', params: { name: event.organizerActor.preferredUsername } }"
>
<figure v-if="event.organizerActor.avatarUrl">
<img :src="event.organizerActor.avatarUrl">
</figure>
</router-link>
<span
v-if="event.organizerActor"
>Organisé par {{ event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername }}</span>
<div class="field has-addons">
</div>
<div v-if="!actorIsOrganizer()" class="participate-button has-text-centered">
<a v-if="!actorIsParticipant()" @click="joinEvent" class="button is-large is-primary is-rounded">
<b-icon icon="circle-outline"></b-icon>
<translate>Join</translate>
</a>
<a v-if="actorIsParticipant()" @click="leaveEvent" class="button is-large is-primary is-rounded">
<b-icon icon="check-circle"></b-icon>
<translate>Leave</translate>
</a>
</div>
</div>
<div class="metadata columns">
<div class="column is-three-quarters-desktop">
<p class="tags" v-if="event.category || event.tags.length > 0">
<span class="tag" v-if="event.category">{{ event.category }}</span>
<span class="tag" v-if="event.tags" v-for="tag in event.tags">{{ tag.title }}</span>
<span class="visibility">
<translate v-if="event.visibility === EventVisibility.PUBLIC">public event</translate>
</span>
</p>
<div class="date-and-add-to-calendar">
<div class="date-and-privacy" v-if="event.beginsOn">
<b-icon icon="calendar-clock" />
<event-full-date :beginsOn="event.beginsOn" :endsOn="event.endsOn" />
</div>
<a class="add-to-calendar" @click="downloadIcsEvent()">
<b-icon icon="calendar-plus" />
<translate>Add to my calendar</translate>
</a>
</div>
<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
v-if="actorIsOrganizer()"
class="button"
:to="{ name: 'EditEvent', params: {uuid: event.uuid}}"
>
@ -33,83 +61,174 @@
</router-link>
</p>
<p class="control">
<a class="button" @click="downloadIcsEvent()">
<translate>Download</translate>
</a>
</p>
<p class="control">
<a class="button is-danger" v-if="actorIsOrganizer()" @click="deleteEvent()">
<a class="button is-danger" @click="deleteEvent()">
<translate>Delete</translate>
</a>
</p>
</div>
<div>
<span>{{ event.beginsOn | formatDate }} - {{ event.endsOn | formatDate }}</span>
</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">
<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>
<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>
<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 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">
<div class="organizer">
<router-link
class="card column"
v-for="participant in event.participants"
:key="participant.preferredUsername"
:to="{name: 'Profile', params: { name: participant.actor.preferredUsername }}"
:to="{name: 'Profile', params: { name: event.organizerActor.preferredUsername } }"
>
<div>
<figure>
<img v-if="!participant.actor.avatarUrl" src="https://picsum.photos/125/125/">
<img v-else :src="participant.actor.avatarUrl">
<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>
<span>{{ participant.actor.preferredUsername }}</span>
</div>
</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>
<!-- <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>
</template>
@ -118,14 +237,22 @@
import { DELETE_EVENT, FETCH_EVENT, JOIN_EVENT, LEAVE_EVENT } from '@/graphql/event';
import { Component, Prop, Vue } from 'vue-property-decorator';
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 { RouteName } from '@/router';
import 'vue-simple-markdown/dist/vue-simple-markdown.css';
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({
components: {
EventFullDate,
EventCard,
BIcon,
DateCalendarIcon,
'map-leaflet': () => import('@/components/Map.vue'),
},
apollo: {
@ -148,6 +275,9 @@ export default class Event extends Vue {
event!: IEvent;
loggedPerson!: IPerson;
validationSent: boolean = false;
showMap: boolean = false;
EventVisibility = EventVisibility;
async deleteEvent() {
const router = this.$router;
@ -241,12 +371,255 @@ export default class Event extends Vue {
return this.loggedPerson &&
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>
<style lang="scss">
.address div.map {
height: 400px;
width: 400px;
padding: 25px 35px;
<style lang="scss" scoped>
@import "../../variables";
div.sidebar {
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>

View file

@ -1,14 +1,12 @@
<template>
<section>
<div class="columns">
<div class="column">
<div class="card" v-if="group">
<section class="container">
<div v-if="group">
<div class="card-image" v-if="group.bannerUrl">
<figure class="image">
<img :src="group.bannerUrl">
</figure>
</div>
<div class="card-content">
<div class="box">
<div class="media">
<div class="media-left">
<figure class="image is-48x48">
@ -25,7 +23,7 @@
<p v-html="group.summary"></p>
</div>
</div>
<section v-if="group.organizedEvents.length > 0">
<section class="box" v-if="group.organizedEvents.length > 0">
<h2 class="subtitle">
<translate>Organized</translate>
</h2>
@ -46,31 +44,30 @@
<div class="columns">
<span
v-for="member in group.members"
:key="member"
:key="member.actor.preferredUsername"
>{{ member.actor.preferredUsername }}</span>
</div>
</section>
</div>
<b-message v-if-else="!group && $apollo.loading === false" type="is-danger">
<b-message v-else-if="!group && $apollo.loading === false" type="is-danger">
<translate>No group found</translate>
</b-message>
</div>
</div>
</section>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
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({
apollo: {
person: {
query: FETCH_PERSON,
group: {
query: FETCH_GROUP,
variables() {
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 {
@Prop({ type: String, required: true }) name!: string;
@Prop({ type: String, required: true }) preferredUsername!: string;
group = null;
group!: IGroup;
loading = true;
created() {
@ -110,3 +107,8 @@ export default class Group extends Vue {
}
}
</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"
/>
</div>
<router-link class="button" :to="{ name: 'CreateGroup' }">
<router-link class="button" :to="{ name: RouteName.CREATE_GROUP }">
<translate>Create group</translate>
</router-link>
</section>
@ -27,6 +27,8 @@ export default class GroupList extends Vue {
groups = [];
loading = true;
RouteName = RouteName;
created() {
this.fetchData();
}

View file

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

View file

@ -1,8 +1,63 @@
<template>
<section>
<h1>
<translate>Page not found!</translate>
<img src="../assets/oh_no.jpg">
<section class="container has-text-centered not-found">
<div class="columns is-vertical">
<div class="column is-centered">
<img src="../assets/oh_no.jpg" alt="Not found 'oh no' picture">
<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>
</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>
<div>
<div class="container">
<section class="hero">
<h1 class="title">
<translate>Welcome back!</translate>
@ -12,14 +12,14 @@
<section v-if="!currentUser.isLoggedIn">
<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>
<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-field>
<b-field label="Password">
<b-field :label="$gettext('Password')">
<b-input
aria-required="true"
required
@ -70,20 +70,20 @@ import { ILogin } from '@/types/login.model';
import { CURRENT_USER_CLIENT, UPDATE_CURRENT_USER_CLIENT } from '@/graphql/user';
import { onLogin } from '@/vue-apollo';
import { RouteName } from '@/router';
import { LoginErrorCode } from '@/types/login-error-code.model'
import { ICurrentUser } from '@/types/current-user.model'
import { CONFIG } from '@/graphql/config'
import { IConfig } from '@/types/config.model'
import { LoginErrorCode } from '@/types/login-error-code.model';
import { ICurrentUser } from '@/types/current-user.model';
import { CONFIG } from '@/graphql/config';
import { IConfig } from '@/types/config.model';
@Component({
apollo: {
config: {
query: CONFIG
query: CONFIG,
},
currentUser: {
query: CURRENT_USER_CLIENT
}
}
query: CURRENT_USER_CLIENT,
},
},
})
export default class Login extends Vue {
@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.password = this.password;
let query = this.$route.query;
this.errorCode = query[ 'code' ] as LoginErrorCode;
this.redirect = query[ 'redirect' ] as string;
const query = this.$route.query;
this.errorCode = query['code'] as LoginErrorCode;
this.redirect = query['redirect'] as string;
}
async loginAction(e: Event) {
@ -146,7 +146,7 @@ export default class Login extends Vue {
onLogin(this.$apollo);
if (this.redirect) {
this.$router.push(this.redirect)
this.$router.push(this.redirect);
} else {
this.$router.push({ name: RouteName.HOME });
}

View file

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

View file

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

View file

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

View file

@ -27,9 +27,18 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({
name: 'SearchResult',
possibleTypes: [
{ 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' },
],
},
],
},
},

10827
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])
end
def instance_description() do
instance_config()
|> get_in([:description])
end
defp instance_config(), do: Application.get_env(:mobilizon, :instance)
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
"""
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

View file

@ -141,7 +141,7 @@ defmodule MobilizonWeb.Resolvers.Group do
actor_id: actor.id,
role: role
}) do
{:ok, %{parent: group, person: actor, role: role}}
{:ok, %{parent: group, actor: actor, role: role}}
else
{:is_owned, false} ->
{:error, "Actor id is not owned by authenticated user"}
@ -185,7 +185,7 @@ defmodule MobilizonWeb.Resolvers.Group do
parent: %{
id: group_id
},
person: %{
actor: %{
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
pipeline :browser do
plug(Plug.Static, at: "/", from: "priv/static")
plug(:accepts, ["html"])
plug(:fetch_session)
plug(:fetch_flash)

View file

@ -6,6 +6,7 @@ defmodule MobilizonWeb.Schema.Actors.GroupType do
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
import_types(MobilizonWeb.Schema.Actors.MemberType)
alias MobilizonWeb.Resolvers
alias Mobilizon.Events
@desc """
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"
)
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
@desc """

View file

@ -11,14 +11,14 @@ defmodule MobilizonWeb.Schema.Actors.MemberType do
"""
object :member do
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")
end
@desc "Represents a deleted member"
object :deleted_member do
field(:parent, :deleted_object)
field(:person, :deleted_object)
field(:actor, :deleted_object)
end
object :member_mutations do

View file

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

View file

@ -18,7 +18,7 @@ defmodule Mobilizon.Service.Export.ICalendar do
@spec export_public_event(Event.t()) :: {:ok, String.t()}
def export_public_event(%Event{visibility: visibility} = event)
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
@spec export_public_event(Event.t()) :: {:error, :event_not_public}
@ -29,6 +29,7 @@ defmodule Mobilizon.Service.Export.ICalendar do
%ICalendar.Event{
summary: event.title,
dtstart: event.begins_on,
dtstamp: event.publish_at || DateTime.utc_now(),
dtend: event.ends_on,
description: event.description,
uid: event.uuid,

View file

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

View file

@ -38,8 +38,8 @@ once the server is running:
* Install the NodeJS (we guarantee support for the latest LTS and later) ![](https://img.shields.io/badge/node-%3E%3D%2010.0+-brightgreen.svg)
* Change directory to `js/` and do:
* Install JavaScript package dependencies: `npm install`.
* Run the developement server in watch mode: `npm run dev`. This will open a
* Install JavaScript package dependencies: `yarn install`.
* Run the developement server in watch mode: `yarn run dev`. This will open a
browser on [`localhost:8080`](http://localhost:8080) that gets
automatically reloaded on change.

View file

@ -13,9 +13,9 @@ These two commands must not return an error code, since they are required to pas
# Front
We use `tslint` with the `tslint-config-airbnb` preset.
Errors should be reported when running in dev mode `npm run dev` or when building a production bundle `npm run build`.
Errors should be reported when running in dev mode `yarn run dev` or when building a production bundle `yarn run build`.
Please run the following command before pushing code `npm run lint`.
Please run the following command before pushing code `yanr run lint`.
This command must not return an error code, since it's required to pass inside CI.

View file

@ -18,6 +18,7 @@ sudo apt-get install curl sudo unzip vim
[https://certbot.eff.org/all-instructions](https://certbot.eff.org/all-instructions)
4. Install NodeJS 10.x (current LTS):
[https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions)
5. Install yarn, and be sure to have [a recent version](https://github.com/yarnpkg/yarn/releases/latest): [yarnpkg.com/en/docs/install#linux-tab](https://yarnpkg.com/en/docs/install#linux-tab)
5. Install Erlang and Elixir:
[https://elixir-lang.org/install.html#unix-and-unix-like](https://elixir-lang.org/install.html#unix-and-unix-like)
6. Install PostGIS:
@ -39,7 +40,7 @@ sudo systemctl start postgresql
1. Run:
```
sudo pacman -S nodejs postgresql openssl git wget unzip base-devel npm nginx elixir postgis
sudo pacman -S nodejs postgresql openssl git wget unzip base-devel yarn nginx elixir postgis
```
Now that dependencies are installed, before running MobiliZon you should start PostgreSQL and Redis:

View file

@ -77,12 +77,12 @@ cd js/
and install the Javascript dependencies
```bash
npm install
yarn install
```
Finally, build the front-end with
```bash
npm run build
yarn run build
```
### Testing

View file

@ -23,7 +23,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
group_id: #{group.id}
) {
role,
person {
actor {
id
},
parent {
@ -41,7 +41,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
assert json_response(res, 200)["errors"] == nil
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"]["person"]["id"] == actor.id
assert json_response(res, 200)["data"]["joinGroup"]["actor"]["id"] == actor.id
mutation = """
mutation {
@ -151,7 +151,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
actor_id: #{actor.id},
group_id: #{group.id}
) {
person {
actor {
id
},
parent {
@ -168,7 +168,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
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"]["person"]["id"] == actor.id
assert json_response(res, 200)["data"]["leaveGroup"]["actor"]["id"] == actor.id
end
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},
group_id: #{group.id}
) {
person {
actor {
id
},
parent {
@ -214,7 +214,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
actor_id: #{actor.id},
group_id: #{group.id}
) {
person {
actor {
id
}
}
@ -242,7 +242,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
actor_id: 1042,
group_id: #{group.id}
) {
person {
actor {
id
}
}
@ -271,7 +271,7 @@ defmodule MobilizonWeb.Resolvers.MemberResolverTest do
actor_id: #{actor.id},
group_id: 1042
) {
person {
actor {
id
}
}