From ec2937ec1f3b0ae153f79604eb35b57ffe0f9af2 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Nov 2023 17:29:25 +0200
Subject: add options for marking single notification as read

---
 src/components/notifications/notifications.vue | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

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

diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 999f8e9c..27ae23cf 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -67,9 +67,13 @@
             role="listitem"
             class="notification"
             :class="{unseen: !minimalMode && !notification.seen}"
+            @click="e => notificationClicked(notification)"
           >
             <div class="notification-overlay" />
-            <notification :notification="notification" />
+            <notification
+              :notification="notification"
+              @interacted="e => notificationInteracted(notification)"
+            />
           </div>
         </div>
         <div class="panel-footer">
-- 
cgit v1.2.3-70-g09d2


From e3ee3eaccae6a9ece674d7d76db8e67da499d6c3 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Sun, 19 Nov 2023 15:24:34 +0200
Subject: added some settings for notifications

---
 src/components/mobile_nav/mobile_nav.js            |  9 ++++++--
 src/components/mobile_nav/mobile_nav.vue           | 11 ++++++++++
 src/components/notifications/notifications.js      | 25 +++++++++++++++++++---
 src/components/notifications/notifications.vue     |  2 +-
 .../settings_modal/tabs/notifications_tab.vue      | 23 ++++++++++++++++++++
 src/i18n/en.json                                   |  5 +++++
 src/modules/config.js                              |  5 ++++-
 src/modules/instance.js                            |  3 +++
 8 files changed, 76 insertions(+), 7 deletions(-)

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

diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index b5325116..6f4e35e5 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -14,7 +14,8 @@ import {
   faBell,
   faBars,
   faArrowUp,
-  faMinus
+  faMinus,
+  faCheckDouble
 } from '@fortawesome/free-solid-svg-icons'
 
 library.add(
@@ -22,7 +23,8 @@ library.add(
   faBell,
   faBars,
   faArrowUp,
-  faMinus
+  faMinus,
+  faCheckDouble
 )
 
 const MobileNav = {
@@ -67,6 +69,9 @@ const MobileNav = {
     shouldConfirmLogout () {
       return this.$store.getters.mergedConfig.modalOnLogout
     },
+    closingDrawerMarksAsSeen () {
+      return this.$store.getters.mergedConfig.closingDrawerMarksAsSeen
+    },
     ...mapGetters(['unreadChatCount'])
   },
   methods: {
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index c2746abe..ecd8290a 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -66,6 +66,17 @@
             />
           </FALayers>
         </button>
+        <button
+          v-if="!closingDrawerMarksAsSeen"
+          class="button-unstyled mobile-nav-button"
+          :title="$t('nav.mobile_notifications_close')"
+          @click.stop.prevent="markNotificationsAsSeen()"
+        >
+          <FAIcon
+            class="fa-scale-110 fa-old-padding"
+            icon="check-double"
+          />
+        </button>
         <button
           class="button-unstyled mobile-nav-button"
           :title="$t('nav.mobile_notifications_close')"
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index a210e19d..00d3a511 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -21,6 +21,7 @@ library.add(
 )
 
 const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
+const ACTIONABLE_NOTIFICATION_TYPES = new Set(['mention', 'pleroma:report', 'follow_request'])
 
 const Notifications = {
   components: {
@@ -71,14 +72,26 @@ const Notifications = {
       return unseenNotificationsFromStore(this.$store)
     },
     filteredNotifications () {
-      return filteredNotificationsFromStore(this.$store, this.filterMode)
+      if (this.unseenAtTop) {
+        return [
+          ...filteredNotificationsFromStore(this.$store).filter(n => this.shouldShowUnseen(n)),
+          ...filteredNotificationsFromStore(this.$store).filter(n => !this.shouldShowUnseen(n))
+        ]
+      } else {
+        return filteredNotificationsFromStore(this.$store, this.filterMode)
+      }
     },
     unseenCountBadgeText () {
       return `${this.unseenCount ? this.unseenCount : ''}${this.extraNotificationsCount ? '*' : ''}`
     },
     unseenCount () {
-      return this.unseenNotifications.length
+      if (this.ignoreInactionableSeen) {
+        return this.unseenNotifications.filter(n => ACTIONABLE_NOTIFICATION_TYPES.has(n.type)).length
+      } else {
+        return this.unseenNotifications.length
+      }
     },
+    ignoreInactionableSeen () { return this.$store.getters.mergedConfig.ignoreInactionableSeen },
     extraNotificationsCount () {
       return countExtraNotifications(this.$store)
     },
@@ -108,6 +121,7 @@ const Notifications = {
       return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
     },
     noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
+    unseenAtTop () { return this.$store.getters.mergedConfig.unseenAtTop },
     showExtraNotifications () {
       return !this.noExtra
     },
@@ -154,11 +168,16 @@ const Notifications = {
     scrollToTop () {
       const scrollable = this.scrollerRef
       scrollable.scrollTo({ top: this.$refs.root.offsetTop })
-      // this.$refs.root.scrollIntoView({ behavior: 'smooth', block: 'start' })
     },
     updateScrollPosition () {
       this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop
     },
+    shouldShowUnseen (notification) {
+      if (notification.seen) return false
+
+      const actionable = ACTIONABLE_NOTIFICATION_TYPES.has(notification.type)
+      return this.ignoreInactionableSeen ? actionable : true
+    },
     /* "Interacted" really refers to "actionable" notifications that require user input,
      * everything else (likes/repeats/reacts) cannot be acted and therefore we just clear
      * the "seen" status upon any clicks on them
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 27ae23cf..a0025182 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -66,7 +66,7 @@
             :key="notification.id"
             role="listitem"
             class="notification"
-            :class="{unseen: !minimalMode && !notification.seen}"
+            :class="{unseen: !minimalMode && shouldShowUnseen(notification)}"
             @click="e => notificationClicked(notification)"
           >
             <div class="notification-overlay" />
diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue
index 4dfba444..fcee85a0 100644
--- a/src/components/settings_modal/tabs/notifications_tab.vue
+++ b/src/components/settings_modal/tabs/notifications_tab.vue
@@ -1,5 +1,28 @@
 <template>
   <div :label="$t('settings.notifications')">
+    <div class="setting-item">
+      <h2>{{ $t('settings.notification_setting_annoyance') }}</h2>
+      <ul class="setting-list">
+        <li>
+          <BooleanSetting path="closingDrawerMarksAsSeen">
+            {{ $t('settings.notification_setting_drawer_marks_as_seen') }}
+          </BooleanSetting>
+        </li>
+        <li>
+          <BooleanSetting path="ignoreInactionableSeen">
+            {{ $t('settings.notification_setting_ignore_inactionable_seen') }}
+          </BooleanSetting>
+          <p>
+            {{ $t('settings.notification_setting_ignore_inactionable_seen_tip') }}
+          </p>
+        </li>
+        <li>
+          <BooleanSetting path="unseenAtTop">
+            {{ $t('settings.notification_setting_unseen_at_top') }}
+          </BooleanSetting>
+        </li>
+      </ul>
+    </div>
     <div class="setting-item">
       <h2>{{ $t('settings.notification_setting_filters') }}</h2>
       <ul class="setting-list">
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 034022cb..b31793cb 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -688,6 +688,11 @@
     "greentext": "Meme arrows",
     "show_yous": "Show (You)s",
     "notifications": "Notifications",
+    "notification_setting_annoyance": "Annoyance",
+    "notification_setting_drawer_marks_as_seen": "Closing drawer (mobile) marks all notifications as read",
+    "notification_setting_ignore_inactionable_seen": "Ignore read state of inactionable notifications (likes, repeats etc)",
+    "notification_setting_ignore_inactionable_seen_tip": "This will not actually mark those notifications as read, and you'll still get desktop notifications about them if you chose so",
+    "notification_setting_unseen_at_top": "Show unread notifications above others",
     "notification_setting_filters": "Filters",
     "notification_setting_block_from_strangers": "Block notifications from users who you do not follow",
     "notification_setting_privacy": "Privacy",
diff --git a/src/modules/config.js b/src/modules/config.js
index 05c15998..a3f7bcaf 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -127,7 +127,10 @@ export const defaultState = {
   showAnnouncementsInExtraNotifications: undefined, // instance default
   showFollowRequestsInExtraNotifications: undefined, // instance default
   maxDepthInThread: undefined, // instance default
-  autocompleteSelect: undefined // instance default
+  autocompleteSelect: undefined, // instance default
+  closingDrawerMarksAsSeen: undefined, // instance default
+  unseenAtTop: undefined, // instance default
+  ignoreInactionableSeen: undefined // instance default
 }
 
 // caching the instance default properties
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 034348ff..3022a41a 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -110,6 +110,9 @@ const defaultState = {
   showFollowRequestsInExtraNotifications: true,
   maxDepthInThread: 6,
   autocompleteSelect: false,
+  closingDrawerMarksAsSeen: true,
+  unseenAtTop: false,
+  ignoreInactionableSeen: false,
 
   // Nasty stuff
   customEmoji: [],
-- 
cgit v1.2.3-70-g09d2

