Merge remote-tracking branch 'origin/develop' into timed-user-mutes
This commit is contained in:
commit
385f921c41
72 changed files with 1336 additions and 851 deletions
|
|
@ -2,7 +2,8 @@ import { h, resolveComponent } from 'vue'
|
|||
import LoginForm from '../login_form/login_form.vue'
|
||||
import MFARecoveryForm from '../mfa_form/recovery_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 = {
|
||||
name: 'AuthForm',
|
||||
|
|
@ -15,7 +16,7 @@ const AuthForm = {
|
|||
if (this.requiredRecovery) { return 'MFARecoveryForm' }
|
||||
return 'LoginForm'
|
||||
},
|
||||
...mapGetters('authFlow', ['requiredTOTP', 'requiredRecovery'])
|
||||
...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery'])
|
||||
},
|
||||
components: {
|
||||
MFARecoveryForm,
|
||||
|
|
|
|||
18
src/components/bubble_timeline/bubble_timeline.js
Normal file
18
src/components/bubble_timeline/bubble_timeline.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import Timeline from '../timeline/timeline.vue'
|
||||
const BubbleTimeline = {
|
||||
components: {
|
||||
Timeline
|
||||
},
|
||||
computed: {
|
||||
timeline () { return this.$store.state.statuses.timelines.bubble }
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('startFetchingTimeline', { timeline: 'bubble' })
|
||||
},
|
||||
unmounted () {
|
||||
this.$store.dispatch('stopFetchingTimeline', 'bubble')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BubbleTimeline
|
||||
9
src/components/bubble_timeline/bubble_timeline.vue
Normal file
9
src/components/bubble_timeline/bubble_timeline.vue
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<Timeline
|
||||
:title="$t('nav.bubble')"
|
||||
:timeline="timeline"
|
||||
:timeline-name="'bubble'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script src="./bubble_timeline.js"></script>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
class="textColor unstyled"
|
||||
:class="{ disabled: !present || disabled }"
|
||||
type="text"
|
||||
:value="modelValue || fallback"
|
||||
:value="modelValue ?? fallback"
|
||||
:disabled="!present || disabled"
|
||||
@input="updateValue($event.target.value)"
|
||||
>
|
||||
|
|
|
|||
82
src/components/component_preview/component_preview.js
Normal file
82
src/components/component_preview/component_preview.js
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||
import ColorInput from 'src/components/color_input/color_input.vue'
|
||||
|
||||
import genRandomSeed from 'src/services/random_seed/random_seed.service.js'
|
||||
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Checkbox,
|
||||
ColorInput
|
||||
},
|
||||
props: [
|
||||
'shadow',
|
||||
'shadowControl',
|
||||
'previewClass',
|
||||
'previewStyle',
|
||||
'previewCss',
|
||||
'disabled',
|
||||
'invalid',
|
||||
'noColorControl'
|
||||
],
|
||||
emits: ['update:shadow'],
|
||||
data () {
|
||||
return {
|
||||
colorOverride: undefined,
|
||||
lightGrid: false,
|
||||
zoom: 100,
|
||||
randomSeed: genRandomSeed()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.update()
|
||||
},
|
||||
computed: {
|
||||
hideControls () {
|
||||
return typeof this.shadow === 'string'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
previewCss () {
|
||||
this.update()
|
||||
},
|
||||
previewStyle () {
|
||||
this.update()
|
||||
},
|
||||
zoom () {
|
||||
this.update()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateProperty (axis, value) {
|
||||
this.$emit('update:shadow', { axis, value: Number(value) })
|
||||
},
|
||||
update () {
|
||||
const sheet = createStyleSheet('style-component-preview', 90)
|
||||
|
||||
sheet.clear()
|
||||
|
||||
const result = [this.previewCss]
|
||||
if (this.colorOverride) result.push(`--background: ${this.colorOverride}`)
|
||||
|
||||
const styleRule = [
|
||||
'#component-preview-', this.randomSeed, ' {\n',
|
||||
'.preview-block {\n',
|
||||
`zoom: ${this.zoom / 100};`,
|
||||
this.previewStyle,
|
||||
'\n}',
|
||||
'\n}'
|
||||
].join('')
|
||||
|
||||
sheet.addRule(styleRule)
|
||||
sheet.addRule([
|
||||
'#component-preview-', this.randomSeed, ' {\n',
|
||||
...result,
|
||||
'\n}'
|
||||
].join(''))
|
||||
|
||||
sheet.ready = true
|
||||
adoptStyleSheets()
|
||||
}
|
||||
}
|
||||
}
|
||||
151
src/components/component_preview/component_preview.scss
Normal file
151
src/components/component_preview/component_preview.scss
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
.ComponentPreview {
|
||||
display: grid;
|
||||
grid-template-columns: 1em 1fr 1fr 1em;
|
||||
grid-template-rows: 2em 1fr 1fr 1fr 1em 2em max-content;
|
||||
grid-template-areas:
|
||||
"header header header header "
|
||||
"preview preview preview y-slide"
|
||||
"preview preview preview y-slide"
|
||||
"preview preview preview y-slide"
|
||||
"x-slide x-slide x-slide . "
|
||||
"x-num x-num y-num y-num "
|
||||
"assists assists assists assists";
|
||||
grid-gap: 0.5em;
|
||||
|
||||
&:not(.-shadow-controls) {
|
||||
grid-template-areas:
|
||||
"header header header header "
|
||||
"preview preview preview y-slide"
|
||||
"preview preview preview y-slide"
|
||||
"preview preview preview y-slide"
|
||||
"assists assists assists assists";
|
||||
grid-template-rows: 2em 1fr 1fr 1fr max-content;
|
||||
}
|
||||
|
||||
.header {
|
||||
grid-area: header;
|
||||
place-self: baseline center;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.invalid-container {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center center;
|
||||
background-color: rgb(100 0 0 / 50%);
|
||||
|
||||
.alert {
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.assists {
|
||||
grid-area: assists;
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
grid-auto-rows: 2em;
|
||||
grid-gap: 0.5em;
|
||||
}
|
||||
|
||||
.input-light-grid {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.input-number {
|
||||
min-width: 2em;
|
||||
}
|
||||
|
||||
.x-shift-number {
|
||||
grid-area: x-num;
|
||||
justify-self: right;
|
||||
}
|
||||
|
||||
.y-shift-number {
|
||||
grid-area: y-num;
|
||||
justify-self: left;
|
||||
}
|
||||
|
||||
.x-shift-number,
|
||||
.y-shift-number {
|
||||
input {
|
||||
max-width: 4em;
|
||||
}
|
||||
}
|
||||
|
||||
.x-shift-slider {
|
||||
grid-area: x-slide;
|
||||
height: auto;
|
||||
align-self: start;
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
.y-shift-slider {
|
||||
grid-area: y-slide;
|
||||
writing-mode: vertical-lr;
|
||||
justify-self: left;
|
||||
min-height: 10em;
|
||||
}
|
||||
|
||||
.x-shift-slider,
|
||||
.y-shift-slider {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.preview-window {
|
||||
--__grid-color1: rgb(102 102 102);
|
||||
--__grid-color2: rgb(153 153 153);
|
||||
--__grid-color1-disabled: rgb(102 102 102 / 20%);
|
||||
--__grid-color2-disabled: rgb(153 153 153 / 20%);
|
||||
|
||||
&.-light-grid {
|
||||
--__grid-color1: rgb(205 205 205);
|
||||
--__grid-color2: rgb(255 255 255);
|
||||
--__grid-color1-disabled: rgb(205 205 205 / 20%);
|
||||
--__grid-color2-disabled: rgb(255 255 255 / 20%);
|
||||
}
|
||||
|
||||
position: relative;
|
||||
grid-area: preview;
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 10em;
|
||||
min-height: 10em;
|
||||
background-color: var(--__grid-color2);
|
||||
background-image:
|
||||
linear-gradient(45deg, var(--__grid-color1) 25%, transparent 25%),
|
||||
linear-gradient(-45deg, var(--__grid-color1) 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, var(--__grid-color1) 75%),
|
||||
linear-gradient(-45deg, transparent 75%, var(--__grid-color1) 75%);
|
||||
background-size: 20px 20px;
|
||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
|
||||
border-radius: var(--roundness);
|
||||
|
||||
&.disabled {
|
||||
background-color: var(--__grid-color2-disabled);
|
||||
background-image:
|
||||
linear-gradient(45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
|
||||
linear-gradient(-45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, var(--__grid-color1-disabled) 75%),
|
||||
linear-gradient(-45deg, transparent 75%, var(--__grid-color1-disabled) 75%);
|
||||
}
|
||||
|
||||
.preview-block {
|
||||
background: var(--background, var(--bg));
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-width: 33%;
|
||||
min-height: 33%;
|
||||
max-width: 80%;
|
||||
max-height: 80%;
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
border-color: var(--border);
|
||||
border-radius: var(--roundness);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,9 @@
|
|||
<template>
|
||||
<div
|
||||
:id="'component-preview-' + randomSeed"
|
||||
class="ComponentPreview"
|
||||
:class="{ '-shadow-controls': shadowControl }"
|
||||
>
|
||||
<!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
|
||||
<component
|
||||
:is="'style'"
|
||||
v-html="previewCss"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
|
||||
<label
|
||||
v-show="shadowControl"
|
||||
role="heading"
|
||||
|
|
@ -74,7 +69,6 @@
|
|||
<div
|
||||
class="preview-block"
|
||||
:class="previewClass"
|
||||
:style="style"
|
||||
>
|
||||
{{ $t('settings.style.themes3.editor.test_string') }}
|
||||
</div>
|
||||
|
|
@ -116,203 +110,5 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||
import ColorInput from 'src/components/color_input/color_input.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Checkbox,
|
||||
ColorInput
|
||||
},
|
||||
props: [
|
||||
'shadow',
|
||||
'shadowControl',
|
||||
'previewClass',
|
||||
'previewStyle',
|
||||
'previewCss',
|
||||
'disabled',
|
||||
'invalid',
|
||||
'noColorControl'
|
||||
],
|
||||
emits: ['update:shadow'],
|
||||
data () {
|
||||
return {
|
||||
colorOverride: undefined,
|
||||
lightGrid: false,
|
||||
zoom: 100
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style () {
|
||||
const result = [
|
||||
this.previewStyle,
|
||||
`zoom: ${this.zoom / 100}`
|
||||
]
|
||||
if (this.colorOverride) result.push(`--background: ${this.colorOverride}`)
|
||||
return result
|
||||
},
|
||||
hideControls () {
|
||||
return typeof this.shadow === 'string'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateProperty (axis, value) {
|
||||
this.$emit('update:shadow', { axis, value: Number(value) })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.ComponentPreview {
|
||||
display: grid;
|
||||
grid-template-columns: 1em 1fr 1fr 1em;
|
||||
grid-template-rows: 2em 1fr 1fr 1fr 1em 2em max-content;
|
||||
grid-template-areas:
|
||||
"header header header header "
|
||||
"preview preview preview y-slide"
|
||||
"preview preview preview y-slide"
|
||||
"preview preview preview y-slide"
|
||||
"x-slide x-slide x-slide . "
|
||||
"x-num x-num y-num y-num "
|
||||
"assists assists assists assists";
|
||||
grid-gap: 0.5em;
|
||||
|
||||
&:not(.-shadow-controls) {
|
||||
grid-template-areas:
|
||||
"header header header header "
|
||||
"preview preview preview y-slide"
|
||||
"preview preview preview y-slide"
|
||||
"preview preview preview y-slide"
|
||||
"assists assists assists assists";
|
||||
grid-template-rows: 2em 1fr 1fr 1fr max-content;
|
||||
}
|
||||
|
||||
.header {
|
||||
grid-area: header;
|
||||
place-self: baseline center;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.invalid-container {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center center;
|
||||
background-color: rgb(100 0 0 / 50%);
|
||||
|
||||
.alert {
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.assists {
|
||||
grid-area: assists;
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
grid-auto-rows: 2em;
|
||||
grid-gap: 0.5em;
|
||||
}
|
||||
|
||||
.input-light-grid {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.input-number {
|
||||
min-width: 2em;
|
||||
}
|
||||
|
||||
.x-shift-number {
|
||||
grid-area: x-num;
|
||||
justify-self: right;
|
||||
}
|
||||
|
||||
.y-shift-number {
|
||||
grid-area: y-num;
|
||||
justify-self: left;
|
||||
}
|
||||
|
||||
.x-shift-number,
|
||||
.y-shift-number {
|
||||
input {
|
||||
max-width: 4em;
|
||||
}
|
||||
}
|
||||
|
||||
.x-shift-slider {
|
||||
grid-area: x-slide;
|
||||
height: auto;
|
||||
align-self: start;
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
.y-shift-slider {
|
||||
grid-area: y-slide;
|
||||
writing-mode: vertical-lr;
|
||||
justify-self: left;
|
||||
min-height: 10em;
|
||||
}
|
||||
|
||||
.x-shift-slider,
|
||||
.y-shift-slider {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.preview-window {
|
||||
--__grid-color1: rgb(102 102 102);
|
||||
--__grid-color2: rgb(153 153 153);
|
||||
--__grid-color1-disabled: rgb(102 102 102 / 20%);
|
||||
--__grid-color2-disabled: rgb(153 153 153 / 20%);
|
||||
|
||||
&.-light-grid {
|
||||
--__grid-color1: rgb(205 205 205);
|
||||
--__grid-color2: rgb(255 255 255);
|
||||
--__grid-color1-disabled: rgb(205 205 205 / 20%);
|
||||
--__grid-color2-disabled: rgb(255 255 255 / 20%);
|
||||
}
|
||||
|
||||
position: relative;
|
||||
grid-area: preview;
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 10em;
|
||||
min-height: 10em;
|
||||
background-color: var(--__grid-color2);
|
||||
background-image:
|
||||
linear-gradient(45deg, var(--__grid-color1) 25%, transparent 25%),
|
||||
linear-gradient(-45deg, var(--__grid-color1) 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, var(--__grid-color1) 75%),
|
||||
linear-gradient(-45deg, transparent 75%, var(--__grid-color1) 75%);
|
||||
background-size: 20px 20px;
|
||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
|
||||
border-radius: var(--roundness);
|
||||
|
||||
&.disabled {
|
||||
background-color: var(--__grid-color2-disabled);
|
||||
background-image:
|
||||
linear-gradient(45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
|
||||
linear-gradient(-45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, var(--__grid-color1-disabled) 75%),
|
||||
linear-gradient(-45deg, transparent 75%, var(--__grid-color1-disabled) 75%);
|
||||
}
|
||||
|
||||
.preview-block {
|
||||
background: var(--background, var(--bg));
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-width: 33%;
|
||||
min-height: 33%;
|
||||
max-width: 80%;
|
||||
max-height: 80%;
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
border-color: var(--border);
|
||||
border-radius: var(--roundness);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script src="./component_preview.js" />
|
||||
<style src="./component_preview.scss" lang="scss" />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
|
||||
import { mapStores } from 'pinia'
|
||||
import { mapState } from 'vuex'
|
||||
import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
|
||||
import oauthApi from '../../services/new_api/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 {
|
||||
faTimes
|
||||
|
|
@ -25,13 +26,10 @@ const LoginForm = {
|
|||
instance: state => state.instance,
|
||||
loggingIn: state => state.users.loggingIn,
|
||||
}),
|
||||
...mapGetters(
|
||||
'authFlow', ['requiredPassword', 'requiredToken', 'requiredMFA']
|
||||
)
|
||||
...mapPiniaState(useAuthFlowStore, ['requiredPassword', 'requiredToken', 'requiredMFA'])
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('authFlow', ['requireMFA']),
|
||||
...mapActions({ login: 'authFlow/login' }),
|
||||
...mapActions(useAuthFlowStore, ['requireMFA', 'login']),
|
||||
submit () {
|
||||
this.isTokenAuth ? this.submitToken() : this.submitPassword()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import mfaApi from '../../services/new_api/mfa.js'
|
||||
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
|
||||
import { mapStores } from 'pinia'
|
||||
import { mapState } from 'vuex'
|
||||
import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faTimes
|
||||
|
|
@ -17,8 +18,8 @@ export default {
|
|||
error: false
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters({
|
||||
authSettings: 'authFlow/settings'
|
||||
...mapPiniaState(useAuthFlowStore, {
|
||||
authSettings: store => store.settings
|
||||
}),
|
||||
...mapStores(useOAuthStore),
|
||||
...mapState({
|
||||
|
|
@ -26,8 +27,7 @@ export default {
|
|||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('authFlow', ['requireTOTP', 'abortMFA']),
|
||||
...mapActions({ login: 'authFlow/login' }),
|
||||
...mapActions(useAuthFlowStore, ['requireTOTP', 'abortMFA', 'login']),
|
||||
clearError () { this.error = false },
|
||||
|
||||
focusOnCodeInput () {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import mfaApi from '../../services/new_api/mfa.js'
|
||||
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
|
||||
import { mapStores } from 'pinia'
|
||||
import { mapState } from 'vuex'
|
||||
import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faTimes
|
||||
|
|
@ -17,8 +18,8 @@ export default {
|
|||
error: false
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters({
|
||||
authSettings: 'authFlow/settings'
|
||||
...mapPiniaState(useAuthFlowStore, {
|
||||
authSettings: store => store.settings
|
||||
}),
|
||||
...mapStores(useOAuthStore),
|
||||
...mapState({
|
||||
|
|
@ -26,8 +27,7 @@ export default {
|
|||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations('authFlow', ['requireRecovery', 'abortMFA']),
|
||||
...mapActions({ login: 'authFlow/login' }),
|
||||
...mapActions(useAuthFlowStore, ['requireRecovery', 'abortMFA', 'login']),
|
||||
clearError () { this.error = false },
|
||||
|
||||
focusOnCodeInput () {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
|
|||
import {
|
||||
faUsers,
|
||||
faGlobe,
|
||||
faCity,
|
||||
faBookmark,
|
||||
faEnvelope,
|
||||
faChevronDown,
|
||||
|
|
@ -31,6 +32,7 @@ import {
|
|||
library.add(
|
||||
faUsers,
|
||||
faGlobe,
|
||||
faCity,
|
||||
faBookmark,
|
||||
faEnvelope,
|
||||
faChevronDown,
|
||||
|
|
@ -108,12 +110,15 @@ const NavPanel = {
|
|||
privateMode: state => state.instance.private,
|
||||
federating: state => state.instance.federating,
|
||||
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
|
||||
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable
|
||||
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable,
|
||||
bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
|
||||
}),
|
||||
timelinesItems () {
|
||||
return filterNavigation(
|
||||
Object
|
||||
.entries({ ...TIMELINES })
|
||||
// do not show in timeliens list since it's in a better place now
|
||||
.filter(([key]) => key !== 'bookmarks')
|
||||
.map(([k, v]) => ({ ...v, name: k })),
|
||||
{
|
||||
hasChats: this.pleromaChatMessagesAvailable,
|
||||
|
|
@ -121,6 +126,7 @@ const NavPanel = {
|
|||
isFederating: this.federating,
|
||||
isPrivate: this.privateMode,
|
||||
currentUser: this.currentUser,
|
||||
supportsBubbleTimeline: this.bubbleTimeline,
|
||||
supportsBookmarkFolders: this.bookmarkFolders
|
||||
}
|
||||
)
|
||||
|
|
@ -136,6 +142,7 @@ const NavPanel = {
|
|||
isFederating: this.federating,
|
||||
isPrivate: this.privateMode,
|
||||
currentUser: this.currentUser,
|
||||
supportsBubbleTimeline: this.bubbleTimeline,
|
||||
supportsBookmarkFolders: this.bookmarkFolders
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFederating, isPrivate, currentUser, supportsBookmarkFolders }) => {
|
||||
export const filterNavigation = (list = [], {
|
||||
hasChats,
|
||||
hasAnnouncements,
|
||||
isFederating,
|
||||
isPrivate,
|
||||
currentUser,
|
||||
supportsBookmarkFolders,
|
||||
supportsBubbleTimeline
|
||||
}) => {
|
||||
return list.filter(({ criteria, anon, anonRoute }) => {
|
||||
const set = new Set(criteria || [])
|
||||
if (!isFederating && set.has('federating')) return false
|
||||
|
|
@ -7,6 +15,8 @@ export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFede
|
|||
if ((!currentUser || !currentUser.locked) && set.has('lockedUser')) return false
|
||||
if (!hasChats && set.has('chats')) return false
|
||||
if (!hasAnnouncements && set.has('announcements')) return false
|
||||
if (!supportsBubbleTimeline && set.has('supportsBubbleTimeline')) return false
|
||||
if (!supportsBookmarkFolders && set.has('supportsBookmarkFolders')) return false
|
||||
if (supportsBookmarkFolders && set.has('!supportsBookmarkFolders')) return false
|
||||
return true
|
||||
})
|
||||
|
|
@ -19,11 +29,11 @@ export const getListEntries = store => store.allLists.map(list => ({
|
|||
iconLetter: list.title[0]
|
||||
}))
|
||||
|
||||
export const getBookmarkFolderEntries = store => store.allFolders.map(folder => ({
|
||||
export const getBookmarkFolderEntries = store => store.allFolders ? store.allFolders.map(folder => ({
|
||||
name: 'bookmark-folder-' + folder.id,
|
||||
routeObject: { name: 'bookmark-folder', params: { id: folder.id } },
|
||||
labelRaw: folder.name,
|
||||
iconEmoji: folder.emoji,
|
||||
iconEmojiUrl: folder.emoji_url,
|
||||
iconLetter: folder.name[0]
|
||||
}))
|
||||
})) : []
|
||||
|
|
|
|||
|
|
@ -27,6 +27,13 @@ export const TIMELINES = {
|
|||
label: 'nav.public_tl',
|
||||
criteria: ['!private']
|
||||
},
|
||||
bubble: {
|
||||
route: 'bubble',
|
||||
anon: true,
|
||||
icon: 'city',
|
||||
label: 'nav.bubble',
|
||||
criteria: ['!private', 'federating', 'supportsBubbleTimeline']
|
||||
},
|
||||
twkn: {
|
||||
route: 'public-external-timeline',
|
||||
anon: true,
|
||||
|
|
@ -34,11 +41,11 @@ export const TIMELINES = {
|
|||
label: 'nav.twkn',
|
||||
criteria: ['!private', 'federating']
|
||||
},
|
||||
// bookmarks are still technically a timeline so we should show it in the dropdown
|
||||
bookmarks: {
|
||||
route: 'bookmarks',
|
||||
icon: 'bookmark',
|
||||
label: 'nav.bookmarks',
|
||||
criteria: ['!supportsBookmarkFolders']
|
||||
},
|
||||
favorites: {
|
||||
routeObject: { name: 'user-profile', query: { tab: 'favorites' } },
|
||||
|
|
@ -53,6 +60,15 @@ export const TIMELINES = {
|
|||
}
|
||||
|
||||
export const ROOT_ITEMS = {
|
||||
bookmarks: {
|
||||
route: 'bookmarks',
|
||||
icon: 'bookmark',
|
||||
label: 'nav.bookmarks',
|
||||
// shows bookmarks entry in a better suited location
|
||||
// hides it when bookmark folders are supported since
|
||||
// we show custom component instead of it
|
||||
criteria: ['!supportsBookmarkFolders']
|
||||
},
|
||||
interactions: {
|
||||
route: 'interactions',
|
||||
icon: 'bell',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
|
|||
import {
|
||||
faUsers,
|
||||
faGlobe,
|
||||
faCity,
|
||||
faBookmark,
|
||||
faEnvelope,
|
||||
faComments,
|
||||
|
|
@ -25,6 +26,7 @@ import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
|
|||
library.add(
|
||||
faUsers,
|
||||
faGlobe,
|
||||
faCity,
|
||||
faBookmark,
|
||||
faEnvelope,
|
||||
faComments,
|
||||
|
|
@ -65,7 +67,8 @@ const NavPanel = {
|
|||
followRequestCount: state => state.api.followRequests.length,
|
||||
privateMode: state => state.instance.private,
|
||||
federating: state => state.instance.federating,
|
||||
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
|
||||
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
|
||||
bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
|
||||
}),
|
||||
pinnedList () {
|
||||
if (!this.currentUser) {
|
||||
|
|
@ -79,7 +82,9 @@ const NavPanel = {
|
|||
hasAnnouncements: this.supportsAnnouncements,
|
||||
isFederating: this.federating,
|
||||
isPrivate: this.privateMode,
|
||||
currentUser: this.currentUser
|
||||
currentUser: this.currentUser,
|
||||
supportsBubbleTimeline: this.bubbleTimeline,
|
||||
supportsBookmarkFolders: this.bookmarks
|
||||
})
|
||||
}
|
||||
return filterNavigation(
|
||||
|
|
@ -98,6 +103,8 @@ const NavPanel = {
|
|||
{
|
||||
hasChats: this.pleromaChatMessagesAvailable,
|
||||
hasAnnouncements: this.supportsAnnouncements,
|
||||
supportsBubbleTimeline: this.bubbleTimeline,
|
||||
supportsBookmarkFolders: this.bookmarks,
|
||||
isFederating: this.federating,
|
||||
isPrivate: this.privateMode,
|
||||
currentUser: this.currentUser
|
||||
|
|
|
|||
|
|
@ -60,11 +60,14 @@
|
|||
}
|
||||
|
||||
.extra-button {
|
||||
border-left: 1px solid var(--icon);
|
||||
border-left: 1px solid;
|
||||
border-image-source: linear-gradient(to bottom, transparent 0%, var(--icon) var(--__horizontal-gap) calc(100% - var(--__horizontal-gap)), transparent 100%);
|
||||
border-image-slice: 1;
|
||||
padding-left: calc(var(--__horizontal-gap) - 1px);
|
||||
border-right: var(--__horizontal-gap) solid transparent;
|
||||
border-top: var(--__horizontal-gap) solid transparent;
|
||||
border-bottom: var(--__horizontal-gap) solid transparent;
|
||||
padding-right: var(--__horizontal-gap);
|
||||
padding-top: var(--__horizontal-gap);
|
||||
padding-bottom: var(--__horizontal-gap);
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.main-button {
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ import { newImporter } from 'src/services/export_import/export_import.js'
|
|||
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
|
||||
import { init } from 'src/services/theme_data/theme_data_3.service.js'
|
||||
import {
|
||||
getCssRules,
|
||||
getScopedVersion
|
||||
getCssRules
|
||||
} from 'src/services/theme_data/css_utils.js'
|
||||
import { deserialize } from 'src/services/theme_data/iss_deserializer.js'
|
||||
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
|
||||
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
|
||||
|
|
@ -155,19 +155,23 @@ const AppearanceTab = {
|
|||
}))
|
||||
})
|
||||
|
||||
this.previewTheme('stock', 'v3')
|
||||
|
||||
if (window.IntersectionObserver) {
|
||||
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach(({ target, isIntersecting }) => {
|
||||
if (!isIntersecting) return
|
||||
const theme = this.availableStyles.find(x => x.key === target.dataset.themeKey)
|
||||
this.$nextTick(() => {
|
||||
if (theme) theme.ready = true
|
||||
if (theme) this.previewTheme(theme.key, theme.version, theme.data)
|
||||
})
|
||||
observer.unobserve(target)
|
||||
})
|
||||
}, {
|
||||
root: this.$refs.themeList
|
||||
})
|
||||
} else {
|
||||
this.availableStyles.forEach(theme => this.previewTheme(theme.key, theme.version, theme.data))
|
||||
}
|
||||
},
|
||||
updated () {
|
||||
|
|
@ -391,7 +395,6 @@ const AppearanceTab = {
|
|||
inputRuleset: [...input, paletteRule].filter(x => x),
|
||||
ultimateBackgroundColor: '#000000',
|
||||
liteMode: true,
|
||||
debug: true,
|
||||
onlyNormalState: true
|
||||
})
|
||||
}
|
||||
|
|
@ -400,7 +403,6 @@ const AppearanceTab = {
|
|||
inputRuleset: [],
|
||||
ultimateBackgroundColor: '#000000',
|
||||
liteMode: true,
|
||||
debug: true,
|
||||
onlyNormalState: true
|
||||
})
|
||||
}
|
||||
|
|
@ -409,10 +411,15 @@ const AppearanceTab = {
|
|||
this.compilationCache[key] = theme3
|
||||
}
|
||||
|
||||
return getScopedVersion(
|
||||
getCssRules(theme3.eager),
|
||||
'#theme-preview-' + key
|
||||
).join('\n')
|
||||
|
||||
const sheet = createStyleSheet('appearance-tab-previews', 90)
|
||||
sheet.addRule([
|
||||
'#theme-preview-', key, ' {\n',
|
||||
getCssRules(theme3.eager).join('\n'),
|
||||
'\n}'
|
||||
].join(''))
|
||||
sheet.ready = true
|
||||
adoptStyleSheets()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,6 @@
|
|||
:disabled="switchInProgress"
|
||||
@click="resetTheming"
|
||||
>
|
||||
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<component
|
||||
:is="'style'"
|
||||
v-html="previewTheme('stock', 'v3')"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
|
||||
<preview id="theme-preview-stock" />
|
||||
<h4 class="theme-name">
|
||||
{{ $t('settings.style.stock_theme_used') }}
|
||||
|
|
@ -61,16 +53,6 @@
|
|||
:disabled="switchInProgress"
|
||||
@click="style.version === 'v2' ? setTheme(style.key) : setStyle(style.key)"
|
||||
>
|
||||
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div v-if="style.ready || noIntersectionObserver">
|
||||
<component
|
||||
:is="'style'"
|
||||
v-html="previewTheme(style.key, style.version, style.data)"
|
||||
/>
|
||||
</div>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
|
||||
<preview :id="'theme-preview-' + style.key" />
|
||||
<h4 class="theme-name">
|
||||
{{ style.name }}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import InterfaceLanguageSwitcher from 'src/components/interface_language_switche
|
|||
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
|
||||
import { clearCache, cacheKey, emojiCacheKey } from 'src/services/sw/sw.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faGlobe
|
||||
|
|
@ -98,6 +99,21 @@ const GeneralTab = {
|
|||
methods: {
|
||||
changeDefaultScope (value) {
|
||||
this.$store.dispatch('setProfileOption', { name: 'defaultScope', value })
|
||||
},
|
||||
clearCache (key) {
|
||||
clearCache(key)
|
||||
.then(() => {
|
||||
this.$store.dispatch('settingsSaved', { success: true })
|
||||
})
|
||||
.catch(error => {
|
||||
this.$store.dispatch('settingsSaved', { error })
|
||||
})
|
||||
},
|
||||
clearAssetCache () {
|
||||
this.clearCache(cacheKey)
|
||||
},
|
||||
clearEmojiCache () {
|
||||
this.clearCache(emojiCacheKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -509,6 +509,29 @@
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="setting-item"
|
||||
>
|
||||
<h2>{{ $t('settings.cache') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<button
|
||||
class="btn button-default"
|
||||
@click="clearAssetCache"
|
||||
>
|
||||
{{ $t('settings.clear_asset_cache') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
class="btn button-default"
|
||||
@click="clearEmojiCache"
|
||||
>
|
||||
{{ $t('settings.clear_emoji_cache') }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ const SecurityTab = {
|
|||
user () {
|
||||
return this.$store.state.users.currentUser
|
||||
},
|
||||
pleromaBackend () {
|
||||
return this.$store.state.instance.pleromaBackend
|
||||
pleromaExtensionsAvailable () {
|
||||
return this.$store.state.instance.pleromaExtensionsAvailable
|
||||
},
|
||||
oauthTokens () {
|
||||
return useOAuthTokensStore().tokens.map(oauthToken => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ref, reactive, computed, watch, watchEffect, provide, getCurrentInstance } from 'vue'
|
||||
import { ref, reactive, computed, watch, provide, getCurrentInstance } from 'vue'
|
||||
import { useInterfaceStore } from 'src/stores/interface'
|
||||
import { get, set, unset, throttle } from 'lodash'
|
||||
|
||||
|
|
@ -19,11 +19,9 @@ import Preview from '../theme_tab/theme_preview.vue'
|
|||
|
||||
import VirtualDirectivesTab from './virtual_directives_tab.vue'
|
||||
|
||||
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
|
||||
import { init, findColor } from 'src/services/theme_data/theme_data_3.service.js'
|
||||
import {
|
||||
getCssRules,
|
||||
getScopedVersion
|
||||
} from 'src/services/theme_data/css_utils.js'
|
||||
import { getCssRules } from 'src/services/theme_data/css_utils.js'
|
||||
import { serialize } from 'src/services/theme_data/iss_serializer.js'
|
||||
import { deserializeShadow, deserialize } from 'src/services/theme_data/iss_deserializer.js'
|
||||
import {
|
||||
|
|
@ -372,6 +370,9 @@ export default {
|
|||
const path = getPath(component, directive)
|
||||
|
||||
usedRule = get(real, path) // get real
|
||||
if (usedRule === '') {
|
||||
return usedRule
|
||||
}
|
||||
if (!usedRule) {
|
||||
usedRule = get(fallback, path)
|
||||
}
|
||||
|
|
@ -379,7 +380,7 @@ export default {
|
|||
return postProcess(usedRule)
|
||||
},
|
||||
set (value) {
|
||||
if (value) {
|
||||
if (value != null) {
|
||||
set(allEditedRules.value, getPath(component, directive), value)
|
||||
} else {
|
||||
unset(allEditedRules.value, getPath(component, directive))
|
||||
|
|
@ -667,7 +668,7 @@ export default {
|
|||
})
|
||||
|
||||
exports.clearStyle = () => {
|
||||
onImport(interfaceStore().styleDataUsed)
|
||||
onImport(interfaceStore.styleDataUsed)
|
||||
}
|
||||
|
||||
exports.exportStyle = () => {
|
||||
|
|
@ -685,19 +686,26 @@ export default {
|
|||
const overallPreviewRules = ref([])
|
||||
exports.overallPreviewRules = overallPreviewRules
|
||||
|
||||
const overallPreviewCssRules = ref([])
|
||||
watchEffect(throttle(() => {
|
||||
watch([overallPreviewRules], () => {
|
||||
let css = null
|
||||
try {
|
||||
overallPreviewCssRules.value = getScopedVersion(
|
||||
getCssRules(overallPreviewRules.value),
|
||||
'#edited-style-preview'
|
||||
).join('\n')
|
||||
css = getCssRules(overallPreviewRules.value).map(r => r.replace('html', '&'))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return
|
||||
}
|
||||
}, 500))
|
||||
|
||||
exports.overallPreviewCssRules = overallPreviewCssRules
|
||||
const sheet = createStyleSheet('style-tab-overall-preview', 90)
|
||||
|
||||
sheet.clear()
|
||||
sheet.addRule([
|
||||
'#edited-style-preview {\n',
|
||||
css.join('\n'),
|
||||
'\n}'
|
||||
].join(''))
|
||||
sheet.ready = true
|
||||
adoptStyleSheets()
|
||||
})
|
||||
|
||||
const updateOverallPreview = throttle(() => {
|
||||
try {
|
||||
|
|
@ -721,12 +729,12 @@ export default {
|
|||
console.error('Could not compile preview theme', e)
|
||||
return null
|
||||
}
|
||||
}, 5000)
|
||||
}, 1000)
|
||||
//
|
||||
// Apart from "hover" we can't really show how component looks like in
|
||||
// certain states, so we have to fake them.
|
||||
const simulatePseudoSelectors = (css, prefix) => css
|
||||
.replace(prefix, '.component-preview .preview-block')
|
||||
.replace(prefix, '.preview-block')
|
||||
.replace(':active', '.preview-active')
|
||||
.replace(':hover', '.preview-hover')
|
||||
.replace(':active', '.preview-active')
|
||||
|
|
|
|||
|
|
@ -6,14 +6,6 @@
|
|||
<div class="setting-item heading">
|
||||
<h2> {{ $t('settings.style.themes3.editor.title') }} </h2>
|
||||
<div class="meta-preview">
|
||||
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<component
|
||||
:is="'style'"
|
||||
v-html="overallPreviewCssRules"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
|
||||
<Preview id="edited-style-preview" />
|
||||
<teleport
|
||||
v-if="isActive"
|
||||
|
|
@ -155,12 +147,6 @@
|
|||
</ul>
|
||||
</div>
|
||||
<div class="preview-container">
|
||||
<!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
|
||||
<component
|
||||
:is="'style'"
|
||||
v-html="previewCss"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
|
||||
<ComponentPreview
|
||||
class="component-preview"
|
||||
:show-text="componentHas('Text')"
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import {
|
|||
getCssRules,
|
||||
getScopedVersion
|
||||
} from 'src/services/theme_data/css_utils.js'
|
||||
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
|
||||
|
||||
import ColorInput from 'src/components/color_input/color_input.vue'
|
||||
import RangeInput from 'src/components/range_input/range_input.vue'
|
||||
|
|
@ -68,7 +69,6 @@ const colorConvert = (color) => {
|
|||
export default {
|
||||
data () {
|
||||
return {
|
||||
themeV3Preview: [],
|
||||
themeImporter: newImporter({
|
||||
validator: this.importValidator,
|
||||
onImport: this.onImport,
|
||||
|
|
@ -697,10 +697,16 @@ export default {
|
|||
liteMode: true
|
||||
})
|
||||
|
||||
this.themeV3Preview = getScopedVersion(
|
||||
const sheet = createStyleSheet('theme-tab-overall-preview', 90)
|
||||
const rule = getScopedVersion(
|
||||
getCssRules(theme3.eager),
|
||||
'#theme-preview'
|
||||
'&'
|
||||
).join('\n')
|
||||
|
||||
sheet.clear()
|
||||
sheet.addRule('#theme-preview {\n' + rule + '\n}')
|
||||
sheet.ready = true
|
||||
adoptStyleSheets()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
|
|||
|
|
@ -123,12 +123,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
|
||||
<component
|
||||
:is="'style'"
|
||||
v-html="themeV3Preview"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
|
||||
<preview id="theme-preview" />
|
||||
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
faLock,
|
||||
faLockOpen,
|
||||
faGlobe,
|
||||
faIgloo,
|
||||
faTimes,
|
||||
faRetweet,
|
||||
faReply,
|
||||
|
|
@ -43,6 +44,7 @@ import {
|
|||
library.add(
|
||||
faEnvelope,
|
||||
faGlobe,
|
||||
faIgloo,
|
||||
faLock,
|
||||
faLockOpen,
|
||||
faTimes,
|
||||
|
|
@ -484,6 +486,8 @@ const Status = {
|
|||
return 'lock-open'
|
||||
case 'direct':
|
||||
return 'envelope'
|
||||
case 'local':
|
||||
return 'igloo'
|
||||
default:
|
||||
return 'globe'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,4 +102,20 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.-extra {
|
||||
.action-counter {
|
||||
justify-self: end;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.chevron-icon {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.extra-button {
|
||||
justify-self: end;
|
||||
justify-content: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
/>
|
||||
</component>
|
||||
<span
|
||||
v-if="!extra && button.counter?.(funcArg) > 0"
|
||||
v-if="button.counter?.(funcArg) > 0"
|
||||
class="action-counter"
|
||||
>
|
||||
{{ button.counter?.(funcArg) }}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,23 @@ const StatusContent = {
|
|||
hideTallStatus () {
|
||||
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 () {
|
||||
return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@
|
|||
&.-tall-status {
|
||||
position: relative;
|
||||
height: 16em;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.media-body {
|
||||
|
|
@ -82,6 +81,10 @@
|
|||
mask-composite: exclude;
|
||||
}
|
||||
}
|
||||
|
||||
&.-expanded {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
& .tall-status-hider,
|
||||
|
|
@ -95,6 +98,13 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.status-unhider {
|
||||
margin-top: auto;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.tall-status-hider {
|
||||
position: absolute;
|
||||
height: 5em;
|
||||
|
|
@ -118,6 +128,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.toggle-button {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
&.-compact {
|
||||
align-items: start;
|
||||
flex-direction: row;
|
||||
|
|
@ -166,11 +180,11 @@
|
|||
line-height: inherit;
|
||||
margin: 0;
|
||||
border: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.text-wrapper {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,17 +31,9 @@
|
|||
</button>
|
||||
</div>
|
||||
<div
|
||||
:class="{'-tall-status': hideTallStatus}"
|
||||
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
|
||||
v-if="!hideSubjectStatus && !(singleLine && status.summary_raw_html)"
|
||||
:class="{ '-single-line': singleLine }"
|
||||
|
|
@ -54,45 +46,45 @@
|
|||
:attentions="status.attentions"
|
||||
@parse-ready="onParseReady"
|
||||
/>
|
||||
|
||||
<button
|
||||
v-show="hideSubjectStatus"
|
||||
class="button-unstyled -link cw-status-hider"
|
||||
@click.prevent="toggleShowMore"
|
||||
<div
|
||||
v-show="shouldShowToggle"
|
||||
:class="toggleButtonClasses"
|
||||
>
|
||||
{{ $t("status.show_content") }}
|
||||
<FAIcon
|
||||
v-if="attachmentTypes.includes('image')"
|
||||
icon="image"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="attachmentTypes.includes('video')"
|
||||
icon="video"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="attachmentTypes.includes('audio')"
|
||||
icon="music"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="attachmentTypes.includes('unknown')"
|
||||
icon="file"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="status.poll && status.poll.options"
|
||||
icon="poll-h"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="status.card"
|
||||
icon="link"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-show="showingMore && !fullContent"
|
||||
class="button-unstyled -link status-unhider"
|
||||
@click.prevent="toggleShowMore"
|
||||
>
|
||||
{{ tallStatus ? $t("general.show_less") : $t("status.hide_content") }}
|
||||
</button>
|
||||
<button
|
||||
class="btn button-default toggle-button"
|
||||
:class="{ '-focused': focused }"
|
||||
:aria-expanded="showingMore"
|
||||
@click.prevent="toggleShowMore"
|
||||
>
|
||||
{{ toggleText }}
|
||||
<template v-if="!showingMore">
|
||||
<FAIcon
|
||||
v-if="attachmentTypes.includes('image')"
|
||||
icon="image"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="attachmentTypes.includes('video')"
|
||||
icon="video"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="attachmentTypes.includes('audio')"
|
||||
icon="music"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="attachmentTypes.includes('unknown')"
|
||||
icon="file"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="status.poll && status.poll.options"
|
||||
icon="poll-h"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="status.card"
|
||||
icon="link"
|
||||
/>
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<slot v-if="!hideSubjectStatus" />
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ export const timelineNames = (supportsBookmarkFolders) => {
|
|||
dms: 'nav.dms',
|
||||
'public-timeline': 'nav.public_tl',
|
||||
'public-external-timeline': 'nav.twkn',
|
||||
quotes: 'nav.quotes'
|
||||
quotes: 'nav.quotes',
|
||||
bubble: 'nav.bubble'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +59,8 @@ const TimelineMenu = {
|
|||
currentUser: state => state.users.currentUser,
|
||||
privateMode: state => state.instance.private,
|
||||
federating: state => state.instance.federating,
|
||||
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable
|
||||
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable,
|
||||
bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
|
||||
}),
|
||||
timelinesList () {
|
||||
return filterNavigation(
|
||||
|
|
@ -68,7 +70,8 @@ const TimelineMenu = {
|
|||
isFederating: this.federating,
|
||||
isPrivate: this.privateMode,
|
||||
currentUser: this.currentUser,
|
||||
supportsBookmarkFolders: this.bookmarkFolders
|
||||
supportsBookmarkFolders: this.bookmarkFolders,
|
||||
supportsBubbleTimeline: this.bubbleTimeline
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,7 +149,10 @@ export default {
|
|||
},
|
||||
showModerationMenu () {
|
||||
const privileges = this.loggedIn.privileges
|
||||
return this.loggedIn.role === 'admin' || privileges.includes('users_manage_activation_state') || privileges.includes('users_delete') || privileges.includes('users_manage_tags')
|
||||
return this.loggedIn.role === 'admin' ||
|
||||
privileges.includes('users_manage_activation_state') ||
|
||||
privileges.includes('users_delete') ||
|
||||
privileges.includes('users_manage_tags')
|
||||
},
|
||||
hasNote () {
|
||||
return this.relationship.note
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ const UserProfile = {
|
|||
return this.isUs || !this.user.hide_followers
|
||||
},
|
||||
favoritesTabVisible () {
|
||||
return this.isUs || !this.user.hide_favorites
|
||||
return this.isUs || (this.$store.state.instance.pleromaPublicFavouritesAvailable && !this.user.hide_favorites)
|
||||
},
|
||||
formattedBirthday () {
|
||||
const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue