Merge branch 'smallfixes' into shigusegubu-themes3

This commit is contained in:
Henry Jameson 2025-06-26 00:09:29 +03:00
commit 3a59fddb4a
19 changed files with 173 additions and 163 deletions

View file

View file

View file

@ -0,0 +1 @@
Unify show/hide content buttons

View file

@ -135,6 +135,7 @@
</style> </style>
<style id="pleroma-eager-styles" type="text/css"></style> <style id="pleroma-eager-styles" type="text/css"></style>
<style id="pleroma-lazy-styles" type="text/css"></style> <style id="pleroma-lazy-styles" type="text/css"></style>
<style id="theme-holder" type="text/css"></style>
<!--server-generated-meta--> <!--server-generated-meta-->
</head> </head>
<body style="margin: 0; padding: 0"> <body style="margin: 0; padding: 0">

View file

@ -21,6 +21,7 @@ import { useOAuthStore } from 'src/stores/oauth'
import { useI18nStore } from 'src/stores/i18n' import { useI18nStore } from 'src/stores/i18n'
import { useInterfaceStore } from 'src/stores/interface' import { useInterfaceStore } from 'src/stores/interface'
import { useAnnouncementsStore } from 'src/stores/announcements' import { useAnnouncementsStore } from 'src/stores/announcements'
import { useAuthFlowStore } from 'src/stores/auth_flow'
let staticInitialResults = null let staticInitialResults = null
@ -156,7 +157,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
: config.logoMargin : config.logoMargin
}) })
copyInstanceOption('logoLeft') copyInstanceOption('logoLeft')
store.commit('authFlow/setInitialStrategy', config.loginMethod) useAuthFlowStore().setInitialStrategy(config.loginMethod)
copyInstanceOption('redirectRootNoLogin') copyInstanceOption('redirectRootNoLogin')
copyInstanceOption('redirectRootLogin') copyInstanceOption('redirectRootLogin')

View file

@ -2,7 +2,8 @@ import { h, resolveComponent } from 'vue'
import LoginForm from '../login_form/login_form.vue' import LoginForm from '../login_form/login_form.vue'
import MFARecoveryForm from '../mfa_form/recovery_form.vue' import MFARecoveryForm from '../mfa_form/recovery_form.vue'
import MFATOTPForm from '../mfa_form/totp_form.vue' import MFATOTPForm from '../mfa_form/totp_form.vue'
import { mapGetters } from 'vuex' import { mapState } from 'pinia'
import { useAuthFlowStore } from 'src/stores/auth_flow'
const AuthForm = { const AuthForm = {
name: 'AuthForm', name: 'AuthForm',
@ -15,7 +16,7 @@ const AuthForm = {
if (this.requiredRecovery) { return 'MFARecoveryForm' } if (this.requiredRecovery) { return 'MFARecoveryForm' }
return 'LoginForm' return 'LoginForm'
}, },
...mapGetters('authFlow', ['requiredTOTP', 'requiredRecovery']) ...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery'])
}, },
components: { components: {
MFARecoveryForm, MFARecoveryForm,

View file

@ -26,7 +26,7 @@
class="textColor unstyled" class="textColor unstyled"
:class="{ disabled: !present || disabled }" :class="{ disabled: !present || disabled }"
type="text" type="text"
:value="modelValue || fallback" :value="modelValue ?? fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
@input="updateValue($event.target.value)" @input="updateValue($event.target.value)"
> >

View file

@ -1,7 +1,8 @@
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex' import { mapState } from 'vuex'
import { mapStores } from 'pinia' import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
import oauthApi from '../../services/new_api/oauth.js' import oauthApi from '../../services/new_api/oauth.js'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faTimes faTimes
@ -25,13 +26,10 @@ const LoginForm = {
instance: state => state.instance, instance: state => state.instance,
loggingIn: state => state.users.loggingIn, loggingIn: state => state.users.loggingIn,
}), }),
...mapGetters( ...mapPiniaState(useAuthFlowStore, ['requiredPassword', 'requiredToken', 'requiredMFA'])
'authFlow', ['requiredPassword', 'requiredToken', 'requiredMFA']
)
}, },
methods: { methods: {
...mapMutations('authFlow', ['requireMFA']), ...mapActions(useAuthFlowStore, ['requireMFA', 'login']),
...mapActions({ login: 'authFlow/login' }),
submit () { submit () {
this.isTokenAuth ? this.submitToken() : this.submitPassword() this.isTokenAuth ? this.submitToken() : this.submitPassword()
}, },

View file

@ -1,7 +1,8 @@
import mfaApi from '../../services/new_api/mfa.js' import mfaApi from '../../services/new_api/mfa.js'
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex' import { mapState } from 'vuex'
import { mapStores } from 'pinia' import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faTimes faTimes
@ -17,8 +18,8 @@ export default {
error: false error: false
}), }),
computed: { computed: {
...mapGetters({ ...mapPiniaState(useAuthFlowStore, {
authSettings: 'authFlow/settings' authSettings: store => store.settings
}), }),
...mapStores(useOAuthStore), ...mapStores(useOAuthStore),
...mapState({ ...mapState({
@ -26,8 +27,7 @@ export default {
}) })
}, },
methods: { methods: {
...mapMutations('authFlow', ['requireTOTP', 'abortMFA']), ...mapActions(useAuthFlowStore, ['requireTOTP', 'abortMFA', 'login']),
...mapActions({ login: 'authFlow/login' }),
clearError () { this.error = false }, clearError () { this.error = false },
focusOnCodeInput () { focusOnCodeInput () {

View file

@ -1,7 +1,8 @@
import mfaApi from '../../services/new_api/mfa.js' import mfaApi from '../../services/new_api/mfa.js'
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex' import { mapState } from 'vuex'
import { mapStores } from 'pinia' import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faTimes faTimes
@ -17,8 +18,8 @@ export default {
error: false error: false
}), }),
computed: { computed: {
...mapGetters({ ...mapPiniaState(useAuthFlowStore, {
authSettings: 'authFlow/settings' authSettings: store => store.settings
}), }),
...mapStores(useOAuthStore), ...mapStores(useOAuthStore),
...mapState({ ...mapState({
@ -26,8 +27,7 @@ export default {
}) })
}, },
methods: { methods: {
...mapMutations('authFlow', ['requireRecovery', 'abortMFA']), ...mapActions(useAuthFlowStore, ['requireRecovery', 'abortMFA', 'login']),
...mapActions({ login: 'authFlow/login' }),
clearError () { this.error = false }, clearError () { this.error = false },
focusOnCodeInput () { focusOnCodeInput () {

View file

@ -372,6 +372,9 @@ export default {
const path = getPath(component, directive) const path = getPath(component, directive)
usedRule = get(real, path) // get real usedRule = get(real, path) // get real
if (usedRule === '') {
return usedRule
}
if (!usedRule) { if (!usedRule) {
usedRule = get(fallback, path) usedRule = get(fallback, path)
} }
@ -379,7 +382,7 @@ export default {
return postProcess(usedRule) return postProcess(usedRule)
}, },
set (value) { set (value) {
if (value) { if (value != null) {
set(allEditedRules.value, getPath(component, directive), value) set(allEditedRules.value, getPath(component, directive), value)
} else { } else {
unset(allEditedRules.value, getPath(component, directive)) unset(allEditedRules.value, getPath(component, directive))

View file

@ -72,6 +72,23 @@ const StatusContent = {
hideTallStatus () { hideTallStatus () {
return this.mightHideBecauseTall && !this.showingTall return this.mightHideBecauseTall && !this.showingTall
}, },
shouldShowToggle () {
return this.mightHideBecauseSubject || this.mightHideBecauseTall
},
toggleButtonClasses () {
return {
'cw-status-hider': !this.showingMore && this.mightHideBecauseSubject,
'tall-status-hider': !this.showingMore && this.mightHideBecauseTall,
'status-unhider': this.showingMore,
}
},
toggleText () {
if (this.showingMore) {
return this.mightHideBecauseSubject ? this.$t('status.hide_content') : this.$t('general.show_less')
} else {
return this.mightHideBecauseSubject ? this.$t('status.show_content') : this.$t('general.show_more')
}
},
showingMore () { showingMore () {
return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject) return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject)
}, },

View file

@ -62,7 +62,6 @@
&.-tall-status { &.-tall-status {
position: relative; position: relative;
height: 16em; height: 16em;
overflow: hidden;
z-index: 1; z-index: 1;
.media-body { .media-body {
@ -82,6 +81,10 @@
mask-composite: exclude; mask-composite: exclude;
} }
} }
&.-expanded {
overflow: visible;
}
} }
& .tall-status-hider, & .tall-status-hider,
@ -95,6 +98,13 @@
text-align: center; text-align: center;
} }
.status-unhider {
margin-top: auto;
position: sticky;
bottom: 0;
padding-bottom: 1em;
}
.tall-status-hider { .tall-status-hider {
position: absolute; position: absolute;
height: 5em; height: 5em;
@ -118,6 +128,10 @@
} }
} }
.toggle-button {
padding: 0.5em;
}
&.-compact { &.-compact {
align-items: start; align-items: start;
flex-direction: row; flex-direction: row;
@ -166,11 +180,11 @@
line-height: inherit; line-height: inherit;
margin: 0; margin: 0;
border: none; border: none;
display: inline-block;
} }
.text-wrapper { .text-wrapper {
display: inline-block; display: inline-block;
width: 100%;
} }
} }
} }

View file

@ -31,17 +31,9 @@
</button> </button>
</div> </div>
<div <div
:class="{'-tall-status': hideTallStatus}"
class="text-wrapper" class="text-wrapper"
:class="{'-tall-status': hideTallStatus, '-expanded': showingMore}"
> >
<button
v-show="hideTallStatus"
class="button-unstyled -link tall-status-hider"
:class="{ '-focused': focused }"
@click.prevent="toggleShowMore"
>
{{ $t("general.show_more") }}
</button>
<RichContent <RichContent
v-if="!hideSubjectStatus && !(singleLine && status.summary_raw_html)" v-if="!hideSubjectStatus && !(singleLine && status.summary_raw_html)"
:class="{ '-single-line': singleLine }" :class="{ '-single-line': singleLine }"
@ -54,45 +46,45 @@
:attentions="status.attentions" :attentions="status.attentions"
@parse-ready="onParseReady" @parse-ready="onParseReady"
/> />
<div
<button v-show="shouldShowToggle"
v-show="hideSubjectStatus" :class="toggleButtonClasses"
class="button-unstyled -link cw-status-hider"
@click.prevent="toggleShowMore"
> >
{{ $t("status.show_content") }} <button
<FAIcon class="btn button-default toggle-button"
v-if="attachmentTypes.includes('image')" :class="{ '-focused': focused }"
icon="image" :aria-expanded="showingMore"
/> @click.prevent="toggleShowMore"
<FAIcon >
v-if="attachmentTypes.includes('video')" {{ toggleText }}
icon="video" <template v-if="!showingMore">
/> <FAIcon
<FAIcon v-if="attachmentTypes.includes('image')"
v-if="attachmentTypes.includes('audio')" icon="image"
icon="music" />
/> <FAIcon
<FAIcon v-if="attachmentTypes.includes('video')"
v-if="attachmentTypes.includes('unknown')" icon="video"
icon="file" />
/> <FAIcon
<FAIcon v-if="attachmentTypes.includes('audio')"
v-if="status.poll && status.poll.options" icon="music"
icon="poll-h" />
/> <FAIcon
<FAIcon v-if="attachmentTypes.includes('unknown')"
v-if="status.card" icon="file"
icon="link" />
/> <FAIcon
</button> v-if="status.poll && status.poll.options"
<button icon="poll-h"
v-show="showingMore && !fullContent" />
class="button-unstyled -link status-unhider" <FAIcon
@click.prevent="toggleShowMore" v-if="status.card"
> icon="link"
{{ tallStatus ? $t("general.show_less") : $t("status.hide_content") }} />
</button> </template>
</button>
</div>
</div> </div>
</div> </div>
<slot v-if="!hideSubjectStatus" /> <slot v-if="!hideSubjectStatus" />

View file

@ -1,86 +0,0 @@
import { useOAuthStore } from 'src/stores/oauth.js'
const PASSWORD_STRATEGY = 'password'
const TOKEN_STRATEGY = 'token'
// MFA strategies
const TOTP_STRATEGY = 'totp'
const RECOVERY_STRATEGY = 'recovery'
// initial state
const state = {
settings: {},
strategy: PASSWORD_STRATEGY,
initStrategy: PASSWORD_STRATEGY // default strategy from config
}
const resetState = (state) => {
state.strategy = state.initStrategy
state.settings = {}
}
// getters
const getters = {
settings: (state) => {
return state.settings
},
requiredPassword: (state) => {
return state.strategy === PASSWORD_STRATEGY
},
requiredToken: (state) => {
return state.strategy === TOKEN_STRATEGY
},
requiredTOTP: (state) => {
return state.strategy === TOTP_STRATEGY
},
requiredRecovery: (state) => {
return state.strategy === RECOVERY_STRATEGY
}
}
// mutations
const mutations = {
setInitialStrategy (state, strategy) {
if (strategy) {
state.initStrategy = strategy
state.strategy = strategy
}
},
requirePassword (state) {
state.strategy = PASSWORD_STRATEGY
},
requireToken (state) {
state.strategy = TOKEN_STRATEGY
},
requireMFA (state, { settings }) {
state.settings = settings
state.strategy = TOTP_STRATEGY // default strategy of MFA
},
requireRecovery (state) {
state.strategy = RECOVERY_STRATEGY
},
requireTOTP (state) {
state.strategy = TOTP_STRATEGY
},
abortMFA (state) {
resetState(state)
}
}
// actions
const actions = {
async login ({ state, dispatch }, { access_token: accessToken }) {
useOAuthStore().setToken(accessToken)
await dispatch('loginUser', accessToken, { root: true })
resetState(state)
}
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}

View file

@ -6,7 +6,6 @@ import api from './api.js'
import config from './config.js' import config from './config.js'
import profileConfig from './profileConfig.js' import profileConfig from './profileConfig.js'
import adminSettings from './adminSettings.js' import adminSettings from './adminSettings.js'
import authFlow from './auth_flow.js'
import drafts from './drafts.js' import drafts from './drafts.js'
import chats from './chats.js' import chats from './chats.js'
@ -19,7 +18,6 @@ export default {
config, config,
profileConfig, profileConfig,
adminSettings, adminSettings,
authFlow,
drafts, drafts,
chats chats
} }

View file

@ -242,7 +242,7 @@ export const applyConfig = (input) => {
.map(([k, v]) => `--${k}: ${v}`).join(';') .map(([k, v]) => `--${k}: ${v}`).join(';')
document.getElementById('style-config')?.remove() document.getElementById('style-config')?.remove()
const styleEl = document.createElement('style') const styleEl = document.getElementById('theme-holder')
styleEl.id = 'style-config' styleEl.id = 'style-config'
head.appendChild(styleEl) head.appendChild(styleEl)
const styleSheet = styleEl.sheet const styleSheet = styleEl.sheet

69
src/stores/auth_flow.js Normal file
View file

@ -0,0 +1,69 @@
import { useOAuthStore } from 'src/stores/oauth.js'
import { defineStore } from 'pinia'
const PASSWORD_STRATEGY = 'password'
const TOKEN_STRATEGY = 'token'
// MFA strategies
const TOTP_STRATEGY = 'totp'
const RECOVERY_STRATEGY = 'recovery'
export const useAuthFlowStore = defineStore('authFlow', {
// initial state
state: () => ({
settings: {},
strategy: PASSWORD_STRATEGY,
initStrategy: PASSWORD_STRATEGY // default strategy from config
}),
// getters
getters: {
requiredPassword: (state) => {
return state.strategy === PASSWORD_STRATEGY
},
requiredToken: (state) => {
return state.strategy === TOKEN_STRATEGY
},
requiredTOTP: (state) => {
return state.strategy === TOTP_STRATEGY
},
requiredRecovery: (state) => {
return state.strategy === RECOVERY_STRATEGY
},
},
actions: {
setInitialStrategy (strategy) {
if (strategy) {
this.initStrategy = strategy
this.strategy = strategy
}
},
requirePassword () {
this.strategy = PASSWORD_STRATEGY
},
requireToken () {
this.strategy = TOKEN_STRATEGY
},
requireMFA ({ settings }) {
this.settings = settings
this.strategy = TOTP_STRATEGY // default strategy of MFA
},
requireRecovery () {
this.strategy = RECOVERY_STRATEGY
},
requireTOTP () {
this.strategy = TOTP_STRATEGY
},
abortMFA () {
this.resetState()
},
resetState () {
this.strategy = this.initStrategy
this.settings = {}
},
async login ({ access_token: accessToken }) {
useOAuthStore().setToken(accessToken)
await window.vuex.dispatch('loginUser', accessToken, { root: true })
this.resetState()
}
}
})

View file

@ -223,7 +223,8 @@ self.addEventListener('fetch', (event) => {
// Do not mess up with remote things // Do not mess up with remote things
const isSameOrigin = (new URL(event.request.url)).origin === self.location.origin const isSameOrigin = (new URL(event.request.url)).origin === self.location.origin
if (shouldCache && event.request.method === 'GET' && isSameOrigin && isNotMedia(event.request)) { if (shouldCache && event.request.method === 'GET' && isSameOrigin && isNotMedia(event.request)) {
console.debug('[Service worker] fetch:', event.request.url) // this is a bit spammy
// console.debug('[Service worker] fetch:', event.request.url)
event.respondWith((async () => { event.respondWith((async () => {
const r = await caches.match(event.request) const r = await caches.match(event.request)
const isEmojiReq = isEmoji(event.request) const isEmojiReq = isEmoji(event.request)