From 0673511fc246d67356dd656ad81ba401e3179108 Mon Sep 17 00:00:00 2001
From: Shpuld Shpuldson <shp@cock.li>
Date: Wed, 3 Mar 2021 16:46:53 +0200
Subject: fix shoutbox header, use custom scroll-to-bottom system, remove
 vue-chat-scroll, temporarily add chat test hack

---
 src/components/chat_panel/chat_panel.vue | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

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

diff --git a/src/components/chat_panel/chat_panel.vue b/src/components/chat_panel/chat_panel.vue
index 7993c94d..8a829115 100644
--- a/src/components/chat_panel/chat_panel.vue
+++ b/src/components/chat_panel/chat_panel.vue
@@ -10,17 +10,15 @@
         @click.stop.prevent="togglePanel"
       >
         <div class="title">
-          <span>{{ $t('shoutbox.title') }}</span>
+          {{ $t('shoutbox.title') }}
           <FAIcon
             v-if="floating"
             icon="times"
+            class="close-icon"
           />
         </div>
       </div>
-      <div
-        v-chat-scroll
-        class="chat-window"
-      >
+      <div class="chat-window">
         <div
           v-for="message in messages"
           :key="message.id"
@@ -94,6 +92,13 @@
     .icon {
       color: $fallback--text;
       color: var(--text, $fallback--text);
+      margin-right: 0.5em;
+    }
+
+    .title {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
     }
   }
 
-- 
cgit v1.2.3-70-g09d2


From 0604b1d5b7b19db41096876d0d911ea95c3c778f Mon Sep 17 00:00:00 2001
From: Mark Felder <feld@FreeBSD.org>
Date: Mon, 3 Aug 2020 18:44:35 -0500
Subject: Rename legacy PleromaFE Chat functionality to "Shout"

---
 src/App.js                                       |   6 +-
 src/App.vue                                      |   6 +-
 src/boot/after_store.js                          |   2 +-
 src/boot/routes.js                               |   4 +-
 src/components/chat_panel/chat_panel.js          |  53 --------
 src/components/chat_panel/chat_panel.vue         | 148 -----------------------
 src/components/features_panel/features_panel.js  |   2 +-
 src/components/features_panel/features_panel.vue |   4 +-
 src/components/shout_panel/shout_panel.js        |  53 ++++++++
 src/components/shout_panel/shout_panel.vue       | 148 +++++++++++++++++++++++
 src/components/side_drawer/side_drawer.js        |   2 +-
 src/main.js                                      |   4 +-
 src/modules/api.js                               |   4 +-
 src/modules/chat.js                              |  34 ------
 src/modules/instance.js                          |   6 +-
 src/modules/shout.js                             |  33 +++++
 src/modules/users.js                             |   2 +-
 static/config.json                               |   2 +-
 18 files changed, 256 insertions(+), 257 deletions(-)
 delete mode 100644 src/components/chat_panel/chat_panel.js
 delete mode 100644 src/components/chat_panel/chat_panel.vue
 create mode 100644 src/components/shout_panel/shout_panel.js
 create mode 100644 src/components/shout_panel/shout_panel.vue
 delete mode 100644 src/modules/chat.js
 create mode 100644 src/modules/shout.js

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

diff --git a/src/App.js b/src/App.js
index 231c6ae1..fe4c30cb 100644
--- a/src/App.js
+++ b/src/App.js
@@ -4,7 +4,7 @@ import Notifications from './components/notifications/notifications.vue'
 import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
 import FeaturesPanel from './components/features_panel/features_panel.vue'
 import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
-import ChatPanel from './components/chat_panel/chat_panel.vue'
+import ShoutPanel from './components/shout_panel/shout_panel.vue'
 import SettingsModal from './components/settings_modal/settings_modal.vue'
 import MediaModal from './components/media_modal/media_modal.vue'
 import SideDrawer from './components/side_drawer/side_drawer.vue'
@@ -26,7 +26,7 @@ export default {
     InstanceSpecificPanel,
     FeaturesPanel,
     WhoToFollowPanel,
-    ChatPanel,
+    ShoutPanel,
     MediaModal,
     SideDrawer,
     MobilePostStatusButton,
@@ -65,7 +65,7 @@ export default {
         }
       }
     },
-    chat () { return this.$store.state.chat.channel.state === 'joined' },
+    shout () { return this.$store.state.shout.channel.state === 'joined' },
     suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
     showInstanceSpecificPanel () {
       return this.$store.state.instance.showInstanceSpecificPanel &&
diff --git a/src/App.vue b/src/App.vue
index 1a166778..6c582c03 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -49,10 +49,10 @@
       </div>
       <media-modal />
     </div>
-    <chat-panel
-      v-if="currentUser && chat"
+    <shout-panel
+      v-if="currentUser && shout"
       :floating="true"
-      class="floating-chat mobile-hidden"
+      class="floating-shout mobile-hidden"
     />
     <MobilePostStatusButton />
     <UserReportingModal />
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 45090e5d..613228e9 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -240,7 +240,7 @@ const getNodeInfo = async ({ store }) => {
       store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
       store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
       store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
-      store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') })
+      store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('shout') })
       store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
       store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
       store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
diff --git a/src/boot/routes.js b/src/boot/routes.js
index b5d3c631..1bc1f9f7 100644
--- a/src/boot/routes.js
+++ b/src/boot/routes.js
@@ -16,7 +16,7 @@ import FollowRequests from 'components/follow_requests/follow_requests.vue'
 import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
 import Notifications from 'components/notifications/notifications.vue'
 import AuthForm from 'components/auth_form/auth_form.js'
-import ChatPanel from 'components/chat_panel/chat_panel.vue'
+import ShoutPanel from 'components/shout_panel/shout_panel.vue'
 import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
 import About from 'components/about/about.vue'
 import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
@@ -64,7 +64,7 @@ export default (store) => {
     { name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
     { name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
     { name: 'login', path: '/login', component: AuthForm },
-    { name: 'chat-panel', path: '/chat-panel', component: ChatPanel, props: () => ({ floating: false }) },
+    { name: 'shout-panel', path: '/shout-panel', component: ShoutPanel, props: () => ({ floating: false }) },
     { name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
     { name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
     { name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
diff --git a/src/components/chat_panel/chat_panel.js b/src/components/chat_panel/chat_panel.js
deleted file mode 100644
index 556694ae..00000000
--- a/src/components/chat_panel/chat_panel.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
-import { library } from '@fortawesome/fontawesome-svg-core'
-import {
-  faBullhorn,
-  faTimes
-} from '@fortawesome/free-solid-svg-icons'
-
-library.add(
-  faBullhorn,
-  faTimes
-)
-
-const chatPanel = {
-  props: [ 'floating' ],
-  data () {
-    return {
-      currentMessage: '',
-      channel: null,
-      collapsed: true
-    }
-  },
-  computed: {
-    messages () {
-      return this.$store.state.chat.messages
-    }
-  },
-  methods: {
-    submit (message) {
-      this.$store.state.chat.channel.push('new_msg', { text: message }, 10000)
-      this.currentMessage = ''
-    },
-    togglePanel () {
-      this.collapsed = !this.collapsed
-    },
-    userProfileLink (user) {
-      return generateProfileLink(user.id, user.username, this.$store.state.instance.restrictedNicknames)
-    }
-  },
-  watch: {
-    messages (newVal) {
-      const scrollEl = this.$el.querySelector('.chat-window')
-      if (!scrollEl) return
-      if (scrollEl.scrollTop + scrollEl.offsetHeight + 20 > scrollEl.scrollHeight) {
-        this.$nextTick(() => {
-          if (!scrollEl) return
-          scrollEl.scrollTop = scrollEl.scrollHeight - scrollEl.offsetHeight
-        })
-      }
-    }
-  }
-}
-
-export default chatPanel
diff --git a/src/components/chat_panel/chat_panel.vue b/src/components/chat_panel/chat_panel.vue
deleted file mode 100644
index 8a829115..00000000
--- a/src/components/chat_panel/chat_panel.vue
+++ /dev/null
@@ -1,148 +0,0 @@
-<template>
-  <div
-    v-if="!collapsed || !floating"
-    class="chat-panel"
-  >
-    <div class="panel panel-default">
-      <div
-        class="panel-heading timeline-heading"
-        :class="{ 'chat-heading': floating }"
-        @click.stop.prevent="togglePanel"
-      >
-        <div class="title">
-          {{ $t('shoutbox.title') }}
-          <FAIcon
-            v-if="floating"
-            icon="times"
-            class="close-icon"
-          />
-        </div>
-      </div>
-      <div class="chat-window">
-        <div
-          v-for="message in messages"
-          :key="message.id"
-          class="chat-message"
-        >
-          <span class="chat-avatar">
-            <img :src="message.author.avatar">
-          </span>
-          <div class="chat-content">
-            <router-link
-              class="chat-name"
-              :to="userProfileLink(message.author)"
-            >
-              {{ message.author.username }}
-            </router-link>
-            <br>
-            <span class="chat-text">
-              {{ message.text }}
-            </span>
-          </div>
-        </div>
-      </div>
-      <div class="chat-input">
-        <textarea
-          v-model="currentMessage"
-          class="chat-input-textarea"
-          rows="1"
-          @keyup.enter="submit(currentMessage)"
-        />
-      </div>
-    </div>
-  </div>
-  <div
-    v-else
-    class="chat-panel"
-  >
-    <div class="panel panel-default">
-      <div
-        class="panel-heading stub timeline-heading chat-heading"
-        @click.stop.prevent="togglePanel"
-      >
-        <div class="title">
-          <FAIcon
-            class="icon"
-            icon="bullhorn"
-          />
-          {{ $t('shoutbox.title') }}
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script src="./chat_panel.js"></script>
-
-<style lang="scss">
-@import '../../_variables.scss';
-
-.floating-chat {
-  position: fixed;
-  right: 0px;
-  bottom: 0px;
-  z-index: 1000;
-  max-width: 25em;
-}
-
-.chat-panel {
-  .chat-heading {
-    cursor: pointer;
-
-    .icon {
-      color: $fallback--text;
-      color: var(--text, $fallback--text);
-      margin-right: 0.5em;
-    }
-
-    .title {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-    }
-  }
-
-  .chat-window {
-    overflow-y: auto;
-    overflow-x: hidden;
-    max-height: 20em;
-  }
-
-  .chat-window-container {
-    height: 100%;
-  }
-
-  .chat-message {
-    display: flex;
-    padding: 0.2em 0.5em
-  }
-
-  .chat-avatar {
-    img {
-      height: 24px;
-      width: 24px;
-      border-radius: $fallback--avatarRadius;
-      border-radius: var(--avatarRadius, $fallback--avatarRadius);
-      margin-right: 0.5em;
-      margin-top: 0.25em;
-    }
-  }
-
-  .chat-input {
-    display: flex;
-    textarea {
-      flex: 1;
-      margin: 0.6em;
-      min-height: 3.5em;
-      resize: none;
-    }
-  }
-
-  .chat-panel {
-    .title {
-      display: flex;
-      justify-content: space-between;
-    }
-  }
-}
-</style>
diff --git a/src/components/features_panel/features_panel.js b/src/components/features_panel/features_panel.js
index 8b142d08..d177efeb 100644
--- a/src/components/features_panel/features_panel.js
+++ b/src/components/features_panel/features_panel.js
@@ -2,7 +2,7 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for
 
 const FeaturesPanel = {
   computed: {
-    chat: function () { return this.$store.state.instance.chatAvailable },
+    shout: function () { return this.$store.state.instance.shoutAvailable },
     pleromaChatMessages: function () { return this.$store.state.instance.pleromaChatMessagesAvailable },
     gopher: function () { return this.$store.state.instance.gopherAvailable },
     whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
diff --git a/src/components/features_panel/features_panel.vue b/src/components/features_panel/features_panel.vue
index 9605d09d..a58a99af 100644
--- a/src/components/features_panel/features_panel.vue
+++ b/src/components/features_panel/features_panel.vue
@@ -8,8 +8,8 @@
       </div>
       <div class="panel-body features-panel">
         <ul>
-          <li v-if="chat">
-            {{ $t('features_panel.chat') }}
+          <li v-if="shout">
+            {{ $t('features_panel.shout') }}
           </li>
           <li v-if="pleromaChatMessages">
             {{ $t('features_panel.pleroma_chat_messages') }}
diff --git a/src/components/shout_panel/shout_panel.js b/src/components/shout_panel/shout_panel.js
new file mode 100644
index 00000000..a6168971
--- /dev/null
+++ b/src/components/shout_panel/shout_panel.js
@@ -0,0 +1,53 @@
+import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+  faBullhorn,
+  faTimes
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+  faBullhorn,
+  faTimes
+)
+
+const shoutPanel = {
+  props: [ 'floating' ],
+  data () {
+    return {
+      currentMessage: '',
+      channel: null,
+      collapsed: true
+    }
+  },
+  computed: {
+    messages () {
+      return this.$store.state.shout.messages
+    }
+  },
+  methods: {
+    submit (message) {
+      this.$store.state.shout.channel.push('new_msg', { text: message }, 10000)
+      this.currentMessage = ''
+    },
+    togglePanel () {
+      this.collapsed = !this.collapsed
+    },
+    userProfileLink (user) {
+      return generateProfileLink(user.id, user.username, this.$store.state.instance.restrictedNicknames)
+    }
+  },
+  watch: {
+    messages (newVal) {
+      const scrollEl = this.$el.querySelector('.chat-window')
+      if (!scrollEl) return
+      if (scrollEl.scrollTop + scrollEl.offsetHeight + 20 > scrollEl.scrollHeight) {
+        this.$nextTick(() => {
+          if (!scrollEl) return
+          scrollEl.scrollTop = scrollEl.scrollHeight - scrollEl.offsetHeight
+        })
+      }
+    }
+  }
+}
+
+export default shoutPanel
diff --git a/src/components/shout_panel/shout_panel.vue b/src/components/shout_panel/shout_panel.vue
new file mode 100644
index 00000000..f90baf80
--- /dev/null
+++ b/src/components/shout_panel/shout_panel.vue
@@ -0,0 +1,148 @@
+<template>
+  <div
+    v-if="!collapsed || !floating"
+    class="shout-panel"
+  >
+    <div class="panel panel-default">
+      <div
+        class="panel-heading timeline-heading"
+        :class="{ 'shout-heading': floating }"
+        @click.stop.prevent="togglePanel"
+      >
+        <div class="title">
+          {{ $t('shoutbox.title') }}
+          <FAIcon
+            v-if="floating"
+            icon="times"
+            class="close-icon"
+          />
+        </div>
+      </div>
+      <div class="shout-window">
+        <div
+          v-for="message in messages"
+          :key="message.id"
+          class="shout-message"
+        >
+          <span class="shout-avatar">
+            <img :src="message.author.avatar">
+          </span>
+          <div class="shout-content">
+            <router-link
+              class="shout-name"
+              :to="userProfileLink(message.author)"
+            >
+              {{ message.author.username }}
+            </router-link>
+            <br>
+            <span class="shout-text">
+              {{ message.text }}
+            </span>
+          </div>
+        </div>
+      </div>
+      <div class="shout-input">
+        <textarea
+          v-model="currentMessage"
+          class="shout-input-textarea"
+          rows="1"
+          @keyup.enter="submit(currentMessage)"
+        />
+      </div>
+    </div>
+  </div>
+  <div
+    v-else
+    class="shout-panel"
+  >
+    <div class="panel panel-default">
+      <div
+        class="panel-heading stub timeline-heading shout-heading"
+        @click.stop.prevent="togglePanel"
+      >
+        <div class="title">
+          <FAIcon
+            class="icon"
+            icon="bullhorn"
+          />
+          {{ $t('shoutbox.title') }}
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script src="./shout_panel.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.floating-shout {
+  position: fixed;
+  right: 0px;
+  bottom: 0px;
+  z-index: 1000;
+  max-width: 25em;
+}
+
+.shout-panel {
+  .shout-heading {
+    cursor: pointer;
+
+    .icon {
+      color: $fallback--text;
+      color: var(--text, $fallback--text);
+      margin-right: 0.5em;
+    }
+
+    .title {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+    }
+  }
+
+  .shout-window {
+    overflow-y: auto;
+    overflow-x: hidden;
+    max-height: 20em;
+  }
+
+  .shout-window-container {
+    height: 100%;
+  }
+
+  .shout-message {
+    display: flex;
+    padding: 0.2em 0.5em
+  }
+
+  .shout-avatar {
+    img {
+      height: 24px;
+      width: 24px;
+      border-radius: $fallback--avatarRadius;
+      border-radius: var(--avatarRadius, $fallback--avatarRadius);
+      margin-right: 0.5em;
+      margin-top: 0.25em;
+    }
+  }
+
+  .shout-input {
+    display: flex;
+    textarea {
+      flex: 1;
+      margin: 0.6em;
+      min-height: 3.5em;
+      resize: none;
+    }
+  }
+
+  .shout-panel {
+    .title {
+      display: flex;
+      justify-content: space-between;
+    }
+  }
+}
+</style>
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index fe736168..89719df3 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -49,7 +49,7 @@ const SideDrawer = {
     currentUser () {
       return this.$store.state.users.currentUser
     },
-    chat () { return this.$store.state.chat.channel.state === 'joined' },
+    shout () { return this.$store.state.shout.channel.state === 'joined' },
     unseenNotifications () {
       return unseenNotificationsFromStore(this.$store)
     },
diff --git a/src/main.js b/src/main.js
index e1cac748..3895da89 100644
--- a/src/main.js
+++ b/src/main.js
@@ -11,7 +11,7 @@ import statusesModule from './modules/statuses.js'
 import usersModule from './modules/users.js'
 import apiModule from './modules/api.js'
 import configModule from './modules/config.js'
-import chatModule from './modules/chat.js'
+import shoutModule from './modules/shout.js'
 import oauthModule from './modules/oauth.js'
 import authFlowModule from './modules/auth_flow.js'
 import mediaViewerModule from './modules/media_viewer.js'
@@ -88,7 +88,7 @@ const persistedStateOptions = {
       users: usersModule,
       api: apiModule,
       config: configModule,
-      chat: chatModule,
+      shout: shoutModule,
       oauth: oauthModule,
       authFlow: authFlowModule,
       mediaViewer: mediaViewerModule,
diff --git a/src/modules/api.js b/src/modules/api.js
index 8654b90a..54f94356 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -255,12 +255,12 @@ const api = {
     initializeSocket ({ dispatch, commit, state, rootState }) {
       // Set up websocket connection
       const token = state.wsToken
-      if (rootState.instance.chatAvailable && typeof token !== 'undefined' && state.socket === null) {
+      if (rootState.instance.shoutAvailable && typeof token !== 'undefined' && state.socket === null) {
         const socket = new Socket('/socket', { params: { token } })
         socket.connect()
 
         commit('setSocket', socket)
-        dispatch('initializeChat', socket)
+        dispatch('initializeShout', socket)
       }
     },
     disconnectFromSocket ({ commit, state }) {
diff --git a/src/modules/chat.js b/src/modules/chat.js
deleted file mode 100644
index ffeb272b..00000000
--- a/src/modules/chat.js
+++ /dev/null
@@ -1,34 +0,0 @@
-const chat = {
-  state: {
-    messages: [],
-    channel: { state: '' }
-  },
-  mutations: {
-    setChannel (state, channel) {
-      state.channel = channel
-    },
-    addMessage (state, message) {
-      state.messages.push(message)
-      state.messages = state.messages.slice(-19, 20)
-    },
-    setMessages (state, messages) {
-      state.messages = messages.slice(-19, 20)
-    }
-  },
-  actions: {
-    initializeChat (store, socket) {
-      const channel = socket.channel('chat:public')
-
-      channel.on('new_msg', (msg) => {
-        store.commit('addMessage', msg)
-      })
-      channel.on('messages', ({ messages }) => {
-        store.commit('setMessages', messages)
-      })
-      channel.join()
-      store.commit('setChannel', channel)
-    }
-  }
-}
-
-export default chat
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 96de73ca..0dff256a 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -19,7 +19,7 @@ const defaultState = {
   defaultBanner: '/images/banner.png',
   background: '/static/aurora_borealis.jpg',
   collapseMessageWithSubject: false,
-  disableChat: false,
+  disableShout: false,
   greentext: false,
   hideFilteredStatuses: false,
   hideMutedPosts: false,
@@ -57,7 +57,7 @@ const defaultState = {
   knownDomains: [],
 
   // Feature-set, apparently, not everything here is reported...
-  chatAvailable: false,
+  shoutAvailable: false,
   pleromaChatMessagesAvailable: false,
   gopherAvailable: false,
   mediaProxyAvailable: false,
@@ -107,7 +107,7 @@ const instance = {
         case 'name':
           dispatch('setPageTitle')
           break
-        case 'chatAvailable':
+        case 'shoutAvailable':
           if (value) {
             dispatch('initializeSocket')
           }
diff --git a/src/modules/shout.js b/src/modules/shout.js
new file mode 100644
index 00000000..c04f08e9
--- /dev/null
+++ b/src/modules/shout.js
@@ -0,0 +1,33 @@
+const shout = {
+  state: {
+    messages: [],
+    channel: { state: '' }
+  },
+  mutations: {
+    setChannel (state, channel) {
+      state.channel = channel
+    },
+    addMessage (state, message) {
+      state.messages.push(message)
+      state.messages = state.messages.slice(-19, 20)
+    },
+    setMessages (state, messages) {
+      state.messages = messages.slice(-19, 20)
+    }
+  },
+  actions: {
+    initializeShout (store, socket) {
+      const channel = socket.channel('shout:public')
+      channel.on('new_msg', (msg) => {
+        store.commit('addMessage', msg)
+      })
+      channel.on('messages', ({ messages }) => {
+        store.commit('setMessages', messages)
+      })
+      channel.join()
+      store.commit('setChannel', channel)
+    }
+  }
+}
+
+export default shout
diff --git a/src/modules/users.js b/src/modules/users.js
index 8a764a16..2b416f94 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -531,7 +531,7 @@ const users = {
               if (user.token) {
                 store.dispatch('setWsToken', user.token)
 
-                // Initialize the chat socket.
+                // Initialize the shout socket.
                 store.dispatch('initializeSocket')
               }
 
diff --git a/static/config.json b/static/config.json
index f59e645a..0574b50e 100644
--- a/static/config.json
+++ b/static/config.json
@@ -2,7 +2,7 @@
   "alwaysShowSubjectInput": true,
   "background": "/static/aurora_borealis.jpg",
   "collapseMessageWithSubject": false,
-  "disableChat": false,
+  "disableShout": false,
   "greentext": false,
   "hideFilteredStatuses": false,
   "hideMutedPosts": false,
-- 
cgit v1.2.3-70-g09d2

