From 9c1814d12243f45cb67a797780a8c393f301080c Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 22 Feb 2022 23:31:40 +0200 Subject: [PATCH 001/128] expert settings toggle + server-side settings --- .../settings_modal/helpers/boolean_setting.js | 15 ++- .../helpers/boolean_setting.vue | 2 + .../settings_modal/helpers/choice_setting.js | 8 +- .../settings_modal/helpers/choice_setting.vue | 2 + .../helpers/server_side_indicator.vue | 51 +++++++ .../helpers/shared_computed_object.js | 9 ++ .../settings_modal/settings_modal.js | 11 ++ .../settings_modal/settings_modal.scss | 7 + .../settings_modal/settings_modal.vue | 6 +- .../settings_modal/tabs/filtering_tab.vue | 46 +------ .../settings_modal/tabs/general_tab.js | 11 +- .../settings_modal/tabs/general_tab.vue | 122 +++++++++-------- .../settings_modal/tabs/notifications_tab.js | 8 +- .../settings_modal/tabs/notifications_tab.vue | 72 +++++++--- .../settings_modal/tabs/profile_tab.js | 23 +--- .../settings_modal/tabs/profile_tab.vue | 124 +++++++++--------- src/i18n/en.json | 5 + src/main.js | 2 + src/modules/config.js | 1 + src/modules/serverSideConfig.js | 111 ++++++++++++++++ .../entity_normalizer.service.js | 1 + 21 files changed, 433 insertions(+), 204 deletions(-) create mode 100644 src/components/settings_modal/helpers/server_side_indicator.vue create mode 100644 src/modules/serverSideConfig.js diff --git a/src/components/settings_modal/helpers/boolean_setting.js b/src/components/settings_modal/helpers/boolean_setting.js index 5c52f697ce..353e551c9f 100644 --- a/src/components/settings_modal/helpers/boolean_setting.js +++ b/src/components/settings_modal/helpers/boolean_setting.js @@ -1,14 +1,17 @@ import { get, set } from 'lodash' import Checkbox from 'src/components/checkbox/checkbox.vue' import ModifiedIndicator from './modified_indicator.vue' +import ServerSideIndicator from './server_side_indicator.vue' export default { components: { Checkbox, - ModifiedIndicator + ModifiedIndicator, + ServerSideIndicator }, props: [ 'path', - 'disabled' + 'disabled', + 'expert' ], computed: { pathDefault () { @@ -26,8 +29,14 @@ export default { defaultState () { return get(this.$parent, this.pathDefault) }, + isServerSide () { + return this.path.startsWith('serverSide_') + }, isChanged () { - return this.state !== this.defaultState + return !this.path.startsWith('serverSide_') && this.state !== this.defaultState + }, + matchesExpertLevel () { + return (this.expert || 0) <= this.$parent.expertLevel } }, methods: { diff --git a/src/components/settings_modal/helpers/boolean_setting.vue b/src/components/settings_modal/helpers/boolean_setting.vue index c3ee6583f6..c82cf23f83 100644 --- a/src/components/settings_modal/helpers/boolean_setting.vue +++ b/src/components/settings_modal/helpers/boolean_setting.vue @@ -1,6 +1,7 @@ diff --git a/src/components/settings_modal/helpers/choice_setting.js b/src/components/settings_modal/helpers/choice_setting.js index a15f6bac0b..07d0f76d32 100644 --- a/src/components/settings_modal/helpers/choice_setting.js +++ b/src/components/settings_modal/helpers/choice_setting.js @@ -9,7 +9,8 @@ export default { props: [ 'path', 'disabled', - 'options' + 'options', + 'expert' ], computed: { pathDefault () { @@ -28,7 +29,10 @@ export default { return get(this.$parent, this.pathDefault) }, isChanged () { - return this.state !== this.defaultState + return !this.path.startsWith('serverSide_') && this.state !== this.defaultState + }, + matchesExpertLevel () { + return (this.expert || 0) <= this.$parent.expertLevel } }, methods: { diff --git a/src/components/settings_modal/helpers/choice_setting.vue b/src/components/settings_modal/helpers/choice_setting.vue index fa17661bdd..845886ca50 100644 --- a/src/components/settings_modal/helpers/choice_setting.vue +++ b/src/components/settings_modal/helpers/choice_setting.vue @@ -1,6 +1,7 @@ diff --git a/src/components/settings_modal/helpers/server_side_indicator.vue b/src/components/settings_modal/helpers/server_side_indicator.vue new file mode 100644 index 0000000000..143a86a163 --- /dev/null +++ b/src/components/settings_modal/helpers/server_side_indicator.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/components/settings_modal/helpers/shared_computed_object.js b/src/components/settings_modal/helpers/shared_computed_object.js index 2c833c0c08..12431dcaf0 100644 --- a/src/components/settings_modal/helpers/shared_computed_object.js +++ b/src/components/settings_modal/helpers/shared_computed_object.js @@ -1,4 +1,5 @@ import { defaultState as configDefaultState } from 'src/modules/config.js' +import { defaultState as serverSideConfigDefaultState } from 'src/modules/serverSideConfig.js' const SharedComputedObject = () => ({ user () { @@ -22,6 +23,14 @@ const SharedComputedObject = () => ({ } }]) .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), + ...Object.keys(serverSideConfigDefaultState) + .map(key => ['serverSide_' + key, { + get () { return this.$store.state.serverSideConfig[key] }, + set (value) { + this.$store.dispatch('setServerSideOption', { name: key, value }) + } + }]) + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), // Special cases (need to transform values or perform actions first) useStreamingApi: { get () { return this.$store.getters.mergedConfig.useStreamingApi }, diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js index 0404348334..82ea410e82 100644 --- a/src/components/settings_modal/settings_modal.js +++ b/src/components/settings_modal/settings_modal.js @@ -3,6 +3,7 @@ import PanelLoading from 'src/components/panel_loading/panel_loading.vue' import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue' import getResettableAsyncComponent from 'src/services/resettable_async_component.js' import Popover from '../popover/popover.vue' +import Checkbox from 'src/components/checkbox/checkbox.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { cloneDeep } from 'lodash' import { @@ -51,6 +52,7 @@ const SettingsModal = { components: { Modal, Popover, + Checkbox, SettingsModalContent: getResettableAsyncComponent( () => import('./settings_modal_content.vue'), { @@ -159,6 +161,15 @@ const SettingsModal = { }, modalPeeked () { return this.$store.state.interface.settingsModalState === 'minimized' + }, + expertLevel: { + get () { + return this.$store.state.config.expertLevel > 0 + }, + set (value) { + console.log(value) + this.$store.dispatch('setOption', { name: 'expertLevel', value: value ? 1 : 0 }) + } } } } diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss index 90446b3637..fb466f2f35 100644 --- a/src/components/settings_modal/settings_modal.scss +++ b/src/components/settings_modal/settings_modal.scss @@ -48,4 +48,11 @@ } } } + + .settings-footer { + display: flex; + >* { + margin-right: 0.5em; + } + } } diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue index 583c2ecc16..0aad1abb51 100644 --- a/src/components/settings_modal/settings_modal.vue +++ b/src/components/settings_modal/settings_modal.vue @@ -53,7 +53,7 @@
- diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue index 50ee20e096..cd7f0bc41b 100644 --- a/src/components/settings_modal/tabs/filtering_tab.vue +++ b/src/components/settings_modal/tabs/filtering_tab.vue @@ -38,7 +38,7 @@
  • - + {{ $t('settings.hide_post_stats') }}
  • @@ -59,7 +59,7 @@
    {{ $t('settings.filtering_explanation') }}

    {{ $t('settings.attachments') }}

    -
  • +
  • @@ -84,7 +84,7 @@
  • -
    +

    {{ $t('settings.user_profiles') }}

    • @@ -94,46 +94,6 @@
    -
    -

    {{ $t('settings.notifications') }}

    -
      -
    • - {{ $t('settings.notification_visibility') }} -
        -
      • - - {{ $t('settings.notification_visibility_likes') }} - -
      • -
      • - - {{ $t('settings.notification_visibility_repeats') }} - -
      • -
      • - - {{ $t('settings.notification_visibility_follows') }} - -
      • -
      • - - {{ $t('settings.notification_visibility_mentions') }} - -
      • -
      • - - {{ $t('settings.notification_visibility_moves') }} - -
      • -
      • - - {{ $t('settings.notification_visibility_emoji_reactions') }} - -
      • -
      -
    • -
    -
    diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index 952c328dee..9e4e282f58 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -1,8 +1,10 @@ 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 InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue' import SharedComputedObject from '../helpers/shared_computed_object.js' +import ServerSideIndicator from '../helpers/server_side_indicator.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faGlobe @@ -37,7 +39,9 @@ const GeneralTab = { components: { BooleanSetting, ChoiceSetting, - InterfaceLanguageSwitcher + InterfaceLanguageSwitcher, + ScopeSelector, + ServerSideIndicator, }, computed: { postFormats () { @@ -57,6 +61,11 @@ const GeneralTab = { }, instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable }, ...SharedComputedObject() + }, + methods: { + changeDefaultScope (value) { + this.$store.dispatch('setServerSideOption', { name: 'defaultScope', value }) + } } } diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index eba3b2680d..4accf0c1c8 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -27,7 +27,7 @@
  • - + {{ $t('settings.streaming') }}
      {{ $t('settings.pause_on_unfocused') }} @@ -45,7 +46,7 @@
  • - + {{ $t('settings.useStreamingApi') }}
    @@ -54,17 +55,22 @@
  • - + {{ $t('settings.virtual_scrolling') }}
  • - + + {{ $t('settings.always_show_post_button') }} + +
  • +
  • + {{ $t('settings.autohide_floating_post_button') }}
  • - + {{ $t('settings.hide_shoutbox') }}
  • @@ -79,13 +85,18 @@
  • - + {{ $t('settings.emoji_reactions_on_timeline') }}
  • +
  • + + {{ $t('settings.no_rich_text_description') }} + +
  • {{ $t('settings.attachments') }}

  • - + {{ $t('settings.use_contain_fit') }}
  • @@ -97,7 +108,7 @@
  • - + {{ $t('settings.loop_video') }}
    • {{ $t('settings.loop_video_silent_only') }} @@ -137,21 +148,11 @@
  • - + {{ $t('settings.play_videos_in_modal') }}
  • -

    {{ $t('settings.fun') }}

    -
  • - - {{ $t('settings.greentext') }} - -
  • -
  • - - {{ $t('settings.show_yous') }} - -
  • +

    {{ $t('settings.mention_links') }}

  • -
  • - +
  • + {{ $t('settings.mention_link_show_tooltip') }}
  • +
  • - + {{ $t('settings.use_at_icon') }}
  • @@ -182,29 +182,56 @@
  • - + {{ $t('settings.mention_link_fade_domain') }}
  • -
  • - +
  • + {{ $t('settings.mention_link_bolden_you') }}
  • - +

    {{ $t('settings.fun') }}

    +
  • + + {{ $t('settings.greentext') }} + +
  • +
  • + + {{ $t('settings.show_yous') }} + +
  • -
    +

    {{ $t('settings.composing') }}

    • - + +
    • +
    • + + {{ $t('settings.sensitive_by_default') }} + +
    • +
    • + {{ $t('settings.scope_copy') }}
    • - + {{ $t('settings.subject_input_always_show') }}
    • @@ -213,6 +240,7 @@ id="subjectLineBehavior" path="subjectLineBehavior" :options="subjectLineOptions" + expert="1" > {{ $t('settings.subject_line_behavior') }} @@ -227,43 +255,27 @@
    • - + {{ $t('settings.minimal_scopes_mode') }}
    • - - {{ $t('settings.sensitive_by_default') }} - -
    • -
    • - + {{ $t('settings.always_show_post_button') }}
    • - + {{ $t('settings.autohide_floating_post_button') }}
    • - + {{ $t('settings.pad_emoji') }}
    - -
    -

    {{ $t('settings.notifications') }}

    -
      -
    • - - {{ $t('settings.enable_web_push_notifications') }} - -
    • -
    -
    diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js index 3e44c95df9..3c6ab87fe7 100644 --- a/src/components/settings_modal/tabs/notifications_tab.js +++ b/src/components/settings_modal/tabs/notifications_tab.js @@ -1,4 +1,5 @@ -import Checkbox from 'src/components/checkbox/checkbox.vue' +import BooleanSetting from '../helpers/boolean_setting.vue' +import SharedComputedObject from '../helpers/shared_computed_object.js' const NotificationsTab = { data () { @@ -9,12 +10,13 @@ const NotificationsTab = { } }, components: { - Checkbox + BooleanSetting }, computed: { user () { return this.$store.state.users.currentUser - } + }, + ...SharedComputedObject() }, methods: { updateNotificationSettings () { diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue index 7e0568ead0..5e9ce91ec9 100644 --- a/src/components/settings_modal/tabs/notifications_tab.vue +++ b/src/components/settings_modal/tabs/notifications_tab.vue @@ -2,30 +2,68 @@

    {{ $t('settings.notification_setting_filters') }}

    -

    - - {{ $t('settings.notification_setting_block_from_strangers') }} - -

    +
      +
    • + + {{ $t('settings.notification_setting_block_from_strangers') }} + +
    • +
    • + {{ $t('settings.notification_visibility') }} +
        +
      • + + {{ $t('settings.notification_visibility_likes') }} + +
      • +
      • + + {{ $t('settings.notification_visibility_repeats') }} + +
      • +
      • + + {{ $t('settings.notification_visibility_follows') }} + +
      • +
      • + + {{ $t('settings.notification_visibility_mentions') }} + +
      • +
      • + + {{ $t('settings.notification_visibility_moves') }} + +
      • +
      • + + {{ $t('settings.notification_visibility_emoji_reactions') }} + +
      • +
      +
    • +
    -
    +

    {{ $t('settings.notification_setting_privacy') }}

    -

    - - {{ $t('settings.notification_setting_hide_notification_contents') }} - -

    +
      +
    • + + {{ $t('settings.enable_web_push_notifications') }} + +
    • +
    • + + {{ $t('settings.notification_setting_hide_notification_contents') }} + +
    • +

    {{ $t('settings.notification_mutes') }}

    {{ $t('settings.notification_blocks') }}

    -
    diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js index 64079fcdd6..bee8a7bb4c 100644 --- a/src/components/settings_modal/tabs/profile_tab.js +++ b/src/components/settings_modal/tabs/profile_tab.js @@ -8,6 +8,9 @@ import EmojiInput from 'src/components/emoji_input/emoji_input.vue' import suggestor from 'src/components/emoji_input/suggestor.js' import Autosuggest from 'src/components/autosuggest/autosuggest.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' +import BooleanSetting from '../helpers/boolean_setting.vue' +import SharedComputedObject from '../helpers/shared_computed_object.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { faTimes, @@ -27,18 +30,10 @@ const ProfileTab = { newName: this.$store.state.users.currentUser.name_unescaped, newBio: unescape(this.$store.state.users.currentUser.description), newLocked: this.$store.state.users.currentUser.locked, - newNoRichText: this.$store.state.users.currentUser.no_rich_text, - newDefaultScope: this.$store.state.users.currentUser.default_scope, newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })), - hideFollows: this.$store.state.users.currentUser.hide_follows, - hideFollowers: this.$store.state.users.currentUser.hide_followers, - hideFollowsCount: this.$store.state.users.currentUser.hide_follows_count, - hideFollowersCount: this.$store.state.users.currentUser.hide_followers_count, showRole: this.$store.state.users.currentUser.show_role, role: this.$store.state.users.currentUser.role, - discoverable: this.$store.state.users.currentUser.discoverable, bot: this.$store.state.users.currentUser.bot, - allowFollowingMove: this.$store.state.users.currentUser.allow_following_move, pickAvatarBtnVisible: true, bannerUploading: false, backgroundUploading: false, @@ -54,12 +49,14 @@ const ProfileTab = { EmojiInput, Autosuggest, ProgressButton, - Checkbox + Checkbox, + BooleanSetting }, computed: { user () { return this.$store.state.users.currentUser }, + ...SharedComputedObject(), emojiUserSuggestor () { return suggestor({ emoji: [ @@ -123,15 +120,7 @@ const ProfileTab = { /* eslint-disable camelcase */ display_name: this.newName, fields_attributes: this.newFields.filter(el => el != null), - default_scope: this.newDefaultScope, - no_rich_text: this.newNoRichText, - hide_follows: this.hideFollows, - hide_followers: this.hideFollowers, - discoverable: this.discoverable, bot: this.bot, - allow_following_move: this.allowFollowingMove, - hide_follows_count: this.hideFollowsCount, - hide_followers_count: this.hideFollowersCount, show_role: this.showRole /* eslint-enable camelcase */ } }).then((user) => { diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue index bb3c301d5c..2cf3e8bed5 100644 --- a/src/components/settings_modal/tabs/profile_tab.vue +++ b/src/components/settings_modal/tabs/profile_tab.vue @@ -25,61 +25,6 @@ class="bio resize-height" /> -

    - - {{ $t('settings.lock_account_description') }} - -

    -
    - -
    - -
    -
    -

    - - {{ $t('settings.no_rich_text_description') }} - -

    -

    - - {{ $t('settings.hide_follows_description') }} - -

    -

    - - {{ $t('settings.hide_follows_count_description') }} - -

    -

    - - {{ $t('settings.hide_followers_description') }} - -

    -

    - - {{ $t('settings.hide_followers_count_description') }} - -

    -

    - - {{ $t('settings.allow_following_move') }} - -

    -

    - - {{ $t('settings.discoverable') }} - -

    {{ $t('settings.profile_fields.label') }}

    {{ $t('settings.avatar_size_instruction') }}

    -
    - @@ -269,6 +208,67 @@ {{ $t('settings.save') }}
    +
    +

    {{ $t('settings.account_privacy') }}

    +
      +
    • + + {{ $t('settings.lock_account_description') }} + +
    • +
    • + + {{ $t('settings.discoverable') }} + +
    • +
    • + + {{ $t('settings.allow_following_move') }} + +
    • +
    • + + {{ $t('settings.hide_favorites_description') }} + +
    • +
    • + + {{ $t('settings.hide_followers_description') }} + +
        +
      • + + {{ $t('settings.hide_followers_count_description') }} + +
      • +
      +
    • +
    • + + {{ $t('settings.hide_follows_description') }} + +
        +
      • + + {{ $t('settings.hide_follows_count_description') }} + +
      • +
      +
    • +
    +
    diff --git a/src/i18n/en.json b/src/i18n/en.json index 8eb7fcc6ea..932e8af531 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -259,11 +259,14 @@ }, "settings": { "app_name": "App name", + "expert_mode": "Expert mode", "save": "Save changes", "security": "Security", "setting_changed": "Setting is different from default", + "setting_server_side": "This setting is tied to your profile and affects all sessions and clients", "enter_current_password_to_confirm": "Enter your current password to confirm your identity", "post_look_feel": "Posts Look & Feel", + "mention_links": "Mention links", "mfa": { "otp": "OTP", "setup_otp": "Setup OTP", @@ -400,6 +403,7 @@ "name": "Label", "value": "Content" }, + "account_privacy": "Privacy", "use_contain_fit": "Don't crop the attachment in thumbnails", "name": "Name", "name_bio": "Name & bio", @@ -417,6 +421,7 @@ "no_rich_text_description": "Strip rich text formatting from all posts", "no_blocks": "No blocks", "no_mutes": "No mutes", + "hide_favorites_description": "Don't show list of my favorites (people still get notified)", "hide_follows_description": "Don't show who I'm following", "hide_followers_description": "Don't show who's following me", "hide_follows_count_description": "Don't show follow count", diff --git a/src/main.js b/src/main.js index 3895da89b8..73bd49ed59 100644 --- a/src/main.js +++ b/src/main.js @@ -11,6 +11,7 @@ import statusesModule from './modules/statuses.js' import usersModule from './modules/users.js' import apiModule from './modules/api.js' import configModule from './modules/config.js' +import serverSideConfigModule from './modules/serverSideConfig.js' import shoutModule from './modules/shout.js' import oauthModule from './modules/oauth.js' import authFlowModule from './modules/auth_flow.js' @@ -88,6 +89,7 @@ const persistedStateOptions = { users: usersModule, api: apiModule, config: configModule, + serverSideConfig: serverSideConfigModule, shout: shoutModule, oauth: oauthModule, authFlow: authFlowModule, diff --git a/src/modules/config.js b/src/modules/config.js index 20979174a6..775ee39849 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -16,6 +16,7 @@ export const multiChoiceProperties = [ ] export const defaultState = { + expertLevel: 0, // used to track which settings to show and hide colors: {}, theme: undefined, customTheme: undefined, diff --git a/src/modules/serverSideConfig.js b/src/modules/serverSideConfig.js new file mode 100644 index 0000000000..75ea91befc --- /dev/null +++ b/src/modules/serverSideConfig.js @@ -0,0 +1,111 @@ +import { get, set } from 'lodash' + +export const settingsMapGet = { + 'defaultScope': 'source.privacy', + 'defaultNSFW': 'source.sensitive', + 'stripRichContent': 'source.pleroma.no_rich_content', + // Privacy + 'locked': 'locked', + 'acceptChatMessages': 'pleroma.accepts_chat_messages', + 'allowFollowingMove': 'pleroma.allow_following_move', + 'discoverable': 'source.discoverable', + 'hideFavorites': 'pleroma.hide_favorites', + 'hideFollowers': 'pleroma.hide_followers', + 'hideFollows': 'pleroma.hide_follows', + 'hideFollowersCount': 'pleroma.hide_followers_count', + 'hideFollowsCount': 'pleroma.hide_follows_count', + // NotificationSettingsAPIs + 'webPushHideContents': 'pleroma.notification_settings.hide_notification_contents', + 'blockNotificationsFromStrangers': 'pleroma.notification_settings.block_from_strangers' +} + +export const settingsMapSet = { + 'defaultScope': 'source.privacy', + 'defaultNSFW': 'source.sensitive', + 'stripRichContent': 'source.pleroma.no_rich_content', + // Privacy + 'locked': 'locked', + 'acceptChatMessages': 'accepts_chat_messages', + 'allowFollowingMove': 'allow_following_move', + 'discoverable': 'source.discoverable', + 'hideFavorites': 'hide_favorites', + 'hideFollowers': 'hide_followers', + 'hideFollows': 'hide_follows', + 'hideFollowersCount': 'hide_followers_count', + 'hideFollowsCount': 'hide_follows_count', + // NotificationSettingsAPIs + 'webPushHideContents': 'hide_notification_contents', + 'blockNotificationsFromStrangers': 'block_from_strangers' +} + +export const customAPIs = { + __defaultApi: 'updateProfile', + 'webPushHideContents': 'updateNotificationSettings', + 'blockNotificationsFromStrangers': 'updateNotificationSettings' +} + +export const defaultState = Object.fromEntries(Object.keys(settingsMapGet).map(key => [key, undefined])) + +const serverSideConfig = { + state: { ...defaultState }, + mutations: { + confirmServerSideOption (state, { name, value }) { + set(state, name, value) + }, + wipeServerSideOption (state, { name }) { + set(state, name, undefined) + }, + // Set the settings based on their path location + setCurrentUser (state, user) { + Object.entries(settingsMapGet).forEach(([name, path]) => { + set(state, name, get(user._original, path)) + }) + } + }, + actions: { + setServerSideOption ({ rootState, state, commit, dispatch }, { name, value }) { + const oldValue = get(state, name) + const params = {} + const path = settingsMapSet[name] + if (!path) throw new Error('Invalid server-side setting') + commit('wipeServerSideOption', { name }) + const customAPIName = customAPIs[name] || customAPIs.__defaultApi + const api = rootState.api.backendInteractor[customAPIName] + let prefix = '' + switch (customAPIName) { + case 'updateNotificationSettings': + prefix = 'settings.' + break + default: + prefix = 'params.' + break + } + + set(params, prefix + path, value) + api(params) + .then((result) => { + switch (customAPIName) { + case 'updateNotificationSettings': + console.log(result) + if (result.status === 'success') { + commit('confirmServerSideOption', { name, value }) + } else { + commit('confirmServerSideOption', { name, value: oldValue }) + } + break + default: + commit('addNewUsers', [result]) + commit('setCurrentUser', result) + break + } + console.log(state) + }) + .catch((e) => { + console.warn('Error setting server-side option:', e) + commit('confirmServerSideOption', { name, value: oldValue }) + }) + } + } +} + +export default serverSideConfig diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 7025d80324..f219c161f4 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -44,6 +44,7 @@ export const parseUser = (data) => { const mastoShort = masto && !data.hasOwnProperty('avatar') output.id = String(data.id) + output._original = data // used for server-side settings if (masto) { output.screen_name = data.acct From b1b9260a1df561a38b9bb904e103cccb37df8ea6 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 22 Feb 2022 23:32:12 +0200 Subject: [PATCH 002/128] new defaults --- src/modules/config.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/config.js b/src/modules/config.js index 775ee39849..e5321507ee 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -37,12 +37,12 @@ export const defaultState = { preloadImage: true, loopVideo: true, loopVideoSilentOnly: true, - streaming: false, + streaming: true, emojiReactionsOnTimeline: true, alwaysShowNewPostButton: false, autohideFloatingPostButton: false, pauseOnUnfocused: true, - stopGifs: false, + stopGifs: true, replyVisibility: 'all', notificationVisibility: { follows: true, @@ -70,7 +70,7 @@ export const defaultState = { hideFilteredStatuses: undefined, // instance default playVideosInModal: false, useOneClickNsfw: false, - useContainFit: false, + useContainFit: true, greentext: undefined, // instance default useAtIcon: undefined, // instance default mentionLinkDisplay: undefined, // instance default From 9623b0e1409e85c5598989f0cd13f3ccbb504bd2 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 24 Feb 2022 14:41:55 +0200 Subject: [PATCH 003/128] better phrasing --- src/i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index 932e8af531..a66aaa0bd4 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -259,7 +259,7 @@ }, "settings": { "app_name": "App name", - "expert_mode": "Expert mode", + "expert_mode": "Show advanced", "save": "Save changes", "security": "Security", "setting_changed": "Setting is different from default", From f626da838a7abeb3a2d3cd5f71ee4eb2ca272361 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 24 Feb 2022 15:00:08 +0200 Subject: [PATCH 004/128] revert to using local setting for default nsfw since backend is broken --- src/components/post_status_form/post_status_form.js | 1 + src/components/settings_modal/tabs/general_tab.vue | 3 ++- src/modules/serverSideConfig.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index fe07309fc9..9d7bbd753b 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -117,6 +117,7 @@ const PostStatusForm = { ? this.copyMessageScope : this.$store.state.users.currentUser.default_scope + // const { defaultNSFW: sensitiveByDefault } = this.$store.state.serverSideConfig const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig return { diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index 4accf0c1c8..5db70d770c 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -221,7 +221,8 @@
  • - + + {{ $t('settings.sensitive_by_default') }}
  • diff --git a/src/modules/serverSideConfig.js b/src/modules/serverSideConfig.js index 75ea91befc..ea2dc5e369 100644 --- a/src/modules/serverSideConfig.js +++ b/src/modules/serverSideConfig.js @@ -2,7 +2,7 @@ import { get, set } from 'lodash' export const settingsMapGet = { 'defaultScope': 'source.privacy', - 'defaultNSFW': 'source.sensitive', + 'defaultNSFW': 'source.sensitive', // BROKEN: pleroma/pleroma#2837 'stripRichContent': 'source.pleroma.no_rich_content', // Privacy 'locked': 'locked', From c07c0b22604ac57f91f08ed330eee98ee82e214e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 28 Feb 2022 17:43:08 +0200 Subject: [PATCH 005/128] fix firefox rendering (??????????) --- src/components/settings_modal/helpers/boolean_setting.vue | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/settings_modal/helpers/boolean_setting.vue b/src/components/settings_modal/helpers/boolean_setting.vue index c82cf23f83..3a7bd8053d 100644 --- a/src/components/settings_modal/helpers/boolean_setting.vue +++ b/src/components/settings_modal/helpers/boolean_setting.vue @@ -14,9 +14,7 @@ > - - - + From e3d602fdccb0dfe211fd3950210cdf8fda43bf95 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 28 Feb 2022 17:45:07 +0200 Subject: [PATCH 006/128] revert changes related to streaming/firehose setting, reword it so it's not confused with websocket streaming --- src/components/settings_modal/tabs/general_tab.vue | 3 +-- src/i18n/en.json | 4 ++-- src/modules/config.js | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index 5db70d770c..7944b03cf2 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -27,7 +27,7 @@
  • - + {{ $t('settings.streaming') }}
      {{ $t('settings.pause_on_unfocused') }} diff --git a/src/i18n/en.json b/src/i18n/en.json index a66aaa0bd4..da5e662dba 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -435,7 +435,7 @@ "valid_until": "Valid until", "revoke_token": "Revoke", "panelRadius": "Panels", - "pause_on_unfocused": "Pause streaming when tab is not focused", + "pause_on_unfocused": "Pause when tab is not focused", "presets": "Presets", "profile_background": "Profile background", "profile_banner": "Profile banner", @@ -473,7 +473,7 @@ "post_status_content_type": "Post status content type", "sensitive_by_default": "Mark posts as sensitive by default", "stop_gifs": "Pause animated images until you hover on them", - "streaming": "Enable automatic streaming of new posts when scrolled to the top", + "streaming": "Automatically show new posts when scrolled to the top", "user_mutes": "Users", "useStreamingApi": "Receive posts and notifications real-time", "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)", diff --git a/src/modules/config.js b/src/modules/config.js index e5321507ee..866015649d 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -37,7 +37,7 @@ export const defaultState = { preloadImage: true, loopVideo: true, loopVideoSilentOnly: true, - streaming: true, + streaming: false, emojiReactionsOnTimeline: true, alwaysShowNewPostButton: false, autohideFloatingPostButton: false, From 3a5ad18aca6a7303e6f6b97dd3f0919d5532184c Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 28 Feb 2022 18:00:38 +0200 Subject: [PATCH 007/128] fix stripping rich content not working --- src/modules/serverSideConfig.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/serverSideConfig.js b/src/modules/serverSideConfig.js index ea2dc5e369..c9d148d1a2 100644 --- a/src/modules/serverSideConfig.js +++ b/src/modules/serverSideConfig.js @@ -3,7 +3,7 @@ import { get, set } from 'lodash' export const settingsMapGet = { 'defaultScope': 'source.privacy', 'defaultNSFW': 'source.sensitive', // BROKEN: pleroma/pleroma#2837 - 'stripRichContent': 'source.pleroma.no_rich_content', + 'stripRichContent': 'source.pleroma.no_rich_text', // Privacy 'locked': 'locked', 'acceptChatMessages': 'pleroma.accepts_chat_messages', @@ -22,7 +22,7 @@ export const settingsMapGet = { export const settingsMapSet = { 'defaultScope': 'source.privacy', 'defaultNSFW': 'source.sensitive', - 'stripRichContent': 'source.pleroma.no_rich_content', + 'stripRichContent': 'no_rich_text', // Privacy 'locked': 'locked', 'acceptChatMessages': 'accepts_chat_messages', From 8bb97fbfeb8f34c36aec96ee175d6eced49c1fb4 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 28 Feb 2022 18:01:41 +0200 Subject: [PATCH 008/128] fix settings behaving erratically and not updating properly --- src/modules/serverSideConfig.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/serverSideConfig.js b/src/modules/serverSideConfig.js index c9d148d1a2..2db6fc0654 100644 --- a/src/modules/serverSideConfig.js +++ b/src/modules/serverSideConfig.js @@ -44,7 +44,7 @@ export const customAPIs = { 'blockNotificationsFromStrangers': 'updateNotificationSettings' } -export const defaultState = Object.fromEntries(Object.keys(settingsMapGet).map(key => [key, undefined])) +export const defaultState = Object.fromEntries(Object.keys(settingsMapGet).map(key => [key, null])) const serverSideConfig = { state: { ...defaultState }, @@ -53,7 +53,7 @@ const serverSideConfig = { set(state, name, value) }, wipeServerSideOption (state, { name }) { - set(state, name, undefined) + set(state, name, null) }, // Set the settings based on their path location setCurrentUser (state, user) { From cf58df17f6624362ea51f8813874250eff6d185a Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 28 Feb 2022 18:04:13 +0200 Subject: [PATCH 009/128] hidden away more settings when logged out --- src/components/settings_modal/tabs/filtering_tab.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue index cd7f0bc41b..0352e88785 100644 --- a/src/components/settings_modal/tabs/filtering_tab.vue +++ b/src/components/settings_modal/tabs/filtering_tab.vue @@ -22,6 +22,7 @@
    • {{ $t('settings.hide_muted_threads') }} @@ -30,6 +31,7 @@
    • {{ $t('settings.hide_muted_posts') }} @@ -46,6 +48,7 @@ id="replyVisibility" path="replyVisibility" :options="replyVisibilityOptions" + v-if="user" > {{ $t('settings.replies_in_timeline') }} From 67319d0e5b0b36a440dcaf35119e41046cc6e3fd Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 28 Feb 2022 18:15:07 +0200 Subject: [PATCH 010/128] fix typos in profile page --- src/components/settings_modal/tabs/profile_tab.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue index 2cf3e8bed5..699fdcf494 100644 --- a/src/components/settings_modal/tabs/profile_tab.vue +++ b/src/components/settings_modal/tabs/profile_tab.vue @@ -242,7 +242,7 @@
    • {{ $t('settings.hide_followers_count_description') }} @@ -260,7 +260,7 @@
    • {{ $t('settings.hide_follows_count_description') }} From f4b36a9ebf99cc69ab93c148e488401daf3713b7 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 28 Feb 2022 18:15:21 +0200 Subject: [PATCH 011/128] fix errors in choicesetting --- src/components/settings_modal/helpers/choice_setting.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/settings_modal/helpers/choice_setting.js b/src/components/settings_modal/helpers/choice_setting.js index 07d0f76d32..4677d4c194 100644 --- a/src/components/settings_modal/helpers/choice_setting.js +++ b/src/components/settings_modal/helpers/choice_setting.js @@ -1,10 +1,12 @@ import { get, set } from 'lodash' import Select from 'src/components/select/select.vue' import ModifiedIndicator from './modified_indicator.vue' +import ServerSideIndicator from './server_side_indicator.vue' export default { components: { Select, - ModifiedIndicator + ModifiedIndicator, + ServerSideIndicator }, props: [ 'path', @@ -28,6 +30,9 @@ export default { defaultState () { return get(this.$parent, this.pathDefault) }, + isServerSide () { + return this.path.startsWith('serverSide_') + }, isChanged () { return !this.path.startsWith('serverSide_') && this.state !== this.defaultState }, From 39909c8a8590e866082da5d2528b4df2898f2bf5 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 28 Feb 2022 18:17:13 +0200 Subject: [PATCH 012/128] pre-emptively wipe serverside settings on logout --- src/modules/serverSideConfig.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/modules/serverSideConfig.js b/src/modules/serverSideConfig.js index 2db6fc0654..4f0cd96571 100644 --- a/src/modules/serverSideConfig.js +++ b/src/modules/serverSideConfig.js @@ -55,6 +55,11 @@ const serverSideConfig = { wipeServerSideOption (state, { name }) { set(state, name, null) }, + wipeAllServerSideOptions (state) { + Object.keys(settingsMapGet).forEach(key => { + set(state, key, null) + }) + }, // Set the settings based on their path location setCurrentUser (state, user) { Object.entries(settingsMapGet).forEach(([name, path]) => { @@ -104,6 +109,9 @@ const serverSideConfig = { console.warn('Error setting server-side option:', e) commit('confirmServerSideOption', { name, value: oldValue }) }) + }, + logout ({ commit }) { + commit('wipeAllServerSideOptions') } } } From 77bb0b553062b91a69fbf92ce7540565f098951f Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 28 Feb 2022 18:23:32 +0200 Subject: [PATCH 013/128] lint --- src/components/attachment/attachment.vue | 19 +- src/components/gallery/gallery.vue | 4 +- .../helpers/boolean_setting.vue | 4 +- .../settings_modal/helpers/choice_setting.vue | 4 +- .../settings_modal/settings_modal.vue | 4 +- .../settings_modal/tabs/filtering_tab.vue | 16 +- .../settings_modal/tabs/general_tab.js | 2 +- .../settings_modal/tabs/general_tab.vue | 167 +++++++++++++----- .../settings_modal/tabs/notifications_tab.vue | 15 +- .../settings_modal/tabs/profile_tab.vue | 7 +- 10 files changed, 168 insertions(+), 74 deletions(-) diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index 5917375984..c4399f3066 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -81,56 +81,56 @@ @@ -160,7 +160,10 @@ :href="attachment.url" target="_blank" > - +

      {{ localDescription }}

      diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue index f9cad8a9c1..f2e1b5ce51 100644 --- a/src/components/gallery/gallery.vue +++ b/src/components/gallery/gallery.vue @@ -26,8 +26,8 @@ :size="size" :editable="editable" :remove="removeAttachment" - :shiftUp="!(attachmentIndex === 0 && rowIndex === 0) && shiftUpAttachment" - :shiftDn="!(attachmentIndex === row.items.length - 1 && rowIndex === rows.length - 1) && shiftDnAttachment" + :shift-up="!(attachmentIndex === 0 && rowIndex === 0) && shiftUpAttachment" + :shift-dn="!(attachmentIndex === row.items.length - 1 && rowIndex === rows.length - 1) && shiftDnAttachment" :edit="editAttachment" :description="descriptions && descriptions[attachment.id]" :hide-description="size === 'small' || tooManyAttachments && hidingLong" diff --git a/src/components/settings_modal/helpers/boolean_setting.vue b/src/components/settings_modal/helpers/boolean_setting.vue index 3a7bd8053d..e0d825f20c 100644 --- a/src/components/settings_modal/helpers/boolean_setting.vue +++ b/src/components/settings_modal/helpers/boolean_setting.vue @@ -1,7 +1,7 @@ diff --git a/src/components/settings_modal/helpers/choice_setting.vue b/src/components/settings_modal/helpers/choice_setting.vue index 845886ca50..54f5d0a76b 100644 --- a/src/components/settings_modal/helpers/choice_setting.vue +++ b/src/components/settings_modal/helpers/choice_setting.vue @@ -1,7 +1,7 @@ diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue index 0aad1abb51..1805c77fdd 100644 --- a/src/components/settings_modal/settings_modal.vue +++ b/src/components/settings_modal/settings_modal.vue @@ -109,8 +109,8 @@ - - {{ $t("settings.expert_mode")}} + + {{ $t("settings.expert_mode") }}
  • diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue index 0352e88785..e60a8a857a 100644 --- a/src/components/settings_modal/tabs/filtering_tab.vue +++ b/src/components/settings_modal/tabs/filtering_tab.vue @@ -21,8 +21,8 @@
  • {{ $t('settings.hide_muted_threads') }} @@ -30,8 +30,8 @@
  • {{ $t('settings.hide_muted_posts') }} @@ -40,15 +40,18 @@
  • - + {{ $t('settings.hide_post_stats') }}
  • {{ $t('settings.replies_in_timeline') }} @@ -87,7 +90,10 @@ -
    +

    {{ $t('settings.user_profiles') }}

    • diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index 9e4e282f58..fe7deb6ee9 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -41,7 +41,7 @@ const GeneralTab = { ChoiceSetting, InterfaceLanguageSwitcher, ScopeSelector, - ServerSideIndicator, + ServerSideIndicator }, computed: { postFormats () { diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index 7944b03cf2..d4196c3def 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -27,7 +27,7 @@
    • - + {{ $t('settings.streaming') }}
      • - + {{ $t('settings.useStreamingApi') }}
        @@ -54,22 +57,34 @@
      • - + {{ $t('settings.virtual_scrolling') }}
      • - + {{ $t('settings.always_show_post_button') }}
      • - + {{ $t('settings.autohide_floating_post_button') }}
      • - + {{ $t('settings.hide_shoutbox') }}
      • @@ -84,18 +99,28 @@
      • - + {{ $t('settings.emoji_reactions_on_timeline') }}
      • - + {{ $t('settings.no_rich_text_description') }}
      • {{ $t('settings.attachments') }}

      • - + {{ $t('settings.use_contain_fit') }}
      • @@ -107,7 +132,8 @@
        • {{ $t('settings.preload_images') }} @@ -115,7 +141,8 @@
        • {{ $t('settings.use_one_click_nsfw') }} @@ -123,7 +150,10 @@
      • - + {{ $t('settings.loop_video') }}
        • {{ $t('settings.loop_video_silent_only') }} @@ -147,7 +178,10 @@
      • - + {{ $t('settings.play_videos_in_modal') }}
      • @@ -165,51 +199,74 @@ class="setting-list suboptions" >
      • - + {{ $t('settings.mention_link_show_tooltip') }}
      -
    • - - {{ $t('settings.use_at_icon') }} - -
    • -
    • - - {{ $t('settings.mention_link_show_avatar') }} - -
    • -
    • - - {{ $t('settings.mention_link_fade_domain') }} - -
    • -
    • - - {{ $t('settings.mention_link_bolden_you') }} - -
    • -

      {{ $t('settings.fun') }}

    • - + + {{ $t('settings.use_at_icon') }} + +
    • +
    • + + {{ $t('settings.mention_link_show_avatar') }} + +
    • +
    • + + {{ $t('settings.mention_link_fade_domain') }} + +
    • +
    • + + {{ $t('settings.mention_link_bolden_you') }} + +
    • +

      + {{ $t('settings.fun') }} +

      +
    • + {{ $t('settings.greentext') }}
    • - + {{ $t('settings.show_yous') }}
    -
    +

    {{ $t('settings.composing') }}

    • - + {{ $t('settings.scope_copy') }}
    • - + {{ $t('settings.subject_input_always_show') }}
    • @@ -255,22 +318,34 @@
    • - + {{ $t('settings.minimal_scopes_mode') }}
    • - + {{ $t('settings.always_show_post_button') }}
    • - + {{ $t('settings.autohide_floating_post_button') }}
    • - + {{ $t('settings.pad_emoji') }}
    • diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue index 5e9ce91ec9..86be609587 100644 --- a/src/components/settings_modal/tabs/notifications_tab.vue +++ b/src/components/settings_modal/tabs/notifications_tab.vue @@ -46,16 +46,25 @@
    -
    +

    {{ $t('settings.notification_setting_privacy') }}

    • - + {{ $t('settings.enable_web_push_notifications') }}
    • - + {{ $t('settings.notification_setting_hide_notification_contents') }}
    • diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue index 699fdcf494..e00f6e5b99 100644 --- a/src/components/settings_modal/tabs/profile_tab.vue +++ b/src/components/settings_modal/tabs/profile_tab.vue @@ -101,7 +101,8 @@

      {{ $t('settings.avatar_size_instruction') }}

      -
      + @@ -243,7 +244,7 @@ + > {{ $t('settings.hide_followers_count_description') }} @@ -261,7 +262,7 @@ + > {{ $t('settings.hide_follows_count_description') }} From 77b55a559be26e431f68188058c7900c2e16682c Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 28 Feb 2022 19:42:02 +0200 Subject: [PATCH 014/128] fix placeholder attachments opening new tab --- src/components/attachment/attachment.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index 5917375984..d1d43ac345 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -12,6 +12,7 @@ :href="attachment.url" :alt="attachment.description" :title="attachment.description" + @click.prevent > {{ nsfw ? "NSFW / " : "" }}{{ edit ? '' : placeholderName }} From fe0ed7e8f0941195547b924f99f6c0be707bf964 Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Mon, 28 Feb 2022 23:07:20 +0300 Subject: [PATCH 015/128] Mute bot posts --- src/components/settings_modal/tabs/filtering_tab.vue | 5 +++++ src/components/status/status.js | 10 +++++++++- src/components/timeline/timeline_quick_settings.js | 7 +++++++ src/components/timeline/timeline_quick_settings.vue | 9 +++++++++ src/i18n/en.json | 1 + src/modules/config.js | 1 + src/modules/instance.js | 1 + 7 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue index 50ee20e096..87567befac 100644 --- a/src/components/settings_modal/tabs/filtering_tab.vue +++ b/src/components/settings_modal/tabs/filtering_tab.vue @@ -37,6 +37,11 @@
    +
  • + + {{ $t('settings.mute_bot_posts') }} + +
  • {{ $t('settings.hide_post_stats') }} diff --git a/src/components/status/status.js b/src/components/status/status.js index d8f94926e5..ba85114a68 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -166,6 +166,9 @@ const Status = { muteWordHits () { return muteWordHits(this.status, this.muteWords) }, + botStatus () { + return this.status.user.bot + }, mentionsLine () { if (!this.headTailLinks) return [] const writtenSet = new Set(this.headTailLinks.writtenMentions.map(_ => _.url)) @@ -191,7 +194,9 @@ const Status = { // Thread is muted status.thread_muted || // Wordfiltered - this.muteWordHits.length > 0 + this.muteWordHits.length > 0 || + // bot status + (this.muteBotStatuses && this.botStatus) return !this.unmuted && !this.shouldNotMute && reasonsToMute }, userIsMuted () { @@ -293,6 +298,9 @@ const Status = { hidePostStats () { return this.mergedConfig.hidePostStats }, + muteBotStatuses () { + return this.mergedConfig.muteBotStatuses + }, currentUser () { return this.$store.state.users.currentUser }, diff --git a/src/components/timeline/timeline_quick_settings.js b/src/components/timeline/timeline_quick_settings.js index 7b4931ce6a..92d5ac1464 100644 --- a/src/components/timeline/timeline_quick_settings.js +++ b/src/components/timeline/timeline_quick_settings.js @@ -53,6 +53,13 @@ const TimelineQuickSettings = { const value = !this.hideMutedPosts this.$store.dispatch('setOption', { name: 'hideFilteredStatuses', value }) } + }, + muteBotStatuses: { + get () { return this.mergedConfig.muteBotStatuses }, + set () { + const value = !this.muteBotStatuses + this.$store.dispatch('setOption', { name: 'muteBotStatuses', value }) + } } } } diff --git a/src/components/timeline/timeline_quick_settings.vue b/src/components/timeline/timeline_quick_settings.vue index 98996ebda2..4d67e06b6d 100644 --- a/src/components/timeline/timeline_quick_settings.vue +++ b/src/components/timeline/timeline_quick_settings.vue @@ -39,6 +39,15 @@ class="dropdown-divider" />
  • + @@ -161,7 +161,10 @@ :href="attachment.url" target="_blank" > - +

    {{ localDescription }}

    diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue index f9cad8a9c1..f2e1b5ce51 100644 --- a/src/components/gallery/gallery.vue +++ b/src/components/gallery/gallery.vue @@ -26,8 +26,8 @@ :size="size" :editable="editable" :remove="removeAttachment" - :shiftUp="!(attachmentIndex === 0 && rowIndex === 0) && shiftUpAttachment" - :shiftDn="!(attachmentIndex === row.items.length - 1 && rowIndex === rows.length - 1) && shiftDnAttachment" + :shift-up="!(attachmentIndex === 0 && rowIndex === 0) && shiftUpAttachment" + :shift-dn="!(attachmentIndex === row.items.length - 1 && rowIndex === rows.length - 1) && shiftDnAttachment" :edit="editAttachment" :description="descriptions && descriptions[attachment.id]" :hide-description="size === 'small' || tooManyAttachments && hidingLong" From 0582f19e7c2c6f916b427d5ecfbbb571178ce841 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 6 Aug 2021 20:18:27 -0400 Subject: [PATCH 019/128] Add tree-style thread display --- src/components/conversation/conversation.js | 60 ++++++++++++++++++- src/components/conversation/conversation.vue | 59 ++++++++++++------ .../settings_modal/tabs/general_tab.js | 5 ++ .../settings_modal/tabs/general_tab.vue | 9 +++ src/components/thread_tree/thread_tree.js | 52 ++++++++++++++++ src/components/thread_tree/thread_tree.vue | 55 +++++++++++++++++ src/modules/config.js | 4 +- src/modules/instance.js | 1 + 8 files changed, 224 insertions(+), 21 deletions(-) create mode 100644 src/components/thread_tree/thread_tree.js create mode 100644 src/components/thread_tree/thread_tree.vue diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 069c0b4077..e663ba157e 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -1,5 +1,8 @@ import { reduce, filter, findIndex, clone, get } from 'lodash' import Status from '../status/status.vue' +import ThreadTree from '../thread_tree/thread_tree.vue' + +const debug = console.log const sortById = (a, b) => { const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id @@ -53,6 +56,15 @@ const conversation = { } }, computed: { + displayStyle () { + return this.$store.state.config.conversationDisplay + }, + isTreeView () { + return this.displayStyle === 'tree' + }, + isLinearView () { + return this.displayStyle === 'linear' + }, hideStatus () { if (this.$refs.statusComponent && this.$refs.statusComponent[0]) { return this.virtualHidden && this.$refs.statusComponent[0].suspendable @@ -90,6 +102,49 @@ const conversation = { return sortAndFilterConversation(conversation, this.status) }, + threadTree () { + const reverseLookupTable = this.conversation.reduce((table, status, index) => { + table[status.id] = index + return table + }, {}) + + const threads = this.conversation.reduce((a, cur) => { + const id = cur.id + a.forest[id] = this.getReplies(id) + .map(s => s.id) + .sort((a, b) => reverseLookupTable[a] - reverseLookupTable[b]) + + a.topLevel = a.topLevel.filter(k => a.forest[id].contains(k)) + return a + }, { + forest: {}, + topLevel: this.conversation.map(s => s.id) + }) + + const walk = (forest, topLevel, depth = 0, processed = {}) => topLevel.map(id => { + if (processed[id]) { + return [] + } + + processed[id] = true + return [{ + status: this.conversation[reverseLookupTable[id]], + id, + depth + }, walk(forest, forest[child], depth + 1, processed)].reduce((a, b) => a.concat(b), []) + }).reduce((a, b) => a.concat(b), []) + + const linearized = walk(threads.forest, threads.topLevel) + + return linearized + }, + topLevel () { + const topLevel = this.conversation.reduce((tl, cur) => + tl.filter(k => this.getReplies(cur.id).map(v => v.id).indexOf(k.id) === -1), this.conversation) + debug("toplevel =", topLevel) + debug("toplevel =", topLevel) + return topLevel + }, replies () { let i = 1 // eslint-disable-next-line camelcase @@ -109,7 +164,7 @@ const conversation = { }, {}) }, isExpanded () { - return this.expanded || this.isPage + return !!(this.expanded || this.isPage) }, hiddenStyle () { const height = (this.status && this.status.virtualHeight) || '120px' @@ -117,7 +172,8 @@ const conversation = { } }, components: { - Status + Status, + ThreadTree }, watch: { statusId (newVal, oldVal) { diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 3fb26d9239..cea5f88f08 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -18,24 +18,47 @@ {{ $t('timeline.collapse') }}
    - +
    + +
    +
    + +
    ({ + key: mode, + value: mode, + label: this.$t(`settings.conversation_display_${mode}`) + })), mentionLinkDisplayOptions: ['short', 'full_for_remote', 'full'].map(mode => ({ key: mode, value: mode, diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index eba3b2680d..8951c021dc 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -152,6 +152,15 @@ {{ $t('settings.show_yous') }} +
  • + + {{ $t('settings.conversation_display') }} + +
  • { + table[status.id] = index + return table + }, {}) + }, + currentReplies () { + debug('status:', this.status) + debug('getReplies:', this.getReplies(this.status.id)) + return this.getReplies(this.status.id).map(({ id }) => this.statusById(id)) + }, + }, + methods: { + statusById (id) { + return this.conversation[this.reverseLookupTable[id]] + }, + collapseThread () { + }, + showThread () { + }, + showAllSubthreads () { + } + } +} + +export default ThreadTree diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue new file mode 100644 index 0000000000..8256eee6c3 --- /dev/null +++ b/src/components/thread_tree/thread_tree.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/modules/config.js b/src/modules/config.js index 20979174a6..ec75dbfb59 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -12,6 +12,7 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0] export const multiChoiceProperties = [ 'postContentType', 'subjectLineBehavior', + 'conversationDisplay', // tree | linear 'mentionLinkDisplay' // short | full_for_remote | full ] @@ -81,7 +82,8 @@ export const defaultState = { hidePostStats: undefined, // instance default hideUserStats: undefined, // instance default virtualScrolling: undefined, // instance default - sensitiveByDefault: undefined // instance default + sensitiveByDefault: undefined, // instance default + conversationDisplay: undefined // instance default } // caching the instance default properties diff --git a/src/modules/instance.js b/src/modules/instance.js index 1abd784f06..a160a5570f 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -53,6 +53,7 @@ const defaultState = { theme: 'pleroma-dark', virtualScrolling: true, sensitiveByDefault: false, + conversationDisplay: 'tree', // Nasty stuff customEmoji: [], From 0f2fd8a3523e9e2cd1ca6fe287eb7304895f2cba Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 00:33:06 -0400 Subject: [PATCH 020/128] Implement thread folding/expanding --- src/components/conversation/conversation.js | 105 +++++++++++++++++-- src/components/conversation/conversation.vue | 7 ++ src/components/status/status.js | 22 +++- src/components/status/status.vue | 13 +++ src/components/thread_tree/thread_tree.js | 12 ++- src/components/thread_tree/thread_tree.vue | 35 ++++++- 6 files changed, 180 insertions(+), 14 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index e663ba157e..0abb7abd23 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -38,7 +38,8 @@ const conversation = { data () { return { highlight: null, - expanded: false + expanded: false, + threadDisplayStatusObject: {} // id => 'showing' | 'hidden' } }, props: [ @@ -56,6 +57,9 @@ const conversation = { } }, computed: { + maxDepthToShowByDefault () { + return 4 + }, displayStyle () { return this.$store.state.config.conversationDisplay }, @@ -112,15 +116,14 @@ const conversation = { const id = cur.id a.forest[id] = this.getReplies(id) .map(s => s.id) - .sort((a, b) => reverseLookupTable[a] - reverseLookupTable[b]) - a.topLevel = a.topLevel.filter(k => a.forest[id].contains(k)) return a }, { forest: {}, - topLevel: this.conversation.map(s => s.id) }) + debug('threads = ', threads) + const walk = (forest, topLevel, depth = 0, processed = {}) => topLevel.map(id => { if (processed[id]) { return [] @@ -131,18 +134,63 @@ const conversation = { status: this.conversation[reverseLookupTable[id]], id, depth - }, walk(forest, forest[child], depth + 1, processed)].reduce((a, b) => a.concat(b), []) + }, walk(forest, forest[id], depth + 1, processed)].reduce((a, b) => a.concat(b), []) }).reduce((a, b) => a.concat(b), []) - const linearized = walk(threads.forest, threads.topLevel) + const linearized = walk(threads.forest, this.topLevel.map(k => k.id)) return linearized }, + replyIds () { + return this.conversation.map(k => k.id) + .reduce((res, id) => { + res[id] = (this.replies[id] || []).map(k => k.id) + return res + }, {}) + }, + totalReplyCount () { + debug('replyIds=', this.replyIds) + const sizes = {} + const subTreeSizeFor = (id) => { + if (sizes[id]) { + return sizes[id] + } + sizes[id] = 1 + this.replyIds[id].map(cid => subTreeSizeFor(cid)).reduce((a, b) => a + b, 0) + return sizes[id] + } + this.conversation.map(k => k.id).map(subTreeSizeFor) + debug('totalReplyCount=', sizes) + return Object.keys(sizes).reduce((res, id) => { + res[id] = sizes[id] - 1 // exclude itself + return res + }, {}) + }, + totalReplyDepth () { + const depths = {} + const subTreeDepthFor = (id) => { + if (depths[id]) { + return depths[id] + } + depths[id] = 1 + this.replyIds[id].map(cid => subTreeDepthFor(cid)).reduce((a, b) => a > b ? a : b, 0) + return depths[id] + } + this.conversation.map(k => k.id).map(subTreeDepthFor) + return Object.keys(depths).reduce((res, id) => { + res[id] = depths[id] - 1 // exclude itself + return res + }, {}) + }, + depths () { + debug('threadTree', this.threadTree) + return this.threadTree.reduce((a, k) => { + a[k.id] = k.depth + return a + }, {}) + }, topLevel () { const topLevel = this.conversation.reduce((tl, cur) => tl.filter(k => this.getReplies(cur.id).map(v => v.id).indexOf(k.id) === -1), this.conversation) debug("toplevel =", topLevel) - debug("toplevel =", topLevel) return topLevel }, replies () { @@ -169,6 +217,25 @@ const conversation = { hiddenStyle () { const height = (this.status && this.status.virtualHeight) || '120px' return this.virtualHidden ? { height } : {} + }, + threadDisplayStatus () { + return this.conversation.reduce((a, k) => { + const id = k.id + const depth = this.depths[id] + const status = (() => { + if (this.threadDisplayStatusObject[id]) { + return this.threadDisplayStatusObject[id] + } + if (depth <= this.maxDepthToShowByDefault) { + return 'showing' + } else { + return 'hidden' + } + })() + + a[id] = status + return a + }, {}) } }, components: { @@ -235,6 +302,30 @@ const conversation = { getConversationId (statusId) { const status = this.$store.state.statuses.allStatusesObject[statusId] return get(status, 'retweeted_status.statusnet_conversation_id', get(status, 'statusnet_conversation_id')) + }, + setThreadDisplay (id, nextStatus) { + this.threadDisplayStatusObject = { + ...this.threadDisplayStatusObject, + [id]: nextStatus + } + }, + toggleThreadDisplay (id) { + const depth = this.depths[id] + debug('depth = ', depth) + debug( + 'threadDisplayStatus = ', this.threadDisplayStatus, + 'threadDisplayStatusObject = ', this.threadDisplayStatusObject) + const curStatus = this.threadDisplayStatus[id] + const nextStatus = curStatus === 'showing' ? 'hidden' : 'showing' + debug('toggling', id, 'to', nextStatus) + this.setThreadDisplay(id, nextStatus) + }, + setThreadDisplayRecursively (id, nextStatus) { + this.setThreadDisplay(id, nextStatus) + this.getReplies(id).map(k => k.id).map(id => this.setThreadDisplayRecursively(id, nextStatus)) + }, + showThreadRecursively (id) { + this.setThreadDisplayRecursively(id, 'showing') } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index cea5f88f08..783cc8941c 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -23,6 +23,7 @@ v-for="status in topLevel" :key="status.id" ref="statusComponent" + :depth="0" :status="status" :in-profile="inProfile" @@ -37,6 +38,12 @@ :get-highlight="getHighlight" :set-highlight="setHighlight" :toggle-expanded="toggleExpanded" + + :toggle-thread-display="toggleThreadDisplay" + :thread-display-status="threadDisplayStatus" + :show-thread-recursively="showThreadRecursively" + :total-reply-count="totalReplyCount" + :total-reply-depth="totalReplyDepth" />
  • diff --git a/src/components/status/status.js b/src/components/status/status.js index d8f94926e5..9d423631a0 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -35,7 +35,9 @@ import { faStar, faEyeSlash, faEye, - faThumbtack + faThumbtack, + faAngleDoubleUp, + faAngleDoubleDown } from '@fortawesome/free-solid-svg-icons' library.add( @@ -52,7 +54,9 @@ library.add( faEllipsisH, faEyeSlash, faEye, - faThumbtack + faThumbtack, + faAngleDoubleUp, + faAngleDoubleDown ) const Status = { @@ -89,7 +93,10 @@ const Status = { 'inlineExpanded', 'showPinned', 'inProfile', - 'profileUserId' + 'profileUserId', + + 'controlledThreadDisplayStatus', + 'controlledToggleThreadDisplay' ], data () { return { @@ -304,6 +311,12 @@ const Status = { }, isSuspendable () { return !this.replying && this.mediaPlaying.length === 0 + }, + inThreadForest () { + return !!this.controlledThreadDisplayStatus + }, + threadShowing () { + return this.controlledThreadDisplayStatus === 'showing' } }, methods: { @@ -353,6 +366,9 @@ const Status = { }, setHeadTailLinks (headTailLinks) { this.headTailLinks = headTailLinks + }, + toggleThreadDisplay () { + this.controlledToggleThreadDisplay() } }, watch: { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 3bb29db665..2ebf56385f 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -219,6 +219,19 @@ class="fa-scale-110" /> +
    this.statusById(id)) }, + threadShowing () { + return this.threadDisplayStatus[this.status.id] === 'showing' + } }, methods: { statusById (id) { diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index 8256eee6c3..9f59158511 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -13,18 +13,23 @@ :replies="getReplies(status.id)" :in-profile="inProfile" :profile-user-id="profileUserId" - class="conversation-status status-fadein panel-body" + class="conversation-status conversation-status-treeview status-fadein panel-body" + + :controlled-thread-display-status="threadDisplayStatus[status.id]" + :controlled-toggle-thread-display="() => toggleThreadDisplay(status.id)" + @goto="setHighlight" @toggleExpanded="toggleExpanded" />
    +
    + +
    From 414ee55957851a30b9455e47e1e6d258743953a3 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 01:05:26 -0400 Subject: [PATCH 021/128] Make show full thread message account for numbers --- src/components/thread_tree/thread_tree.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index 9f59158511..bbdda6fbf1 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -60,7 +60,7 @@ class="button-unstyled -link thread-tree-show-replies-button" @click="showThreadRecursively(status.id)" > - {{ $t('status.thread_show_full', { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }) }} + {{ $tc('status.thread_show_full', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }) }}
    From cd0f6a4f7820b27e3d776e598c842328ad64ab18 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 01:06:16 -0400 Subject: [PATCH 022/128] Add English translations for message threading --- src/i18n/en.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index 8eb7fcc6ea..6be2d9aa08 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -465,6 +465,9 @@ "subject_line_email": "Like email: \"re: subject\"", "subject_line_mastodon": "Like mastodon: copy as is", "subject_line_noop": "Do not copy", + "conversation_display": "Conversation display style", + "conversation_display_tree": "Tree-style", + "conversation_display_linear": "Linear-style", "post_status_content_type": "Post status content type", "sensitive_by_default": "Mark posts as sensitive by default", "stop_gifs": "Pause animated images until you hover on them", @@ -747,7 +750,10 @@ "attachment_stop_flash": "Stop Flash player", "move_up": "Shift attachment left", "move_down": "Shift attachment right", - "open_gallery": "Open gallery" + "open_gallery": "Open gallery", + "thread_hide": "Hide this thread", + "thread_show": "Show this thread", + "thread_show_full": "Show everything under this thread ({numStatus} status in total, max depth {depth}) | Show everything under this thread ({numStatus} statuses in total, max depth {depth})" }, "user_card": { "approve": "Approve", From ace1f5067c90be2fa0b8da22d39b0e2c88f590fb Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 10:28:45 -0400 Subject: [PATCH 023/128] Make status display controlled --- src/components/conversation/conversation.js | 37 +++++++++++- src/components/conversation/conversation.vue | 3 + src/components/status/status.js | 9 ++- src/components/status/status.vue | 6 ++ .../status_content/status_content.js | 59 ++++++++++++++++++- src/components/thread_tree/thread_tree.js | 11 +++- src/components/thread_tree/thread_tree.vue | 10 ++++ 7 files changed, 130 insertions(+), 5 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 0abb7abd23..6fc86b2c24 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -2,7 +2,8 @@ import { reduce, filter, findIndex, clone, get } from 'lodash' import Status from '../status/status.vue' import ThreadTree from '../thread_tree/thread_tree.vue' -const debug = console.log +// const debug = console.log +const debug = () => {} const sortById = (a, b) => { const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id @@ -39,7 +40,8 @@ const conversation = { return { highlight: null, expanded: false, - threadDisplayStatusObject: {} // id => 'showing' | 'hidden' + threadDisplayStatusObject: {}, // id => 'showing' | 'hidden' + statusContentPropertiesObject: {} } }, props: [ @@ -236,6 +238,25 @@ const conversation = { a[id] = status return a }, {}) + }, + statusContentProperties () { + return this.conversation.reduce((a, k) => { + const id = k.id + const depth = this.depths[id] + const props = (() => { + if (this.statusContentPropertiesObject[id]) { + return this.statusContentPropertiesObject[id] + } + return { + showingTall: false, + expandingSubject: false, + showingLongSubject: false, + } + })() + + a[id] = props + return a + }, {}) } }, components: { @@ -326,6 +347,18 @@ const conversation = { }, showThreadRecursively (id) { this.setThreadDisplayRecursively(id, 'showing') + }, + setStatusContentProperty (id, name, value) { + this.statusContentPropertiesObject = { + ...this.statusContentPropertiesObject, + [id]: { + ...this.statusContentPropertiesObject[id], + [name]: value + } + } + }, + toggleStatusContentProperty (id, name) { + this.setStatusContentProperty(id, name, !this.statusContentProperties[id][name]) } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 783cc8941c..08cb72d042 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -44,6 +44,9 @@ :show-thread-recursively="showThreadRecursively" :total-reply-count="totalReplyCount" :total-reply-depth="totalReplyDepth" + :status-content-properties="statusContentProperties" + :set-status-content-property="setStatusContentProperty" + :toggle-status-content-property="toggleStatusContentProperty" />
    diff --git a/src/components/status/status.js b/src/components/status/status.js index 9d423631a0..d5ee7d4e8b 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -96,7 +96,14 @@ const Status = { 'profileUserId', 'controlledThreadDisplayStatus', - 'controlledToggleThreadDisplay' + 'controlledToggleThreadDisplay', + + 'controlledShowingTall', + 'controlledToggleShowingTall', + 'controlledExpandingSubject', + 'controlledToggleExpandingSubject', + 'controlledShowingLongSubject', + 'controlledToggleShowingLongSubject' ], data () { return { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 2ebf56385f..8fdebe44be 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -319,6 +319,12 @@ :no-heading="noHeading" :highlight="highlight" :focused="isFocused" + :controlled-showing-tall="controlledShowingTall" + :controlled-expanding-subject="controlledExpandingSubject" + :controlled-showing-long-subject="controlledShowingLongSubject" + :controlled-toggle-showing-tall="controlledToggleShowingTall" + :controlled-toggle-expanding-subject="controlledToggleExpandingSubject" + :controlled-toggle-showing-long-subject="controlledToggleShowingLongSubject" @mediaplay="addMediaPlaying($event)" @mediapause="removeMediaPlaying($event)" @parseReady="setHeadTailLinks" diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js index dec8914a32..65ec85c4a0 100644 --- a/src/components/status_content/status_content.js +++ b/src/components/status_content/status_content.js @@ -23,6 +23,31 @@ library.add( faPollH ) +const camelCase = name => name.charAt(0).toUpperCase() + name.slice(1) + +const controlledOrUncontrolledGetters = list => list.reduce((res, name) => { + const camelized = camelCase(name) + const toggle = `controlledToggle${camelized}` + const controlledName = `controlled${camelized}` + const uncontrolledName = `uncontrolled${camelized}` + res[name] = function () { + return this[toggle] ? this[controlledName] : this[uncontrolledName] + } + return res +}, {}) + +const controlledOrUncontrolledToggle = (obj, name) => { + const camelized = camelCase(name) + const toggle = `controlledToggle${camelized}` + const controlledName = `controlled${camelized}` + const uncontrolledName = `uncontrolled${camelized}` + if (obj[toggle]) { + obj[toggle]() + } else { + obj[uncontrolledName] = !obj[uncontrolledName] + } +} + const StatusContent = { name: 'StatusContent', props: [ @@ -31,9 +56,22 @@ const StatusContent = { 'focused', 'noHeading', 'fullContent', - 'singleLine' + 'singleLine', + 'controlledShowingTall', + 'controlledExpandingSubject', + 'controlledToggleShowingTall', + 'controlledToggleExpandingSubject' ], + data () { + return { + uncontrolledShowingTall: this.fullContent || (this.inConversation && this.focused), + uncontrolledShowingLongSubject: false, + // not as computed because it sets the initial state which will be changed later + uncontrolledExpandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject + } + }, computed: { + ...controlledOrUncontrolledGetters(['showingTall', 'expandingSubject', 'showingLongSubject']), hideAttachments () { return (this.mergedConfig.hideAttachments && !this.inConversation) || (this.mergedConfig.hideAttachmentsInConv && this.inConversation) @@ -71,6 +109,25 @@ const StatusContent = { Gallery, LinkPreview, StatusBody + }, + methods: { + toggleShowingTall () { + controlledOrUncontrolledToggle(this, 'showingTall') + }, + toggleExpandingSubject () { + controlledOrUncontrolledToggle(this, 'expandingSubject') + }, + toggleShowMore () { + if (this.mightHideBecauseTall) { + this.toggleShowingTall() + } else if (this.mightHideBecauseSubject) { + this.toggleExpandingSubject() + } + }, + setMedia () { + const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments + return () => this.$store.dispatch('setMedia', attachments) + } } } diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js index 88b6010972..46245bdfc0 100644 --- a/src/components/thread_tree/thread_tree.js +++ b/src/components/thread_tree/thread_tree.js @@ -28,7 +28,10 @@ const ThreadTree = { threadDisplayStatus: Object, showThreadRecursively: Function, totalReplyCount: Object, - totalReplyDepth: Object + totalReplyDepth: Object, + statusContentProperties: Object, + setStatusContentProperty: Function, + toggleStatusContentProperty: Function }, computed: { reverseLookupTable () { @@ -44,6 +47,9 @@ const ThreadTree = { }, threadShowing () { return this.threadDisplayStatus[this.status.id] === 'showing' + }, + currentProp () { + return this.statusContentProperties[this.status.id] } }, methods: { @@ -55,6 +61,9 @@ const ThreadTree = { showThread () { }, showAllSubthreads () { + }, + toggleCurrentProp (name) { + this.toggleStatusContentProperty(this.status.id, name) } } } diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index bbdda6fbf1..d7077bfded 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -18,6 +18,13 @@ :controlled-thread-display-status="threadDisplayStatus[status.id]" :controlled-toggle-thread-display="() => toggleThreadDisplay(status.id)" + :controlled-showing-tall="currentProp.showingTall" + :controlled-expanding-subject="currentProp.expandingSubject" + :controlled-showing-long-subject="currentProp.showingLongSubject" + :controlled-toggle-showing-tall="() => toggleCurrentProp('ShowingTall')" + :controlled-toggle-expanding-subject="() => toggleCurrentProp('expandingSubject')" + :controlled-toggle-showing-long-subject="() => toggleCurrentProp('showingLongSubject')" + @goto="setHighlight" @toggleExpanded="toggleExpanded" /> @@ -50,6 +57,9 @@ :show-thread-recursively="showThreadRecursively" :total-reply-count="totalReplyCount" :total-reply-depth="totalReplyDepth" + :status-content-properties="statusContentProperties" + :set-status-content-property="setStatusContentProperty" + :toggle-status-content-property="toggleStatusContentProperty" />
    Date: Sat, 7 Aug 2021 11:59:10 -0400 Subject: [PATCH 024/128] Support diving into one status in a conversation --- src/components/conversation/conversation.js | 32 +++++++++++++++++++- src/components/conversation/conversation.vue | 30 +++++++++++++++++- src/components/status/status.js | 13 +++++--- src/components/status/status.vue | 14 ++++++++- src/components/thread_tree/thread_tree.js | 3 +- src/components/thread_tree/thread_tree.vue | 15 ++++++--- 6 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 6fc86b2c24..584f310ce3 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -2,6 +2,17 @@ import { reduce, filter, findIndex, clone, get } from 'lodash' import Status from '../status/status.vue' import ThreadTree from '../thread_tree/thread_tree.vue' +import { library } from '@fortawesome/fontawesome-svg-core' +import { + faAngleDoubleDown, + faAngleDoubleLeft +} from '@fortawesome/free-solid-svg-icons' + +library.add( + faAngleDoubleDown, + faAngleDoubleLeft +) + // const debug = console.log const debug = () => {} @@ -41,7 +52,8 @@ const conversation = { highlight: null, expanded: false, threadDisplayStatusObject: {}, // id => 'showing' | 'hidden' - statusContentPropertiesObject: {} + statusContentPropertiesObject: {}, + diveRoot: null } }, props: [ @@ -195,6 +207,18 @@ const conversation = { debug("toplevel =", topLevel) return topLevel }, + showingTopLevel () { + if (this.diveRoot) { + return [this.conversation.filter(k => this.diveRoot === k.id)[0]] + } + return this.topLevel + }, + diveDepth () { + return this.diveRoot ? this.depths[this.diveRoot] : 0 + }, + diveMode () { + return !!this.diveRoot + }, replies () { let i = 1 // eslint-disable-next-line camelcase @@ -359,6 +383,12 @@ const conversation = { }, toggleStatusContentProperty (id, name) { this.setStatusContentProperty(id, name, !this.statusContentProperties[id][name]) + }, + diveIntoStatus (id) { + this.diveRoot = id + }, + undive () { + this.diveRoot = null } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 08cb72d042..84e8d8b2ad 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -18,9 +18,22 @@ {{ $t('timeline.collapse') }}
    +
    + + + +
    @@ -65,6 +79,16 @@ :in-profile="inProfile" :profile-user-id="profileUserId" class="conversation-status status-fadein panel-body" + + :toggle-thread-display="toggleThreadDisplay" + :thread-display-status="threadDisplayStatus" + :show-thread-recursively="showThreadRecursively" + :total-reply-count="totalReplyCount" + :total-reply-depth="totalReplyDepth" + :status-content-properties="statusContentProperties" + :set-status-content-property="setStatusContentProperty" + :toggle-status-content-property="toggleStatusContentProperty" + @goto="setHighlight" @toggleExpanded="toggleExpanded" /> @@ -82,6 +106,10 @@ @import '../../_variables.scss'; .Conversation { + .conversation-undive-box { + padding: 1em; + } + .conversation-undive-box, .conversation-status { border-bottom-width: 1px; border-bottom-style: solid; diff --git a/src/components/status/status.js b/src/components/status/status.js index d5ee7d4e8b..f119f42e55 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -36,8 +36,9 @@ import { faEyeSlash, faEye, faThumbtack, - faAngleDoubleUp, - faAngleDoubleDown + faChevronUp, + faChevronDown, + faAngleDoubleRight } from '@fortawesome/free-solid-svg-icons' library.add( @@ -55,8 +56,9 @@ library.add( faEyeSlash, faEye, faThumbtack, - faAngleDoubleUp, - faAngleDoubleDown + faChevronUp, + faChevronDown, + faAngleDoubleRight ) const Status = { @@ -103,7 +105,8 @@ const Status = { 'controlledExpandingSubject', 'controlledToggleExpandingSubject', 'controlledShowingLongSubject', - 'controlledToggleShowingLongSubject' + 'controlledToggleShowingLongSubject', + 'dive' ], data () { return { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 8fdebe44be..c2df602187 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -229,7 +229,19 @@ + + diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js index 46245bdfc0..3e8eedb143 100644 --- a/src/components/thread_tree/thread_tree.js +++ b/src/components/thread_tree/thread_tree.js @@ -31,7 +31,8 @@ const ThreadTree = { totalReplyDepth: Object, statusContentProperties: Object, setStatusContentProperty: Function, - toggleStatusContentProperty: Function + toggleStatusContentProperty: Function, + dive: Function }, computed: { reverseLookupTable () { diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index d7077bfded..adf7bcdf74 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -24,6 +24,7 @@ :controlled-toggle-showing-tall="() => toggleCurrentProp('ShowingTall')" :controlled-toggle-expanding-subject="() => toggleCurrentProp('expandingSubject')" :controlled-toggle-showing-long-subject="() => toggleCurrentProp('showingLongSubject')" + :dive="dive ? () => dive(status.id) : undefined" @goto="setHighlight" @toggleExpanded="toggleExpanded" @@ -60,18 +61,24 @@ :status-content-properties="statusContentProperties" :set-status-content-property="setStatusContentProperty" :toggle-status-content-property="toggleStatusContentProperty" + :dive="dive" />
    - + + + {{ $tc('status.thread_show_full', totalReplyCount[status.id], { numStatus: totalReplyCount[status.id], depth: totalReplyDepth[status.id] }) }} + +
    From 31c4300456192582786a7f5da420f7ce834a3e2b Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 11:59:41 -0400 Subject: [PATCH 025/128] Add English translations for diving --- src/i18n/en.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index 6be2d9aa08..7f92e95a32 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -753,7 +753,10 @@ "open_gallery": "Open gallery", "thread_hide": "Hide this thread", "thread_show": "Show this thread", - "thread_show_full": "Show everything under this thread ({numStatus} status in total, max depth {depth}) | Show everything under this thread ({numStatus} statuses in total, max depth {depth})" + "thread_show_full": "Show everything under this thread ({numStatus} status in total, max depth {depth}) | Show everything under this thread ({numStatus} statuses in total, max depth {depth})", + "thread_show_full_with_icon": "{icon} {text}", + "show_all_conversation": "{0} Show full conversation", + "show_only_conversation_under_this": "Only show replies to this status" }, "user_card": { "approve": "Approve", From d15d24c11c57ecfc49705af648b1e8f73caec51e Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 7 Aug 2021 14:11:34 -0400 Subject: [PATCH 026/128] Add dive functionality --- src/components/conversation/conversation.js | 69 +++++++++++++++++--- src/components/conversation/conversation.vue | 15 ++++- src/components/status/status.vue | 2 +- src/components/thread_tree/thread_tree.js | 3 +- src/components/thread_tree/thread_tree.vue | 5 +- 5 files changed, 80 insertions(+), 14 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 584f310ce3..b2af1d6cb4 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -5,12 +5,14 @@ import ThreadTree from '../thread_tree/thread_tree.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { faAngleDoubleDown, - faAngleDoubleLeft + faAngleDoubleLeft, + faChevronLeft } from '@fortawesome/free-solid-svg-icons' library.add( faAngleDoubleDown, - faAngleDoubleLeft + faAngleDoubleLeft, + faChevronLeft ) // const debug = console.log @@ -53,7 +55,7 @@ const conversation = { expanded: false, threadDisplayStatusObject: {}, // id => 'showing' | 'hidden' statusContentPropertiesObject: {}, - diveRoot: null + diveHistory: [] } }, props: [ @@ -120,6 +122,14 @@ const conversation = { return sortAndFilterConversation(conversation, this.status) }, + conversationDive () { + }, + statusMap () { + return this.conversation.reduce((res, s) => { + res[s.id] = s + return res + }, {}) + }, threadTree () { const reverseLookupTable = this.conversation.reduce((table, status, index) => { table[status.id] = index @@ -208,16 +218,19 @@ const conversation = { return topLevel }, showingTopLevel () { - if (this.diveRoot) { - return [this.conversation.filter(k => this.diveRoot === k.id)[0]] + if (this.canDive && this.diveRoot) { + return [this.statusMap[this.diveRoot]] } return this.topLevel }, + diveRoot () { + return this.diveHistory[this.diveHistory.length - 1] + }, diveDepth () { - return this.diveRoot ? this.depths[this.diveRoot] : 0 + return this.canDive && this.diveRoot ? this.depths[this.diveRoot] : 0 }, diveMode () { - return !!this.diveRoot + return this.canDive && !!this.diveRoot }, replies () { let i = 1 @@ -252,7 +265,7 @@ const conversation = { if (this.threadDisplayStatusObject[id]) { return this.threadDisplayStatusObject[id] } - if (depth <= this.maxDepthToShowByDefault) { + if ((depth - this.diveDepth) <= this.maxDepthToShowByDefault) { return 'showing' } else { return 'hidden' @@ -281,6 +294,9 @@ const conversation = { a[id] = props return a }, {}) + }, + canDive () { + return this.isTreeView && this.isExpanded } }, components: { @@ -310,6 +326,25 @@ const conversation = { } }, methods: { + conversationFetched () { + if (!this.isExpanded) { + return + } + + if (!this._diven) { + if (!this.threadDisplayStatus[this.statusId]) { + return + } + this._diven = true + const parentOrSelf = this.parentOrSelf(this.originalStatusId) + console.log( + 'this.threadDisplayStatus ', this.threadDisplayStatus, + 'this.statusId', this.statusId) + if (this.threadDisplayStatus[this.statusId] === 'hidden') { + this.diveIntoStatus(parentOrSelf) + } + } + }, fetchConversation () { if (this.status) { this.$store.state.api.backendInteractor.fetchConversation({ id: this.statusId }) @@ -318,6 +353,7 @@ const conversation = { this.$store.dispatch('addNewStatuses', { statuses: descendants }) this.setHighlight(this.originalStatusId) }) + .then(this.conversationFetched) } else { this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId }) .then((status) => { @@ -385,10 +421,23 @@ const conversation = { this.setStatusContentProperty(id, name, !this.statusContentProperties[id][name]) }, diveIntoStatus (id) { - this.diveRoot = id + this.diveHistory = [...this.diveHistory, id] + }, + diveBack () { + this.diveHistory = [...this.diveHistory.slice(0, this.diveHistory.length - 1)] }, undive () { - this.diveRoot = null + this.diveHistory = [] + }, + statusById (id) { + return this.statusMap[id] + }, + parentOf (id) { + const { in_reply_to_status_id: parentId } = this.statusById(id) + return parentId + }, + parentOrSelf (id) { + return this.parentOf(id) || id } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 84e8d8b2ad..99bc7bccbb 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -31,6 +31,19 @@ +
    + + + +
    diff --git a/src/components/status/status.vue b/src/components/status/status.vue index c2df602187..47d35de84c 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -220,7 +220,7 @@ />
    -
    - +
    - - -
    -
    - + + +
    +
    - - -
    -
    - + + +
    +
    + -
    -
    - +
    +
    + + @goto="setHighlight" + @toggleExpanded="toggleExpanded" + /> +
    .conversation-status { + border-top-left-radius: $fallback--panelRadius; + border-top-left-radius: var(--panelRadius, $fallback--panelRadius); + } + + /* first element in conversation body */ + &.-expanded .conversation-body { + .conversation-undive-box:nth-child(1), + & > .conversation-status:nth-child(1), + & > .thread-body:nth-child(1) > .thread-tree:nth-child(1) > .conversation-status:nth-child(1), { + border-top: none; + } + } + + /* first unexpanded statuses in timeline */ + &:first-child:not(.-expanded) { + .conversation-body { + .conversation-status { + border-top: none; + } + } + } + + /* expanded conversation in timeline */ + &.status-fadein.-expanded .thread-body { + border-left-width: 4px; + border-left-style: solid; + border-left-color: $fallback--cRed; + border-left-color: var(--cRed, $fallback--cRed); + border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; + border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); + border-bottom: 1px solid var(--border, $fallback--border); + } &.-expanded { .conversation-status:last-child { border-bottom: none; - border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; - border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); } } } diff --git a/src/components/status/status.scss b/src/components/status/status.scss index 2028ade9e0..80bc392da2 100644 --- a/src/components/status/status.scss +++ b/src/components/status/status.scss @@ -1,7 +1,5 @@ @import '../../_variables.scss'; -$status-margin: 0.75em; - .Status { min-width: 0; white-space: normal; @@ -28,13 +26,6 @@ $status-margin: 0.75em; --icon: var(--selectedPostIcon, $fallback--icon); } - &.-conversation { - border-left-width: 4px; - border-left-style: solid; - border-left-color: $fallback--cRed; - border-left-color: var(--cRed, $fallback--cRed); - } - .gravestone { padding: $status-margin; color: $fallback--faint; diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index fa1e5f8620..aafad66ea7 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -109,14 +109,16 @@ From 8780246ce5566e60520354f5a3de92eacd059f61 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 8 Aug 2021 12:55:04 -0400 Subject: [PATCH 040/128] Optimize thread border radius --- src/components/conversation/conversation.vue | 12 +++++++++--- src/components/thread_tree/thread_tree.vue | 3 +-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index cd108f695f..11b5b3d817 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -130,10 +130,13 @@ .Conversation { .conversation-undive-box { padding: $status-margin; + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: var(--border, $fallback--border); + border-radius: 0; } /* HACK: we want the border width to scale with the status *below it* */ - .conversation-undive-box, .conversation-status { border-top-width: 1px; border-top-style: solid; @@ -146,11 +149,14 @@ border-top-left-radius: var(--panelRadius, $fallback--panelRadius); } - /* first element in conversation body */ + /* first element in a reply tree, the border is supplied by reply tree instead + for radius to display properly + */ &.-expanded .conversation-body { .conversation-undive-box:nth-child(1), & > .conversation-status:nth-child(1), - & > .thread-body:nth-child(1) > .thread-tree:nth-child(1) > .conversation-status:nth-child(1), { + & > .thread-body:nth-child(1) > .thread-tree:nth-child(1) > .conversation-status:nth-child(1), + .thread-tree:nth-child(1) > .conversation-status:nth-child(1) { border-top: none; } } diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index aafad66ea7..79ba0cc55d 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -111,10 +111,9 @@ .thread-tree-replies { margin-left: $status-margin; border-left: 1px solid var(--border, $fallback--border); + border-top: 1px solid var(--border, $fallback--border); border-top-left-radius: $fallback--panelRadius; border-top-left-radius: var(--panelRadius, $fallback--panelRadius); - border-bottom-left-radius: $fallback--panelRadius; - border-bottom-left-radius: var(--panelRadius, $fallback--panelRadius); } .thread-tree-replies-hidden { From 0e4a7c3d05333d1e3c86b26b4d6b7cd296ff2582 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 8 Aug 2021 13:29:49 -0400 Subject: [PATCH 041/128] Make dive/undive button clickable along the whole row --- src/components/conversation/conversation.vue | 4 ++++ src/components/thread_tree/thread_tree.vue | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 11b5b3d817..86a227ce96 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -134,6 +134,10 @@ border-bottom-style: solid; border-bottom-color: var(--border, $fallback--border); border-radius: 0; + /* Make the button stretch along the whole row */ + display: flex; + align-items: stretch; + flex-direction: column; } /* HACK: we want the border width to scale with the status *below it* */ diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index 79ba0cc55d..46d65101db 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -119,5 +119,9 @@ .thread-tree-replies-hidden { padding: $status-margin; border-top: 1px solid var(--border, $fallback--border); + /* Make the button stretch along the whole row */ + display: flex; + align-items: stretch; + flex-direction: column; } From 4adffb483579108c0bfe7593157e9bed3571903f Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Tue, 10 Aug 2021 21:28:13 -0400 Subject: [PATCH 042/128] Remove horizontal border and thicken vertical border in a thread tree --- src/components/conversation/conversation.vue | 32 +++----------------- src/components/thread_tree/thread_tree.vue | 7 ++--- 2 files changed, 7 insertions(+), 32 deletions(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 86a227ce96..c866b98302 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -142,36 +142,14 @@ /* HACK: we want the border width to scale with the status *below it* */ .conversation-status { - border-top-width: 1px; - border-top-style: solid; - border-top-color: var(--border, $fallback--border); + border-bottom-width: 1px; + border-bottom-style: solid; + border-bottom-color: var(--border, $fallback--border); border-radius: 0; } - &.-expanded .conversation-body .thread-tree:nth-child(1) > .conversation-status { - border-top-left-radius: $fallback--panelRadius; - border-top-left-radius: var(--panelRadius, $fallback--panelRadius); - } - - /* first element in a reply tree, the border is supplied by reply tree instead - for radius to display properly - */ - &.-expanded .conversation-body { - .conversation-undive-box:nth-child(1), - & > .conversation-status:nth-child(1), - & > .thread-body:nth-child(1) > .thread-tree:nth-child(1) > .conversation-status:nth-child(1), - .thread-tree:nth-child(1) > .conversation-status:nth-child(1) { - border-top: none; - } - } - - /* first unexpanded statuses in timeline */ - &:first-child:not(.-expanded) { - .conversation-body { - .conversation-status { - border-top: none; - } - } + &.-expanded .thread-tree .conversation-status { + border-bottom: none; } /* expanded conversation in timeline */ diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index 46d65101db..dce03f27a0 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -110,15 +110,12 @@ @import '../../_variables.scss'; .thread-tree-replies { margin-left: $status-margin; - border-left: 1px solid var(--border, $fallback--border); - border-top: 1px solid var(--border, $fallback--border); - border-top-left-radius: $fallback--panelRadius; - border-top-left-radius: var(--panelRadius, $fallback--panelRadius); + border-left: 2px solid var(--border, $fallback--border); } .thread-tree-replies-hidden { padding: $status-margin; - border-top: 1px solid var(--border, $fallback--border); + //border-top: 1px solid var(--border, $fallback--border); /* Make the button stretch along the whole row */ display: flex; align-items: stretch; From e560fbc9352f9f8754451f38c5e3ecef6da96686 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Tue, 10 Aug 2021 23:58:27 -0400 Subject: [PATCH 043/128] Implement Misskey-style tree view Now the tree will be always rooted at the highlighted status, and all its ancestors shown linearly on the top. Enhancement: If an ancestor has more than one reply (i.e. it has a child that is not on current status's ancestor chain), we are given a link to root the thread at that status. --- src/components/conversation/conversation.js | 61 +++++----- src/components/conversation/conversation.vue | 118 +++++++++++++++---- 2 files changed, 124 insertions(+), 55 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index bd8315b89f..817b9bda7d 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -55,7 +55,7 @@ const conversation = { expanded: false, threadDisplayStatusObject: {}, // id => 'showing' | 'hidden' statusContentPropertiesObject: {}, - diveHistory: [] + inlineDivePosition: null } }, props: [ @@ -231,7 +231,10 @@ const conversation = { return this.topLevel }, diveRoot () { - return this.diveHistory[this.diveHistory.length - 1] + (() => {})(this.conversation) + const statusId = this.inlineDivePosition || this.statusId + const isTopLevel = !this.parentOf(statusId) + return isTopLevel ? null : statusId }, diveDepth () { return this.canDive && this.diveRoot ? this.depths[this.diveRoot] : 0 @@ -332,7 +335,6 @@ const conversation = { this.fetchConversation() } else { // if we collapse it, we should reset the dive - this._diven = false this.undive() } }, @@ -348,19 +350,6 @@ const conversation = { if (!this.isExpanded) { return } - - if (!this._diven) { - if (!this.threadDisplayStatus[this.statusId]) { - return - } - this._diven = true - const parentOrSelf = this.parentOrSelf(this.originalStatusId) - // If current status is not visible - if (this.threadDisplayStatus[parentOrSelf] === 'hidden') { - this.diveIntoStatus(parentOrSelf, /* preventScroll */ true) - this.tryScrollTo(this.statusId) - } - } }, fetchConversation () { if (this.status) { @@ -449,26 +438,15 @@ const conversation = { return this.topLevel[0] ? this.topLevel[0].id : undefined }, diveIntoStatus (id, preventScroll) { - this.diveHistory = [...this.diveHistory, id] - if (!preventScroll) { - this.goToCurrent() - } + this.tryScrollTo(id) }, - diveBack () { - const oldHighlight = this.highlight - this.diveHistory = [...this.diveHistory.slice(0, this.diveHistory.length - 1)] - if (oldHighlight) { - this.tryScrollTo(this.leastVisibleAncestor(oldHighlight)) - } + diveToTopLevel () { + this.tryScrollTo(this.topLevel[0].id) }, + // only used when we are not on a page undive () { - const oldHighlight = this.highlight - this.diveHistory = [] - if (oldHighlight) { - this.tryScrollTo(this.leastVisibleAncestor(oldHighlight)) - } else { - this.goToCurrent() - } + this.inlineDivePosition = null + this.setHighlight(this.statusId) }, tryScrollTo (id) { if (!id) { @@ -477,8 +455,9 @@ const conversation = { if (this.isPage) { // set statusId this.$router.push({ name: 'conversation', params: { id } }) + } else { + this.inlineDivePosition = id } - this.setHighlight(id) }, goToCurrent () { @@ -493,10 +472,24 @@ const conversation = { return undefined } const { in_reply_to_status_id: parentId } = status + if (!this.statusMap[parentId]) { + return undefined + } return parentId }, parentOrSelf (id) { return this.parentOf(id) || id + }, + // Ancestors of some status, from top to bottom + ancestorsOf (id) { + const ancestors = [] + let cur = this.parentOf(id) + while (cur) { + ancestors.unshift(this.statusMap[cur]) + cur = this.parentOf(cur) + } + // console.log('ancestors = ', ancestors, 'conversation = ', this.conversation.map(k => k.id), 'statusContentProperties=', this.statusContentProperties) + return ancestors } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index c866b98302..20ce54a66e 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -21,34 +21,88 @@
    -
    - - - -
    +
    +
    + +
    +
    + + + + {{ $tc('status.ancestor_follow', getReplies(status.id).length - 1, { numReplies: getReplies(status.id).length - 1 }) }} + + +
    +
    +
    +
    From d78c8e8ea4c1b5a9801552b519573037564a8f66 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Tue, 10 Aug 2021 23:59:51 -0400 Subject: [PATCH 044/128] Add English translation for Misskey-style tree view --- src/i18n/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index b8ddc3d27a..02dd7200c5 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -759,8 +759,9 @@ "thread_show_full_with_icon": "{icon} {text}", "thread_follow": "See the remaining part of this thread ({numStatus} status in total) | See the remaining part of this thread ({numStatus} statuses in total)", "thread_follow_with_icon": "{icon} {text}", + "ancestor_follow": "See {numReplies} other reply under this status | See {numReplies} other replies under this status", + "ancestor_follow_with_icon": "{icon} {text}", "show_all_conversation": "{0} Show full conversation", - "return_to_last_showing": "{0} Return to last position", "show_only_conversation_under_this": "Only show replies to this status" }, "user_card": { From f1db5e8f4bfc8d43eb74d0e3f784c0aa8b06bf3f Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 11 Aug 2021 00:12:16 -0400 Subject: [PATCH 045/128] Highlight ancestor of the current status when diving back to top --- src/components/conversation/conversation.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 817b9bda7d..e9bbca1803 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -441,7 +441,7 @@ const conversation = { this.tryScrollTo(id) }, diveToTopLevel () { - this.tryScrollTo(this.topLevel[0].id) + this.tryScrollTo(this.topLevelAncestorOrSelfId(this.diveRoot) || this.topLevel[0].id) }, // only used when we are not on a page undive () { @@ -490,6 +490,15 @@ const conversation = { } // console.log('ancestors = ', ancestors, 'conversation = ', this.conversation.map(k => k.id), 'statusContentProperties=', this.statusContentProperties) return ancestors + }, + topLevelAncestorOrSelfId (id) { + let cur = id + let parent = this.parentOf(id) + while (parent) { + cur = this.parentOf(cur) + parent = this.parentOf(parent) + } + return cur } } } From 10cd03c7186fa57e8cbdd22c4523c98e4ea53c47 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 11 Aug 2021 00:17:17 -0400 Subject: [PATCH 046/128] Clean up --- src/components/conversation/conversation.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index e9bbca1803..3cc5f88618 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -346,11 +346,6 @@ const conversation = { } }, methods: { - conversationFetched () { - if (!this.isExpanded) { - return - } - }, fetchConversation () { if (this.status) { this.$store.state.api.backendInteractor.fetchConversation({ id: this.statusId }) @@ -359,7 +354,6 @@ const conversation = { this.$store.dispatch('addNewStatuses', { statuses: descendants }) this.setHighlight(this.originalStatusId) }) - .then(this.conversationFetched) } else { this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId }) .then((status) => { @@ -488,7 +482,6 @@ const conversation = { ancestors.unshift(this.statusMap[cur]) cur = this.parentOf(cur) } - // console.log('ancestors = ', ancestors, 'conversation = ', this.conversation.map(k => k.id), 'statusContentProperties=', this.statusContentProperties) return ancestors }, topLevelAncestorOrSelfId (id) { From 26670e90035104fbd24e0884c00b17c6266ba354 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 11 Aug 2021 00:22:47 -0400 Subject: [PATCH 047/128] Reset thread open state when collapsed --- src/components/conversation/conversation.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 3cc5f88618..4c42916183 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -334,8 +334,7 @@ const conversation = { if (value) { this.fetchConversation() } else { - // if we collapse it, we should reset the dive - this.undive() + this.resetDisplayState() } }, virtualHidden (value) { @@ -492,6 +491,10 @@ const conversation = { parent = this.parentOf(parent) } return cur + }, + resetDisplayState () { + this.undive() + this.threadDisplayStatusObject = {} } } } From 17863f54fef4203365aa2b7ce58da3bb9bb3cc8c Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 11 Aug 2021 00:30:27 -0400 Subject: [PATCH 048/128] Optimise thread ancestor display style --- src/components/conversation/conversation.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 20ce54a66e..f0e118e56f 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -194,7 +194,12 @@ flex-direction: column; } - .thread-ancestor { + .thread-ancestors { + margin-left: $status-margin; + border-left: 2px solid var(--border, $fallback--border); + } + + .thread-ancestor .StatusContent { --link: var(--faintLink); --text: var(--faint); color: var(--text); From ba8598858b4a90d25b76a515dc6a9125d2809f9d Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 11 Aug 2021 00:49:38 -0400 Subject: [PATCH 049/128] Optimise thread ancestor borders --- src/components/conversation/conversation.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index f0e118e56f..9aea7b20bd 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -220,7 +220,6 @@ //border-left: 2px solid var(--border, $fallback--border); } - /* HACK: we want the border width to scale with the status *below it* */ .conversation-status { border-bottom-width: 1px; border-bottom-style: solid; @@ -229,10 +228,18 @@ } .thread-ancestor-has-other-replies .conversation-status, + .thread-ancestor:last-child .conversation-status, + .thread-ancestor:last-child .thread-ancestor-dive-box, &.-expanded .thread-tree .conversation-status { border-bottom: none; } + .thread-ancestors + .thread-tree > .conversation-status { + border-top-width: 1px; + border-top-style: solid; + border-top-color: var(--border, $fallback--border); + } + /* expanded conversation in timeline */ &.status-fadein.-expanded .thread-body { border-left-width: 4px; From 22bdcda9c0a9869f8a09507bb60215b8a5af709a Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 13 Aug 2021 18:53:31 -0400 Subject: [PATCH 050/128] Make other replies button stretch along the row --- src/components/conversation/conversation.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 9aea7b20bd..6c8c6ef070 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -211,9 +211,11 @@ border-bottom-color: var(--border, $fallback--border); border-radius: 0; /* Make the button stretch along the whole row */ - display: flex; - align-items: stretch; - flex-direction: column; + &, &-inner { + display: flex; + align-items: stretch; + flex-direction: column; + } } .thread-ancestor-dive-box-inner { padding: $status-margin; From 244174a32b94c1373847f0ea20bb6127de5ef222 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 5 Sep 2021 11:16:48 -0400 Subject: [PATCH 051/128] Improve "show full conversation" interaction Now we only show that button when there are other statuses out of sight (other toplevel statuses exist outside of the current thread tree). --- src/components/conversation/conversation.js | 8 ++++++++ src/components/conversation/conversation.vue | 12 +++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 4c42916183..423930afb0 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -224,6 +224,9 @@ const conversation = { debug('toplevel =', topLevel) return topLevel }, + otherTopLevelCount () { + return this.topLevel.length - 1 + }, showingTopLevel () { if (this.canDive && this.diveRoot) { return [this.statusMap[this.diveRoot]] @@ -242,6 +245,11 @@ const conversation = { diveMode () { return this.canDive && !!this.diveRoot }, + shouldShowAllConversationButton () { + // The "show all conversation" button tells the user that there exist + // other toplevel statuses, so do not show it if there is only a single root + return this.diveMode && this.topLevel.length > 1 + }, replies () { let i = 1 // eslint-disable-next-line camelcase diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 6c8c6ef070..f0eb88c124 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -20,16 +20,22 @@
    - + + + {{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }} +
    Date: Sun, 5 Sep 2021 11:19:43 -0400 Subject: [PATCH 052/128] Add English translation for show all conversation button improvement --- src/i18n/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index 02dd7200c5..ef96e0c50d 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -761,7 +761,8 @@ "thread_follow_with_icon": "{icon} {text}", "ancestor_follow": "See {numReplies} other reply under this status | See {numReplies} other replies under this status", "ancestor_follow_with_icon": "{icon} {text}", - "show_all_conversation": "{0} Show full conversation", + "show_all_conversation_with_icon": "{icon} {text}", + "show_all_conversation": "Show full conversation ({numStatus} other status) | Show full conversation ({numStatus} other statuses)", "show_only_conversation_under_this": "Only show replies to this status" }, "user_card": { From 863255d52fdcbbabe45c86c7e36ebafc0f7e1c53 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 5 Sep 2021 16:35:47 -0400 Subject: [PATCH 053/128] Make position of other replies button a pref --- src/components/conversation/conversation.js | 9 +++++ src/components/conversation/conversation.vue | 7 ++-- .../settings_modal/tabs/general_tab.js | 5 +++ .../settings_modal/tabs/general_tab.vue | 40 +++++++++++++------ src/components/status/status.js | 1 + src/components/status/status.vue | 15 ++++++- src/modules/config.js | 2 + src/modules/instance.js | 1 + 8 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 423930afb0..d4972fbc74 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -92,6 +92,15 @@ const conversation = { isLinearView () { return this.displayStyle === 'linear' }, + otherRepliesButtonPosition () { + return this.$store.getters.mergedConfig.conversationOtherRepliesButton + }, + showOtherRepliesButtonBelowStatus () { + return this.otherRepliesButtonPosition === 'below' + }, + showOtherRepliesButtonInsideStatus () { + return this.otherRepliesButtonPosition === 'inside' + }, hideStatus () { if (this.$refs.statusComponent && this.$refs.statusComponent[0]) { return this.virtualHidden && this.$refs.statusComponent[0].suspendable diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index f0eb88c124..b3d97075d3 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -66,13 +66,14 @@ :profile-user-id="profileUserId" class="conversation-status status-fadein panel-body" - :simple="treeViewIsSimple" + :simple-tree="treeViewIsSimple" :toggle-thread-display="toggleThreadDisplay" :thread-display-status="threadDisplayStatus" :show-thread-recursively="showThreadRecursively" :total-reply-count="totalReplyCount" :total-reply-depth="totalReplyDepth" - :dive="(!treeViewIsSimple) ? () => diveIntoStatus(status.id) : null" + :show-other-replies-as-button="showOtherRepliesButtonInsideStatus" + :dive="() => diveIntoStatus(status.id)" :controlled-showing-tall="statusContentProperties[status.id].showingTall" :controlled-expanding-subject="statusContentProperties[status.id].expandingSubject" @@ -85,7 +86,7 @@ @toggleExpanded="toggleExpanded" />
    ({ + key: mode, + value: mode, + label: this.$t(`settings.conversation_other_replies_button_${mode}`) + })), mentionLinkDisplayOptions: ['short', 'full_for_remote', 'full'].map(mode => ({ key: mode, value: mode, diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index f97d92c352..d5ae781087 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -161,19 +161,33 @@ {{ $t('settings.conversation_display') }} -
  • - - -
  • +
      +
    • + + +
    • +
    • + + {{ $t('settings.conversation_other_replies_button') }} + +
    • +
  • - {{ $t('status.replies_list') }} + + + {{ $t('status.replies_list') }} + Date: Sun, 5 Sep 2021 16:36:27 -0400 Subject: [PATCH 054/128] Add English translation for position of other replies button pref --- src/i18n/en.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/i18n/en.json b/src/i18n/en.json index ef96e0c50d..7b37357af0 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -469,6 +469,9 @@ "conversation_display_tree": "Tree-style", "conversation_display_simple_tree": "Simplified tree-style", "conversation_display_linear": "Linear-style", + "conversation_other_replies_button": "Show the \"other replies\" button", + "conversation_other_replies_button_below": "Below statuses", + "conversation_other_replies_button_inside": "Inside statuses", "max_depth_in_thread": "Maximum number of levels in thread to display by default", "post_status_content_type": "Post status content type", "sensitive_by_default": "Mark posts as sensitive by default", From cebb4224ac0143f6969c7d3e907a7d25eb38b4c7 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 8 Sep 2021 23:22:11 -0400 Subject: [PATCH 055/128] Do not display replies inside status as link if there are no other replies --- src/components/status/status.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/status/status.vue b/src/components/status/status.vue index bc0aeaf06c..31908815b9 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -347,7 +347,7 @@ class="replies" > Date: Wed, 8 Sep 2021 23:29:17 -0400 Subject: [PATCH 057/128] Add English translations for other replies count --- src/i18n/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/en.json b/src/i18n/en.json index 7b37357af0..b185dcab40 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -729,6 +729,7 @@ "reply_to": "Reply to", "mentions": "Mentions", "replies_list": "Replies:", + "replies_list_with_others": "Replies (+{numReplies} other): | Replies (+{numReplies} others):", "mute_conversation": "Mute conversation", "unmute_conversation": "Unmute conversation", "status_unavailable": "Status unavailable", From 0db5a5a581aa6560637dd85886dfd9d7934f40fa Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 9 Sep 2021 00:03:10 -0400 Subject: [PATCH 058/128] Fix controlled status display toggles --- src/components/status_body/status_body.js | 16 +++++++++------- src/components/status_content/status_content.js | 8 ++------ src/components/status_content/status_content.vue | 6 ++++++ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/components/status_body/status_body.js b/src/components/status_body/status_body.js index 91c331359d..b8f6f9a0b6 100644 --- a/src/components/status_body/status_body.js +++ b/src/components/status_body/status_body.js @@ -26,14 +26,16 @@ const StatusContent = { 'focused', 'noHeading', 'fullContent', - 'singleLine' + 'singleLine', + 'showingTall', + 'expandingSubject', + 'showingLongSubject', + 'toggleShowingTall', + 'toggleExpandingSubject', + 'toggleShowingLongSubject' ], data () { return { - showingTall: this.fullContent || (this.inConversation && this.focused), - showingLongSubject: false, - // not as computed because it sets the initial state which will be changed later - expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject, postLength: this.status.text.length, parseReadyDone: false } @@ -115,9 +117,9 @@ const StatusContent = { }, toggleShowMore () { if (this.mightHideBecauseTall) { - this.showingTall = !this.showingTall + this.toggleShowingTall() } else if (this.mightHideBecauseSubject) { - this.expandingSubject = !this.expandingSubject + this.toggleExpandingSubject() } }, generateTagLink (tag) { diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js index 527a4cf543..cf72ccb845 100644 --- a/src/components/status_content/status_content.js +++ b/src/components/status_content/status_content.js @@ -116,12 +116,8 @@ const StatusContent = { toggleExpandingSubject () { controlledOrUncontrolledToggle(this, 'expandingSubject') }, - toggleShowMore () { - if (this.mightHideBecauseTall) { - this.toggleShowingTall() - } else if (this.mightHideBecauseSubject) { - this.toggleExpandingSubject() - } + toggleShowingLongSubject () { + controlledOrUncontrolledToggle(this, 'showingLongSubject') }, setMedia () { const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue index 69635aad19..0a09cda48e 100644 --- a/src/components/status_content/status_content.vue +++ b/src/components/status_content/status_content.vue @@ -8,6 +8,12 @@ :status="status" :compact="compact" :single-line="singleLine" + :showing-tall="showingTall" + :expanding-subject="expandingSubject" + :showing-long-subject="showingLongSubject" + :toggle-showing-tall="toggleShowingTall" + :toggle-expanding-subject="toggleExpandingSubject" + :toggle-showing-long-subject="toggleShowingLongSubject" @parseReady="$emit('parseReady', $event)" >
    From 2a510205c3e18bc1c3ff253dc4521909857cd530 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Wed, 15 Sep 2021 23:35:17 -0400 Subject: [PATCH 059/128] Fix virtual scrolling for tree threading Ref: tree-threading --- src/components/conversation/conversation.js | 15 ++++++++++----- src/components/conversation/conversation.vue | 2 +- src/components/thread_tree/thread_tree.js | 7 +++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index d4972fbc74..1e97bbf0eb 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -101,13 +101,16 @@ const conversation = { showOtherRepliesButtonInsideStatus () { return this.otherRepliesButtonPosition === 'inside' }, - hideStatus () { + suspendable () { if (this.$refs.statusComponent && this.$refs.statusComponent[0]) { - return this.virtualHidden && this.$refs.statusComponent[0].suspendable + return this.$refs.statusComponent.every(s => s.suspendable) } else { - return this.virtualHidden + return true } }, + hideStatus () { + return this.virtualHidden && this.suspendable + }, status () { return this.$store.state.statuses.allStatusesObject[this.statusId] }, @@ -243,7 +246,6 @@ const conversation = { return this.topLevel }, diveRoot () { - (() => {})(this.conversation) const statusId = this.inlineDivePosition || this.statusId const isTopLevel = !this.parentOf(statusId) return isTopLevel ? null : statusId @@ -257,7 +259,10 @@ const conversation = { shouldShowAllConversationButton () { // The "show all conversation" button tells the user that there exist // other toplevel statuses, so do not show it if there is only a single root - return this.diveMode && this.topLevel.length > 1 + return this.isTreeView && this.isExpanded && this.diveMode && this.topLevel.length > 1 + }, + shouldShowAncestors () { + return this.isTreeView && this.isExpanded && this.ancestorsOf(this.diveRoot).length }, replies () { let i = 1 diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index b3d97075d3..0cd7453995 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -43,7 +43,7 @@ class="thread-body" >
    s.suspendable) + } + return selfSuspendable + }, reverseLookupTable () { return this.conversation.reduce((table, status, index) => { table[status.id] = index From cc5cff2038c067ceacd98f218bbcffa2a50069eb Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Fri, 10 Sep 2021 15:24:23 -0400 Subject: [PATCH 060/128] Clean up debug code for tree threading --- src/components/conversation/conversation.js | 15 --------------- src/components/thread_tree/thread_tree.js | 5 ----- 2 files changed, 20 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 1e97bbf0eb..7f9f24b574 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -15,9 +15,6 @@ library.add( faChevronLeft ) -// const debug = console.log -const debug = () => {} - const sortById = (a, b) => { const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id const idB = b.type === 'retweet' ? b.retweeted_status.id : b.id @@ -165,8 +162,6 @@ const conversation = { forest: {} }) - debug('threads = ', threads) - const walk = (forest, topLevel, depth = 0, processed = {}) => topLevel.map(id => { if (processed[id]) { return [] @@ -192,7 +187,6 @@ const conversation = { }, {}) }, totalReplyCount () { - debug('replyIds=', this.replyIds) const sizes = {} const subTreeSizeFor = (id) => { if (sizes[id]) { @@ -202,7 +196,6 @@ const conversation = { return sizes[id] } this.conversation.map(k => k.id).map(subTreeSizeFor) - debug('totalReplyCount=', sizes) return Object.keys(sizes).reduce((res, id) => { res[id] = sizes[id] - 1 // exclude itself return res @@ -224,7 +217,6 @@ const conversation = { }, {}) }, depths () { - debug('threadTree', this.threadTree) return this.threadTree.reduce((a, k) => { a[k.id] = k.depth return a @@ -233,7 +225,6 @@ const conversation = { topLevel () { const topLevel = this.conversation.reduce((tl, cur) => tl.filter(k => this.getReplies(cur.id).map(v => v.id).indexOf(k.id) === -1), this.conversation) - debug('toplevel =', topLevel) return topLevel }, otherTopLevelCount () { @@ -409,14 +400,8 @@ const conversation = { } }, toggleThreadDisplay (id) { - const depth = this.depths[id] - debug('depth = ', depth) - debug( - 'threadDisplayStatus = ', this.threadDisplayStatus, - 'threadDisplayStatusObject = ', this.threadDisplayStatusObject) const curStatus = this.threadDisplayStatus[id] const nextStatus = curStatus === 'showing' ? 'hidden' : 'showing' - debug('toggling', id, 'to', nextStatus) this.setThreadDisplay(id, nextStatus) }, setThreadDisplayRecursively (id, nextStatus) { diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js index fd21c5bc57..0e499b85be 100644 --- a/src/components/thread_tree/thread_tree.js +++ b/src/components/thread_tree/thread_tree.js @@ -11,9 +11,6 @@ library.add( faAngleDoubleRight ) -// const debug = console.log -const debug = () => {} - const ThreadTree = { components: { Status @@ -62,8 +59,6 @@ const ThreadTree = { }, {}) }, currentReplies () { - debug('status:', this.status) - debug('getReplies:', this.getReplies(this.status.id)) return this.getReplies(this.status.id).map(({ id }) => this.statusById(id)) }, threadShowing () { From 20880cdf0bd33d6c189549441ab0ee26b59abf6d Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 16 Sep 2021 00:29:14 -0400 Subject: [PATCH 061/128] Make replying and mediaPlaying controlled $refs is not a reliable way to deal with child components under tree threading as it is not reactive, but the children may change at any time. The only good way seems to be making these states aggregated on the conversation component. Ref: tree-threading --- src/components/conversation/conversation.js | 21 ++++++-- src/components/conversation/conversation.vue | 4 ++ src/components/status/status.js | 50 ++++++++++++++++++-- src/components/thread_tree/thread_tree.js | 3 ++ src/components/thread_tree/thread_tree.vue | 4 ++ 5 files changed, 72 insertions(+), 10 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 7f9f24b574..9aa7b18350 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -99,6 +99,10 @@ const conversation = { return this.otherRepliesButtonPosition === 'inside' }, suspendable () { + if (this.isTreeView) { + return Object.entries(this.statusContentProperties) + .every(([k, prop]) => !prop.replying && prop.mediaPlaying.length === 0) + } if (this.$refs.statusComponent && this.$refs.statusComponent[0]) { return this.$refs.statusComponent.every(s => s.suspendable) } else { @@ -303,14 +307,21 @@ const conversation = { return this.conversation.reduce((a, k) => { const id = k.id const props = (() => { - if (this.statusContentPropertiesObject[id]) { - return this.statusContentPropertiesObject[id] - } - return { + const def = { showingTall: false, expandingSubject: false, - showingLongSubject: false + showingLongSubject: false, + isReplying: false, + mediaPlaying: [] } + + if (this.statusContentPropertiesObject[id]) { + return { + ...def, + ...this.statusContentPropertiesObject[id] + } + } + return def })() a[id] = props diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 0cd7453995..4d64cf08b5 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -78,9 +78,13 @@ :controlled-showing-tall="statusContentProperties[status.id].showingTall" :controlled-expanding-subject="statusContentProperties[status.id].expandingSubject" :controlled-showing-long-subject="statusContentProperties[status.id].showingLongSubject" + :controlled-replying="statusContentProperties[status.id].replying" + :controlled-media-playing="statusContentProperties[status.id].mediaPlaying" :controlled-toggle-showing-tall="() => toggleStatusContentProperty(status.id, 'showingTall')" :controlled-toggle-expanding-subject="() => toggleStatusContentProperty(status.id, 'expandingSubject')" :controlled-toggle-showing-long-subject="() => toggleStatusContentProperty(status.id, 'showingLongSubject')" + :controlled-toggle-replying="() => toggleStatusContentProperty(status.id, 'replying')" + :controlled-set-media-playing="(newVal) => toggleStatusContentProperty(status.id, 'mediaPlaying', newVal)" @goto="setHighlight" @toggleExpanded="toggleExpanded" diff --git a/src/components/status/status.js b/src/components/status/status.js index 700b97640e..73fad45fb8 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -61,6 +61,41 @@ library.add( faAngleDoubleRight ) +const camelCase = name => name.charAt(0).toUpperCase() + name.slice(1) + +const controlledOrUncontrolledGetters = list => list.reduce((res, name) => { + const camelized = camelCase(name) + const toggle = `controlledToggle${camelized}` + const controlledName = `controlled${camelized}` + const uncontrolledName = `uncontrolled${camelized}` + res[name] = function () { + return this[toggle] ? this[controlledName] : this[uncontrolledName] + } + return res +}, {}) + +const controlledOrUncontrolledToggle = (obj, name) => { + const camelized = camelCase(name) + const toggle = `controlledToggle${camelized}` + const uncontrolledName = `uncontrolled${camelized}` + if (obj[toggle]) { + obj[toggle]() + } else { + obj[uncontrolledName] = !obj[uncontrolledName] + } +} + +const controlledOrUncontrolledSet = (obj, name, val) => { + const camelized = camelCase(name) + const set = `controlledSet${camelized}` + const uncontrolledName = `uncontrolled${camelized}` + if (obj[set]) { + obj[set](val) + } else { + obj[uncontrolledName] = val + } +} + const Status = { name: 'Status', components: { @@ -108,20 +143,25 @@ const Status = { 'controlledToggleExpandingSubject', 'controlledShowingLongSubject', 'controlledToggleShowingLongSubject', + 'controlledReplying', + 'controlledToggleReplying', + 'controlledMediaPlaying', + 'controlledSetMediaPlaying', 'dive' ], data () { return { - replying: false, + uncontrolledReplying: false, unmuted: false, userExpanded: false, - mediaPlaying: [], + uncontrolledMediaPlaying: [], suspendable: true, error: null, headTailLinks: null } }, computed: { + ...controlledOrUncontrolledGetters(['replying', 'mediaPlaying']), muteWords () { return this.mergedConfig.muteWords }, @@ -351,7 +391,7 @@ const Status = { this.error = undefined }, toggleReplying () { - this.replying = !this.replying + controlledOrUncontrolledToggle(this, 'replying') }, gotoOriginal (id) { if (this.inConversation) { @@ -371,10 +411,10 @@ const Status = { return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames) }, addMediaPlaying (id) { - this.mediaPlaying.push(id) + controlledOrUncontrolledSet(this, 'mediaPlaying', this.mediaPlaying.concat(id)) }, removeMediaPlaying (id) { - this.mediaPlaying = this.mediaPlaying.filter(mediaId => mediaId !== id) + controlledOrUncontrolledSet(this, 'mediaPlaying', this.mediaPlaying.filter(mediaId => mediaId !== id)) }, setHeadTailLinks (headTailLinks) { this.headTailLinks = headTailLinks diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js index 0e499b85be..71e6372541 100644 --- a/src/components/thread_tree/thread_tree.js +++ b/src/components/thread_tree/thread_tree.js @@ -80,6 +80,9 @@ const ThreadTree = { }, toggleCurrentProp (name) { this.toggleStatusContentProperty(this.status.id, name) + }, + setCurrentProp (name, newVal) { + this.setStatusContentProperty(this.status.id, name) } } } diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index dce03f27a0..cee223e818 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -22,9 +22,13 @@ :controlled-showing-tall="currentProp.showingTall" :controlled-expanding-subject="currentProp.expandingSubject" :controlled-showing-long-subject="currentProp.showingLongSubject" + :controlled-replying="currentProp.replying" + :controlled-media-playing="currentProp.mediaPlaying" :controlled-toggle-showing-tall="() => toggleCurrentProp('showingTall')" :controlled-toggle-expanding-subject="() => toggleCurrentProp('expandingSubject')" :controlled-toggle-showing-long-subject="() => toggleCurrentProp('showingLongSubject')" + :controlled-toggle-replying="() => toggleCurrentProp('replying')" + :controlled-set-media-playing="(newVal) => setCurrentProp('mediaPlaying', newVal)" :dive="dive ? () => dive(status.id) : undefined" @goto="setHighlight" From f8c5cbcd0d5d092c1264032a1be003f828dfc499 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 16 Sep 2021 09:22:49 -0400 Subject: [PATCH 062/128] Fix timeline jump when scrolling Ref: tree-threading --- src/components/conversation/conversation.js | 19 ++++++++++++++++++- src/components/status/status.js | 3 --- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 9aa7b18350..b9ebe7ebe7 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -469,7 +469,24 @@ const conversation = { } else { this.inlineDivePosition = id } - this.setHighlight(id) + // Because the conversation can be unmounted when out of sight + // and mounted again when it comes into sight, + // the `mounted` or `created` function in `status` should not + // contain scrolling calls, as we do not want the page to jump + // when we scroll with an expanded conversation. + // + // Now the method is to rely solely on the `highlight` watcher + // in `status` components. + // In linear views, all statuses are rendered at all times, but + // in tree views, it is possible that a change in active status + // removes and adds status components (e.g. an originally child + // status becomes an ancestor status, and thus they will be + // different). + // Here, let the components be rendered first, in order to trigger + // the `highlight` watcher. + this.$nextTick(() => { + this.setHighlight(id) + }) }, goToCurrent () { this.tryScrollTo(this.diveRoot || this.topLevel[0].id) diff --git a/src/components/status/status.js b/src/components/status/status.js index 73fad45fb8..7bdcb66591 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -439,9 +439,6 @@ const Status = { } } }, - mounted () { - this.scrollIfHighlighted(this.highlight) - }, watch: { 'highlight': function (id) { this.scrollIfHighlighted(id) From 5768806d1ba65bf49e4313f4a7ace602ae456a89 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Mon, 22 Nov 2021 11:20:22 -0500 Subject: [PATCH 063/128] Fix showingLongSubject not correctly propagated --- src/components/status_body/status_body.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue index a088e6bc85..24d842c2a7 100644 --- a/src/components/status_body/status_body.vue +++ b/src/components/status_body/status_body.vue @@ -17,14 +17,14 @@ From 9432fcec7ddfce7fd52ee2ba3f0ef531d61d9b46 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 6 Mar 2022 13:50:15 -0500 Subject: [PATCH 064/128] Make 'Show full conversation' button have left border in embbeded mode --- src/components/conversation/conversation.vue | 38 ++++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 4d64cf08b5..73c613b94b 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -19,29 +19,29 @@
    -
    - - - - {{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }} - - -
    +
    + + + + {{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }} + + +
    Date: Sun, 6 Mar 2022 13:57:48 -0500 Subject: [PATCH 065/128] Split conversation display style into two different settings linear => linear (now default) simple_tree => tree / conversationTreeAdvanced=false tree => tree / conversationTreeAdvanced=true --- src/components/conversation/conversation.js | 4 ++-- src/components/settings_modal/tabs/general_tab.js | 2 +- src/components/settings_modal/tabs/general_tab.vue | 5 +++++ src/modules/config.js | 1 + src/modules/instance.js | 3 ++- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index b9ebe7ebe7..46228e37b4 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -81,10 +81,10 @@ const conversation = { return this.$store.getters.mergedConfig.conversationDisplay }, isTreeView () { - return this.displayStyle === 'tree' || this.displayStyle === 'simple_tree' + return !this.isLinearView }, treeViewIsSimple () { - return this.displayStyle === 'simple_tree' + return !this.$store.getters.mergedConfig.conversationTreeAdvanced }, isLinearView () { return this.displayStyle === 'linear' diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index a963d2047a..8ae0021ce6 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -20,7 +20,7 @@ const GeneralTab = { value: mode, label: this.$t(`settings.subject_line_${mode === 'masto' ? 'mastodon' : mode}`) })), - conversationDisplayOptions: ['tree', 'simple_tree', 'linear'].map(mode => ({ + conversationDisplayOptions: ['tree', 'linear'].map(mode => ({ key: mode, value: mode, label: this.$t(`settings.conversation_display_${mode}`) diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index d5ae781087..28b39d7b2a 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -165,6 +165,11 @@ v-if="conversationDisplay !== 'linear'" class="setting-list suboptions" > +
  • + + {{ $t('settings.tree_advanced') }} + +
  • +
  • + + {{ $t('settings.tree_fade_ancestors') }} + +