From 39c03301c94bf5cf340d15be89ef790a4cdfbec2 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Thu, 25 Jun 2020 18:47:17 +0200
Subject: [PATCH 1/3] Allow to properly move group resources

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 js/src/components/Resource/FolderItem.vue     |   4 +-
 js/src/components/Resource/ResourceItem.vue   |   2 +-
 .../components/Resource/ResourceSelector.vue  | 129 ++++++++++++++++++
 js/src/graphql/resources.ts                   |  13 ++
 js/src/i18n/en_US.json                        |   9 +-
 js/src/i18n/fr_FR.json                        |   9 +-
 js/src/views/Resources/ResourceFolder.vue     | 119 ++++++++++++++--
 lib/graphql/schema.ex                         |  14 +-
 lib/graphql/schema/resource.ex                |   5 +-
 lib/service/rich_media/parsers/ogp.ex         |  13 +-
 10 files changed, 298 insertions(+), 19 deletions(-)
 create mode 100644 js/src/components/Resource/ResourceSelector.vue

diff --git a/js/src/components/Resource/FolderItem.vue b/js/src/components/Resource/FolderItem.vue
index ecda49c88..e254c9224 100644
--- a/js/src/components/Resource/FolderItem.vue
+++ b/js/src/components/Resource/FolderItem.vue
@@ -29,7 +29,7 @@
       class="actions"
       v-if="!inline"
       @delete="$emit('delete', resource.id)"
-      @move="$emit('move', resource.id)"
+      @move="$emit('move', resource)"
       @rename="$emit('rename', resource)"
     />
   </div>
@@ -70,8 +70,6 @@ export default class FolderItem extends Mixins(ResourceMixin) {
   usernameWithDomain = usernameWithDomain;
 
   async onChange(evt: ChangeEvent<IResource>): Promise<Route | undefined> {
-    console.log("into folder item");
-    console.log(evt);
     if (evt.added && evt.added.element) {
       const movedResource = evt.added.element as IResource;
       const updatedResource = await this.moveResource(movedResource);
diff --git a/js/src/components/Resource/ResourceItem.vue b/js/src/components/Resource/ResourceItem.vue
index 4751e2a3b..c5865ed0d 100644
--- a/js/src/components/Resource/ResourceItem.vue
+++ b/js/src/components/Resource/ResourceItem.vue
@@ -29,7 +29,7 @@
       class="actions"
       v-if="!inline"
       @delete="$emit('delete', resource.id)"
-      @move="$emit('move', resource.id)"
+      @move="$emit('move', resource)"
       @rename="$emit('rename', resource)"
     />
   </div>
diff --git a/js/src/components/Resource/ResourceSelector.vue b/js/src/components/Resource/ResourceSelector.vue
new file mode 100644
index 000000000..a16bb3be1
--- /dev/null
+++ b/js/src/components/Resource/ResourceSelector.vue
@@ -0,0 +1,129 @@
+<template>
+  <div v-if="resource">
+    <article class="panel is-primary">
+      <p class="panel-heading">
+        {{ $t('Move "{resourceName}"', { resourceName: initialResource.title }) }}
+      </p>
+      <a class="panel-block clickable" @click="resource = resource.parent" v-if="resource.parent">
+        <span class="panel-icon">
+          <b-icon icon="chevron-up" size="is-small" />
+        </span>
+        {{ $t("Parent folder") }}
+      </a>
+      <a
+        class="panel-block clickable"
+        @click="resource = { path: '/', username }"
+        v-else-if="resource.path.length > 1"
+      >
+        <span class="panel-icon">
+          <b-icon icon="chevron-up" size="is-small" />
+        </span>
+        {{ $t("Parent folder") }}
+      </a>
+      <a
+        class="panel-block"
+        v-for="element in resource.children.elements"
+        :class="{ clickable: element.type === 'folder' && element.id !== initialResource.id }"
+        :key="element.id"
+        @click="goDown(element)"
+      >
+        <span class="panel-icon">
+          <b-icon icon="folder" size="is-small" v-if="element.type === 'folder'" />
+          <b-icon icon="link" size="is-small" v-else />
+        </span>
+        {{ element.title }}
+        <span v-if="element.id === initialResource.id">
+          <em v-if="element.type === 'folder'"> {{ $t("(this folder)") }}</em>
+          <em v-else> {{ $t("(this link)") }}</em>
+        </span>
+      </a>
+      <p
+        class="panel-block content has-text-grey has-text-centered"
+        v-if="resource.children.total === 0"
+      >
+        {{ $t("No resources in this folder") }}
+      </p>
+    </article>
+    <b-button type="is-primary" @click="updateResource" :disabled="moveDisabled">{{
+      $t("Move resource to {folder}", { folder: resource.title })
+    }}</b-button>
+    <b-button type="is-text" @click="$emit('closeMoveModal')">{{ $t("Cancel") }}</b-button>
+  </div>
+</template>
+<script lang="ts">
+import { Component, Vue, Prop } from "vue-property-decorator";
+import { GET_RESOURCE } from "../../graphql/resources";
+import { IResource } from "../../types/resource";
+
+@Component({
+  apollo: {
+    resource: {
+      query: GET_RESOURCE,
+      variables() {
+        if (this.resource && this.resource.path) {
+          return {
+            path: this.resource.path,
+            username: this.username,
+          };
+        }
+        return { path: "/", username: this.username };
+      },
+      skip() {
+        return !this.username;
+      },
+    },
+  },
+})
+export default class ResourceSelector extends Vue {
+  @Prop({ required: true }) initialResource!: IResource;
+  @Prop({ required: true }) username!: string;
+
+  resource: IResource | undefined = this.initialResource.parent;
+
+  goDown(element: IResource) {
+    if (element.type === "folder" && element.id !== this.initialResource.id) {
+      this.resource = element;
+    }
+  }
+
+  updateResource() {
+    this.$emit(
+      "updateResource",
+      {
+        id: this.initialResource.id,
+        title: this.initialResource.title,
+        parent: this.resource && this.resource.path === "/" ? null : this.resource,
+        path: this.initialResource.path,
+      },
+      this.initialResource.parent
+    );
+  }
+
+  get moveDisabled() {
+    return (
+      (this.initialResource.parent &&
+        this.resource &&
+        this.initialResource.parent.path === this.resource.path) ||
+      (this.initialResource.parent == undefined && this.resource && this.resource.path === "/")
+    );
+  }
+}
+</script>
+<style lang="scss" scoped>
+@import "../../variables.scss";
+
+.panel {
+  a.panel-block {
+    cursor: default;
+
+    &.clickable {
+      cursor: pointer;
+    }
+  }
+
+  &.is-primary .panel-heading {
+    background: $primary;
+    color: #fff;
+  }
+}
+</style>
diff --git a/js/src/graphql/resources.ts b/js/src/graphql/resources.ts
index 65ea76422..5b1e90525 100644
--- a/js/src/graphql/resources.ts
+++ b/js/src/graphql/resources.ts
@@ -18,6 +18,7 @@ export const GET_RESOURCE = gql`
       summary
       url
       path
+      type
       metadata {
         ...ResourceMetadataBasicFields
         authorName
@@ -28,6 +29,8 @@ export const GET_RESOURCE = gql`
       }
       parent {
         id
+        path
+        type
       }
       actor {
         id
@@ -44,6 +47,11 @@ export const GET_RESOURCE = gql`
           type
           path
           resourceUrl
+          parent {
+            id
+            path
+            type
+          }
           metadata {
             ...ResourceMetadataBasicFields
           }
@@ -112,7 +120,12 @@ export const UPDATE_RESOURCE = gql`
       summary
       url
       path
+      type
       resourceUrl
+      parent {
+        id
+        path
+      }
     }
   }
 `;
diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json
index 951400e12..75cbc481a 100644
--- a/js/src/i18n/en_US.json
+++ b/js/src/i18n/en_US.json
@@ -696,5 +696,12 @@
   "Can be an email or a link, or just plain text.": "Can be an email or a link, or just plain text.",
   "No profiles found": "No profiles found",
   "URL copied to clipboard": "URL copied to clipboard",
-  "Report #{reportNumber}": "Report #{reportNumber}"
+  "Report #{reportNumber}": "Report #{reportNumber}",
+  "Move \"{resourceName}\"": "Move \"{resourceName}\"",
+  "Parent folder": "Parent folder",
+  "(this folder)": "(this folder)",
+  "(this link)": "(this link)",
+  "Move resource to {folder}": "Move resource to {folder}",
+  "Create a folder": "Create a folder",
+  "No resources in this folder": "No resources in this folder"
 }
diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json
index fc20e2fc5..450552732 100644
--- a/js/src/i18n/fr_FR.json
+++ b/js/src/i18n/fr_FR.json
@@ -696,5 +696,12 @@
   "contact uninformed": "contact non renseigné",
   "Can be an email or a link, or just plain text.": "Peut être une adresse email ou bien un lien, ou alors du simple texte brut.",
   "URL copied to clipboard": "URL copiée dans le presse-papiers",
-  "Report #{reportNumber}": "Signalement #{reportNumber}"
+  "Report #{reportNumber}": "Signalement #{reportNumber}",
+  "Move \"{resourceName}\"": "Déplacer « {resourceName} »",
+  "Parent folder": "Dossier parent",
+  "(this folder)": "(ce dossier)",
+  "(this link)": "(ce lien)",
+  "Move resource to {folder}": "Déplacer la ressource dans {folder}",
+  "Create a folder": "Créer un dossier",
+  "No resources in this folder": "Aucune ressource dans ce dossier"
 }
diff --git a/js/src/views/Resources/ResourceFolder.vue b/js/src/views/Resources/ResourceFolder.vue
index eea87b067..28888591e 100644
--- a/js/src/views/Resources/ResourceFolder.vue
+++ b/js/src/views/Resources/ResourceFolder.vue
@@ -66,7 +66,7 @@
     <section>
       <div class="list-header">
         <div class="list-header-right">
-          <b-checkbox v-model="checkedAll" />
+          <b-checkbox v-model="checkedAll" v-if="resource.children.total > 0" />
           <div class="actions" v-if="validCheckedResources.length > 0">
             <small>
               {{
@@ -85,7 +85,12 @@
           </div>
         </div>
       </div>
-      <draggable v-model="resource.children.elements" :sort="false" :group="groupObject">
+      <draggable
+        v-model="resource.children.elements"
+        :sort="false"
+        :group="groupObject"
+        v-if="resource.children.total > 0"
+      >
         <transition-group>
           <div v-for="localResource in resource.children.elements" :key="localResource.id">
             <div class="resource-item">
@@ -100,18 +105,22 @@
                 v-if="localResource.type !== 'folder'"
                 @delete="deleteResource"
                 @rename="handleRename"
+                @move="handleMove"
               />
               <folder-item
                 :resource="localResource"
                 :group="resource.actor"
                 @delete="deleteResource"
-                @rename="handleRename"
+                @move="handleMove"
                 v-else
               />
             </div>
           </div>
         </transition-group>
       </draggable>
+      <div class="content has-text-centered has-text-grey">
+        <p>{{ $t("No resources in this folder") }}</p>
+      </div>
     </section>
     <b-modal :active.sync="renameModal" has-modal-card>
       <div class="modal-card">
@@ -126,6 +135,18 @@
         </section>
       </div>
     </b-modal>
+    <b-modal :active.sync="moveModal" has-modal-card>
+      <div class="modal-card">
+        <section class="modal-card-body">
+          <resource-selector
+            :initialResource="updatedResource"
+            :username="resource.actor.preferredUsername"
+            @updateResource="moveResource"
+            @closeMoveModal="moveModal = false"
+          />
+        </section>
+      </div>
+    </b-modal>
     <b-modal :active.sync="createResourceModal" has-modal-card>
       <div class="modal-card">
         <section class="modal-card-body">
@@ -190,9 +211,10 @@ import {
 import { CONFIG } from "../../graphql/config";
 import { IConfig } from "../../types/config.model";
 import ResourceMixin from "../../mixins/resource";
+import ResourceSelector from "../../components/Resource/ResourceSelector.vue";
 
 @Component({
-  components: { FolderItem, ResourceItem, Draggable },
+  components: { FolderItem, ResourceItem, Draggable, ResourceSelector },
   apollo: {
     resource: {
       query: GET_RESOURCE,
@@ -253,6 +275,8 @@ export default class Resources extends Mixins(ResourceMixin) {
 
   createLinkResourceModal = false;
 
+  moveModal = false;
+
   renameModal = false;
 
   groupObject: object = {
@@ -332,6 +356,8 @@ export default class Resources extends Mixins(ResourceMixin) {
 
   createSentenceForType(type: string) {
     switch (type) {
+      case "folder":
+        return this.$t("Create a folder");
       case "pad":
         return this.$t("Create a pad");
       case "calc":
@@ -347,7 +373,6 @@ export default class Resources extends Mixins(ResourceMixin) {
   }
 
   createResourceFromProvider(provider: IProvider) {
-    console.log(provider);
     this.newResource.resourceUrl = this.generateFullResourceUrl(provider);
     this.newResource.type = provider.software;
     this.createResourceModal = true;
@@ -445,24 +470,100 @@ export default class Resources extends Mixins(ResourceMixin) {
 
   handleRename(resource: IResource) {
     this.renameModal = true;
-    this.updatedResource = resource;
+    this.updatedResource = Object.assign({}, resource);
+  }
+
+  handleMove(resource: IResource) {
+    this.moveModal = true;
+    this.updatedResource = Object.assign({}, resource);
+  }
+
+  async moveResource(resource: IResource, oldParent: IResource | undefined) {
+    const parentPath = oldParent && oldParent.path ? oldParent.path || "/" : "/";
+    await this.updateResource(resource, parentPath);
+    this.moveModal = false;
   }
 
   async renameResource() {
     await this.updateResource(this.updatedResource);
+    this.renameModal = false;
   }
 
-  async updateResource(resource: IResource) {
+  async updateResource(resource: IResource, parentPath: string | null = null) {
     try {
-      if (!resource.parent) return;
       await this.$apollo.mutate<{ updateResource: IResource }>({
         mutation: UPDATE_RESOURCE,
         variables: {
           id: resource.id,
           title: resource.title,
-          parentId: resource.parent.id,
+          parentId: resource.parent ? resource.parent.id : null,
           path: resource.path,
         },
+        update: (store, { data }) => {
+          if (!data || data.updateResource == null || parentPath == null) return;
+          if (!this.resource.actor) return;
+
+          console.log("Removing ressource from old parent");
+          const oldParentCachedData = store.readQuery<{ resource: IResource }>({
+            query: GET_RESOURCE,
+            variables: {
+              path: parentPath,
+              username: this.resource.actor.preferredUsername,
+            },
+          });
+          if (oldParentCachedData == null) return;
+          const { resource: oldParentCachedResource } = oldParentCachedData;
+          if (oldParentCachedResource == null) {
+            console.error("Cannot update resource cache, because of null value.");
+            return;
+          }
+          const resource: IResource = data.updateResource;
+
+          oldParentCachedResource.children.elements = oldParentCachedResource.children.elements.filter(
+            (cachedResource) => cachedResource.id !== resource.id
+          );
+
+          store.writeQuery({
+            query: GET_RESOURCE,
+            variables: {
+              path: parentPath,
+              username: this.resource.actor.preferredUsername,
+            },
+            data: { oldParentCachedResource },
+          });
+          console.log("Finished removing ressource from old parent");
+
+          console.log("Adding resource to new parent");
+          if (!resource.parent || !resource.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,
+              username: this.resource.actor.preferredUsername,
+            },
+          });
+          if (newParentCachedData == null) return;
+          const { resource: newParentCachedResource } = newParentCachedData;
+          if (newParentCachedResource == null) {
+            console.error("Cannot update resource cache, because of null value.");
+            return;
+          }
+
+          newParentCachedResource.children.elements.push(resource);
+
+          store.writeQuery({
+            query: GET_RESOURCE,
+            variables: {
+              path: resource.parent.path,
+              username: this.resource.actor.preferredUsername,
+            },
+            data: { newParentCachedResource },
+          });
+          console.log("Finished adding resource to new parent");
+        },
       });
     } catch (e) {
       console.error(e);
diff --git a/lib/graphql/schema.ex b/lib/graphql/schema.ex
index 3f20d9f11..34fe0c309 100644
--- a/lib/graphql/schema.ex
+++ b/lib/graphql/schema.ex
@@ -5,7 +5,18 @@ defmodule Mobilizon.GraphQL.Schema do
 
   use Absinthe.Schema
 
-  alias Mobilizon.{Actors, Addresses, Conversations, Events, Media, Reports, Todos, Users}
+  alias Mobilizon.{
+    Actors,
+    Addresses,
+    Conversations,
+    Events,
+    Media,
+    Reports,
+    Resources,
+    Todos,
+    Users
+  }
+
   alias Mobilizon.Actors.{Actor, Follower, Member}
   alias Mobilizon.Conversations.Comment
   alias Mobilizon.Events.{Event, Participant}
@@ -109,6 +120,7 @@ defmodule Mobilizon.GraphQL.Schema do
       |> Dataloader.add_source(Addresses, default_source)
       |> Dataloader.add_source(Media, default_source)
       |> Dataloader.add_source(Reports, default_source)
+      |> Dataloader.add_source(Resources, default_source)
       |> Dataloader.add_source(Todos, default_source)
 
     Map.put(ctx, :loader, loader)
diff --git a/lib/graphql/schema/resource.ex b/lib/graphql/schema/resource.ex
index f5a1e717b..6b91ba21a 100644
--- a/lib/graphql/schema/resource.ex
+++ b/lib/graphql/schema/resource.ex
@@ -4,6 +4,8 @@ defmodule Mobilizon.GraphQL.Schema.ResourceType do
   """
   use Absinthe.Schema.Notation
   alias Mobilizon.GraphQL.Resolvers.Resource
+  alias Mobilizon.Resources
+  import Absinthe.Resolution.Helpers, only: [dataloader: 1]
 
   @desc "A resource"
   object :resource do
@@ -19,7 +21,8 @@ defmodule Mobilizon.GraphQL.Schema.ResourceType do
     field(:updated_at, :naive_datetime, description: "The resource's last update date")
     field(:type, :string, description: "The resource's type (if it's a folder)")
     field(:path, :string, description: "The resource's path")
-    field(:parent, :resource, description: "The resource's parent")
+
+    field(:parent, :resource, description: "The resource's parent", resolve: dataloader(Resources))
 
     field :children, :paginated_resource_list do
       description("Children resources in folder")
diff --git a/lib/service/rich_media/parsers/ogp.ex b/lib/service/rich_media/parsers/ogp.ex
index 1630d3841..8be34d71d 100644
--- a/lib/service/rich_media/parsers/ogp.ex
+++ b/lib/service/rich_media/parsers/ogp.ex
@@ -30,7 +30,16 @@ defmodule Mobilizon.Service.RichMedia.Parsers.OGP do
   defp transform_tags(data) do
     data
     |> Map.put(:image_remote_url, Map.get(data, :image))
-    |> Map.put(:width, Map.get(data, :"image:width"))
-    |> Map.put(:height, Map.get(data, :"image:height"))
+    |> Map.put(:width, get_integer_value(data, :"image:width"))
+    |> Map.put(:height, get_integer_value(data, :"image:height"))
+  end
+
+  defp get_integer_value(data, key) do
+    with value <- Map.get(data, key),
+         {value, ""} <- Integer.parse(value) do
+      value
+    else
+      _ -> nil
+    end
   end
 end

From c3f73f4f87e7896109dbd6a53e91c4ee8b64ed92 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Fri, 26 Jun 2020 12:08:07 +0200
Subject: [PATCH 2/3] Rename conversation strings to discussion

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 js/src/i18n/en_US.json                           | 10 +++++-----
 js/src/i18n/fr_FR.json                           | 10 +++++-----
 js/src/views/Conversations/Conversation.vue      |  2 +-
 js/src/views/Conversations/ConversationsList.vue |  4 ++--
 js/src/views/Conversations/Create.vue            |  4 ++--
 js/src/views/Group/Group.vue                     |  2 +-
 6 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json
index 75cbc481a..0f8cad665 100644
--- a/js/src/i18n/en_US.json
+++ b/js/src/i18n/en_US.json
@@ -510,10 +510,7 @@
   "Website": "Website",
   "Actor": "Actor",
   "Statut": "Statut",
-  "Conversations": "Conversations",
   "Text": "Text",
-  "New conversation": "New conversation",
-  "Create a new conversation": "Create a new conversation",
   "All group members and other eventual server admins will still be able to view this information.": "All group members and other eventual server admins will still be able to view this information.",
   "Upcoming events": "Upcoming events",
   "View all upcoming events": "View all upcoming events",
@@ -525,7 +522,6 @@
   "Post a public message": "Post a public message",
   "View all todos": "View all todos",
   "Discussions": "Discussions",
-  "View all conversations": "View all conversations",
   "No public upcoming events": "No public upcoming events",
   "Latest posts": "Latest posts",
   "Invite a new member": "Invite a new member",
@@ -703,5 +699,9 @@
   "(this link)": "(this link)",
   "Move resource to {folder}": "Move resource to {folder}",
   "Create a folder": "Create a folder",
-  "No resources in this folder": "No resources in this folder"
+  "No resources in this folder": "No resources in this folder",
+  "New discussion": "New discussion",
+  "Create a discussion": "Create a discussion",
+  "Create the discussion": "Create the discussion",
+  "View all discussions": "View all discussions"
 }
diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json
index 450552732..0c0a9965a 100644
--- a/js/src/i18n/fr_FR.json
+++ b/js/src/i18n/fr_FR.json
@@ -78,11 +78,9 @@
   "Confirmed: Will happen": "Confirmé : aura lieu",
   "Contact": "Contact",
   "Continue editing": "Continuer la modification",
-  "Conversations": "Conversations",
   "Country": "Pays",
   "Create": "Créer",
   "Create a calc": "Créer un calc",
-  "Create a new conversation": "Créer une nouvelle conversation",
   "Create a new event": "Créer un nouvel évènement",
   "Create a new group": "Créer un nouveau groupe",
   "Create a new identity": "Créer une nouvelle identité",
@@ -260,7 +258,6 @@
   "My groups": "Mes groupes",
   "My identities": "Mes identités",
   "Name": "Nom",
-  "New conversation": "Nouvelle conversation",
   "New email": "Nouvelle adresse e-mail",
   "New folder": "Nouveau dossier",
   "New link": "Nouveau lien",
@@ -478,7 +475,6 @@
   "Username": "Pseudo",
   "Users": "Utilisateur⋅ice⋅s",
   "View a reply": "Aucune réponse | Voir une réponse | Voir {totalReplies} réponses",
-  "View all conversations": "Voir toutes les conversations",
   "View all resources": "Voir toutes les resources",
   "View all todos": "Voir tous les todos",
   "View all upcoming events": "Voir tous les événements à venir",
@@ -703,5 +699,9 @@
   "(this link)": "(ce lien)",
   "Move resource to {folder}": "Déplacer la ressource dans {folder}",
   "Create a folder": "Créer un dossier",
-  "No resources in this folder": "Aucune ressource dans ce dossier"
+  "No resources in this folder": "Aucune ressource dans ce dossier",
+  "New discussion": "Nouvelle discussion",
+  "Create a discussion": "Créer une discussion",
+  "Create the discussion": "Créer la discussion",
+  "View all discussions": "Voir toutes les discussions"
 }
diff --git a/js/src/views/Conversations/Conversation.vue b/js/src/views/Conversations/Conversation.vue
index fe7d6e55b..220cb5b10 100644
--- a/js/src/views/Conversations/Conversation.vue
+++ b/js/src/views/Conversations/Conversation.vue
@@ -20,7 +20,7 @@
               name: RouteName.CONVERSATION_LIST,
               params: { preferredUsername: conversation.actor.preferredUsername },
             }"
-            >{{ $t("Conversations") }}</router-link
+            >{{ $t("Discussions") }}</router-link
           >
         </li>
         <li class="is-active">
diff --git a/js/src/views/Conversations/ConversationsList.vue b/js/src/views/Conversations/ConversationsList.vue
index c77a764d8..977c32022 100644
--- a/js/src/views/Conversations/ConversationsList.vue
+++ b/js/src/views/Conversations/ConversationsList.vue
@@ -20,7 +20,7 @@
               name: RouteName.CONVERSATION_LIST,
               params: { preferredUsername: usernameWithDomain(group) },
             }"
-            >{{ $t("Conversations") }}</router-link
+            >{{ $t("Discussions") }}</router-link
           >
         </li>
       </ul>
@@ -39,7 +39,7 @@
           name: RouteName.CREATE_CONVERSATION,
           params: { preferredUsername: this.preferredUsername },
         }"
-        >{{ $t("New conversation") }}</b-button
+        >{{ $t("New discussion") }}</b-button
       >
     </section>
   </div>
diff --git a/js/src/views/Conversations/Create.vue b/js/src/views/Conversations/Create.vue
index 10cc9ad23..e16cdea19 100644
--- a/js/src/views/Conversations/Create.vue
+++ b/js/src/views/Conversations/Create.vue
@@ -1,6 +1,6 @@
 <template>
   <section class="section container">
-    <h1>{{ $t("Create a new conversation") }}</h1>
+    <h1>{{ $t("Create a discussion") }}</h1>
 
     <form @submit.prevent="createConversation">
       <b-field :label="$t('Title')">
@@ -11,7 +11,7 @@
         <editor v-model="conversation.text" />
       </b-field>
 
-      <button class="button is-primary" type="submit">{{ $t("Create my group") }}</button>
+      <button class="button is-primary" type="submit">{{ $t("Create the discussion") }}</button>
     </form>
   </section>
 </template>
diff --git a/js/src/views/Group/Group.vue b/js/src/views/Group/Group.vue
index 311ffd262..281b3f8ff 100644
--- a/js/src/views/Group/Group.vue
+++ b/js/src/views/Group/Group.vue
@@ -133,7 +133,7 @@
               name: RouteName.CONVERSATION_LIST,
               params: { preferredUsername: usernameWithDomain(group) },
             }"
-            >{{ $t("View all conversations") }}</router-link
+            >{{ $t("View all discussions") }}</router-link
           >
         </section>
       </div>

From b11674fc9e6365a381c11ba2ec08fedc38d24ad6 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Fri, 26 Jun 2020 14:42:40 +0200
Subject: [PATCH 3/3] Fix opengraph issue

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 lib/service/rich_media/parsers/ogp.ex | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/service/rich_media/parsers/ogp.ex b/lib/service/rich_media/parsers/ogp.ex
index 8be34d71d..0b879aab1 100644
--- a/lib/service/rich_media/parsers/ogp.ex
+++ b/lib/service/rich_media/parsers/ogp.ex
@@ -34,8 +34,9 @@ defmodule Mobilizon.Service.RichMedia.Parsers.OGP do
     |> Map.put(:height, get_integer_value(data, :"image:height"))
   end
 
+  @spec get_integer_value(Map.t(), atom()) :: integer() | nil
   defp get_integer_value(data, key) do
-    with value <- Map.get(data, key),
+    with value when not is_nil(value) <- Map.get(data, key),
          {value, ""} <- Integer.parse(value) do
       value
     else