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 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

