Merge branch 'profile-bg' into shigusegubu-themes3

This commit is contained in:
Henry Jameson 2026-06-01 22:24:23 +03:00
commit 2bfc423108
24 changed files with 107 additions and 53 deletions

View file

@ -149,13 +149,16 @@ export default {
userBackground() { userBackground() {
return this.currentUser.background_image return this.currentUser.background_image
}, },
foreignProfileBackground() {
return useMergedConfigStore().mergedConfig.allowForeignUserBackground && useInterfaceStore().foreignProfileBackground
},
instanceBackground() { instanceBackground() {
return useMergedConfigStore().mergedConfig.hideInstanceWallpaper return useMergedConfigStore().mergedConfig.hideInstanceWallpaper
? null ? null
: this.instanceBackgroundUrl : this.instanceBackgroundUrl
}, },
background() { background() {
return this.userBackground || this.instanceBackground return this.foreignProfileBackground || this.userBackground || this.instanceBackground
}, },
bgStyle() { bgStyle() {
if (this.background) { if (this.background) {

View file

@ -50,7 +50,7 @@ body {
// have a cursor/pointer to operate them // have a cursor/pointer to operate them
@media (any-pointer: fine) { @media (any-pointer: fine) {
* { * {
scrollbar-color: var(--text) transparent; scrollbar-color: var(--icon) transparent;
&::-webkit-scrollbar { &::-webkit-scrollbar {
background: transparent; background: transparent;
@ -130,7 +130,7 @@ body {
} }
// Body should have background to scrollbar otherwise it will use white (body color?) // Body should have background to scrollbar otherwise it will use white (body color?)
html { html {
scrollbar-color: var(--text) var(--wallpaper); scrollbar-color: var(--icon) var(--wallpaper);
background: var(--wallpaper); background: var(--wallpaper);
} }
} }
@ -200,6 +200,7 @@ nav {
background-color: var(--wallpaper); background-color: var(--wallpaper);
background-image: var(--body-background-image); background-image: var(--body-background-image);
background-position: 50%; background-position: 50%;
transition: background-image 1s;
} }
.underlay { .underlay {

View file

@ -129,6 +129,7 @@ const EmojiPicker = {
hideCustomEmojiInPicker: false, hideCustomEmojiInPicker: false,
// Lazy-load only after the first time `showing` becomes true. // Lazy-load only after the first time `showing` becomes true.
contentLoaded: false, contentLoaded: false,
popoverShown: false,
groupRefs: {}, groupRefs: {},
emojiRefs: {}, emojiRefs: {},
filteredEmojiGroups: [], filteredEmojiGroups: [],
@ -176,6 +177,13 @@ const EmojiPicker = {
const fullEmojiSize = emojiSizeReal + 2 * 0.2 * fontSizeMultiplier * 14 const fullEmojiSize = emojiSizeReal + 2 * 0.2 * fontSizeMultiplier * 14
this.emojiSize = fullEmojiSize this.emojiSize = fullEmojiSize
}, },
togglePicker() {
if (this.popoverShown) {
this.hidePicker()
} else {
this.showPicker()
}
},
showPicker() { showPicker() {
this.$refs.popover.showPopover() this.$refs.popover.showPopover()
this.$nextTick(() => { this.$nextTick(() => {
@ -194,10 +202,10 @@ const EmojiPicker = {
} }
}, },
onPopoverShown() { onPopoverShown() {
this.$emit('show') this.popoverShown = true
}, },
onPopoverClosed() { onPopoverClosed() {
this.$emit('close') this.popoverShown = false
}, },
onStickerUploaded(e) { onStickerUploaded(e) {
this.$emit('sticker-uploaded', e) this.$emit('sticker-uploaded', e)

View file

@ -11,8 +11,8 @@ import ModifiedIndicator from '../helpers/modified_indicator.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js' import SharedComputedObject from '../helpers/shared_computed_object.js'
import StringSetting from '../helpers/string_setting.vue' import StringSetting from '../helpers/string_setting.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useEmojiStore } from 'src/stores/emoji.js' import { useEmojiStore } from 'src/stores/emoji.js'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
@ -176,21 +176,24 @@ const EmojiTab = {
}, },
refreshPackList() { refreshPackList() {
useEmojiStore().getAdminPacks( useEmojiStore()
this.remotePackInstance, .getAdminPacks(
this.$store.state.api.backendInteractor.listEmojiPacks, this.remotePackInstance,
).then((allPacks) => { this.$store.state.api.backendInteractor.listEmojiPacks,
this.knownLocalPacks = allPacks )
for (const name of Object.keys(this.knownLocalPacks)) { .then((allPacks) => {
this.sortPackFiles(name) this.knownLocalPacks = allPacks
} for (const name of Object.keys(this.knownLocalPacks)) {
}) this.sortPackFiles(name)
}
})
}, },
listRemotePacks() { listRemotePacks() {
useEmojiStore().getAdminPacks( useEmojiStore()
this.remotePackInstance, .getAdminPacks(
this.$store.state.api.backendInteractor.listRemoteEmojiPacks, this.remotePackInstance,
) this.$store.state.api.backendInteractor.listRemoteEmojiPacks,
)
.then((allPacks) => { .then((allPacks) => {
let inst = this.remotePackInstance let inst = this.remotePackInstance
if (!inst.startsWith('http')) { if (!inst.startsWith('http')) {

View file

@ -41,6 +41,7 @@
.tab-slot-wrapper { .tab-slot-wrapper {
flex: 1 1 auto; flex: 1 1 auto;
height: 100%; height: 100%;
padding: 0 1em;
overflow-y: auto; overflow-y: auto;
display: grid; display: grid;
grid-template-columns: minmax(1em, 1fr) minmax(min-content, 45em) minmax(1em, 1fr); grid-template-columns: minmax(1em, 1fr) minmax(min-content, 45em) minmax(1em, 1fr);

View file

@ -38,6 +38,7 @@
p { p {
line-height: 1.5; line-height: 1.5;
margin-left: 2em;
} }
.sidenote { .sidenote {
@ -49,6 +50,7 @@
.setting-description { .setting-description {
margin-top: 0.2em; margin-top: 0.2em;
margin-bottom: 0; margin-bottom: 0;
margin-left: 0;
font-size: 80%; font-size: 80%;
} }

View file

@ -263,6 +263,11 @@
{{ $t('settings.hide_wallpaper') }} {{ $t('settings.hide_wallpaper') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li>
<BooleanSetting path="allowForeignUserBackground">
{{ $t('settings.foreign_user_background') }}
</BooleanSetting>
</li>
<li> <li>
<BooleanSetting path="compactProfiles"> <BooleanSetting path="compactProfiles">
{{ $t('settings.compact_profiles') }} {{ $t('settings.compact_profiles') }}

View file

@ -169,7 +169,10 @@
</div> </div>
<span class="heading-right"> <span class="heading-right">
<span class="pin" v-if="showPinned"> <span
v-if="showPinned"
class="pin"
>
<FAIcon <FAIcon
icon="thumbtack" icon="thumbtack"
class="faint" class="faint"

View file

@ -14,8 +14,8 @@ import {
import { import {
faBookmark, faBookmark,
faCheck, faCheck,
faChevronRight,
faChevronDown, faChevronDown,
faChevronRight,
faExternalLinkAlt, faExternalLinkAlt,
faEyeSlash, faEyeSlash,
faHistory, faHistory,
@ -131,6 +131,12 @@ export default {
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji }) this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
} }
}, },
onShowEmojiPicker() {
this.$emit('emojiPickerShown', true)
},
onHideEmojiPicker() {
this.$emit('emojiPickerShown', false)
},
doActionWrap( doActionWrap(
button, button,
close = () => { close = () => {
@ -142,7 +148,7 @@ export default {
) )
return return
if (button.name === 'emoji') { if (button.name === 'emoji') {
this.$refs.picker.showPicker() this.$refs.picker.togglePicker()
} else { } else {
this.animationState = true this.animationState = true
this.getComponent(button) === 'button' && this.doAction(button) this.getComponent(button) === 'button' && this.doAction(button)

View file

@ -72,9 +72,8 @@
<Popover <Popover
v-if="button.name === 'bookmark'" v-if="button.name === 'bookmark'"
class="chevron-popover" class="chevron-popover"
placement="bottom"
:trigger="extra ? 'hover' : 'click'" :trigger="extra ? 'hover' : 'click'"
:placement="extra ? 'right' : 'top'" :placement="extra ? 'right' : 'bottom'"
:offset="extra ? { x: 10 } : { y: 10 }" :offset="extra ? { x: 10 } : { y: 10 }"
:trigger-attrs="{ class: 'extra-button' }" :trigger-attrs="{ class: 'extra-button' }"
> >
@ -101,6 +100,8 @@
:hide-custom-emoji="hideCustomEmoji" :hide-custom-emoji="hideCustomEmoji"
class="emoji-picker-panel" class="emoji-picker-panel"
@emoji="addReaction" @emoji="addReaction"
@show="onShowEmojiPicker"
@close="onHideEmojiPicker"
/> />
</div> </div>
</template> </template>

View file

@ -20,6 +20,7 @@ export default {
UserTimedFilterModal, UserTimedFilterModal,
}, },
props: ['button', 'status'], props: ['button', 'status'],
emits: ['emojiPickerShown'],
mounted() { mounted() {
if (this.button.name === 'mute') { if (this.button.name === 'mute') {
this.$store.dispatch('fetchDomainMutes') this.$store.dispatch('fetchDomainMutes')

View file

@ -79,6 +79,7 @@
:button="button" :button="button"
:status="status" :status="status"
v-bind="$attrs" v-bind="$attrs"
@emojiPickerShown="e => $emit('emojiPickerShown', e)"
/> />
<teleport to="#modal"> <teleport to="#modal">
<MuteConfirm <MuteConfirm

View file

@ -98,7 +98,7 @@ export const BUTTONS = [
label: 'tool_tip.add_reaction', label: 'tool_tip.add_reaction',
icon: ['far', 'smile-beam'], icon: ['far', 'smile-beam'],
interactive: () => true, interactive: () => true,
active: () => false, active: ({ emojiPickerShown }) => emojiPickerShown,
toggleable: true, toggleable: true,
anonLink: true, anonLink: true,
}, },

View file

@ -28,6 +28,7 @@ const StatusActionButtons = {
/* no-op */ /* no-op */
}, },
randomSeed: genRandomSeed(), randomSeed: genRandomSeed(),
emojiPickerShown: false,
} }
}, },
components: { components: {
@ -56,6 +57,7 @@ const StatusActionButtons = {
return { return {
status: this.status, status: this.status,
replying: this.replying, replying: this.replying,
emojiPickerShown: this.emojiPickerShown,
emit: this.$emit, emit: this.$emit,
dispatch: this.$store.dispatch, dispatch: this.$store.dispatch,
state: this.$store.state, state: this.$store.state,
@ -107,6 +109,9 @@ const StatusActionButtons = {
onExtraClose() { onExtraClose() {
this.showPin = false this.showPin = false
}, },
onEmojiPickerShown(state) {
this.emojiPickerShown = state
},
isPinned(button) { isPinned(button) {
return this.pinnedItems.has(button.name) return this.pinnedItems.has(button.name)
}, },

View file

@ -20,6 +20,7 @@
:get-component="getComponent" :get-component="getComponent"
:close="() => { /* no-op */ }" :close="() => { /* no-op */ }"
:do-action="doAction" :do-action="doAction"
@emojiPickerShown="onEmojiPickerShown"
/> />
<button <button
v-if="showPin && currentUser" v-if="showPin && currentUser"

View file

@ -138,8 +138,6 @@
align-items: start; align-items: start;
flex-direction: row; flex-direction: row;
--emoji-size: calc(var(--emojiSize, 32px) / 2);
& .body, & .body,
& .attachments { & .attachments {
max-height: 3.25em; max-height: 3.25em;

View file

@ -1,11 +1,11 @@
import Popover from 'components/popover/popover.vue' import Popover from 'components/popover/popover.vue'
import SelectComponent from 'components/select/select.vue' import SelectComponent from 'components/select/select.vue'
import StillImage from './still-image.vue'
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { useInterfaceStore } from 'src/stores/interface' import StillImage from './still-image.vue'
import { useEmojiStore } from 'src/stores/emoji' import { useEmojiStore } from 'src/stores/emoji'
import { useInterfaceStore } from 'src/stores/interface'
export default { export default {
components: { StillImage, Popover, SelectComponent }, components: { StillImage, Popover, SelectComponent },
@ -28,7 +28,7 @@ export default {
isUserAdmin() { isUserAdmin() {
return this.$store.state.users.currentUser?.rights.admin return this.$store.state.users.currentUser?.rights.admin
}, },
...mapState(useEmojiStore, ['adminPacksLocal', 'adminPacksLocalLoading']) ...mapState(useEmojiStore, ['adminPacksLocal', 'adminPacksLocalLoading']),
}, },
methods: { methods: {
displayError(msg) { displayError(msg) {
@ -64,9 +64,11 @@ export default {
}, },
fetchEmojiPacksIfAdmin() { fetchEmojiPacksIfAdmin() {
useEmojiStore().getAdminPacksLocal().then(() => { useEmojiStore()
this.$refs.emojiPopover.updateStyles() .getAdminPacksLocal()
}) .then(() => {
this.$refs.emojiPopover.updateStyles()
})
}, },
}, },
} }

View file

@ -3,8 +3,8 @@ import { defineAsyncComponent } from 'vue'
import RichContent from 'src/components/rich_content/rich_content.jsx' import RichContent from 'src/components/rich_content/rich_content.jsx'
import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue' import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -40,7 +40,7 @@ const UserListPopover = {
useInstanceStore().restrictedNicknames, useInstanceStore().restrictedNicknames,
) )
}, },
} },
} }
export default UserListPopover export default UserListPopover

View file

@ -12,8 +12,8 @@
<template v-if="users.length"> <template v-if="users.length">
<router-link <router-link
v-for="(user) in usersCapped" v-for="(user) in usersCapped"
:to="generateProfileLink(user)"
:key="user.id" :key="user.id"
:to="generateProfileLink(user)"
class="user-list-row" class="user-list-row"
> >
<UserAvatar <UserAvatar

View file

@ -11,6 +11,7 @@ import Timeline from '../timeline/timeline.vue'
import UserCard from '../user_card/user_card.vue' import UserCard from '../user_card/user_card.vue'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
@ -56,9 +57,14 @@ const UserProfile = {
const routeParams = this.$route.params const routeParams = this.$route.params
this.load({ name: routeParams.name, id: routeParams.id }) this.load({ name: routeParams.name, id: routeParams.id })
this.tab = get(this.$route, 'query.tab', defaultTabKey) this.tab = get(this.$route, 'query.tab', defaultTabKey)
useInterfaceStore().setForeignProfileBackground(this.user?.background_image)
},
updated() {
useInterfaceStore().setForeignProfileBackground(this.user?.background_image)
}, },
unmounted() { unmounted() {
this.stopFetching() this.stopFetching()
useInterfaceStore().setForeignProfileBackground(null)
}, },
computed: { computed: {
timeline() { timeline() {

View file

@ -636,6 +636,7 @@
"navbar_column_stretch": "Stretch navbar to columns width", "navbar_column_stretch": "Stretch navbar to columns width",
"always_show_post_button": "Always show floating New Post button", "always_show_post_button": "Always show floating New Post button",
"hide_wallpaper": "Hide instance wallpaper", "hide_wallpaper": "Hide instance wallpaper",
"foreign_user_background": "Allow other user's profiles to override wallpaper",
"preload_images": "Preload images", "preload_images": "Preload images",
"use_one_click_nsfw": "Open NSFW attachments with just one click", "use_one_click_nsfw": "Open NSFW attachments with just one click",
"hide_post_stats": "Hide post statistics (e.g. the number of favorites)", "hide_post_stats": "Hide post statistics (e.g. the number of favorites)",

View file

@ -140,6 +140,10 @@ export const INSTANCE_DEFAULT_CONFIG_DEFINITIONS = {
description: 'Hide Instance-specific panel', description: 'Hide Instance-specific panel',
default: false, default: false,
}, },
allowForeignUserBackground: {
description: 'Allow other user\'s profiles to override wallpaper',
default: true,
},
hideInstanceWallpaper: { hideInstanceWallpaper: {
description: 'Hide Instance default background', description: 'Hide Instance default background',
default: false, default: false,
@ -631,7 +635,7 @@ export const LOCAL_DEFAULT_CONFIG_DEFINITIONS = {
}, },
imageCompression: { imageCompression: {
description: 'Image compression (WebP/JPEG)', description: 'Image compression (WebP/JPEG)',
default: true, default: false,
}, },
alwaysUseJpeg: { alwaysUseJpeg: {
description: 'Compress images using JPEG only', description: 'Compress images using JPEG only',

View file

@ -1,6 +1,6 @@
import { merge } from 'lodash'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { merge } from 'lodash'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { ensureFinalFallback } from 'src/i18n/languages.js' import { ensureFinalFallback } from 'src/i18n/languages.js'
@ -187,7 +187,10 @@ export const useEmojiStore = defineStore('emoji', {
const listFunction = backendInteractor.listEmojiPacks const listFunction = backendInteractor.listEmojiPacks
this.adminPacksLocalLoading = true this.adminPacksLocalLoading = true
this.adminPacksLocal = await this.getAdminPacks(useInstanceStore().server, listFunction) this.adminPacksLocal = await this.getAdminPacks(
useInstanceStore().server,
listFunction,
)
this.adminPacksLocalLoading = false this.adminPacksLocalLoading = false
}, },
@ -197,8 +200,6 @@ export const useEmojiStore = defineStore('emoji', {
if (!currentUser.rights.admin) return if (!currentUser.rights.admin) return
const pageSize = 25 const pageSize = 25
const allPacks = {}
return await listFunction({ return await listFunction({
instance, instance,
@ -222,25 +223,22 @@ export const useEmojiStore = defineStore('emoji', {
}) })
.then((data) => data.json()) .then((data) => data.json())
.then((pageData) => { .then((pageData) => {
if (pageData.error !== undefined) { if (pageData.error !== undefined) {
return Promise.reject(pageData.error) return Promise.reject(pageData.error)
} }
return pageData.packs return pageData.packs
}) }),
) )
} }
return Promise return Promise.all(promises).then((results) => {
.all(promises) return merge({}, ...results)
.then((results) => { })
return merge({}, ...results)
})
}) })
.then((allPacks) => { .then((allPacks) => {
// Sort by key // Sort by key
return Object return Object.keys(allPacks)
.keys(allPacks)
.sort() .sort()
.reduce((acc, key) => { .reduce((acc, key) => {
if (key.length === 0) return acc if (key.length === 0) return acc

View file

@ -60,6 +60,7 @@ export const useInterfaceStore = defineStore('interface', {
globalNotices: [], globalNotices: [],
layoutHeight: 0, layoutHeight: 0,
lastTimeline: null, lastTimeline: null,
foreignProfileBackground: null,
}), }),
actions: { actions: {
setTemporaryChanges({ confirm, revert }) { setTemporaryChanges({ confirm, revert }) {
@ -96,6 +97,9 @@ export const useInterfaceStore = defineStore('interface', {
console.error(`${error}`) console.error(`${error}`)
} }
}, },
setForeignProfileBackground(url) {
this.foreignProfileBackground = url
},
settingsSaved({ success, error }) { settingsSaved({ success, error }) {
if (success) { if (success) {
if (this.noticeClearTimeout) { if (this.noticeClearTimeout) {