From 6df99133548fb209bf365b77665931be477f0a30 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 11 Aug 2022 14:30:58 +0300
Subject: ability to pin items in navigation menu, initial draft version

---
 .../timeline_menu/timeline_menu_content.vue        | 69 ++++++----------------
 1 file changed, 17 insertions(+), 52 deletions(-)

(limited to 'src/components/timeline_menu/timeline_menu_content.vue')

diff --git a/src/components/timeline_menu/timeline_menu_content.vue b/src/components/timeline_menu/timeline_menu_content.vue
index 59e9e43c..7dc76b62 100644
--- a/src/components/timeline_menu/timeline_menu_content.vue
+++ b/src/components/timeline_menu/timeline_menu_content.vue
@@ -1,63 +1,28 @@
 <template>
   <ul>
-    <li v-if="currentUser">
+    <li v-for="item in list" :key="item.name">
       <router-link
         class="menu-item"
-        :to="{ name: 'friends' }"
+        :to="{ name: (currentUser || item.anon) ? item.route : item.anonRoute, params: { username: currentUser.screen_name } }"
       >
         <FAIcon
           fixed-width
           class="fa-scale-110 fa-old-padding "
-          icon="home"
-        />{{ $t("nav.home_timeline") }}
-      </router-link>
-    </li>
-    <li v-if="currentUser || !privateMode">
-      <router-link
-        class="menu-item"
-        :to="{ name: 'public-timeline' }"
-      >
-        <FAIcon
-          fixed-width
-          class="fa-scale-110 fa-old-padding "
-          icon="users"
-        />{{ $t("nav.public_tl") }}
-      </router-link>
-    </li>
-    <li v-if="federating && (currentUser || !privateMode)">
-      <router-link
-        class="menu-item"
-        :to="{ name: 'public-external-timeline' }"
-      >
-        <FAIcon
-          fixed-width
-          class="fa-scale-110 fa-old-padding "
-          icon="globe"
-        />{{ $t("nav.twkn") }}
-      </router-link>
-    </li>
-    <li v-if="currentUser">
-      <router-link
-        class="menu-item"
-        :to="{ name: 'bookmarks'}"
-      >
-        <FAIcon
-          fixed-width
-          class="fa-scale-110 fa-old-padding "
-          icon="bookmark"
-        />{{ $t("nav.bookmarks") }}
-      </router-link>
-    </li>
-    <li v-if="currentUser">
-      <router-link
-        class="menu-item"
-        :to="{ name: 'dms', params: { username: currentUser.screen_name } }"
-      >
-        <FAIcon
-          fixed-width
-          class="fa-scale-110 fa-old-padding "
-          icon="envelope"
-        />{{ $t("nav.dms") }}
+          :icon="item.icon"
+        />{{ $t(item.label) }}
+        <button
+          type="button"
+          class="button-unstyled"
+          @click.stop.prevent="togglePin(item.name)"
+          >
+          <FAIcon
+            fixed-width
+            class="fa-scale-110 fa-old-padding "
+            :class="{ 'veryfaint': !isPinned(item.name) }"
+            :transform="!isPinned(item.name) ? 'rotate-45' : ''"
+            icon="thumbtack"
+          />
+        </button>
       </router-link>
     </li>
   </ul>
-- 
cgit v1.2.3-70-g09d2


From 77127e2a588abb5bf329506ff7e006021b086e90 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 11 Aug 2022 21:00:27 +0300
Subject: you can now pin lists

---
 src/App.scss                                       |  15 ++-
 src/components/lists_menu/lists_menu_content.js    |  32 +++---
 src/components/lists_menu/lists_menu_content.vue   |  16 +--
 src/components/nav_panel/nav_panel.js              | 123 ++++++---------------
 src/components/nav_panel/nav_panel.vue             |  56 +++-------
 src/components/timeline/timeline.js                |   1 +
 src/components/timeline/timeline.vue               |   2 +-
 src/components/timeline_menu/timeline_menu.js      |  16 ++-
 src/components/timeline_menu/timeline_menu.vue     |   8 +-
 .../timeline_menu/timeline_menu_content.js         |  52 ---------
 .../timeline_menu/timeline_menu_content.vue        |  31 ------
 11 files changed, 103 insertions(+), 249 deletions(-)
 delete mode 100644 src/components/timeline_menu/timeline_menu_content.js
 delete mode 100644 src/components/timeline_menu/timeline_menu_content.vue

(limited to 'src/components/timeline_menu/timeline_menu_content.vue')

diff --git a/src/App.scss b/src/App.scss
index c75c990a..4c9a8884 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -117,8 +117,15 @@ h4 {
   margin: 0;
 }
 
+.iconLetter {
+  display: inline-block;
+  text-align: center;
+  font-weight: 1000;
+}
+
 i[class*=icon-],
-.svg-inline--fa {
+.svg-inline--fa,
+.iconLetter {
   color: $fallback--icon;
   color: var(--icon, $fallback--icon);
 }
@@ -746,13 +753,15 @@ option {
 }
 
 .fa-scale-110 {
-  &.svg-inline--fa {
+  &.svg-inline--fa,
+  &.iconLetter {
     font-size: 1.1em;
   }
 }
 
 .fa-old-padding {
-  &.svg-inline--fa {
+  &.svg-inline--fa,
+  &.iconLetter {
     padding: 0 0.3em;
   }
 }
diff --git a/src/components/lists_menu/lists_menu_content.js b/src/components/lists_menu/lists_menu_content.js
index 37e7868c..99fea0f0 100644
--- a/src/components/lists_menu/lists_menu_content.js
+++ b/src/components/lists_menu/lists_menu_content.js
@@ -1,28 +1,26 @@
 import { mapState } from 'vuex'
-import { library } from '@fortawesome/fontawesome-svg-core'
-import {
-  faUsers,
-  faGlobe,
-  faBookmark,
-  faEnvelope,
-  faHome
-} from '@fortawesome/free-solid-svg-icons'
+import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
 
-library.add(
-  faUsers,
-  faGlobe,
-  faBookmark,
-  faEnvelope,
-  faHome
-)
+export const getListEntries = state => state.lists.allLists.map(list => ({
+  name: 'list-' + list.id,
+  routeObject: { name: 'lists-timeline', params: { id: list.id } },
+  labelRaw: list.title,
+  iconLetter: list.title[0]
+}))
 
-const ListsMenuContent = {
+export const ListsMenuContent = {
+  props: [
+    'showPin'
+  ],
   created () {
     this.$store.dispatch('startFetchingLists')
   },
+  components: {
+    NavigationEntry
+  },
   computed: {
     ...mapState({
-      lists: state => state.lists.allLists,
+      lists: getListEntries,
       currentUser: state => state.users.currentUser,
       privateMode: state => state.instance.private,
       federating: state => state.instance.federating
diff --git a/src/components/lists_menu/lists_menu_content.vue b/src/components/lists_menu/lists_menu_content.vue
index e910d6eb..9c58b092 100644
--- a/src/components/lists_menu/lists_menu_content.vue
+++ b/src/components/lists_menu/lists_menu_content.vue
@@ -1,17 +1,7 @@
 <template>
-  <ul>
-    <li
-      v-for="list in lists.slice().reverse()"
-      :key="list.id"
-    >
-      <router-link
-        class="menu-item"
-        :to="{ name: 'lists-timeline', params: { id: list.id } }"
-      >
-        {{ list.title }}
-      </router-link>
-    </li>
-  </ul>
+<ul>
+  <NavigationEntry v-for="item in lists" :key="item.name" :show-pin="showPin" :item="item" />
+</ul>
 </template>
 
 <script src="./lists_menu_content.js"></script>
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index 7cc3122d..10998113 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -1,6 +1,8 @@
-import TimelineMenuContent from '../timeline_menu/timeline_menu_content.vue'
-import ListsMenuContent from '../lists_menu/lists_menu_content.vue'
+import { getListEntries, ListsMenuContent } from '../lists_menu/lists_menu_content.vue'
 import { mapState, mapGetters } from 'vuex'
+import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js'
+import { filterNavigation } from 'src/components/navigation/filter.js'
+import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
 
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
@@ -30,67 +32,6 @@ library.add(
   faStream,
   faList
 )
-
-export const TIMELINES = {
-  home: {
-    route: 'friends',
-    anonRoute: 'public-timeline',
-    icon: 'home',
-    label: 'nav.home_timeline',
-    criteria: ['!private']
-  },
-  public: {
-    route: 'public-timeline',
-    anon: true,
-    icon: 'users',
-    label: 'nav.public_tl',
-    criteria: ['!private']
-  },
-  twkn: {
-    route: 'public-external-timeline',
-    anon: true,
-    icon: 'globe',
-    label: 'nav.twkn',
-    criteria: ['!private', 'federating']
-  },
-  bookmarks: {
-    route: 'bookmarks',
-    icon: 'bookmark',
-    label: 'nav.bookmarks'
-  },
-  dms: {
-    route: 'dms',
-    icon: 'envelope',
-    label: 'nav.dms'
-  }
-}
-export const ROOT_ITEMS = {
-  interactions: {
-    route: 'interactions',
-    icon: 'bell',
-    label: 'nav.interactions'
-  },
-  chats: {
-    route: 'chats',
-    icon: 'comments',
-    label: 'nav.chats',
-    badgeGetter: 'unreadChatCount'
-  },
-  friendRequests: {
-    route: 'friend-requests',
-    icon: 'user-plus',
-    label: 'nav.friend_requests',
-    criteria: ['lockedUser'],
-    badgeGetter: 'followRequestCount'
-  },
-  about: {
-    route: 'about',
-    anon: true,
-    icon: 'info-circle',
-    label: 'nav.about'
-  }
-}
-
 const NavPanel = {
   created () {
     if (this.currentUser && this.currentUser.locked) {
@@ -98,8 +39,8 @@ const NavPanel = {
     }
   },
   components: {
-    TimelineMenuContent,
-    ListsMenuContent
+    ListsMenuContent,
+    NavigationEntry
   },
   data () {
     return {
@@ -134,6 +75,7 @@ const NavPanel = {
   },
   computed: {
     ...mapState({
+      lists: getListEntries,
       currentUser: state => state.users.currentUser,
       followRequestCount: state => state.api.followRequests.length,
       privateMode: state => state.instance.private,
@@ -143,31 +85,36 @@ const NavPanel = {
       collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav
     }),
     rootItems () {
-      return Object
-        .entries({ ...ROOT_ITEMS })
-        .map(([k, v]) => ({ ...v, name: k }))
-        .filter(({ criteria, anon, anonRoute }) => {
-          const set = new Set(criteria || [])
-          if (!this.federating && set.has('federating')) return false
-          if (this.private && set.has('!private')) return false
-          if (!this.currentUser && !(anon || anonRoute)) return false
-          if ((!this.currentUser || !this.currentUser.locked) && set.has('lockedUser')) return false
-          return true
-        })
+      return filterNavigation(
+        Object
+          .entries({ ...ROOT_ITEMS })
+          .map(([k, v]) => ({ ...v, name: k })),
+        {
+          isFederating: this.federating,
+          isPrivate: this.private,
+          currentUser: this.currentUser
+        }
+      )
     },
     pinnedList () {
-      return Object
-        .entries({ ...TIMELINES, ...ROOT_ITEMS })
-        .filter(([k]) => this.pinnedItems.has(k))
-        .map(([k, v]) => ({ ...v, name: k }))
-        .filter(({ criteria, anon, anonRoute }) => {
-          const set = new Set(criteria || [])
-          if (!this.federating && set.has('federating')) return false
-          if (this.private && set.has('!private')) return false
-          if (!this.currentUser && !(anon || anonRoute)) return false
-          if (this.currentUser && !this.currentUser.locked && set.has('locked')) return false
-          return true
-        })
+      return filterNavigation(
+        [
+          ...Object
+            .entries({
+              ...TIMELINES,
+              ...ROOT_ITEMS
+            })
+            .filter(([k]) => this.pinnedItems.has(k))
+            .map(([k, v]) => ({ ...v, name: k })),
+          ...this.lists.filter((k) => this.pinnedItems.has(k.name))
+
+        ],
+        {
+          isFederating: this.federating,
+          isPrivate: this.private,
+          currentUser: this.currentUser
+        }
+      )
     },
     ...mapGetters(['unreadChatCount'])
   }
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 99a4571e..767ba6ec 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -5,13 +5,18 @@
         <span>
           <span v-for="item in pinnedList" :key="item.name" class="pinned-item">
             <router-link
-              :to="{ name: (currentUser || item.anon) ? item.route : item.anonRoute, params: { username: currentUser.screen_name } }"
+              :to="item.routeObject || { name: (currentUser || item.anon) ? item.route : item.anonRoute, params: { username: currentUser.screen_name } }"
             >
               <FAIcon
+                v-if="item.icon"
                 fixed-width
-                class="fa-scale-110 fa-old-padding "
+                class="fa-scale-110 fa-old-padding"
                 :icon="item.icon"
               />
+              <span
+                v-if="item.iconLetter"
+                class="iconLetter fa-scale-110 fa-old-padding"
+              >{{ item.iconLetter }}</span>
             </router-link>
           </span>
         </span>
@@ -48,7 +53,9 @@
             v-show="showTimelines"
             class="timelines-background"
           >
-            <TimelineMenuContent class="timelines" :content="timelinesList" />
+            <ul class="timelines">
+              <NavigationEntry v-for="item in timelinesList" :key="item.name" :show-pin="true" :item="item" />
+            </ul>
           </div>
         </li>
         <li v-if="currentUser">
@@ -81,34 +88,10 @@
             v-show="showLists"
             class="timelines-background"
           >
-            <ListsMenuContent class="timelines" />
+            <ListsMenuContent :showPin="true" class="timelines" />
           </div>
         </li>
-        <li v-for="item in rootItems" :key="item.name">
-          <router-link
-            class="menu-item"
-            :to="{ name: (currentUser || item.anon) ? item.route : item.anonRoute, params: { username: currentUser.screen_name } }"
-          >
-            <FAIcon
-              fixed-width
-              class="fa-scale-110 fa-old-padding "
-              :icon="item.icon"
-            />{{ $t(item.label) }}
-            <button
-              type="button"
-              class="button-unstyled"
-              @click.stop.prevent="togglePin(item.name)"
-              >
-              <FAIcon
-                fixed-width
-                class="fa-scale-110 fa-old-padding "
-                :class="{ 'veryfaint': !isPinned(item.name) }"
-                :transform="!isPinned(item.name) ? 'rotate-45' : ''"
-                icon="thumbtack"
-              />
-            </button>
-          </router-link>
-        </li>
+        <NavigationEntry v-for="item in rootItems" :key="item.name" :show-pin="true" :item="item" />
       </ul>
     </div>
   </div>
@@ -220,16 +203,13 @@
     margin-right: 0.8em;
   }
 
-  .badge {
-    position: absolute;
-    right: 0.6rem;
-    top: 1.25em;
-  }
-
   .pinned-item {
-    .router-link-exact-active .svg-inline--fa {
-      color: $fallback--text;
-      color: var(--selectedMenuText, $fallback--text);
+    .router-link-active {
+      & .svg-inline--fa,
+      & .iconLetter {
+        color: $fallback--text;
+        color: var(--selectedMenuText, $fallback--text);
+      }
     }
   }
 }
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 8f6cae66..ee590c4b 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -28,6 +28,7 @@ const Timeline = {
     'footerSlipgate' // reference to an element where we should put our footer
   ],
   data () {
+    console.log(this.timelineName)
     return {
       paused: false,
       unfocused: false,
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index 627cafbb..fad3ff13 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -1,7 +1,7 @@
 <template>
   <div :class="['Timeline', classes.root]">
     <div :class="classes.header">
-      <TimelineMenu v-if="!embedded" />
+      <TimelineMenu v-if="!embedded" :timeline-name="timelineName"/>
       <button
         v-if="showLoadButton"
         class="button-default loadmore-button"
diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js
index 5a67b0a5..d74fbf4e 100644
--- a/src/components/timeline_menu/timeline_menu.js
+++ b/src/components/timeline_menu/timeline_menu.js
@@ -1,6 +1,8 @@
 import Popover from '../popover/popover.vue'
-import TimelineMenuContent from './timeline_menu_content.vue'
+import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
+import { ListsMenuContent } from '../lists_menu/lists_menu_content.vue'
 import { library } from '@fortawesome/fontawesome-svg-core'
+import { TIMELINES } from 'src/components/navigation/navigation.js'
 import {
   faChevronDown
 } from '@fortawesome/free-solid-svg-icons'
@@ -22,11 +24,13 @@ export const timelineNames = () => {
 const TimelineMenu = {
   components: {
     Popover,
-    TimelineMenuContent
+    NavigationEntry,
+    ListsMenuContent
   },
   data () {
     return {
-      isOpen: false
+      isOpen: false,
+      timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ ...v, name: k }))
     }
   },
   created () {
@@ -34,6 +38,12 @@ const TimelineMenu = {
       this.$store.dispatch('setLastTimeline', this.$route.name)
     }
   },
+  computed: {
+    useListsMenu () {
+      const route = this.$route.name
+      return route === 'lists-timeline'
+    }
+  },
   methods: {
     openMenu () {
       // $nextTick is too fast, animation won't play back but
diff --git a/src/components/timeline_menu/timeline_menu.vue b/src/components/timeline_menu/timeline_menu.vue
index 5ef96328..9acaff46 100644
--- a/src/components/timeline_menu/timeline_menu.vue
+++ b/src/components/timeline_menu/timeline_menu.vue
@@ -10,7 +10,10 @@
     @close="() => isOpen = false"
   >
     <template #content>
-      <TimelineMenuContent :content="timelinesList" />
+    <ListsMenuContent v-if="useListsMenu" :showPin="false" class="timelines" />
+    <ul v-else>
+      <NavigationEntry v-for="item in timelinesList" :key="item.name" :show-pin="false" :item="item" />
+    </ul>
     </template>
     <template #trigger>
       <span class="button-unstyled title timeline-menu-title">
@@ -138,8 +141,7 @@
       background-color: $fallback--lightBg;
       background-color: var(--selectedMenu, $fallback--lightBg);
       color: $fallback--text;
-      color: var(--selectedMenuText, $fallback--text);
-      --faint: var(--selectedMenuFaintText, $fallback--faint);
+      color: var(--selectedMenuText, $fallback--text); --faint: var(--selectedMenuFaintText, $fallback--faint);
       --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
       --lightText: var(--selectedMenuLightText, $fallback--lightText);
       --icon: var(--selectedMenuIcon, $fallback--icon);
diff --git a/src/components/timeline_menu/timeline_menu_content.js b/src/components/timeline_menu/timeline_menu_content.js
deleted file mode 100644
index c5b1cff2..00000000
--- a/src/components/timeline_menu/timeline_menu_content.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import { mapState } from 'vuex'
-import { library } from '@fortawesome/fontawesome-svg-core'
-import {
-  faUsers,
-  faGlobe,
-  faBookmark,
-  faEnvelope,
-  faHome
-} from '@fortawesome/free-solid-svg-icons'
-
-library.add(
-  faUsers,
-  faGlobe,
-  faBookmark,
-  faEnvelope,
-  faHome
-)
-
-const TimelineMenuContent = {
-  props: ['content'],
-  methods: {
-    isPinned (item) {
-      return this.pinnedItems.has(item)
-    },
-    togglePin (item) {
-      if (this.isPinned(item)) {
-        this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value: item })
-      } else {
-        this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value: item })
-      }
-    }
-  },
-  computed: {
-    ...mapState({
-      currentUser: state => state.users.currentUser,
-      privateMode: state => state.instance.private,
-      federating: state => state.instance.federating,
-      pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
-    }),
-    list () {
-      return (this.content || []).filter(({ criteria, anon, anonRoute }) => {
-        const set = new Set(criteria || [])
-        if (!this.federating && set.has('federating')) return false
-        if (this.private && set.has('!private')) return false
-        if (!this.currentUser && !(anon || anonRoute)) return false
-        return true
-      })
-    }
-  }
-}
-
-export default TimelineMenuContent
diff --git a/src/components/timeline_menu/timeline_menu_content.vue b/src/components/timeline_menu/timeline_menu_content.vue
deleted file mode 100644
index 7dc76b62..00000000
--- a/src/components/timeline_menu/timeline_menu_content.vue
+++ /dev/null
@@ -1,31 +0,0 @@
-<template>
-  <ul>
-    <li v-for="item in list" :key="item.name">
-      <router-link
-        class="menu-item"
-        :to="{ name: (currentUser || item.anon) ? item.route : item.anonRoute, params: { username: currentUser.screen_name } }"
-      >
-        <FAIcon
-          fixed-width
-          class="fa-scale-110 fa-old-padding "
-          :icon="item.icon"
-        />{{ $t(item.label) }}
-        <button
-          type="button"
-          class="button-unstyled"
-          @click.stop.prevent="togglePin(item.name)"
-          >
-          <FAIcon
-            fixed-width
-            class="fa-scale-110 fa-old-padding "
-            :class="{ 'veryfaint': !isPinned(item.name) }"
-            :transform="!isPinned(item.name) ? 'rotate-45' : ''"
-            icon="thumbtack"
-          />
-        </button>
-      </router-link>
-    </li>
-  </ul>
-</template>
-
-<script src="./timeline_menu_content.js"></script>
-- 
cgit v1.2.3-70-g09d2

