From e067783a30be41644abe1aecca631ef31cf476a8 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Thu, 17 Mar 2022 12:59:10 -0400
Subject: Add announcement display with placeholder messages

---
 .../announcements_page/announcements_page.vue       | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
 create mode 100644 src/components/announcements_page/announcements_page.vue

(limited to 'src/components/announcements_page/announcements_page.vue')

diff --git a/src/components/announcements_page/announcements_page.vue b/src/components/announcements_page/announcements_page.vue
new file mode 100644
index 00000000..16b2f955
--- /dev/null
+++ b/src/components/announcements_page/announcements_page.vue
@@ -0,0 +1,21 @@
+<template>
+  <div class="panel panel-default announcements-page">
+    <div class="panel-heading">
+      <span>
+        {{ $t('announcements.page_header') }}
+      </span>
+    </div>
+    <div class="panel-body">
+      <section
+        v-for="announcement in announcements"
+        :key="announcement.id"
+      >
+        <announcement
+          :announcement="announcement"
+        />
+      </section>
+    </div>
+  </div>
+</template>
+
+<script src="./announcements_page.js"></script>
-- 
cgit v1.2.3-70-g09d2


From e494e746439e3e622c5b12a182b6c6a9de540821 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Thu, 17 Mar 2022 14:54:52 -0400
Subject: Implement posting announcements

---
 .../announcements_page/announcements_page.js       | 28 ++++++++
 .../announcements_page/announcements_page.vue      | 76 ++++++++++++++++++++++
 src/modules/announcements.js                       |  6 ++
 src/services/api/api.service.js                    | 27 +++++++-
 4 files changed, 136 insertions(+), 1 deletion(-)

(limited to 'src/components/announcements_page/announcements_page.vue')

diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js
index a2a218fd..ec038361 100644
--- a/src/components/announcements_page/announcements_page.js
+++ b/src/components/announcements_page/announcements_page.js
@@ -1,16 +1,44 @@
+import { mapState } from 'vuex'
 import Announcement from '../announcement/announcement.vue'
 
 const AnnouncementsPage = {
   components: {
     Announcement
   },
+  data () {
+    return {
+      newAnnouncement: {
+        content: ''
+      },
+      posting: false,
+      error: undefined
+    }
+  },
   mounted () {
     this.$store.dispatch('fetchAnnouncements')
   },
   computed: {
+    ...mapState({
+      currentUser: state => state.users.currentUser
+    }),
     announcements () {
       return this.$store.state.announcements.announcements
     }
+  },
+  methods: {
+    postAnnouncement () {
+      this.posting = true
+      this.$store.dispatch('postAnnouncement', this.newAnnouncement)
+        .catch(error => {
+          this.error = error.error
+        })
+        .finally(() => {
+          this.posting = false
+        })
+    },
+    clearError () {
+      this.error = undefined
+    }
   }
 }
 
diff --git a/src/components/announcements_page/announcements_page.vue b/src/components/announcements_page/announcements_page.vue
index 16b2f955..1b3bd578 100644
--- a/src/components/announcements_page/announcements_page.vue
+++ b/src/components/announcements_page/announcements_page.vue
@@ -6,6 +6,51 @@
       </span>
     </div>
     <div class="panel-body">
+      <section
+        v-if="currentUser && currentUser.role === 'admin'"
+      >
+        <div class="post-form">
+          <div class="heading">
+            <h4>{{ $t('announcements.post_form_header') }}</h4>
+          </div>
+          <div class="body">
+            <textarea
+              ref="textarea"
+              v-model="newAnnouncement.content"
+              class="post-textarea"
+              rows="1"
+              cols="1"
+              :placeholder="$t('announcements.post_placeholder')"
+              :disabled="posting"
+            />
+          </div>
+          <div class="footer">
+            <button
+              class="btn button-default post-button"
+              :disabled="posting"
+              @click.prevent="postAnnouncement"
+            >
+              {{ $t('announcements.post_action') }}
+            </button>
+            <div
+              v-if="error"
+              class="alert error"
+            >
+              {{ $t('announcements.post_error', { error }) }}
+              <button
+                class="button-unstyled"
+                @click="clearError"
+              >
+                <FAIcon
+                  class="fa-scale-110 fa-old-padding"
+                  icon="times"
+                  :title="$t('announcements.close_error')"
+                />
+              </button>
+            </div>
+          </div>
+        </div>
+      </section>
       <section
         v-for="announcement in announcements"
         :key="announcement.id"
@@ -19,3 +64,34 @@
 </template>
 
 <script src="./announcements_page.js"></script>
+
+<style lang="scss">
+@import "../../variables";
+
+.announcements-page {
+  .post-form {
+    padding: var(--status-margin, $status-margin);
+
+    .heading, .body {
+      margin-bottom: var(--status-margin, $status-margin);
+    }
+
+    .body {
+      display: flex;
+      align-items: stretch;
+      flex-direction: column;
+    }
+
+    .post-textarea {
+      resize: vertical;
+      height: 10em;
+      overflow: none;
+      box-sizing: content-box;
+    }
+
+    .post-button {
+      min-width: 10em;
+    }
+  }
+}
+</style>
diff --git a/src/modules/announcements.js b/src/modules/announcements.js
index 2ab2600c..76cea154 100644
--- a/src/modules/announcements.js
+++ b/src/modules/announcements.js
@@ -53,6 +53,12 @@ const announcements = {
       const interval = store.state.fetchAnnouncementsTimer
       store.commit('setFetchAnnouncementsTimer', undefined)
       clearInterval(interval)
+    },
+    postAnnouncement (store, { content, startsAt, endsAt, allDay }) {
+      return store.rootState.api.backendInteractor.postAnnouncement({ content, startsAt, endsAt, allDay })
+        .then(() => {
+          return store.dispatch('fetchAnnouncements')
+        })
     }
   }
 }
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 10d43632..5206602d 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -102,6 +102,7 @@ const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read`
 const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
 const PLEROMA_ADMIN_REPORTS = '/api/pleroma/admin/reports'
 const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
+const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements'
 
 const oldfetch = window.fetch
 
@@ -1375,6 +1376,29 @@ const dismissAnnouncement = ({ id, credentials }) => {
   })
 }
 
+const postAnnouncement = ({ credentials, content, startsAt, endsAt, allDay }) => {
+  const payload = { content }
+
+  if (typeof startsAt !== 'undefined') {
+    payload['starts_at'] = startsAt
+  }
+
+  if (typeof endsAt !== 'undefined') {
+    payload['ends_at'] = endsAt
+  }
+
+  if (typeof allDay !== 'undefined') {
+    payload['all_day'] = allDay
+  }
+
+  return promisedRequest({
+    url: PLEROMA_POST_ANNOUNCEMENT_URL,
+    credentials,
+    method: 'POST',
+    payload
+  })
+}
+
 export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
   return Object.entries({
     ...(credentials
@@ -1703,7 +1727,8 @@ const apiService = {
   setReportState,
   fetchUserInLists,
   fetchAnnouncements,
-  dismissAnnouncement
+  dismissAnnouncement,
+  postAnnouncement
 }
 
 export default apiService
-- 
cgit v1.2.3-70-g09d2


From 56e6d86f882a154f233616f79172391e9d097fcd Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Thu, 17 Mar 2022 15:45:45 -0400
Subject: Allow posting announcements with other metadata

---
 .../announcements_page/announcements_page.js       | 14 ++++++++++--
 .../announcements_page/announcements_page.vue      | 26 ++++++++++++++++++++++
 src/services/api/api.service.js                    |  4 ++--
 3 files changed, 40 insertions(+), 4 deletions(-)

(limited to 'src/components/announcements_page/announcements_page.vue')

diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js
index ec038361..30759533 100644
--- a/src/components/announcements_page/announcements_page.js
+++ b/src/components/announcements_page/announcements_page.js
@@ -1,14 +1,19 @@
 import { mapState } from 'vuex'
 import Announcement from '../announcement/announcement.vue'
+import Checkbox from '../checkbox/checkbox.vue'
 
 const AnnouncementsPage = {
   components: {
-    Announcement
+    Announcement,
+    Checkbox
   },
   data () {
     return {
       newAnnouncement: {
-        content: ''
+        content: '',
+        startsAt: undefined,
+        endsAt: undefined,
+        allDay: false
       },
       posting: false,
       error: undefined
@@ -29,6 +34,11 @@ const AnnouncementsPage = {
     postAnnouncement () {
       this.posting = true
       this.$store.dispatch('postAnnouncement', this.newAnnouncement)
+        .then(() => {
+          this.newAnnouncement.content = ''
+          this.startsAt = undefined
+          this.endsAt = undefined
+        })
         .catch(error => {
           this.error = error.error
         })
diff --git a/src/components/announcements_page/announcements_page.vue b/src/components/announcements_page/announcements_page.vue
index 1b3bd578..e81edee1 100644
--- a/src/components/announcements_page/announcements_page.vue
+++ b/src/components/announcements_page/announcements_page.vue
@@ -23,6 +23,29 @@
               :placeholder="$t('announcements.post_placeholder')"
               :disabled="posting"
             />
+            <span class="announcement-metadata">
+              <label for="announcement-start-time">{{ $t('announcements.start_time_prompt') }}</label>
+              <input
+                id="announcement-start-time"
+                v-model="newAnnouncement.startsAt"
+                :type="newAnnouncement.allDay ? 'date' : 'datetime-local'"
+              >
+            </span>
+            <span class="announcement-metadata">
+              <label for="announcement-end-time">{{ $t('announcements.end_time_prompt') }}</label>
+              <input
+                id="announcement-end-time"
+                v-model="newAnnouncement.endsAt"
+                :type="newAnnouncement.allDay ? 'date' : 'datetime-local'"
+              >
+            </span>
+            <span class="announcement-metadata">
+              <Checkbox
+                id="announcement-all-day"
+                v-model="newAnnouncement.allDay"
+              />
+              <label for="announcement-all-day">{{ $t('announcements.all_day_prompt') }}</label>
+            </span>
           </div>
           <div class="footer">
             <button
@@ -80,6 +103,9 @@
       display: flex;
       align-items: stretch;
       flex-direction: column;
+      .announcement-metadata {
+        margin-top: 0.5em;
+      }
     }
 
     .post-textarea {
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 96b8bd70..752d5811 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1381,11 +1381,11 @@ const postAnnouncement = ({ credentials, content, startsAt, endsAt, allDay }) =>
   const payload = { content }
 
   if (typeof startsAt !== 'undefined') {
-    payload['starts_at'] = startsAt
+    payload['starts_at'] = new Date(startsAt).toISOString()
   }
 
   if (typeof endsAt !== 'undefined') {
-    payload['ends_at'] = endsAt
+    payload['ends_at'] = new Date(endsAt).toISOString()
   }
 
   if (typeof allDay !== 'undefined') {
-- 
cgit v1.2.3-70-g09d2


From 04fbb6d93d6217812dc1b594b65f237bcee692a7 Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Thu, 17 Mar 2022 16:51:32 -0400
Subject: Show announcement dates

---
 src/components/announcement/announcement.js        | 35 ++++++++++++++
 src/components/announcement/announcement.vue       | 54 ++++++++++++++++------
 .../announcement_editor/announcement_editor.js     | 13 ++++++
 .../announcement_editor/announcement_editor.vue    | 52 +++++++++++++++++++++
 .../announcements_page/announcements_page.js       |  4 +-
 .../announcements_page/announcements_page.vue      | 41 +---------------
 src/modules/announcements.js                       |  7 ++-
 src/services/api/api.service.js                    |  8 +++-
 8 files changed, 156 insertions(+), 58 deletions(-)
 create mode 100644 src/components/announcement_editor/announcement_editor.js
 create mode 100644 src/components/announcement_editor/announcement_editor.vue

(limited to 'src/components/announcements_page/announcements_page.vue')

diff --git a/src/components/announcement/announcement.js b/src/components/announcement/announcement.js
index b1f5ee77..309eecea 100644
--- a/src/components/announcement/announcement.js
+++ b/src/components/announcement/announcement.js
@@ -1,6 +1,21 @@
 import { mapState } from 'vuex'
+import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
+import localeService from '../../services/locale/locale.service.js'
 
 const Announcement = {
+  components: {
+    AnnouncementEditor
+  },
+  data () {
+    return {
+      editing: false,
+      newAnnouncement: {
+        content: '',
+        startsAt: undefined,
+        endsAt: undefined
+      }
+    }
+  },
   props: {
     announcement: Object
   },
@@ -13,6 +28,22 @@ const Announcement = {
     },
     isRead () {
       return this.announcement.read
+    },
+    startsAt () {
+      const time = this.announcement['starts_at']
+      if (!time) {
+        return
+      }
+
+      return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
+    },
+    endsAt () {
+      const time = this.announcement['ends_at']
+      if (!time) {
+        return
+      }
+
+      return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
     }
   },
   methods: {
@@ -23,6 +54,10 @@ const Announcement = {
     },
     deleteAnnouncement () {
       return this.$store.dispatch('deleteAnnouncement', this.announcement.id)
+    },
+    formatTimeOrDate (time, locale) {
+      const d = new Date(time)
+      return this.announcement['all_day'] ? d.toLocaleDateString(locale) : d.toLocaleString(locale)
     }
   }
 }
diff --git a/src/components/announcement/announcement.vue b/src/components/announcement/announcement.vue
index d6f35b9f..5ddeebf5 100644
--- a/src/components/announcement/announcement.vue
+++ b/src/components/announcement/announcement.vue
@@ -5,27 +5,42 @@
     </div>
     <div class="body">
       <rich-content
+        v-if="!editing"
         :html="content"
         :emoji="announcement.emojis"
         :handle-links="true"
       />
+      <announcement-editor
+        v-else
+        :announcement="newAnnouncement"
+      />
     </div>
     <div class="footer">
-      <button
-        v-if="currentUser"
-        class="btn button-default"
-        :class="{ toggled: isRead }"
-        @click="markAsRead"
-      >
-        {{ $t('announcements.mark_as_read_action') }}
-      </button>
-      <button
-        v-if="currentUser && currentUser.role === 'admin'"
-        class="btn button-default"
-        @click="deleteAnnouncement"
-      >
-        {{ $t('announcements.delete_action') }}
-      </button>
+      <div class="times">
+        <span v-if="startsAt">
+          {{ $t('announcements.start_time_display', { time: startsAt }) }}
+        </span>
+        <span v-if="endsAt">
+          {{ $t('announcements.end_time_display', { time: endsAt }) }}
+        </span>
+      </div>
+      <div class="actions">
+        <button
+          v-if="currentUser"
+          class="btn button-default"
+          :class="{ toggled: isRead }"
+          @click="markAsRead"
+        >
+          {{ $t('announcements.mark_as_read_action') }}
+        </button>
+        <button
+          v-if="currentUser && currentUser.role === 'admin'"
+          class="btn button-default"
+          @click="deleteAnnouncement"
+        >
+          {{ $t('announcements.delete_action') }}
+        </button>
+      </div>
     </div>
   </div>
 </template>
@@ -47,6 +62,15 @@
   }
 
   .footer {
+    display: flex;
+    flex-direction: column;
+    .times {
+      display: flex;
+      flex-direction: column;
+    }
+  }
+
+  .footer .actions {
     display: flex;
     flex-direction: row;
     justify-content: space-around;
diff --git a/src/components/announcement_editor/announcement_editor.js b/src/components/announcement_editor/announcement_editor.js
new file mode 100644
index 00000000..79a03afe
--- /dev/null
+++ b/src/components/announcement_editor/announcement_editor.js
@@ -0,0 +1,13 @@
+import Checkbox from '../checkbox/checkbox.vue'
+
+const AnnouncementEditor = {
+  components: {
+    Checkbox
+  },
+  props: {
+    announcement: Object,
+    disabled: Boolean
+  }
+}
+
+export default AnnouncementEditor
diff --git a/src/components/announcement_editor/announcement_editor.vue b/src/components/announcement_editor/announcement_editor.vue
new file mode 100644
index 00000000..e2418b8d
--- /dev/null
+++ b/src/components/announcement_editor/announcement_editor.vue
@@ -0,0 +1,52 @@
+<template>
+  <div class="announcement-editor">
+    <textarea
+      ref="textarea"
+      v-model="announcement.content"
+      class="post-textarea"
+      rows="1"
+      cols="1"
+      :placeholder="$t('announcements.post_placeholder')"
+      :disabled="disabled"
+    />
+    <span class="announcement-metadata">
+      <label for="announcement-start-time">{{ $t('announcements.start_time_prompt') }}</label>
+      <input
+        id="announcement-start-time"
+        v-model="announcement.startsAt"
+        :type="announcement.allDay ? 'date' : 'datetime-local'"
+        :disabled="disabled"
+      >
+    </span>
+    <span class="announcement-metadata">
+      <label for="announcement-end-time">{{ $t('announcements.end_time_prompt') }}</label>
+      <input
+        id="announcement-end-time"
+        v-model="announcement.endsAt"
+        :type="announcement.allDay ? 'date' : 'datetime-local'"
+        :disabled="disabled"
+      >
+    </span>
+    <span class="announcement-metadata">
+      <Checkbox
+        id="announcement-all-day"
+        v-model="announcement.allDay"
+        :disabled="disabled"
+      />
+      <label for="announcement-all-day">{{ $t('announcements.all_day_prompt') }}</label>
+    </span>
+  </div>
+</template>
+
+<script src="./announcement_editor.js"></script>
+
+<style lang="scss">
+    .announcement-editor {
+      display: flex;
+      align-items: stretch;
+      flex-direction: column;
+      .announcement-metadata {
+        margin-top: 0.5em;
+      }
+    }
+</style>
diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js
index 30759533..0bb4892e 100644
--- a/src/components/announcements_page/announcements_page.js
+++ b/src/components/announcements_page/announcements_page.js
@@ -1,11 +1,11 @@
 import { mapState } from 'vuex'
 import Announcement from '../announcement/announcement.vue'
-import Checkbox from '../checkbox/checkbox.vue'
+import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
 
 const AnnouncementsPage = {
   components: {
     Announcement,
-    Checkbox
+    AnnouncementEditor
   },
   data () {
     return {
diff --git a/src/components/announcements_page/announcements_page.vue b/src/components/announcements_page/announcements_page.vue
index e81edee1..54307c4d 100644
--- a/src/components/announcements_page/announcements_page.vue
+++ b/src/components/announcements_page/announcements_page.vue
@@ -14,38 +14,10 @@
             <h4>{{ $t('announcements.post_form_header') }}</h4>
           </div>
           <div class="body">
-            <textarea
-              ref="textarea"
-              v-model="newAnnouncement.content"
-              class="post-textarea"
-              rows="1"
-              cols="1"
-              :placeholder="$t('announcements.post_placeholder')"
+            <announcement-editor
+              :announcement="newAnnouncement"
               :disabled="posting"
             />
-            <span class="announcement-metadata">
-              <label for="announcement-start-time">{{ $t('announcements.start_time_prompt') }}</label>
-              <input
-                id="announcement-start-time"
-                v-model="newAnnouncement.startsAt"
-                :type="newAnnouncement.allDay ? 'date' : 'datetime-local'"
-              >
-            </span>
-            <span class="announcement-metadata">
-              <label for="announcement-end-time">{{ $t('announcements.end_time_prompt') }}</label>
-              <input
-                id="announcement-end-time"
-                v-model="newAnnouncement.endsAt"
-                :type="newAnnouncement.allDay ? 'date' : 'datetime-local'"
-              >
-            </span>
-            <span class="announcement-metadata">
-              <Checkbox
-                id="announcement-all-day"
-                v-model="newAnnouncement.allDay"
-              />
-              <label for="announcement-all-day">{{ $t('announcements.all_day_prompt') }}</label>
-            </span>
           </div>
           <div class="footer">
             <button
@@ -99,15 +71,6 @@
       margin-bottom: var(--status-margin, $status-margin);
     }
 
-    .body {
-      display: flex;
-      align-items: stretch;
-      flex-direction: column;
-      .announcement-metadata {
-        margin-top: 0.5em;
-      }
-    }
-
     .post-textarea {
       resize: vertical;
       height: 10em;
diff --git a/src/modules/announcements.js b/src/modules/announcements.js
index ddc51c33..a42acf93 100644
--- a/src/modules/announcements.js
+++ b/src/modules/announcements.js
@@ -28,7 +28,12 @@ const announcements = {
   mutations,
   actions: {
     fetchAnnouncements (store) {
-      return store.rootState.api.backendInteractor.fetchAnnouncements()
+      const currentUser = store.rootState.users.currentUser
+      const isAdmin = currentUser && currentUser.role === 'admin'
+
+      return (isAdmin
+        ? store.rootState.api.backendInteractor.adminFetchAnnouncements()
+        : store.rootState.api.backendInteractor.fetchAnnouncements())
         .then(announcements => {
           store.commit('setAnnouncements', announcements)
         })
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 752d5811..c2bfe8cc 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -102,6 +102,7 @@ const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read`
 const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
 const PLEROMA_ADMIN_REPORTS = '/api/pleroma/admin/reports'
 const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
+const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements'
 const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements'
 const PLEROMA_DELETE_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}`
 
@@ -1365,6 +1366,10 @@ const dismissNotification = ({ credentials, id }) => {
   })
 }
 
+const adminFetchAnnouncements = ({ credentials }) => {
+  return promisedRequest({ url: PLEROMA_ANNOUNCEMENTS_URL, credentials })
+}
+
 const fetchAnnouncements = ({ credentials }) => {
   return promisedRequest({ url: MASTODON_ANNOUNCEMENTS_URL, credentials })
 }
@@ -1738,7 +1743,8 @@ const apiService = {
   fetchAnnouncements,
   dismissAnnouncement,
   postAnnouncement,
-  deleteAnnouncement
+  deleteAnnouncement,
+  adminFetchAnnouncements
 }
 
 export default apiService
-- 
cgit v1.2.3-70-g09d2


From 89c49b6fb46e607aee07afd5bc436e8f2f605bfe Mon Sep 17 00:00:00 2001
From: Tusooa Zhu <tusooa@kazv.moe>
Date: Thu, 17 Mar 2022 17:11:53 -0400
Subject: Allow editing announcements

---
 src/components/announcement/announcement.js        | 31 +++++++++++++-
 src/components/announcement/announcement.vue       | 50 +++++++++++++++++++++-
 .../announcement_editor/announcement_editor.vue    | 24 +++++++----
 .../announcements_page/announcements_page.vue      |  7 ---
 src/modules/announcements.js                       |  6 +++
 src/services/api/api.service.js                    | 23 ++++++++--
 6 files changed, 118 insertions(+), 23 deletions(-)

(limited to 'src/components/announcements_page/announcements_page.vue')

diff --git a/src/components/announcement/announcement.js b/src/components/announcement/announcement.js
index 309eecea..253e2bb2 100644
--- a/src/components/announcement/announcement.js
+++ b/src/components/announcement/announcement.js
@@ -12,8 +12,10 @@ const Announcement = {
       newAnnouncement: {
         content: '',
         startsAt: undefined,
-        endsAt: undefined
-      }
+        endsAt: undefined,
+        allDay: undefined
+      },
+      editError: ''
     }
   },
   props: {
@@ -58,6 +60,31 @@ const Announcement = {
     formatTimeOrDate (time, locale) {
       const d = new Date(time)
       return this.announcement['all_day'] ? d.toLocaleDateString(locale) : d.toLocaleString(locale)
+    },
+    enterEditMode () {
+      this.newAnnouncement.content = this.announcement.pleroma['raw_content']
+      this.newAnnouncement.startsAt = this.announcement['starts_at']
+      this.newAnnouncement.endsAt = this.announcement['ends_at']
+      this.newAnnouncement.allDay = this.announcement['all_day']
+      this.editing = true
+    },
+    submitEdit () {
+      this.$store.dispatch('editAnnouncement', {
+        id: this.announcement.id,
+        ...this.newAnnouncement
+      })
+        .then(() => {
+          this.editing = false
+        })
+        .catch(error => {
+          this.editError = error.error
+        })
+    },
+    cancelEdit () {
+      this.editing = false
+    },
+    clearError () {
+      this.editError = undefined
     }
   }
 }
diff --git a/src/components/announcement/announcement.vue b/src/components/announcement/announcement.vue
index 5ddeebf5..1e4edcca 100644
--- a/src/components/announcement/announcement.vue
+++ b/src/components/announcement/announcement.vue
@@ -16,7 +16,10 @@
       />
     </div>
     <div class="footer">
-      <div class="times">
+      <div
+        v-if="!editing"
+        class="times"
+      >
         <span v-if="startsAt">
           {{ $t('announcements.start_time_display', { time: startsAt }) }}
         </span>
@@ -24,7 +27,10 @@
           {{ $t('announcements.end_time_display', { time: endsAt }) }}
         </span>
       </div>
-      <div class="actions">
+      <div
+        v-if="!editing"
+        class="actions"
+      >
         <button
           v-if="currentUser"
           class="btn button-default"
@@ -33,6 +39,13 @@
         >
           {{ $t('announcements.mark_as_read_action') }}
         </button>
+        <button
+          v-if="currentUser && currentUser.role === 'admin'"
+          class="btn button-default"
+          @click="enterEditMode"
+        >
+          {{ $t('announcements.edit_action') }}
+        </button>
         <button
           v-if="currentUser && currentUser.role === 'admin'"
           class="btn button-default"
@@ -41,6 +54,39 @@
           {{ $t('announcements.delete_action') }}
         </button>
       </div>
+      <div
+        v-else
+        class="actions"
+      >
+        <button
+          class="btn button-default"
+          @click="submitEdit"
+        >
+          {{ $t('announcements.submit_edit_action') }}
+        </button>
+        <button
+          class="btn button-default"
+          @click="cancelEdit"
+        >
+          {{ $t('announcements.cancel_edit_action') }}
+        </button>
+        <div
+          v-if="editing && editError"
+          class="alert error"
+        >
+          {{ $t('announcements.edit_error', { error }) }}
+          <button
+            class="button-unstyled"
+            @click="clearError"
+          >
+            <FAIcon
+              class="fa-scale-110 fa-old-padding"
+              icon="times"
+              :title="$t('announcements.close_error')"
+            />
+          </button>
+        </div>
+      </div>
     </div>
   </div>
 </template>
diff --git a/src/components/announcement_editor/announcement_editor.vue b/src/components/announcement_editor/announcement_editor.vue
index e2418b8d..0f29f9f7 100644
--- a/src/components/announcement_editor/announcement_editor.vue
+++ b/src/components/announcement_editor/announcement_editor.vue
@@ -41,12 +41,20 @@
 <script src="./announcement_editor.js"></script>
 
 <style lang="scss">
-    .announcement-editor {
-      display: flex;
-      align-items: stretch;
-      flex-direction: column;
-      .announcement-metadata {
-        margin-top: 0.5em;
-      }
-    }
+.announcement-editor {
+  display: flex;
+  align-items: stretch;
+  flex-direction: column;
+
+  .announcement-metadata {
+    margin-top: 0.5em;
+  }
+
+  .post-textarea {
+    resize: vertical;
+    height: 10em;
+    overflow: none;
+    box-sizing: content-box;
+  }
+}
 </style>
diff --git a/src/components/announcements_page/announcements_page.vue b/src/components/announcements_page/announcements_page.vue
index 54307c4d..b1489dec 100644
--- a/src/components/announcements_page/announcements_page.vue
+++ b/src/components/announcements_page/announcements_page.vue
@@ -71,13 +71,6 @@
       margin-bottom: var(--status-margin, $status-margin);
     }
 
-    .post-textarea {
-      resize: vertical;
-      height: 10em;
-      overflow: none;
-      box-sizing: content-box;
-    }
-
     .post-button {
       min-width: 10em;
     }
diff --git a/src/modules/announcements.js b/src/modules/announcements.js
index a42acf93..1d97c67c 100644
--- a/src/modules/announcements.js
+++ b/src/modules/announcements.js
@@ -65,6 +65,12 @@ const announcements = {
           return store.dispatch('fetchAnnouncements')
         })
     },
+    editAnnouncement (store, { id, content, startsAt, endsAt, allDay }) {
+      return store.rootState.api.backendInteractor.editAnnouncement({ id, content, startsAt, endsAt, allDay })
+        .then(() => {
+          return store.dispatch('fetchAnnouncements')
+        })
+    },
     deleteAnnouncement (store, id) {
       return store.rootState.api.backendInteractor.deleteAnnouncement({ id })
         .then(() => {
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index c2bfe8cc..0d4c674a 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -104,6 +104,7 @@ const PLEROMA_ADMIN_REPORTS = '/api/pleroma/admin/reports'
 const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
 const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements'
 const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements'
+const PLEROMA_EDIT_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}`
 const PLEROMA_DELETE_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}`
 
 const oldfetch = window.fetch
@@ -1382,26 +1383,39 @@ const dismissAnnouncement = ({ id, credentials }) => {
   })
 }
 
-const postAnnouncement = ({ credentials, content, startsAt, endsAt, allDay }) => {
+const announcementToPayload = ({ content, startsAt, endsAt, allDay }) => {
   const payload = { content }
 
   if (typeof startsAt !== 'undefined') {
-    payload['starts_at'] = new Date(startsAt).toISOString()
+    payload['starts_at'] = startsAt ? new Date(startsAt).toISOString() : null
   }
 
   if (typeof endsAt !== 'undefined') {
-    payload['ends_at'] = new Date(endsAt).toISOString()
+    payload['ends_at'] = endsAt ? new Date(endsAt).toISOString() : null
   }
 
   if (typeof allDay !== 'undefined') {
     payload['all_day'] = allDay
   }
 
+  return payload
+}
+
+const postAnnouncement = ({ credentials, content, startsAt, endsAt, allDay }) => {
   return promisedRequest({
     url: PLEROMA_POST_ANNOUNCEMENT_URL,
     credentials,
     method: 'POST',
-    payload
+    payload: announcementToPayload({ content, startsAt, endsAt, allDay })
+  })
+}
+
+const editAnnouncement = ({ id, credentials, content, startsAt, endsAt, allDay }) => {
+  return promisedRequest({
+    url: PLEROMA_EDIT_ANNOUNCEMENT_URL(id),
+    credentials,
+    method: 'PATCH',
+    payload: announcementToPayload({ content, startsAt, endsAt, allDay })
   })
 }
 
@@ -1743,6 +1757,7 @@ const apiService = {
   fetchAnnouncements,
   dismissAnnouncement,
   postAnnouncement,
+  editAnnouncement,
   deleteAnnouncement,
   adminFetchAnnouncements
 }
-- 
cgit v1.2.3-70-g09d2

