From 171f6f08943dd1d87120df3e4894ddcfd5e1d246 Mon Sep 17 00:00:00 2001
From: Alexander Tumin <iamtakingiteasy@eientei.org>
Date: Sat, 6 Aug 2022 17:26:43 +0300
Subject: Lists implementation

---
 src/components/settings_modal/tabs/general_tab.vue | 47 ++++++++++++++++++++++
 1 file changed, 47 insertions(+)

(limited to 'src/components/settings_modal/tabs/general_tab.vue')

diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index a2609200..91015955 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -124,6 +124,53 @@
             {{ $t('settings.hide_shoutbox') }}
           </BooleanSetting>
         </li>
+        <li>
+          <BooleanSetting path="listsNavigation">
+            {{ $t('settings.lists_navigation') }}
+          </BooleanSetting>
+        </li>
+        <li>
+          <h3>{{ $t('settings.columns') }}</h3>
+        </li>
+        <li>
+          <BooleanSetting path="disableStickyHeaders">
+            {{ $t('settings.disable_sticky_headers') }}
+          </BooleanSetting>
+        </li>
+        <li>
+          <BooleanSetting path="showScrollbars">
+            {{ $t('settings.show_scrollbars') }}
+          </BooleanSetting>
+        </li>
+        <li>
+          <BooleanSetting path="sidebarRight">
+            {{ $t('settings.right_sidebar') }}
+          </BooleanSetting>
+        </li>
+        <li>
+          <ChoiceSetting
+            v-if="user"
+            id="thirdColumnMode"
+            path="thirdColumnMode"
+            :options="thirdColumnModeOptions"
+          >
+            {{ $t('settings.third_column_mode') }}
+          </ChoiceSetting>
+        </li>
+        <li v-if="expertLevel > 0">
+          {{ $t('settings.column_sizes') }}
+          <div class="column-settings">
+            <SizeSetting
+              v-for="column in columns"
+              :key="column"
+              :path="column + 'ColumnWidth'"
+              :units="horizontalUnits"
+              expert="1"
+            >
+              {{ $t('settings.column_sizes_' + column) }}
+            </SizeSetting>
+          </div>
+        </li>
       </ul>
     </div>
     <div class="setting-item">
-- 
cgit v1.2.3-70-g09d2


From 4803fb07c85914fd0c9756a1d399e9803f48b8c7 Mon Sep 17 00:00:00 2001
From: Alexander Tumin <iamtakingiteasy@eientei.org>
Date: Mon, 8 Aug 2022 23:42:22 +0300
Subject: Allow opening profile when clicking an avatar inside of user popover

---
 src/components/settings_modal/tabs/general_tab.js  |  5 +++++
 src/components/settings_modal/tabs/general_tab.vue | 11 +++++++----
 src/components/user_popover/user_popover.js        |  4 ++--
 src/components/user_popover/user_popover.vue       |  2 +-
 src/i18n/en.json                                   |  5 ++++-
 src/modules/config.js                              |  2 +-
 6 files changed, 20 insertions(+), 9 deletions(-)

(limited to 'src/components/settings_modal/tabs/general_tab.vue')

diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index 1e11b9e0..d94a3be9 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -43,6 +43,11 @@ const GeneralTab = {
         value: mode,
         label: this.$t(`settings.third_column_mode_${mode}`)
       })),
+      userPopoverAvatarActionOptions: ['close', 'zoom', 'open'].map(mode => ({
+        key: mode,
+        value: mode,
+        label: this.$t(`settings.user_popover_avatar_action_${mode}`)
+      })),
       loopSilentAvailable:
       // Firefox
       Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index a2609200..7ac61b2e 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -75,12 +75,15 @@
           </BooleanSetting>
         </li>
         <li>
-          <BooleanSetting
-            path="userPopoverZoom"
+          <ChoiceSetting
+            v-if="user"
+            id="userPopoverAvatarAction"
+            path="userPopoverAvatarAction"
+            :options="userPopoverAvatarActionOptions"
             expert="1"
           >
-            {{ $t('settings.user_popover_avatar_zoom') }}
-          </BooleanSetting>
+            {{ $t('settings.user_popover_avatar_action') }}
+          </ChoiceSetting>
         </li>
         <li>
           <BooleanSetting
diff --git a/src/components/user_popover/user_popover.js b/src/components/user_popover/user_popover.js
index 69b25383..3b12aa1e 100644
--- a/src/components/user_popover/user_popover.js
+++ b/src/components/user_popover/user_popover.js
@@ -11,8 +11,8 @@ const UserPopover = {
     Popover: defineAsyncComponent(() => import('../popover/popover.vue'))
   },
   computed: {
-    userPopoverZoom () {
-      return this.$store.getters.mergedConfig.userPopoverZoom
+    userPopoverAvatarAction () {
+      return this.$store.getters.mergedConfig.userPopoverAvatarAction
     },
     userPopoverOverlay () {
       return this.$store.getters.mergedConfig.userPopoverOverlay
diff --git a/src/components/user_popover/user_popover.vue b/src/components/user_popover/user_popover.vue
index 4e999672..53d51fc4 100644
--- a/src/components/user_popover/user_popover.vue
+++ b/src/components/user_popover/user_popover.vue
@@ -14,7 +14,7 @@
         class="user-popover"
         :user-id="userId"
         :hide-bio="true"
-        :avatar-action="userPopoverZoom ? 'zoom' : close"
+        :avatar-action="userPopoverAvatarAction == 'close' ? close : userPopoverAvatarAction"
         :on-close="close"
       />
     </template>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index c54d4750..140e569d 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -556,7 +556,10 @@
     "mention_link_show_avatar_quick": "Show user avatar next to mentions",
     "mention_link_fade_domain": "Fade domains (e.g. {'@'}example.org in {'@'}foo{'@'}example.org)",
     "mention_link_bolden_you": "Highlight mention of you when you are mentioned",
-    "user_popover_avatar_zoom": "Clicking on user avatar in popover zooms it instead of closing the popover",
+    "user_popover_avatar_action": "Popover avatar click action",
+    "user_popover_avatar_action_zoom": "Zoom the avatar",
+    "user_popover_avatar_action_close": "Close the popover",
+    "user_popover_avatar_action_open": "Open profile",
     "user_popover_avatar_overlay": "Show user popover over user avatar",
     "fun": "Fun",
     "greentext": "Meme arrows",
diff --git a/src/modules/config.js b/src/modules/config.js
index eaf67a91..e1e0bdd7 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -81,7 +81,7 @@ export const defaultState = {
   useContainFit: true,
   disableStickyHeaders: false,
   showScrollbars: false,
-  userPopoverZoom: false,
+  userPopoverAvatarAction: 'close',
   userPopoverOverlay: true,
   greentext: undefined, // instance default
   useAtIcon: undefined, // instance default
-- 
cgit v1.2.3-70-g09d2


From a403f93b478eec67d779400b1fcfdc006bb787da Mon Sep 17 00:00:00 2001
From: Alexander Tumin <iamtakingiteasy@eientei.org>
Date: Tue, 9 Aug 2022 10:44:20 +0300
Subject: Allow opening profile: multiChoiceProprties record, anonymous access

---
 src/components/settings_modal/tabs/general_tab.vue | 1 -
 src/modules/config.js                              | 3 ++-
 2 files changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/components/settings_modal/tabs/general_tab.vue')

diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 7ac61b2e..c212a81a 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -76,7 +76,6 @@
         </li>
         <li>
           <ChoiceSetting
-            v-if="user"
             id="userPopoverAvatarAction"
             path="userPopoverAvatarAction"
             :options="userPopoverAvatarActionOptions"
diff --git a/src/modules/config.js b/src/modules/config.js
index e1e0bdd7..eaf48fa1 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -17,7 +17,8 @@ export const multiChoiceProperties = [
   'subjectLineBehavior',
   'conversationDisplay', // tree | linear
   'conversationOtherRepliesButton', // below | inside
-  'mentionLinkDisplay' // short | full_for_remote | full
+  'mentionLinkDisplay', // short | full_for_remote | full
+  'userPopoverAvatarAction' // close | zoom | open
 ]
 
 export const defaultState = {
-- 
cgit v1.2.3-70-g09d2


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

---
 src/App.scss                                       |   3 +
 src/components/nav_panel/nav_panel.js              | 111 +++++++++++++++-
 src/components/nav_panel/nav_panel.vue             | 140 ++++++++++-----------
 src/components/settings_modal/tabs/general_tab.vue |   5 -
 src/components/timeline_menu/timeline_menu.vue     |   2 +-
 .../timeline_menu/timeline_menu_content.js         |  27 +++-
 .../timeline_menu/timeline_menu_content.vue        |  69 +++-------
 src/modules/config.js                              |   1 -
 src/modules/serverSideStorage.js                   |   9 +-
 9 files changed, 221 insertions(+), 146 deletions(-)

(limited to 'src/components/settings_modal/tabs/general_tab.vue')

diff --git a/src/App.scss b/src/App.scss
index ab025d63..c75c990a 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -756,6 +756,9 @@ option {
     padding: 0 0.3em;
   }
 }
+.veryfaint {
+  opacity: 0.25;
+}
 
 .login-hint {
   text-align: center;
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index abeff6bf..758f9af4 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -31,6 +31,66 @@ library.add(
   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) {
@@ -43,8 +103,11 @@ const NavPanel = {
   },
   data () {
     return {
+      collapsed: false,
       showTimelines: false,
-      showLists: false
+      showLists: false,
+      timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ ...v, name: k })),
+      rootList: Object.entries(ROOT_ITEMS).map(([k, v]) => ({ ...v, name: k }))
     }
   },
   methods: {
@@ -53,19 +116,57 @@ const NavPanel = {
     },
     toggleLists () {
       this.showLists = !this.showLists
+    },
+    toggleCollapse () {
+      this.collapsed = !this.collapsed
+    },
+    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: {
-    listsNavigation () {
-      return this.$store.getters.mergedConfig.listsNavigation
-    },
     ...mapState({
       currentUser: state => state.users.currentUser,
       followRequestCount: state => state.api.followRequests.length,
       privateMode: state => state.instance.private,
       federating: state => state.instance.federating,
-      pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
+      pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
+      pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
     }),
+    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
+        })
+    },
+    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
+        })
+    },
     ...mapGetters(['unreadChatCount'])
   }
 }
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 9322e233..99a4571e 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -1,7 +1,33 @@
 <template>
   <div class="NavPanel">
     <div class="panel panel-default">
-      <ul>
+      <div class="panel-heading">
+        <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 } }"
+            >
+              <FAIcon
+                fixed-width
+                class="fa-scale-110 fa-old-padding "
+                :icon="item.icon"
+              />
+            </router-link>
+          </span>
+        </span>
+        <div class="spacer"/>
+        <button
+          class="button-unstyled"
+          @click="toggleCollapse"
+        >
+          <FAIcon
+            class="timelines-chevron"
+            fixed-width
+            :icon="collapsed ? 'chevron-down' : 'chevron-up'"
+          />
+        </button>
+      </div>
+      <ul class="panel-body" v-if="!collapsed">
         <li v-if="currentUser || !privateMode">
           <button
             class="button-unstyled menu-item"
@@ -22,29 +48,34 @@
             v-show="showTimelines"
             class="timelines-background"
           >
-            <TimelineMenuContent class="timelines" />
+            <TimelineMenuContent class="timelines" :content="timelinesList" />
           </div>
         </li>
-        <li v-if="currentUser && listsNavigation">
+        <li v-if="currentUser">
           <button
             class="button-unstyled menu-item"
             @click="toggleLists"
           >
-            <router-link
-              :to="{ name: 'lists' }"
-              @click.stop
-            >
               <FAIcon
                 fixed-width
                 class="fa-scale-110"
                 icon="list"
               />{{ $t("nav.lists") }}
-            </router-link>
             <FAIcon
               class="timelines-chevron"
               fixed-width
               :icon="showLists ? 'chevron-up' : 'chevron-down'"
             />
+            <router-link
+              :to="{ name: 'lists' }"
+              @click.stop
+            >
+              <FAIcon
+                class="timelines-chevron"
+                fixed-width
+                icon="wrench"
+              />
+            </router-link>
           </button>
           <div
             v-show="showLists"
@@ -53,83 +84,31 @@
             <ListsMenuContent class="timelines" />
           </div>
         </li>
-        <li v-if="currentUser && !listsNavigation">
+        <li v-for="item in rootItems" :key="item.name">
           <router-link
-            :to="{ name: 'lists' }"
-            @click.stop
+            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
-              class="button-unstyled menu-item"
-              @click="toggleLists"
-            >
+              type="button"
+              class="button-unstyled"
+              @click.stop.prevent="togglePin(item.name)"
+              >
               <FAIcon
                 fixed-width
-                class="fa-scale-110"
-                icon="list"
-              />{{ $t("nav.lists") }}
+                class="fa-scale-110 fa-old-padding "
+                :class="{ 'veryfaint': !isPinned(item.name) }"
+                :transform="!isPinned(item.name) ? 'rotate-45' : ''"
+                icon="thumbtack"
+              />
             </button>
           </router-link>
         </li>
-        <li v-if="currentUser">
-          <router-link
-            class="menu-item"
-            :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
-          >
-            <FAIcon
-              fixed-width
-              class="fa-scale-110"
-              icon="bell"
-            />{{ $t("nav.interactions") }}
-          </router-link>
-        </li>
-        <li v-if="currentUser && pleromaChatMessagesAvailable">
-          <router-link
-            class="menu-item"
-            :to="{ name: 'chats', params: { username: currentUser.screen_name } }"
-          >
-            <div
-              v-if="unreadChatCount"
-              class="badge badge-notification"
-            >
-              {{ unreadChatCount }}
-            </div>
-            <FAIcon
-              fixed-width
-              class="fa-scale-110"
-              icon="comments"
-            />{{ $t("nav.chats") }}
-          </router-link>
-        </li>
-        <li v-if="currentUser && currentUser.locked">
-          <router-link
-            class="menu-item"
-            :to="{ name: 'friend-requests' }"
-          >
-            <FAIcon
-              fixed-width
-              class="fa-scale-110"
-              icon="user-plus"
-            />{{ $t("nav.friend_requests") }}
-            <span
-              v-if="followRequestCount > 0"
-              class="badge badge-notification"
-            >
-              {{ followRequestCount }}
-            </span>
-          </router-link>
-        </li>
-        <li>
-          <router-link
-            class="menu-item"
-            :to="{ name: 'about' }"
-          >
-            <FAIcon
-              fixed-width
-              class="fa-scale-110"
-              icon="info-circle"
-            />{{ $t("nav.about") }}
-          </router-link>
-        </li>
       </ul>
     </div>
   </div>
@@ -246,5 +225,12 @@
     right: 0.6rem;
     top: 1.25em;
   }
+
+  .pinned-item {
+    .router-link-exact-active .svg-inline--fa {
+      color: $fallback--text;
+      color: var(--selectedMenuText, $fallback--text);
+    }
+  }
 }
 </style>
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 91015955..ae97a744 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -124,11 +124,6 @@
             {{ $t('settings.hide_shoutbox') }}
           </BooleanSetting>
         </li>
-        <li>
-          <BooleanSetting path="listsNavigation">
-            {{ $t('settings.lists_navigation') }}
-          </BooleanSetting>
-        </li>
         <li>
           <h3>{{ $t('settings.columns') }}</h3>
         </li>
diff --git a/src/components/timeline_menu/timeline_menu.vue b/src/components/timeline_menu/timeline_menu.vue
index c24b9d72..5ef96328 100644
--- a/src/components/timeline_menu/timeline_menu.vue
+++ b/src/components/timeline_menu/timeline_menu.vue
@@ -10,7 +10,7 @@
     @close="() => isOpen = false"
   >
     <template #content>
-      <TimelineMenuContent />
+      <TimelineMenuContent :content="timelinesList" />
     </template>
     <template #trigger>
       <span class="button-unstyled title timeline-menu-title">
diff --git a/src/components/timeline_menu/timeline_menu_content.js b/src/components/timeline_menu/timeline_menu_content.js
index 671570dd..c5b1cff2 100644
--- a/src/components/timeline_menu/timeline_menu_content.js
+++ b/src/components/timeline_menu/timeline_menu_content.js
@@ -17,12 +17,35 @@ library.add(
 )
 
 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
-    })
+      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
+      })
+    }
   }
 }
 
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>
diff --git a/src/modules/config.js b/src/modules/config.js
index c34b2c8c..f52ec105 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -87,7 +87,6 @@ export const defaultState = {
   sidebarColumnWidth: '25rem',
   contentColumnWidth: '45rem',
   notifsColumnWidth: '25rem',
-  listsNavigation: false,
   greentext: undefined, // instance default
   useAtIcon: undefined, // instance default
   mentionLinkDisplay: undefined, // instance default
diff --git a/src/modules/serverSideStorage.js b/src/modules/serverSideStorage.js
index bff8e4ba..c4c5ebba 100644
--- a/src/modules/serverSideStorage.js
+++ b/src/modules/serverSideStorage.js
@@ -23,6 +23,9 @@ export const defaultState = {
     _journal: [],
     simple: {
       dontShowUpdateNotifs: false
+    },
+    collections: {
+      pinnedNavItems: ['home', 'dms', 'chats', 'about']
     }
   },
   // raw data
@@ -274,8 +277,8 @@ export const mutations = {
 
     totalFlags = _resetFlags(totalFlags)
 
-    recent.flagStorage = totalFlags
-    recent.prefsStorage = totalPrefs
+    recent.flagStorage = { ...flagsTemplate, ...totalFlags }
+    recent.prefsStorage = { ...defaultState.prefsStorage, ...totalPrefs }
 
     state.dirty = dirty || needsUpload
     state.cache = recent
@@ -320,7 +323,7 @@ export const mutations = {
       return
     }
     const collection = new Set(get(state.prefsStorage, path))
-    collection.remove(value)
+    collection.delete(value)
     set(state.prefsStorage, path, collection)
     state.prefsStorage._journal = [
       ...state.prefsStorage._journal,
-- 
cgit v1.2.3-70-g09d2


From 3e7e31d4a98f4cbdfdd3c92d952e58616c027beb Mon Sep 17 00:00:00 2001
From: Alexander Tumin <iamtakingiteasy@eientei.org>
Date: Sun, 5 Jun 2022 17:10:44 +0300
Subject: Allow column width configuration Group column configuration in
 settings Column width configuration: do not act on defaults

---
 src/App.scss                                       | 19 +++++-
 src/boot/after_store.js                            |  4 +-
 .../settings_modal/helpers/boolean_setting.js      |  3 +
 .../settings_modal/helpers/boolean_setting.vue     |  7 ++-
 .../settings_modal/helpers/choice_setting.js       |  3 +
 .../settings_modal/helpers/choice_setting.vue      |  5 +-
 .../settings_modal/helpers/integer_setting.js      |  3 +
 .../settings_modal/helpers/integer_setting.vue     |  5 +-
 .../settings_modal/helpers/size_setting.js         | 67 ++++++++++++++++++++++
 .../settings_modal/helpers/size_setting.vue        | 54 +++++++++++++++++
 src/components/settings_modal/tabs/general_tab.js  | 16 ++++++
 src/components/settings_modal/tabs/general_tab.vue | 38 +++++-------
 src/i18n/en.json                                   |  5 ++
 src/i18n/ru.json                                   |  9 +++
 src/modules/config.js                              |  9 ++-
 src/services/style_setter/style_setter.js          | 31 ++++++++++
 16 files changed, 244 insertions(+), 34 deletions(-)
 create mode 100644 src/components/settings_modal/helpers/size_setting.js
 create mode 100644 src/components/settings_modal/helpers/size_setting.vue

(limited to 'src/components/settings_modal/tabs/general_tab.vue')

diff --git a/src/App.scss b/src/App.scss
index ab025d63..0aa28933 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -186,9 +186,13 @@ nav {
   --columnGap: 1em;
   --status-margin: 0.75em;
 
+  --effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn)));
+
   position: relative;
   display: grid;
-  grid-template-columns: var(--miniColumn) var(--maxiColumn);
+  grid-template-columns:
+    var(--sidebarColumnWidth, var(--miniColumn))
+    var(--effectiveContentColumnWidth);
   grid-template-areas: "sidebar content";
   grid-template-rows: 1fr;
   box-sizing: border-box;
@@ -282,15 +286,24 @@ nav {
   }
 
   &.-reverse:not(.-wide):not(.-mobile) {
-    grid-template-columns: var(--maxiColumn) var(--miniColumn);
+    grid-template-columns:
+      var(--effectiveContentColumnWidth)
+      minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
     grid-template-areas: "content sidebar";
   }
 
   &.-wide {
-    grid-template-columns: var(--miniColumn) var(--maxiColumn) var(--miniColumn);
+    grid-template-columns:
+      minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)))
+      var(--effectiveContentColumnWidth)
+      minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)));
     grid-template-areas: "sidebar content notifs";
 
     &.-reverse {
+      grid-template-columns:
+        minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)))
+        var(--effectiveContentColumnWidth)
+        minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
       grid-template-areas: "notifs content sidebar";
     }
   }
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 908d905a..38b5f38e 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -12,7 +12,7 @@ import { windowWidth, windowHeight } from '../services/window_utils/window_utils
 import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
 import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
 import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
-import { applyTheme } from '../services/style_setter/style_setter.js'
+import { applyTheme, applyConfig } from '../services/style_setter/style_setter.js'
 import FaviconService from '../services/favicon_service/favicon_service.js'
 
 let staticInitialResults = null
@@ -360,6 +360,8 @@ const afterStoreSetup = async ({ store, i18n }) => {
     console.error('Failed to load any theme!')
   }
 
+  applyConfig(store.state.config)
+
   // Now we can try getting the server settings and logging in
   // Most of these are preloaded into the index.html so blocking is minimized
   await Promise.all([
diff --git a/src/components/settings_modal/helpers/boolean_setting.js b/src/components/settings_modal/helpers/boolean_setting.js
index 353e551c..dc832044 100644
--- a/src/components/settings_modal/helpers/boolean_setting.js
+++ b/src/components/settings_modal/helpers/boolean_setting.js
@@ -42,6 +42,9 @@ export default {
   methods: {
     update (e) {
       set(this.$parent, this.path, e)
+    },
+    reset () {
+      set(this.$parent, this.path, this.defaultState)
     }
   }
 }
diff --git a/src/components/settings_modal/helpers/boolean_setting.vue b/src/components/settings_modal/helpers/boolean_setting.vue
index 69584808..41142966 100644
--- a/src/components/settings_modal/helpers/boolean_setting.vue
+++ b/src/components/settings_modal/helpers/boolean_setting.vue
@@ -15,7 +15,12 @@
         <slot />
       </span>
       {{ ' ' }}
-      <ModifiedIndicator :changed="isChanged" /><ServerSideIndicator :server-side="isServerSide" /> </Checkbox>
+      <ModifiedIndicator
+        :changed="isChanged"
+        :onclick="reset"
+      />
+      <ServerSideIndicator :server-side="isServerSide" />
+    </Checkbox>
   </label>
 </template>
 
diff --git a/src/components/settings_modal/helpers/choice_setting.js b/src/components/settings_modal/helpers/choice_setting.js
index 4677d4c1..3da559fe 100644
--- a/src/components/settings_modal/helpers/choice_setting.js
+++ b/src/components/settings_modal/helpers/choice_setting.js
@@ -43,6 +43,9 @@ export default {
   methods: {
     update (e) {
       set(this.$parent, this.path, e)
+    },
+    reset () {
+      set(this.$parent, this.path, this.defaultState)
     }
   }
 }
diff --git a/src/components/settings_modal/helpers/choice_setting.vue b/src/components/settings_modal/helpers/choice_setting.vue
index 258c7422..d141a0d6 100644
--- a/src/components/settings_modal/helpers/choice_setting.vue
+++ b/src/components/settings_modal/helpers/choice_setting.vue
@@ -19,7 +19,10 @@
         {{ option.value === defaultState ? $t('settings.instance_default_simple') : '' }}
       </option>
     </Select>
-    <ModifiedIndicator :changed="isChanged" />
+    <ModifiedIndicator
+      :changed="isChanged"
+      :onclick="reset"
+    />
     <ServerSideIndicator :server-side="isServerSide" />
   </label>
 </template>
diff --git a/src/components/settings_modal/helpers/integer_setting.js b/src/components/settings_modal/helpers/integer_setting.js
index 17dc0e7b..e64d0cee 100644
--- a/src/components/settings_modal/helpers/integer_setting.js
+++ b/src/components/settings_modal/helpers/integer_setting.js
@@ -36,6 +36,9 @@ export default {
   methods: {
     update (e) {
       set(this.$parent, this.path, parseInt(e.target.value))
+    },
+    reset () {
+      set(this.$parent, this.path, this.defaultState)
     }
   }
 }
diff --git a/src/components/settings_modal/helpers/integer_setting.vue b/src/components/settings_modal/helpers/integer_setting.vue
index e661a025..695e2673 100644
--- a/src/components/settings_modal/helpers/integer_setting.vue
+++ b/src/components/settings_modal/helpers/integer_setting.vue
@@ -17,7 +17,10 @@
       @change="update"
     >
     {{ ' ' }}
-    <ModifiedIndicator :changed="isChanged" />
+    <ModifiedIndicator
+      :changed="isChanged"
+      :onclick="reset"
+    />
   </span>
 </template>
 
diff --git a/src/components/settings_modal/helpers/size_setting.js b/src/components/settings_modal/helpers/size_setting.js
new file mode 100644
index 00000000..58697412
--- /dev/null
+++ b/src/components/settings_modal/helpers/size_setting.js
@@ -0,0 +1,67 @@
+import { get, set } from 'lodash'
+import ModifiedIndicator from './modified_indicator.vue'
+import Select from 'src/components/select/select.vue'
+
+export const allCssUnits = ['cm', 'mm', 'in', 'px', 'pt', 'pc', 'em', 'ex', 'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax', '%']
+export const defaultHorizontalUnits = ['px', 'rem', 'vw']
+export const defaultVerticalUnits = ['px', 'rem', 'vh']
+
+export default {
+  components: {
+    ModifiedIndicator,
+    Select
+  },
+  props: {
+    path: String,
+    disabled: Boolean,
+    min: Number,
+    units: {
+      type: [String],
+      default: () => allCssUnits
+    },
+    expert: [Number, String]
+  },
+  computed: {
+    pathDefault () {
+      const [firstSegment, ...rest] = this.path.split('.')
+      return [firstSegment + 'DefaultValue', ...rest].join('.')
+    },
+    stateUnit () {
+      return (this.state || '').replace(/\d+/, '')
+    },
+    stateValue () {
+      return (this.state || '').replace(/\D+/, '')
+    },
+    state () {
+      const value = get(this.$parent, this.path)
+      if (value === undefined) {
+        return this.defaultState
+      } else {
+        return value
+      }
+    },
+    defaultState () {
+      return get(this.$parent, this.pathDefault)
+    },
+    isChanged () {
+      return this.state !== this.defaultState
+    },
+    matchesExpertLevel () {
+      return (this.expert || 0) <= this.$parent.expertLevel
+    }
+  },
+  methods: {
+    update (e) {
+      set(this.$parent, this.path, e)
+    },
+    reset () {
+      set(this.$parent, this.path, this.defaultState)
+    },
+    updateValue (e) {
+      set(this.$parent, this.path, parseInt(e.target.value) + this.stateUnit)
+    },
+    updateUnit (e) {
+      set(this.$parent, this.path, this.stateValue + e.target.value)
+    }
+  }
+}
diff --git a/src/components/settings_modal/helpers/size_setting.vue b/src/components/settings_modal/helpers/size_setting.vue
new file mode 100644
index 00000000..90c9f538
--- /dev/null
+++ b/src/components/settings_modal/helpers/size_setting.vue
@@ -0,0 +1,54 @@
+<template>
+  <span
+    v-if="matchesExpertLevel"
+    class="SizeSetting"
+  >
+    <label
+      :for="path"
+      class="size-label"
+    >
+      <slot />
+    </label>
+    <input
+      :id="path"
+      class="number-input"
+      type="number"
+      step="1"
+      :disabled="disabled"
+      :min="min || 0"
+      :value="stateValue"
+      @change="updateValue"
+    >
+    <Select
+      :id="path"
+      :model-value="stateUnit"
+      :disabled="disabled"
+      class="css-unit-input"
+      @change="updateUnit"
+    >
+      <option
+        v-for="option in units"
+        :key="option"
+        :value="option"
+      >
+        {{ option }}
+      </option>
+    </Select>
+    {{ ' ' }}
+    <ModifiedIndicator
+      :changed="isChanged"
+      :onclick="reset"
+    />
+  </span>
+</template>
+
+<script src="./size_setting.js"></script>
+
+<style lang="scss">
+.css-unit-input, .css-unit-input select {
+  margin-left: 0.5em;
+  width: 4em !important;
+  max-width: 4em !important;
+  min-width: 4em !important;
+}
+</style>
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index 1e11b9e0..a22b9b03 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -2,6 +2,7 @@ import BooleanSetting from '../helpers/boolean_setting.vue'
 import ChoiceSetting from '../helpers/choice_setting.vue'
 import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
 import IntegerSetting from '../helpers/integer_setting.vue'
+import SizeSetting, { defaultHorizontalUnits } from '../helpers/size_setting.vue'
 import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
 
 import SharedComputedObject from '../helpers/shared_computed_object.js'
@@ -56,11 +57,15 @@ const GeneralTab = {
     BooleanSetting,
     ChoiceSetting,
     IntegerSetting,
+    SizeSetting,
     InterfaceLanguageSwitcher,
     ScopeSelector,
     ServerSideIndicator
   },
   computed: {
+    horizontalUnits () {
+      return defaultHorizontalUnits
+    },
     postFormats () {
       return this.$store.state.instance.postFormats || []
     },
@@ -71,6 +76,17 @@ const GeneralTab = {
         label: this.$t(`post_status.content_type["${format}"]`)
       }))
     },
+    columns () {
+      const mode = this.$store.getters.mergedConfig.thirdColumnMode
+
+      const notif = mode === 'none' ? [] : ['notifs']
+
+      if (this.$store.getters.mergedConfig.sidebarRight || mode === 'postform') {
+        return [...notif, 'content', 'sidebar']
+      } else {
+        return ['sidebar', 'content', ...notif]
+      }
+    },
     instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
     instanceWallpaperUsed () {
       return this.$store.state.instance.background &&
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 91015955..db321363 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -15,11 +15,6 @@
             {{ $t('settings.hide_isp') }}
           </BooleanSetting>
         </li>
-        <li>
-          <BooleanSetting path="sidebarRight">
-            {{ $t('settings.right_sidebar') }}
-          </BooleanSetting>
-        </li>
         <li v-if="instanceWallpaperUsed">
           <BooleanSetting path="hideInstanceWallpaper">
             {{ $t('settings.hide_wallpaper') }}
@@ -64,16 +59,6 @@
             {{ $t('settings.virtual_scrolling') }}
           </BooleanSetting>
         </li>
-        <li>
-          <BooleanSetting path="disableStickyHeaders">
-            {{ $t('settings.disable_sticky_headers') }}
-          </BooleanSetting>
-        </li>
-        <li>
-          <BooleanSetting path="showScrollbars">
-            {{ $t('settings.show_scrollbars') }}
-          </BooleanSetting>
-        </li>
         <li>
           <BooleanSetting
             path="userPopoverZoom"
@@ -90,16 +75,6 @@
             {{ $t('settings.user_popover_avatar_overlay') }}
           </BooleanSetting>
         </li>
-        <li>
-          <ChoiceSetting
-            v-if="user"
-            id="thirdColumnMode"
-            path="thirdColumnMode"
-            :options="thirdColumnModeOptions"
-          >
-            {{ $t('settings.third_column_mode') }}
-          </ChoiceSetting>
-        </li>
         <li>
           <BooleanSetting
             path="alwaysShowNewPostButton"
@@ -480,3 +455,16 @@
 </template>
 
 <script src="./general_tab.js"></script>
+
+<style lang="scss">
+.column-settings {
+  display: flex;
+  justify-content: space-evenly;
+  flex-wrap: wrap;
+}
+.column-settings .size-label {
+  display: block;
+  margin-bottom: 0.5em;
+  margin-top: 0.5em;
+}
+</style>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 2e845959..7e957ffd 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -533,6 +533,11 @@
     "third_column_mode_none": "Don't show third column at all",
     "third_column_mode_notifications": "Notifications column",
     "third_column_mode_postform": "Main post form and navigation",
+    "columns": "Columns",
+    "column_sizes": "Column sizes",
+    "column_sizes_sidebar": "Sidebar",
+    "column_sizes_content": "Content",
+    "column_sizes_notifs": "Notifications",
     "tree_advanced": "Allow more flexible navigation in tree view",
     "tree_fade_ancestors": "Display ancestors of the current status in faint text",
     "conversation_display_linear": "Linear-style",
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 7e6ff3f5..02815f3e 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -456,6 +456,15 @@
     "subject_line_mastodon": "Как в Mastodon: скопировать как есть",
     "subject_line_email": "Как в электронной почте: \"re: тема\"",
     "subject_line_behavior": "Копировать тему в ответах",
+    "third_column_mode": "Когда недостаточно места, показывать третью колонку содержащую",
+    "third_column_mode_none": "Не показывать третью колонку совсем",
+    "third_column_mode_notifications": "Колонку уведомлений",
+    "third_column_mode_postform": "Форму отправки сообщения и навигацию",
+    "columns": "Колонки",
+    "column_sizes": "Размеры колонок",
+    "column_sizes_sidebar": "Боковой",
+    "column_sizes_content": "Содержимого",
+    "column_sizes_notifs": "Уведомлений",
     "no_mutes": "Нет игнорируемых",
     "no_blocks": "Нет блокировок",
     "notification_visibility_emoji_reactions": "Реакции",
diff --git a/src/modules/config.js b/src/modules/config.js
index c34b2c8c..2918f865 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -1,5 +1,5 @@
 import Cookies from 'js-cookie'
-import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
+import { setPreset, applyTheme, applyConfig } from '../services/style_setter/style_setter.js'
 import messages from '../i18n/messages'
 import localeService from '../services/locale/locale.service.js'
 
@@ -165,12 +165,17 @@ const config = {
     setHighlight ({ commit, dispatch }, { user, color, type }) {
       commit('setHighlight', { user, color, type })
     },
-    setOption ({ commit, dispatch }, { name, value }) {
+    setOption ({ commit, dispatch, state }, { name, value }) {
       commit('setOption', { name, value })
       switch (name) {
         case 'theme':
           setPreset(value)
           break
+        case 'sidebarColumnWidth':
+        case 'contentColumnWidth':
+        case 'notifsColumnWidth':
+          applyConfig(state)
+          break
         case 'customTheme':
         case 'customThemeSource':
           applyTheme(value)
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 543aa874..d6e973a1 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -1,6 +1,7 @@
 import { convert } from 'chromatism'
 import { rgb2hex, hex2rgb, rgba2css, getCssColor, relativeLuminance } from '../color_convert/color_convert.js'
 import { getColors, computeDynamicColor, getOpacitySlot } from '../theme_data/theme_data.service.js'
+import { defaultState } from '../../modules/config.js'
 
 export const applyTheme = (input) => {
   const { rules } = generatePreset(input)
@@ -20,6 +21,36 @@ export const applyTheme = (input) => {
   body.classList.remove('hidden')
 }
 
+const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth }) =>
+  ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth })
+
+const defaultConfigColumns = configColumns(defaultState)
+
+export const applyConfig = (config) => {
+  const columns = configColumns(config)
+
+  if (columns === defaultConfigColumns) {
+    return
+  }
+
+  const head = document.head
+  const body = document.body
+  body.classList.add('hidden')
+
+  const rules = Object
+    .entries(columns)
+    .filter(([k, v]) => v)
+    .map(([k, v]) => `--${k}: ${v}`).join(';')
+
+  const styleEl = document.createElement('style')
+  head.appendChild(styleEl)
+  const styleSheet = styleEl.sheet
+
+  styleSheet.toString()
+  styleSheet.insertRule(`:root { ${rules} }`, 'index-max')
+  body.classList.remove('hidden')
+}
+
 export const getCssShadow = (input, usesDropShadow) => {
   if (input.length === 0) {
     return 'none'
-- 
cgit v1.2.3-70-g09d2


From a29835375a62549410a7df7922f8eb3f9b391487 Mon Sep 17 00:00:00 2001
From: Alexander Tumin <iamtakingiteasy@eientei.org>
Date: Wed, 17 Aug 2022 02:33:39 +0300
Subject: Allow column width configuration: allow stretching navbar with
 columns

---
 src/App.js                                         |  7 +++++++
 src/App.scss                                       | 15 ++++++++-------
 src/App.vue                                        |  5 ++++-
 src/components/desktop_nav/desktop_nav.scss        | 20 ++++++++++++++++++++
 src/components/settings_modal/tabs/general_tab.vue |  5 +++++
 src/i18n/en.json                                   |  1 +
 src/modules/config.js                              |  1 +
 7 files changed, 46 insertions(+), 8 deletions(-)

(limited to 'src/components/settings_modal/tabs/general_tab.vue')

diff --git a/src/App.js b/src/App.js
index f5bd7e2e..d1ad16d5 100644
--- a/src/App.js
+++ b/src/App.js
@@ -60,6 +60,13 @@ export default {
         '-' + this.layoutType
       ]
     },
+    navClasses () {
+      const { navbarColumnStretch } = this.$store.getters.mergedConfig
+      return [
+        '-' + this.layoutType,
+        ...(navbarColumnStretch ? ['-column-stretch'] : [])
+      ]
+    },
     currentUser () { return this.$store.state.users.currentUser },
     userBackground () { return this.currentUser.background_image },
     instanceBackground () {
diff --git a/src/App.scss b/src/App.scss
index 3c16007e..02f5e049 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -185,13 +185,14 @@ nav {
   --maxiColumn: 45rem;
   --columnGap: 1em;
   --status-margin: 0.75em;
-
+  --effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
+  --effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)));
   --effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn)));
 
   position: relative;
   display: grid;
   grid-template-columns:
-    minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)))
+    var(--effectiveSidebarColumnWidth)
     var(--effectiveContentColumnWidth);
   grid-template-areas: "sidebar content";
   grid-template-rows: 1fr;
@@ -288,22 +289,22 @@ nav {
   &.-reverse:not(.-wide):not(.-mobile) {
     grid-template-columns:
       var(--effectiveContentColumnWidth)
-      minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
+      var(--effectiveSidebarColumnWidth);
     grid-template-areas: "content sidebar";
   }
 
   &.-wide {
     grid-template-columns:
-      minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)))
+      var(--effectiveSidebarColumnWidth)
       var(--effectiveContentColumnWidth)
-      minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)));
+      var(--effectiveNotifsColumnWidth);
     grid-template-areas: "sidebar content notifs";
 
     &.-reverse {
       grid-template-columns:
-        minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)))
+        var(--effectiveNotifsColumnWidth)
         var(--effectiveContentColumnWidth)
-        minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
+        var(--effectiveSidebarColumnWidth);
       grid-template-areas: "notifs content sidebar";
     }
   }
diff --git a/src/App.vue b/src/App.vue
index c741aa70..1f96efe8 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -8,7 +8,10 @@
       class="app-bg-wrapper"
     />
     <MobileNav v-if="layoutType === 'mobile'" />
-    <DesktopNav v-else />
+    <DesktopNav
+      v-else
+      :class="navClasses"
+    />
     <Notifications v-if="currentUser" />
     <div
       id="content"
diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss
index 71202244..f6c7c43d 100644
--- a/src/components/desktop_nav/desktop_nav.scss
+++ b/src/components/desktop_nav/desktop_nav.scss
@@ -23,6 +23,26 @@
     max-width: 980px;
   }
 
+  &.-column-stretch .inner-nav {
+    --miniColumn: 25rem;
+    --maxiColumn: 45rem;
+    --columnGap: 1em;
+    max-width: calc(
+      var(--sidebarColumnWidth, var(--miniColumn)) +
+      var(--contentColumnWidth, var(--maxiColumn)) +
+      var(--columnGap)
+    );
+  }
+
+  &.-column-stretch.-wide .inner-nav {
+    max-width: calc(
+      var(--sidebarColumnWidth, var(--miniColumn)) +
+      var(--contentColumnWidth, var(--maxiColumn)) +
+      var(--notifsColumnWidth, var(--miniColumn)) +
+      var(--columnGap)
+    );
+  }
+
   &.-logoLeft .inner-nav {
     grid-template-columns: auto 2fr 2fr;
     grid-template-areas: "logo sitename actions";
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index db321363..333b5493 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -122,6 +122,11 @@
             {{ $t('settings.right_sidebar') }}
           </BooleanSetting>
         </li>
+        <li>
+          <BooleanSetting path="navbarColumnStretch">
+            {{ $t('settings.navbar_column_stretch') }}
+          </BooleanSetting>
+        </li>
         <li>
           <ChoiceSetting
             v-if="user"
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 7e957ffd..a5d3cec6 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -412,6 +412,7 @@
     "hide_isp": "Hide instance-specific panel",
     "hide_shoutbox": "Hide instance shoutbox",
     "right_sidebar": "Reverse order of columns",
+    "navbar_column_stretch": "Stretch navbar to columns width",
     "always_show_post_button": "Always show floating New Post button",
     "hide_wallpaper": "Hide instance wallpaper",
     "preload_images": "Preload images",
diff --git a/src/modules/config.js b/src/modules/config.js
index 2918f865..1c2a45c2 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -87,6 +87,7 @@ export const defaultState = {
   sidebarColumnWidth: '25rem',
   contentColumnWidth: '45rem',
   notifsColumnWidth: '25rem',
+  navbarColumnStretch: false,
   listsNavigation: false,
   greentext: undefined, // instance default
   useAtIcon: undefined, // instance default
-- 
cgit v1.2.3-70-g09d2

