Improve texts

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2020-08-31 12:40:30 +02:00
parent d5564570ee
commit 5f0497144a
67 changed files with 4235 additions and 3678 deletions

View File

@@ -9,7 +9,7 @@
class="content"
v-html="
$t(
'From a birthday party with friends and family to a march for climate change, right now, our gatherings are <b>trapped inside the tech giants platforms</b>. How can we organize, how can we click “Attend,” without <b>providing private data</b> to Facebook or <b>locking ourselves up</b> inside MeetUp?'
'From a birthday party with friends and family to a march for climate change, right now, our gatherings are <b>trapped inside the tech giants platforms</b>. How can we organize, how can we click “Attend,” without <b>providing private data</b> to Facebook or <b>locking ourselves</b> inside MeetUp?'
)
"
/>
@@ -37,7 +37,7 @@
<p
v-html="
$t(
'We want to develop a <b>digital common</b>, that everyone can make their own, which respects <b>privacy and activism by design</b>.'
'We want to develop a <b>digital common</b> that everyone can make their own, one which respects <b>privacy and activism by design</b>.'
)
"
/>
@@ -49,9 +49,10 @@
)
"
/>
<span> </span>
<i18n
tag="span"
path="This installation (called “instance“) can easily {interconnect}, thanks to {protocol}."
path="This installation (called “an instance“) can easily {interconnect}, thanks to {protocol}."
>
<b slot="interconnect">{{ $t("interconnect with others like it") }}</b>
<a slot="protocol" href="https://en.wikipedia.org/wiki/ActivityPub">{{
@@ -77,7 +78,7 @@
<blockquote>
{{
$t(
"We wont change the world from Facebook. The tool we dream of, surveillance capitalism corporations wont develop it, as they couldnt profit from it. This is an opportunity to build something better, by taking another approach."
"We cant change the world from within Facebook. The tool we dream of, surveillance capitalism corporations wont develop, as they cannot profit from it. This is an opportunity to build something better, by taking another approach."
)
}}
</blockquote>
@@ -97,7 +98,7 @@
<h2 class="title">{{ $t("Software to the people") }}</h2>
<i18n
tag="p"
path="{license} guarantees {respect} of the people who will use it. Since {source}, anyone can audit it, which guarantees its transparency."
path="{license} guarantees {respect} of the people who use it. Since {source}, anyone can audit it, which guarantees its transparency."
>
<a slot="license" href="https://choosealicense.com/licenses/agpl-3.0/">{{
$t("Mobilizons licence")
@@ -135,17 +136,12 @@
<section>
<div class="columns">
<div class="column has-text-right-desktop">
<h2 class="title">{{ $t("Concieved with care for humans") }}</h2>
<h2 class="title">{{ $t("Conceived with care for humans") }}</h2>
<i18n
tag="p"
path="We asked professional designers to help us develop our vision for Mobilizon. We took time to study the {digital_habits} in order to understand the features they need to gather, organize, and mobilize."
path="We asked professional designers to help us develop our vision for Mobilizon. We took time to study the {digital_habits} in order to understand the features they need to gather, organize, and mobilize so that right from its conception, Mobilizon would {fit_needs_uses_people} who are going to use it."
>
<b slot="digital_habits">{{ $t("digital habits of activists") }}</b>
</i18n>
<i18n
tag="p"
path="So that, right from its conception, Mobilizon would {fit_needs_uses_people} who are going to use it."
>
<b slot="fit_needs_uses_people">{{ $t("fit the needs and uses of the people") }}</b>
</i18n>
</div>

View File

@@ -55,7 +55,7 @@
<dd>
{{
$t(
"A cookie is a small file containing informations that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows to store more data."
"A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data."
)
}}
</dd>

View File

@@ -8,6 +8,7 @@
<a
class="list-item"
v-for="identity in identities"
:key="identity.id"
:class="{ 'is-active': identity.id === currentIdentity.id }"
@click="changeCurrentIdentity(identity)"
>
@@ -49,7 +50,7 @@ export default class IdentityPicker extends Vue {
currentIdentity: IActor = this.value;
changeCurrentIdentity(identity: IActor) {
changeCurrentIdentity(identity: IActor): void {
this.currentIdentity = identity;
this.$emit("input", identity);
}

View File

@@ -3,8 +3,8 @@
<div
v-if="inline"
class="inline box"
:class="{ 'has-background-grey-lighter': masked }"
@click="isComponentModalActive = true"
:class="{ 'has-background-grey-lighter': masked, 'no-other-identity': !hasOtherIdentities }"
@click="activateModal"
>
<div class="media">
<div class="media-left">
@@ -23,29 +23,35 @@
<div class="media-content" v-else>
{{ `@${currentIdentity.preferredUsername}` }}
</div>
<b-button type="is-text" @click="isComponentModalActive = true">
<b-button type="is-text" v-if="identities.length > 1" @click="activateModal">
{{ $t("Change") }}
</b-button>
</div>
</div>
<span v-else class="block" @click="isComponentModalActive = true">
<span v-else class="block" @click="activateModal">
<figure class="image is-48x48" v-if="currentIdentity.avatar">
<img class="is-rounded" :src="currentIdentity.avatar.url" alt="" />
</figure>
<b-icon v-else size="is-large" icon="account-circle" />
</span>
<b-modal :active.sync="isComponentModalActive" has-modal-card>
<b-modal v-model="isComponentModalActive" has-modal-card>
<identity-picker v-model="currentIdentity" @input="relay" />
</b-modal>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { IDENTITIES } from "@/graphql/actor";
import { IActor } from "../../types/actor";
import IdentityPicker from "./IdentityPicker.vue";
@Component({
components: { IdentityPicker },
apollo: {
identities: {
query: IDENTITIES,
},
},
})
export default class IdentityPickerWrapper extends Vue {
@Prop() value!: IActor;
@@ -56,18 +62,30 @@ export default class IdentityPickerWrapper extends Vue {
isComponentModalActive = false;
identities: IActor[] = [];
currentIdentity: IActor = this.value;
@Watch("value")
updateCurrentActor(value: IActor) {
updateCurrentActor(value: IActor): void {
this.currentIdentity = value;
}
relay(identity: IActor) {
relay(identity: IActor): void {
this.currentIdentity = identity;
this.$emit("input", identity);
this.isComponentModalActive = false;
}
get hasOtherIdentities(): boolean {
return this.identities.length > 1;
}
activateModal(): void {
if (this.hasOtherIdentities) {
this.isComponentModalActive = true;
}
}
}
</script>
<style lang="scss">
@@ -76,7 +94,7 @@ export default class IdentityPickerWrapper extends Vue {
cursor: pointer;
}
.inline {
.inline:not(.no-other-identity) {
cursor: pointer;
}

View File

@@ -91,7 +91,7 @@
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import { Component, Prop, Vue } from "vue-property-decorator";
import EventCard from "@/components/Event/EventCard.vue";
import { FETCH_PERSON, CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
import { MOBILIZON_INSTANCE_HOST } from "../../api/_entrypoint";
@@ -123,12 +123,6 @@ export default class Profile extends Vue {
currentActor!: IPerson;
// // call again the method if the route changes
// @Watch('$route')
// onRouteChange() {
// // this.fetchData()
// }
feedUrls(format: "ics" | "webcal:" | "atom", isPublic = true): string {
let url = format === "ics" ? "webcal:" : "";
url += `//${MOBILIZON_INSTANCE_HOST}/`;
@@ -140,7 +134,7 @@ export default class Profile extends Vue {
return url + (format === "ics" ? "ics" : "atom");
}
async createToken() {
async createToken(): Promise<void> {
const { data } = await this.$apollo.mutate({
mutation: CREATE_FEED_TOKEN_ACTOR,
variables: { actor_id: this.person.id },

View File

@@ -33,9 +33,16 @@
</p>
</b-field>
</b-field>
<p class="description">
{{
$t(
"The username is a unique identifier of your account on this and all the other instances. It's as unique as an email address, which makes it easy for other people to interact with it."
)
}}
</p>
<b-field :label="$t('Description')">
<b-input type="textarea" v-model="identity.summary" />
<b-field :label="$t('Bio')">
<b-input type="textarea" maxlength="100" rows="2" v-model="identity.summary" />
</b-field>
<p class="control has-text-centered">
@@ -94,20 +101,20 @@ export default class Register extends mixins(identityEditionMixin) {
host?: string = MOBILIZON_INSTANCE_HOST;
errors: object = {};
errors: Record<string, unknown> = {};
validationSent = false;
sendingValidation = false;
async created() {
async created(): Promise<void> {
// Make sure no one goes to this page if we don't want to
if (!this.email) {
await this.$router.replace({ name: RouteName.PAGE_NOT_FOUND });
}
}
async submit() {
async submit(): Promise<void> {
try {
this.sendingValidation = true;
this.errors = {};
@@ -170,4 +177,9 @@ export default class Register extends mixins(identityEditionMixin) {
.container .columns {
margin: 1rem auto 3rem;
}
p.description {
font-size: 0.9rem;
margin-bottom: 10px;
}
</style>

View File

@@ -197,13 +197,13 @@
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { GET_GROUP, REFRESH_PROFILE } from "@/graphql/group";
import { SUSPEND_PROFILE, UNSUSPEND_PROFILE } from "../../graphql/actor";
import { IGroup, MemberRole } from "../../types/actor";
import { usernameWithDomain, IActor } from "../../types/actor/actor.model";
import RouteName from "../../router/name";
import { IEvent } from "../../types/event.model";
import ActorCard from "../../components/Account/ActorCard.vue";
import { GET_GROUP, REFRESH_PROFILE } from "@/graphql/group";
const EVENTS_PER_PAGE = 10;

View File

@@ -82,9 +82,9 @@
</template>
<script lang="ts">
import { Component, Vue, Watch } from "vue-property-decorator";
import { LIST_GROUPS } from "@/graphql/group";
import { LIST_PROFILES } from "../../graphql/actor";
import RouteName from "../../router/name";
import { LIST_GROUPS } from "@/graphql/group";
const PROFILES_PER_PAGE = 10;

View File

@@ -112,9 +112,9 @@ import { IDiscussion, Discussion } from "@/types/discussions";
import { usernameWithDomain } from "@/types/actor";
import DiscussionComment from "@/components/Discussion/DiscussionComment.vue";
import { GraphQLError } from "graphql";
import { DELETE_COMMENT, UPDATE_COMMENT } from "@/graphql/comment";
import RouteName from "../../router/name";
import { IComment } from "../../types/comment.model";
import { DELETE_COMMENT, UPDATE_COMMENT } from "@/graphql/comment";
@Component({
apollo: {

View File

@@ -26,13 +26,9 @@
</ul>
</nav>
<section>
<div v-if="group.discussions.elements.length > 0">
<discussion-list-item
:discussion="discussion"
v-for="discussion in group.discussions.elements"
:key="discussion.id"
/>
</div>
<p>
{{ $t("Keep the entire conversation about a specific topic together on a single page.") }}
</p>
<b-button
tag="router-link"
:to="{
@@ -41,6 +37,13 @@
}"
>{{ $t("New discussion") }}</b-button
>
<div v-if="group.discussions.elements.length > 0">
<discussion-list-item
:discussion="discussion"
v-for="discussion in group.discussions.elements"
:key="discussion.id"
/>
</div>
</section>
</div>
</template>
@@ -69,7 +72,7 @@ import RouteName from "../../router/name";
},
metaInfo() {
return {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
title: this.$t("Discussions") as string,
// all titles will be injected into this template

View File

@@ -213,7 +213,7 @@
</b-field>
</form>
</div>
<b-modal :active.sync="dateSettingsIsOpen" has-modal-card trap-focus>
<b-modal v-model="dateSettingsIsOpen" has-modal-card trap-focus>
<form action>
<div class="modal-card" style="width: auto">
<header class="modal-card-head">

View File

@@ -0,0 +1,79 @@
<template>
<div class="container section" v-if="group">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li>
<router-link
:to="{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.preferredUsername }}</router-link
>
</li>
<li class="is-active">
<router-link
:to="{
name: RouteName.TODO_LISTS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Events") }}</router-link
>
</li>
</ul>
</nav>
<section>
<h1 class="title" v-if="group">
{{ $t("{group}'s events", { group: group.name || group.preferredUsername }) }}
</h1>
<p>
{{
$t(
"When someone from the group creates an event and attributes it to the group, it will show up here."
)
}}
</p>
<b-loading :active.sync="$apollo.loading"></b-loading>
<section v-if="group && group.organizedEvents.total > 0">
<subtitle>
{{ $t("Past events") }}
</subtitle>
<transition-group name="list" tag="p">
<EventListViewCard v-for="event in group.organizedEvents.elements" :key="event.id" />
</transition-group>
</section>
<b-message
v-if="group.organizedEvents.elements.length === 0 && $apollo.loading === false"
type="is-danger"
>
{{ $t("No events found") }}
</b-message>
</section>
</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { FETCH_GROUP } from "@/graphql/group";
import RouteName from "@/router/name";
import { IGroup, usernameWithDomain } from "../../types/actor";
@Component({
apollo: {
group: {
query: FETCH_GROUP,
variables() {
return {
name: this.$route.params.preferredUsername,
};
},
},
},
})
export default class GroupEvents extends Vue {
group!: IGroup;
usernameWithDomain = usernameWithDomain;
RouteName = RouteName;
}
</script>

View File

@@ -56,9 +56,9 @@ import { Group, IPerson, usernameWithDomain, MemberRole } from "@/types/actor";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "@/graphql/actor";
import { CREATE_GROUP } from "@/graphql/group";
import PictureUpload from "@/components/PictureUpload.vue";
import RouteName from "../../router/name";
import { mixins } from "vue-class-component";
import IdentityEditionMixin from "@/mixins/identityEdition";
import RouteName from "../../router/name";
import { convertToUsername } from "../../utils/username";
@Component({
@@ -98,7 +98,7 @@ export default class CreateGroup extends mixins(IdentityEditionMixin) {
};
const membershipData = store.readQuery<{ person: IPerson }>(query);
if (!membershipData) return;
const person: IPerson = membershipData.person;
const { person } = membershipData;
person.memberships.elements.push({
parent: createGroup,
role: MemberRole.ADMINISTRATOR,

View File

@@ -120,109 +120,181 @@
<!-- Private things -->
<div class="block-column">
<!-- Group discussions -->
<group-section :title="$t('Discussions')" icon="chat">
<div v-if="group.discussions.total > 0">
<discussion-list-item
v-for="discussion in group.discussions.elements"
:key="discussion.id"
:discussion="discussion"
/>
</div>
<div v-else class="content has-text-grey has-text-centered">
<p>{{ $t("No discussions yet") }}</p>
</div>
<router-link
:to="{
name: RouteName.DISCUSSION_LIST,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("View all discussions") }}</router-link
>
</group-section>
<!-- Todos -->
<group-section :title="$t('Ongoing tasks')" icon="checkbox-multiple-marked">
<div v-if="group.todoLists.elements.length > 0">
<div v-for="todoList in group.todoLists.elements" :key="todoList.id">
<router-link :to="{ name: RouteName.TODO_LIST, params: { id: todoList.id } }">
<h2 class="is-size-3">
{{
$tc("{title} ({count} todos)", todoList.todos.total, {
count: todoList.todos.total,
title: todoList.title,
})
}}
</h2>
</router-link>
<compact-todo
:todo="todo"
v-for="todo in todoList.todos.elements.slice(0, 3)"
:key="todo.id"
<group-section
:title="$t('Discussions')"
icon="chat"
:route="{
name: RouteName.DISCUSSION_LIST,
params: { preferredUsername: usernameWithDomain(group) },
}"
>
<template v-slot:default>
<div v-if="group.discussions.total > 0">
<discussion-list-item
v-for="discussion in group.discussions.elements"
:key="discussion.id"
:discussion="discussion"
/>
</div>
</div>
<div v-else class="content has-text-grey has-text-centered">
<p>{{ $t("No ongoing todos") }}</p>
</div>
<router-link :to="{ name: RouteName.TODO_LISTS }">{{ $t("View all todos") }}</router-link>
<div v-else class="content has-text-grey has-text-centered">
<p>{{ $t("No discussions yet") }}</p>
</div>
</template>
<template v-slot:create>
<router-link
:to="{
name: RouteName.CREATE_DISCUSSION,
params: { preferredUsername: usernameWithDomain(group) },
}"
class="button is-primary"
>{{ $t("+ Start a discussion") }}</router-link
>
</template>
</group-section>
<!-- Resources -->
<group-section :title="$t('Resources')" icon="link">
<div v-if="group.resources.elements.length > 0">
<div v-for="resource in group.resources.elements" :key="resource.id">
<resource-item
:resource="resource"
v-if="resource.type !== 'folder'"
:inline="true"
/>
<folder-item :resource="resource" :group="group" v-else :inline="true" />
<group-section
:title="$t('Resources')"
icon="link"
:route="{
name: RouteName.RESOURCE_FOLDER_ROOT,
params: { preferredUsername: usernameWithDomain(group) },
}"
>
<template v-slot:default>
<div v-if="group.resources.elements.length > 0">
<div v-for="resource in group.resources.elements" :key="resource.id">
<resource-item
:resource="resource"
v-if="resource.type !== 'folder'"
:inline="true"
/>
<folder-item :resource="resource" :group="group" v-else :inline="true" />
</div>
</div>
</div>
<div v-else-if="group" class="content has-text-grey has-text-centered">
<p>{{ $t("No resources yet") }}</p>
</div>
<router-link
:to="{
name: RouteName.RESOURCE_FOLDER_ROOT,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("View all resources") }}</router-link
>
<div v-else-if="group" class="content has-text-grey has-text-centered">
<p>{{ $t("No resources yet") }}</p>
</div>
</template>
<template v-slot:create>
<router-link
:to="{
name: RouteName.RESOURCE_FOLDER_ROOT,
params: { preferredUsername: usernameWithDomain(group) },
}"
class="button is-primary"
>{{ $t("+ Add a resource") }}</router-link
>
</template>
</group-section>
<!-- Todos -->
<group-section
:title="$t('Ongoing tasks')"
icon="checkbox-multiple-marked"
:route="{
name: RouteName.TODO_LISTS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>
<template v-slot:default>
<div v-if="group.todoLists.elements.length > 0">
<div v-for="todoList in group.todoLists.elements" :key="todoList.id">
<router-link :to="{ name: RouteName.TODO_LIST, params: { id: todoList.id } }">
<h2 class="is-size-3">
{{
$tc("{title} ({count} todos)", todoList.todos.total, {
count: todoList.todos.total,
title: todoList.title,
})
}}
</h2>
</router-link>
<compact-todo
:todo="todo"
v-for="todo in todoList.todos.elements.slice(0, 3)"
:key="todo.id"
/>
</div>
</div>
<div v-else class="content has-text-grey has-text-centered">
<p>{{ $t("No ongoing todos") }}</p>
</div>
</template>
<template v-slot:create>
<router-link
:to="{
name: RouteName.TODO_LISTS,
params: { preferredUsername: usernameWithDomain(group) },
}"
class="button is-primary"
>{{ $t("+ Add a todo") }}</router-link
>
</template>
</group-section>
</div>
<!-- Public things -->
<div class="block-column">
<!-- Events -->
<group-section :title="$t('Upcoming events')" icon="calendar" :privateSection="false">
<div class="organized-events-wrapper" v-if="group && group.organizedEvents.total > 0">
<EventMinimalistCard
v-for="event in group.organizedEvents.elements"
:event="event"
:key="event.uuid"
class="organized-event"
/>
</div>
<div v-else-if="group" class="content has-text-grey has-text-centered">
<p>{{ $t("No public upcoming events") }}</p>
</div>
<b-skeleton animated v-else></b-skeleton>
<router-link :to="{}">{{ $t("View all events") }}</router-link>
<group-section
:title="$t('Upcoming events')"
icon="calendar"
:privateSection="false"
:route="{
name: RouteName.GROUP_EVENTS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>
<template v-slot:default>
<div class="organized-events-wrapper" v-if="group && group.organizedEvents.total > 0">
<EventMinimalistCard
v-for="event in group.organizedEvents.elements"
:event="event"
:key="event.uuid"
class="organized-event"
/>
</div>
<div v-else-if="group" class="content has-text-grey has-text-centered">
<p>{{ $t("No public upcoming events") }}</p>
</div>
<b-skeleton animated v-else></b-skeleton>
</template>
<template v-slot:create>
<router-link
:to="{
name: RouteName.CREATE_EVENT,
}"
class="button is-primary"
>{{ $t("+ Create an event") }}</router-link
>
</template>
</group-section>
<!-- Posts -->
<group-section :title="$t('Public page')" icon="bullhorn" :privateSection="false">
<div v-if="group.posts.total > 0" class="posts-wrapper">
<post-list-item v-for="post in group.posts.elements" :key="post.id" :post="post" />
</div>
<div v-else-if="group" class="content has-text-grey has-text-centered">
<p>{{ $t("No posts yet") }}</p>
</div>
<router-link
:to="{
name: RouteName.POST_CREATE,
params: { preferredUsername: usernameWithDomain(group) },
}"
class="button is-primary"
>{{ $t("Post a public message") }}</router-link
>
<group-section
:title="$t('Public page')"
icon="bullhorn"
:privateSection="false"
:route="{
name: RouteName.POSTS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>
<template v-slot:default>
<div v-if="group.posts.total > 0" class="posts-wrapper">
<post-list-item v-for="post in group.posts.elements" :key="post.id" :post="post" />
</div>
<div v-else-if="group" class="content has-text-grey has-text-centered">
<p>{{ $t("No posts yet") }}</p>
</div>
</template>
<template v-slot:create>
<router-link
:to="{
name: RouteName.POST_CREATE,
params: { preferredUsername: usernameWithDomain(group) },
}"
class="button is-primary"
>{{ $t("+ Post a public message") }}</router-link
>
</template>
</group-section>
</div>
</div>
@@ -294,11 +366,11 @@ import DiscussionListItem from "@/components/Discussion/DiscussionListItem.vue";
import PostListItem from "@/components/Post/PostListItem.vue";
import ResourceItem from "@/components/Resource/ResourceItem.vue";
import FolderItem from "@/components/Resource/FolderItem.vue";
import RouteName from "../../router/name";
import { Address } from "@/types/address.model";
import GroupSection from "../../components/Group/GroupSection.vue";
import Invitations from "@/components/Group/Invitations.vue";
import addMinutes from "date-fns/addMinutes";
import GroupSection from "../../components/Group/GroupSection.vue";
import RouteName from "../../router/name";
@Component({
apollo: {
@@ -493,7 +565,7 @@ div.container {
.header,
.public-container {
margin: auto 2rem;
margin: auto 1rem;
display: flex;
flex-direction: column;
}
@@ -501,6 +573,7 @@ div.container {
.block-container {
display: flex;
flex-wrap: wrap;
margin-top: 15px;
&.presentation {
border: 2px solid $purple-2;
@@ -560,7 +633,7 @@ div.container {
.block-column {
flex: 1;
margin: 0 2rem;
margin: 0 1rem;
section {
.posts-wrapper {

View File

@@ -21,8 +21,8 @@ import { Component, Vue } from "vue-property-decorator";
import { LIST_GROUPS } from "@/graphql/group";
import { Group, IGroup } from "@/types/actor";
import GroupMemberCard from "@/components/Group/GroupMemberCard.vue";
import RouteName from "../../router/name";
import { Paginate } from "@/types/paginate";
import RouteName from "../../router/name";
@Component({
apollo: {

View File

@@ -102,13 +102,13 @@
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
import RouteName from "../../router/name";
import { FETCH_GROUP, UPDATE_GROUP, DELETE_GROUP } from "../../graphql/group";
import { IGroup, usernameWithDomain } from "../../types/actor";
import { Address, IAddress } from "../../types/address.model";
import { IMember, Group } from "../../types/actor/group.model";
import { Paginate } from "../../types/paginate";
import FullAddressAutoComplete from "@/components/Event/FullAddressAutoComplete.vue";
@Component({
apollo: {

View File

@@ -1,6 +1,13 @@
<template>
<section class="section container">
<h1 class="title">{{ $t("My groups") }}</h1>
<p>
{{
$t(
"Groups are spaces for coordination and preparation to better organize events and manage your community."
)
}}
</p>
<router-link :to="{ name: RouteName.CREATE_GROUP }">{{ $t("Create group") }}</router-link>
<b-loading :active.sync="$apollo.loading"></b-loading>
<invitations
@@ -23,9 +30,8 @@ import { LOGGED_USER_MEMBERSHIPS } from "@/graphql/actor";
import GroupMemberCard from "@/components/Group/GroupMemberCard.vue";
import Invitations from "@/components/Group/Invitations.vue";
import { Paginate } from "@/types/paginate";
import { IGroup, IMember, MemberRole, usernameWithDomain } from "@/types/actor";
import { IMember, MemberRole, usernameWithDomain } from "@/types/actor";
import RouteName from "../../router/name";
import { ACCEPT_INVITATION } from "../../graphql/member";
@Component({
components: {

View File

@@ -79,8 +79,8 @@
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
import { FETCH_GROUP } from "@/graphql/group";
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
import { TAGS } from "../../graphql/tags";
import { CONFIG } from "../../graphql/config";
import { FETCH_POST, CREATE_POST, UPDATE_POST, DELETE_POST } from "../../graphql/post";

View File

@@ -1,47 +1,57 @@
<template>
<div>
<section class="section container">
<nav class="breadcrumb" aria-label="breadcrumbs" v-if="group">
<ul>
<li>
<router-link :to="{ name: RouteName.MY_GROUPS }">{{ $t("My groups") }}</router-link>
</li>
<li>
<router-link
v-if="group"
:to="{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.name || group.preferredUsername }}</router-link
>
<b-skeleton v-else :animated="true"></b-skeleton>
</li>
<li class="is-active">
<router-link
v-if="group"
:to="{
name: RouteName.POSTS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Posts") }}</router-link
>
<b-skeleton v-else :animated="true"></b-skeleton>
</li>
</ul>
</nav>
<div v-if="group">
<router-link
v-for="post in group.posts.elements"
:key="post.id"
:to="{ name: RouteName.POST, params: { slug: post.slug } }"
>
{{ post.title }}
</router-link>
</div>
<b-skeleton v-else :animated="true"></b-skeleton>
<div class="container section" v-if="group">
<nav class="breadcrumb" aria-label="breadcrumbs" v-if="group">
<ul>
<li>
<router-link :to="{ name: RouteName.MY_GROUPS }">{{ $t("My groups") }}</router-link>
</li>
<li>
<router-link
v-if="group"
:to="{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.name || group.preferredUsername }}</router-link
>
<b-skeleton v-else :animated="true"></b-skeleton>
</li>
<li class="is-active">
<router-link
v-if="group"
:to="{
name: RouteName.POSTS,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Posts") }}</router-link
>
<b-skeleton v-else :animated="true"></b-skeleton>
</li>
</ul>
</nav>
<section>
<p>
{{
$t(
"A place to publish something to the whole world, your community or just your group members."
)
}}
</p>
<router-link
v-for="post in group.posts.elements"
:key="post.id"
:to="{ name: RouteName.POST, params: { slug: post.slug } }"
>
{{ post.title }}
</router-link>
<b-loading :active.sync="$apollo.loading"></b-loading>
<b-message
v-if="group.posts.elements.length === 0 && $apollo.loading === false"
type="is-danger"
>
{{ $t("No posts found") }}
</b-message>
</section>
<pre>{{ group }}</pre>
</div>
</template>

View File

@@ -42,8 +42,8 @@
import { Component, Vue, Prop } from "vue-property-decorator";
import Editor from "@/components/Editor.vue";
import { GraphQLError } from "graphql";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "../../graphql/actor";
import { FETCH_GROUP } from "@/graphql/group";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "../../graphql/actor";
import { TAGS } from "../../graphql/tags";
import { CONFIG } from "../../graphql/config";
import { FETCH_POST, CREATE_POST } from "../../graphql/post";

View File

@@ -64,6 +64,9 @@
</ul>
</nav>
<section>
<p v-if="resource.path === '/'" class="module-description">
{{ $t("A place to store links to documents or resources of any type.") }}
</p>
<div class="list-header">
<div class="list-header-right">
<b-checkbox v-model="checkedAll" v-if="resource.children.total > 0" />
@@ -280,7 +283,7 @@ export default class Resources extends Mixins(ResourceMixin) {
renameModal = false;
groupObject: object = {
groupObject: Record<string, unknown> = {
name: "resources",
pull: "clone",
put: true,
@@ -288,10 +291,10 @@ export default class Resources extends Mixins(ResourceMixin) {
mapServiceTypeToIcon = mapServiceTypeToIcon;
async createResource() {
async createResource(): Promise<void> {
if (!this.resource.actor) return;
try {
const { data } = await this.$apollo.mutate({
await this.$apollo.mutate({
mutation: CREATE_RESOURCE,
variables: {
title: this.newResource.title,
@@ -341,7 +344,7 @@ export default class Resources extends Mixins(ResourceMixin) {
}
}
async previewResource() {
async previewResource(): Promise<void> {
if (this.newResource.resourceUrl === "") return;
const { data } = await this.$apollo.mutate({
mutation: PREVIEW_RESOURCE_LINK,
@@ -355,31 +358,33 @@ export default class Resources extends Mixins(ResourceMixin) {
this.newResource.type = "link";
}
createSentenceForType(type: string) {
createSentenceForType(type: string): string {
switch (type) {
case "folder":
return this.$t("Create a folder");
return this.$t("Create a folder") as string;
case "pad":
return this.$t("Create a pad");
return this.$t("Create a pad") as string;
case "calc":
return this.$t("Create a calc");
return this.$t("Create a calc") as string;
case "visio":
return this.$t("Create a visioconference");
return this.$t("Create a visioconference") as string;
default:
return "";
}
}
createFolderModal() {
createFolderModal(): void {
this.newResource.type = "folder";
this.createResourceModal = true;
}
createResourceFromProvider(provider: IProvider) {
this.newResource.resourceUrl = this.generateFullResourceUrl(provider);
createResourceFromProvider(provider: IProvider): void {
this.newResource.resourceUrl = Resources.generateFullResourceUrl(provider);
this.newResource.type = provider.software;
this.createResourceModal = true;
}
generateFullResourceUrl(provider: IProvider): string {
static generateFullResourceUrl(provider: IProvider): string {
const randomString = [...Array(10)]
.map(() => Math.random().toString(36)[3])
.join("")
@@ -393,13 +398,13 @@ export default class Resources extends Mixins(ResourceMixin) {
}
}
get createResourceButtonLabel() {
if (!this.newResource.type) return;
get createResourceButtonLabel(): string {
if (!this.newResource.type) return "";
return this.createSentenceForType(this.newResource.type);
}
@Watch("checkedAll")
watchCheckedAll() {
watchCheckedAll(): void {
this.resource.children.elements.forEach(({ id }) => {
if (!id) return;
this.checkedResources[id] = this.checkedAll;
@@ -409,21 +414,22 @@ export default class Resources extends Mixins(ResourceMixin) {
@Watch("checkedResources", { deep: true })
watchValidCheckedResources(): string[] {
const validCheckedResources: string[] = [];
for (const [key, value] of Object.entries(this.checkedResources)) {
Object.entries(this.checkedResources).forEach(([key, value]) => {
if (value) {
validCheckedResources.push(key);
}
}
return (this.validCheckedResources = validCheckedResources);
});
this.validCheckedResources = validCheckedResources;
return this.validCheckedResources;
}
async deleteMultipleResources() {
for (const resourceID of this.validCheckedResources) {
async deleteMultipleResources(): Promise<void> {
this.validCheckedResources.forEach(async (resourceID) => {
await this.deleteResource(resourceID);
}
});
}
async deleteResource(resourceID: string) {
async deleteResource(resourceID: string): Promise<void> {
try {
await this.$apollo.mutate({
mutation: DELETE_RESOURCE,
@@ -449,7 +455,7 @@ export default class Resources extends Mixins(ResourceMixin) {
const oldResource: IResource = deleteResource;
resource.children.elements = resource.children.elements.filter(
(resource) => resource.id !== oldResource.id
(resourceElement) => resourceElement.id !== oldResource.id
);
store.writeQuery({
@@ -469,28 +475,28 @@ export default class Resources extends Mixins(ResourceMixin) {
}
}
handleRename(resource: IResource) {
handleRename(resource: IResource): void {
this.renameModal = true;
this.updatedResource = { ...resource };
}
handleMove(resource: IResource) {
handleMove(resource: IResource): void {
this.moveModal = true;
this.updatedResource = { ...resource };
}
async moveResource(resource: IResource, oldParent: IResource | undefined) {
async moveResource(resource: IResource, oldParent: IResource | undefined): Promise<void> {
const parentPath = oldParent && oldParent.path ? oldParent.path || "/" : "/";
await this.updateResource(resource, parentPath);
this.moveModal = false;
}
async renameResource() {
async renameResource(): Promise<void> {
await this.updateResource(this.updatedResource);
this.renameModal = false;
}
async updateResource(resource: IResource, parentPath: string | null = null) {
async updateResource(resource: IResource, parentPath: string | null = null): Promise<void> {
try {
await this.$apollo.mutate<{ updateResource: IResource }>({
mutation: UPDATE_RESOURCE,
@@ -518,10 +524,11 @@ export default class Resources extends Mixins(ResourceMixin) {
console.error("Cannot update resource cache, because of null value.");
return;
}
const resource: IResource = data.updateResource;
const updatedResource: IResource = data.updateResource;
// eslint-disable-next-line vue/max-len
oldParentCachedResource.children.elements = oldParentCachedResource.children.elements.filter(
(cachedResource) => cachedResource.id !== resource.id
(cachedResource) => cachedResource.id !== updatedResource.id
);
store.writeQuery({
@@ -535,14 +542,14 @@ export default class Resources extends Mixins(ResourceMixin) {
console.log("Finished removing ressource from old parent");
console.log("Adding resource to new parent");
if (!resource.parent || !resource.parent.path) {
if (!updatedResource.parent || !updatedResource.parent.path) {
console.log("No cache found for new parent");
return;
}
const newParentCachedData = store.readQuery<{ resource: IResource }>({
query: GET_RESOURCE,
variables: {
path: resource.parent.path,
path: updatedResource.parent.path,
username: this.resource.actor.preferredUsername,
},
});
@@ -558,7 +565,7 @@ export default class Resources extends Mixins(ResourceMixin) {
store.writeQuery({
query: GET_RESOURCE,
variables: {
path: resource.parent.path,
path: updatedResource.parent.path,
username: this.resource.actor.preferredUsername,
},
data: { newParentCachedResource },

View File

@@ -110,16 +110,7 @@
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import EventCard from "../components/Event/EventCard.vue";
import { FETCH_EVENTS } from "../graphql/event";
import { IEvent } from "../types/event.model";
import RouteName from "../router/name";
import { IAddress, Address } from "../types/address.model";
import { SearchEvent, SearchGroup } from "../types/search.model";
import AddressAutoComplete from "../components/Event/AddressAutoComplete.vue";
import ngeohash from "ngeohash";
import { SEARCH_EVENTS, SEARCH_GROUPS } from "../graphql/search";
import { Paginate } from "../types/paginate";
import {
endOfToday,
addDays,
@@ -133,6 +124,15 @@ import {
startOfMonth,
eachWeekendOfInterval,
} from "date-fns";
import EventCard from "../components/Event/EventCard.vue";
import { FETCH_EVENTS } from "../graphql/event";
import { IEvent } from "../types/event.model";
import RouteName from "../router/name";
import { IAddress, Address } from "../types/address.model";
import { SearchEvent, SearchGroup } from "../types/search.model";
import AddressAutoComplete from "../components/Event/AddressAutoComplete.vue";
import { SEARCH_EVENTS, SEARCH_GROUPS } from "../graphql/search";
import { Paginate } from "../types/paginate";
import { IGroup } from "../types/actor";
import GroupCard from "../components/Group/GroupCard.vue";
import { CONFIG } from "../graphql/config";
@@ -206,9 +206,11 @@ const tabsName: { events: number; groups: number } = {
})
export default class Search extends Vue {
@Prop({ type: String, required: false }) tag!: string;
events: IEvent[] = [];
searchEvents: Paginate<IEvent> & { initial: boolean } = { total: 0, elements: [], initial: true };
searchGroups: Paginate<IGroup> = { total: 0, elements: [] };
search: string = (this.$route.query.term as string) || "";
@@ -275,7 +277,7 @@ export default class Search extends Vue {
radiusOptions: (number | null)[] = [1, 5, 10, 25, 50, 100, 150, null];
radius: number = 50;
radius = 50;
submit() {
this.$apollo.queries.searchEvents.refetch();
@@ -285,7 +287,7 @@ export default class Search extends Vue {
updateSearchTerm() {
this.$router.push({
name: RouteName.SEARCH,
query: Object.assign({}, this.$route.query, { term: this.search }),
query: { ...this.$route.query, term: this.search },
});
}
@@ -294,7 +296,7 @@ export default class Search extends Vue {
const searchType = this.activeTab === tabsName.events ? "events" : "groups";
this.$router.push({
name: RouteName.SEARCH,
query: Object.assign({}, this.$route.query, { searchType }),
query: { ...this.$route.query, searchType },
});
}

View File

@@ -4,7 +4,10 @@
<ul>
<li>
<router-link
:to="{ name: RouteName.GROUP, params: { preferredUsername: group.preferredUsername } }"
:to="{
name: RouteName.GROUP,
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ group.preferredUsername }}</router-link
>
</li>
@@ -12,7 +15,7 @@
<router-link
:to="{
name: RouteName.TODO_LISTS,
params: { preferredUsername: group.preferredUsername },
params: { preferredUsername: usernameWithDomain(group) },
}"
>{{ $t("Task lists") }}</router-link
>
@@ -20,6 +23,11 @@
</ul>
</nav>
<section>
<p>
{{
$t("Create to-do lists for all the tasks you need to do, assign them and set due dates.")
}}
</p>
<form class="form" @submit.prevent="createNewTodoList">
<b-field :label="$t('List title')">
<b-input v-model="newTodoList.title" />
@@ -45,7 +53,7 @@
<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { FETCH_GROUP } from "@/graphql/group";
import { IGroup } from "@/types/actor";
import { IGroup, usernameWithDomain } from "@/types/actor";
import { ITodoList } from "@/types/todos";
import { CREATE_TODO_LIST } from "@/graphql/todos";
import CompactTodo from "@/components/Todo/CompactTodo.vue";
@@ -80,15 +88,17 @@ export default class TodoLists extends Vue {
RouteName = RouteName;
get todoLists() {
usernameWithDomain = usernameWithDomain;
get todoLists(): ITodoList[] {
return this.group.todoLists.elements;
}
get todoListsCount() {
get todoListsCount(): number {
return this.group.todoLists.total;
}
async createNewTodoList() {
async createNewTodoList(): Promise<void> {
await this.$apollo.mutate({
mutation: CREATE_TODO_LIST,
variables: {

View File

@@ -7,7 +7,7 @@
</h1>
<i18n tag="p" path="{instanceName} is an instance of the {mobilizon} software.">
<b slot="instanceName">{{ config.name }}</b>
<a href="https://joinmobilizon.org" target="_blank" slot="mobilizon">{{
<a href="https://joinmobilizon.org" target="_blank" class="out" slot="mobilizon">{{
$t("Mobilizon")
}}</a>
</i18n>
@@ -23,10 +23,15 @@
<li>{{ $t("Create and manage several identities from the same account") }}</li>
<li>{{ $t("Create, edit or delete events") }}</li>
<li>{{ $t("Register for an event by choosing one of your identities") }}</li>
<li v-if="config.features.groups">
{{ $t("Create or join an group and start organizing with other people") }}
</li>
</ul>
</div>
</div>
<router-link :to="{ name: RouteName.ABOUT }">{{ $t("Learn more") }}</router-link>
<router-link class="out" :to="{ name: RouteName.ABOUT }">{{
$t("Learn more")
}}</router-link>
<hr />
<div class="content">
<subtitle>{{ $t("About this instance") }}</subtitle>
@@ -79,10 +84,10 @@
<b-checkbox required>
<i18n tag="span" path="I agree to the {instanceRules} and {termsOfService}">
<router-link slot="instanceRules" :to="{ name: RouteName.RULES }">{{
<router-link class="out" slot="instanceRules" :to="{ name: RouteName.RULES }">{{
$t("instance rules")
}}</router-link>
<router-link slot="termsOfService" :to="{ name: RouteName.TERMS }">{{
<router-link class="out" slot="termsOfService" :to="{ name: RouteName.TERMS }">{{
$t("terms of service")
}}</router-link>
</i18n>