Merge pull request 'small-fixes-and-improvements' (#3492) from small-fixes-and-improvements into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma-fe/pulls/3492
This commit is contained in:
commit
2b62f96889
129 changed files with 1448 additions and 814 deletions
|
|
@ -36,7 +36,6 @@ export default function () {
|
|||
const warning = warnings[i]
|
||||
console.warn(' ' + warning)
|
||||
}
|
||||
console.warn()
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
changelog.d/minor.add
Normal file
6
changelog.d/minor.add
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
button to remove all drafts
|
||||
option to remove forced aspect ratio for user profiles (requested)
|
||||
showing user tags (mrf policies for user + custom if present)
|
||||
version information now is also in about page
|
||||
mention autosuggest now sorts by recent activity
|
||||
non-square emoji support (toggleable by user)
|
||||
7
changelog.d/minor.change
Normal file
7
changelog.d/minor.change
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
overall improved spacings in status action buttons and post form
|
||||
logout confirm button is now dangerous
|
||||
reply/quote now is a radio group and wraps, fixes overflow on languages where labels are too wide
|
||||
personal note input is now bigger
|
||||
moved "edit pinned" to the bottom for status action buttons.
|
||||
dots status action button drops down instead of up to avoid hiding the action buttons
|
||||
improved attachment description (alt text) input and display
|
||||
12
changelog.d/minor.fix
Normal file
12
changelog.d/minor.fix
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
navbar wide logo cropping search input
|
||||
danger buttons being too bright
|
||||
user background upload failure no longer breaks new uploads + displays an error
|
||||
importing theme from old theme editor
|
||||
removed duplicate federationpolicy entry in admin tab
|
||||
repeater name overflowing content
|
||||
reply popover is now shown if replied-to status is muted
|
||||
second language input not having header
|
||||
post form's bottom left buttons not showing their toggled state
|
||||
some font overrides not working
|
||||
popovers opening outside of window's boundaries
|
||||
occasional blank page when showing new posts
|
||||
2
changelog.d/sync-config.add
Normal file
2
changelog.d/sync-config.add
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
settings synchronization
|
||||
user highlight synchronization
|
||||
|
|
@ -67,6 +67,11 @@ export default {
|
|||
data: () => ({
|
||||
mobileActivePanel: 'timeline',
|
||||
}),
|
||||
provide() {
|
||||
return {
|
||||
allowNonSquareEmoji: useMergedConfigStore().mergedConfig.nonSquareEmoji,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
themeApplied() {
|
||||
this.removeSplash()
|
||||
|
|
|
|||
19
src/App.scss
19
src/App.scss
|
|
@ -50,7 +50,7 @@ body {
|
|||
// have a cursor/pointer to operate them
|
||||
@media (any-pointer: fine) {
|
||||
* {
|
||||
scrollbar-color: var(--fg) transparent;
|
||||
scrollbar-color: var(--icon) transparent;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
background: transparent;
|
||||
|
|
@ -130,7 +130,7 @@ body {
|
|||
}
|
||||
// Body should have background to scrollbar otherwise it will use white (body color?)
|
||||
html {
|
||||
scrollbar-color: var(--fg) var(--wallpaper);
|
||||
scrollbar-color: var(--icon) var(--wallpaper);
|
||||
background: var(--wallpaper);
|
||||
}
|
||||
}
|
||||
|
|
@ -787,6 +787,19 @@ option {
|
|||
padding: 0 0.25em;
|
||||
border-radius: var(--roundness);
|
||||
border: 1px solid var(--border);
|
||||
|
||||
&.-dismissible {
|
||||
display: flex;
|
||||
padding-left: 0.5em;
|
||||
margin: 0;
|
||||
align-items: baseline;
|
||||
line-height: 2;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.faint {
|
||||
|
|
@ -802,9 +815,11 @@ option {
|
|||
align-items: baseline;
|
||||
line-height: 1.5;
|
||||
|
||||
p,
|
||||
span {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dismiss {
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@
|
|||
>
|
||||
<user-panel />
|
||||
<template v-if="layoutType !== 'mobile'">
|
||||
<nav-panel />
|
||||
<instance-specific-panel v-if="showInstanceSpecificPanel" />
|
||||
<features-panel v-if="!currentUser && showFeaturesPanel" />
|
||||
<who-to-follow-panel v-if="currentUser && suggestionsEnabled" />
|
||||
<NavPanel />
|
||||
<InstanceSpecificPanel v-if="showInstanceSpecificPanel" />
|
||||
<FeaturesPanel v-if="!currentUser && showFeaturesPanel" />
|
||||
<WhoToFollowPanel v-if="currentUser && suggestionsEnabled" />
|
||||
<div id="notifs-sidebar" />
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -152,8 +152,10 @@ const getStaticConfig = async () => {
|
|||
throw res
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load static/config.json, continuing without it.')
|
||||
console.warn(error)
|
||||
console.warn(
|
||||
'Failed to load static/config.json, continuing without it.',
|
||||
error,
|
||||
)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
|
@ -175,14 +177,16 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
|||
if (source === 'name') return
|
||||
if (INSTANCE_IDENTIY_EXTERNAL.has(source)) return
|
||||
useInstanceStore().set({
|
||||
value: config[source],
|
||||
value:
|
||||
config[source] ?? INSTANCE_IDENTITY_DEFAULT_DEFINITIONS[source].default,
|
||||
path: `instanceIdentity.${source}`,
|
||||
})
|
||||
})
|
||||
|
||||
Object.keys(INSTANCE_DEFAULT_CONFIG_DEFINITIONS).forEach((source) =>
|
||||
useInstanceStore().set({
|
||||
value: config[source],
|
||||
value:
|
||||
config[source] ?? INSTANCE_DEFAULT_CONFIG_DEFINITIONS[source].default,
|
||||
path: `prefsStorage.${source}`,
|
||||
}),
|
||||
)
|
||||
|
|
@ -440,8 +444,7 @@ const getNodeInfo = async ({ store }) => {
|
|||
throw res
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Could not load nodeinfo')
|
||||
console.warn(e)
|
||||
console.warn('Could not load nodeinfo', e)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { mapState } from 'pinia'
|
||||
|
||||
import FeaturesPanel from '../features_panel/features_panel.vue'
|
||||
import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue'
|
||||
import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue'
|
||||
|
|
@ -7,6 +9,9 @@ import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_pane
|
|||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
|
||||
const pleromaFeCommitUrl =
|
||||
'https://git.pleroma.social/pleroma/pleroma-fe/commit/'
|
||||
|
||||
const About = {
|
||||
components: {
|
||||
InstanceSpecificPanel,
|
||||
|
|
@ -19,6 +24,14 @@ const About = {
|
|||
showFeaturesPanel() {
|
||||
return useInstanceStore().instanceIdentity.showFeaturesPanel
|
||||
},
|
||||
frontendVersionLink() {
|
||||
return pleromaFeCommitUrl + this.frontendVersion
|
||||
},
|
||||
...mapState(useInstanceStore, [
|
||||
'backendVersion',
|
||||
'backendRepository',
|
||||
'frontendVersion',
|
||||
]),
|
||||
showInstanceSpecificPanel() {
|
||||
return (
|
||||
useInstanceStore().instanceIdentity.showInstanceSpecificPanel &&
|
||||
|
|
|
|||
|
|
@ -1,11 +1,47 @@
|
|||
<template>
|
||||
<div class="column-inner">
|
||||
<div class="About column-inner">
|
||||
<instance-specific-panel v-if="showInstanceSpecificPanel" />
|
||||
<staff-panel />
|
||||
<terms-of-service-panel />
|
||||
<MRFTransparencyPanel />
|
||||
<features-panel v-if="showFeaturesPanel" />
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="title">
|
||||
{{ $t('settings.version.title') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<dl>
|
||||
<dt>{{ $t('settings.version.backend_version') }}</dt>
|
||||
<dd>
|
||||
<a
|
||||
:href="backendRepository"
|
||||
target="_blank"
|
||||
>
|
||||
{{ backendVersion }}
|
||||
</a>
|
||||
</dd>
|
||||
<dt>{{ $t('settings.version.frontend_version') }}</dt>
|
||||
<dd>
|
||||
<a
|
||||
:href="frontendVersionLink"
|
||||
target="_blank"
|
||||
>
|
||||
{{ frontendVersion }}
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./about.js"></script>
|
||||
<style>
|
||||
.About {
|
||||
dl {
|
||||
padding-left: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { mapState } from 'pinia'
|
|||
|
||||
import nsfwImage from '../../assets/nsfw.png'
|
||||
import Flash from '../flash/flash.vue'
|
||||
import Popover from '../popover/popover.vue'
|
||||
import StillImage from '../still-image/still-image.vue'
|
||||
import VideoAttachment from '../video_attachment/video_attachment.vue'
|
||||
|
||||
|
|
@ -65,13 +66,13 @@ const Attachment = {
|
|||
modalOpen: false,
|
||||
showHidden: false,
|
||||
flashLoaded: false,
|
||||
showDescription: false,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Flash,
|
||||
StillImage,
|
||||
VideoAttachment,
|
||||
Popover,
|
||||
},
|
||||
computed: {
|
||||
classNames() {
|
||||
|
|
@ -180,9 +181,6 @@ const Attachment = {
|
|||
setFlashLoaded(event) {
|
||||
this.flashLoaded = event
|
||||
},
|
||||
toggleDescription() {
|
||||
this.showDescription = !this.showDescription
|
||||
},
|
||||
toggleHidden(event) {
|
||||
if (
|
||||
this.mergedConfig.useOneClickNsfw &&
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@
|
|||
width: 2em;
|
||||
height: 2em;
|
||||
margin-left: 0.5em;
|
||||
font-size: 1.25em;
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -265,3 +265,27 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description-popover {
|
||||
padding: 1em;
|
||||
width: 50ch;
|
||||
max-width: 90vw;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
|
||||
summary {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5em;
|
||||
font-weight: bold;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
overflow-y: auto;
|
||||
max-height: 10.5em;
|
||||
text-wrap: pretty;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,21 +30,16 @@
|
|||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="size !== 'hide' && !hideDescription && (edit || localDescription || showDescription)"
|
||||
v-if="size !== 'hide' && !hideDescription && edit"
|
||||
class="description-container"
|
||||
:class="{ '-static': !edit }"
|
||||
>
|
||||
<input
|
||||
v-if="edit"
|
||||
<textarea
|
||||
v-model="localDescription"
|
||||
type="text"
|
||||
class="input description-field"
|
||||
:placeholder="$t('post_status.media_description')"
|
||||
@keydown.enter.prevent=""
|
||||
>
|
||||
<p v-else>
|
||||
{{ localDescription }}
|
||||
</p>
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
|
|
@ -87,14 +82,22 @@
|
|||
>
|
||||
<FAIcon icon="stop" />
|
||||
</button>
|
||||
<button
|
||||
<Popover
|
||||
v-if="attachment.description && size !== 'small' && !edit && attachment.type !== 'unknown'"
|
||||
class="button-default attachment-button -transparent"
|
||||
:title="$t('status.show_attachment_description')"
|
||||
@click.prevent="toggleDescription"
|
||||
trigger="click"
|
||||
popover-class="popover popover-default description-popover"
|
||||
:trigger-attrs="{ 'class': 'button-default attachment-button -transparent', 'title': $t('status.attachment_description') }"
|
||||
>
|
||||
<FAIcon icon="align-right" />
|
||||
</button>
|
||||
<template #trigger>
|
||||
<FAIcon icon="align-right" />
|
||||
</template>
|
||||
<template #content>
|
||||
<details open>
|
||||
<summary>{{ $t('status.attachment_description') }}</summary>
|
||||
<span>{{ localDescription }}</span>
|
||||
</details>
|
||||
</template>
|
||||
</Popover>
|
||||
<button
|
||||
v-if="!useModal && attachment.type !== 'unknown'"
|
||||
class="button-default attachment-button -transparent"
|
||||
|
|
@ -244,21 +247,16 @@
|
|||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="size !== 'hide' && !hideDescription && (edit || (localDescription && showDescription))"
|
||||
v-if="size !== 'hide' && !hideDescription && edit"
|
||||
class="description-container"
|
||||
:class="{ '-static': !edit }"
|
||||
>
|
||||
<input
|
||||
v-if="edit"
|
||||
<textarea
|
||||
v-model="localDescription"
|
||||
type="text"
|
||||
class="input description-field"
|
||||
:placeholder="$t('post_status.media_description')"
|
||||
@keydown.enter.prevent=""
|
||||
>
|
||||
<p v-else>
|
||||
{{ localDescription }}
|
||||
</p>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import UserLink from '../user_link/user_link.vue'
|
|||
import UserPopover from '../user_popover/user_popover.vue'
|
||||
|
||||
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'
|
||||
|
||||
|
|
@ -24,6 +25,11 @@ const BasicUserCard = {
|
|||
)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
allowNonSquareEmoji() {
|
||||
return useMergedConfigStore().mergedConfig.nonSquareEmoji
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default BasicUserCard
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
class="basic-user-card-user-name-value"
|
||||
:html="user.name"
|
||||
:emoji="user.emoji"
|
||||
:allow-non-square-emoji="allowNonSquareEmoji"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export default {
|
|||
{
|
||||
variant: 'danger',
|
||||
directives: {
|
||||
background: '--cRed',
|
||||
background: '$blend(--cRed 0.25 --inheritedBackground)',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { defineAsyncComponent } from 'vue'
|
|||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
|
||||
export default {
|
||||
name: 'ChatTitle',
|
||||
components: {
|
||||
|
|
@ -20,5 +22,8 @@ export default {
|
|||
htmlTitle() {
|
||||
return this.user ? this.user.name_html : ''
|
||||
},
|
||||
allowNonSquareEmoji() {
|
||||
return useMergedConfigStore().mergedConfig.nonSquareEmoji
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
:title="'@'+(user && user.screen_name_ui)"
|
||||
:html="htmlTitle"
|
||||
:emoji="user.emoji || []"
|
||||
:allow-non-square-emoji="allowNonSquareEmoji"
|
||||
:is-local="user.is_local"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -61,12 +61,14 @@ export default {
|
|||
<style lang="scss">
|
||||
.checkbox {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
min-height: 1.2em;
|
||||
align-items: baseline;
|
||||
gap: 0 0.5em;
|
||||
|
||||
&-indicator,
|
||||
& .label {
|
||||
vertical-align: middle;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
& > &-indicator {
|
||||
|
|
@ -138,15 +140,5 @@ export default {
|
|||
content: "–";
|
||||
}
|
||||
}
|
||||
|
||||
& > .label {
|
||||
&.-after {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
&.-before {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ export default {
|
|||
label: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// use unstyled, uh, style
|
||||
unstyled: {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ const ConfirmModal = {
|
|||
confirmText: {
|
||||
type: String,
|
||||
},
|
||||
confirmDanger: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
emits: ['cancelled', 'accepted'],
|
||||
computed: {},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
<slot name="footerLeft" />
|
||||
<button
|
||||
class="btn button-default"
|
||||
:class="{ '-danger': confirmDanger }"
|
||||
@click.prevent="onAccept"
|
||||
v-text="confirmText"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
.inner-nav {
|
||||
display: grid;
|
||||
grid-template-rows: var(--navbar-height);
|
||||
grid-template-columns: 2fr auto 2fr;
|
||||
grid-template-columns: minmax(5em, 1fr) auto minmax(5em, 1fr);
|
||||
grid-template-areas: "sitename logo actions";
|
||||
box-sizing: border-box;
|
||||
padding: 0 1.2em;
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
}
|
||||
|
||||
&.-logoLeft .inner-nav {
|
||||
grid-template-columns: auto 2fr 2fr;
|
||||
grid-template-columns: auto minmax(5em, 1fr) minmax(5em, 1fr);
|
||||
grid-template-areas: "logo sitename actions";
|
||||
}
|
||||
|
||||
|
|
@ -92,23 +92,18 @@
|
|||
|
||||
.actions {
|
||||
grid-area: actions;
|
||||
justify-content: flex-end;
|
||||
text-align: right;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
line-height: var(--navbar-height);
|
||||
height: var(--navbar-height);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&.right {
|
||||
justify-content: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: 1em;
|
||||
min-width: 1em;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,54 +32,57 @@
|
|||
>
|
||||
</router-link>
|
||||
<div class="item right actions">
|
||||
<search-bar
|
||||
<SearchBar
|
||||
v-if="currentUser || !privateMode"
|
||||
@toggled="onSearchBarToggled"
|
||||
@click.stop
|
||||
/>
|
||||
<button
|
||||
class="button-unstyled nav-icon"
|
||||
:title="$t('nav.preferences')"
|
||||
@click.stop="openSettingsModal('user')"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="cog"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="currentUser && currentUser.role === 'admin'"
|
||||
class="button-unstyled nav-icon"
|
||||
target="_blank"
|
||||
:title="$t('nav.administration')"
|
||||
@click.stop="openSettingsModal('admin')"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="tachometer-alt"
|
||||
/>
|
||||
</button>
|
||||
<span class="spacer" />
|
||||
<button
|
||||
v-if="currentUser"
|
||||
class="button-unstyled nav-icon"
|
||||
:title="$t('login.logout')"
|
||||
@click.stop.prevent="logout"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="sign-out-alt"
|
||||
/>
|
||||
</button>
|
||||
<template v-if="searchBarHidden">
|
||||
<button
|
||||
class="button-unstyled nav-icon"
|
||||
:title="$t('nav.preferences')"
|
||||
@click.stop="openSettingsModal('user')"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="cog"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="currentUser && currentUser.role === 'admin'"
|
||||
class="button-unstyled nav-icon"
|
||||
target="_blank"
|
||||
:title="$t('nav.administration')"
|
||||
@click.stop="openSettingsModal('admin')"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="tachometer-alt"
|
||||
/>
|
||||
</button>
|
||||
<span class="spacer" />
|
||||
<button
|
||||
v-if="currentUser"
|
||||
class="button-unstyled nav-icon"
|
||||
:title="$t('login.logout')"
|
||||
@click.stop.prevent="logout"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="sign-out-alt"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<teleport to="#modal">
|
||||
<confirm-modal
|
||||
v-if="showingConfirmLogout"
|
||||
:title="$t('login.logout_confirm_title')"
|
||||
:confirm-danger="true"
|
||||
:confirm-text="$t('login.logout_confirm_accept_button')"
|
||||
:cancel-text="$t('login.logout_confirm_cancel_button')"
|
||||
@accepted="doLogout"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
|
||||
import Draft from 'src/components/draft/draft.vue'
|
||||
import List from 'src/components/list/list.vue'
|
||||
|
||||
|
|
@ -5,12 +6,31 @@ const Drafts = {
|
|||
components: {
|
||||
Draft,
|
||||
List,
|
||||
ConfirmModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showingConfirmDialog: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
drafts() {
|
||||
return this.$store.getters.draftsArray
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
abandonAll() {
|
||||
this.showingConfirmDialog = true
|
||||
},
|
||||
doAbandonAll() {
|
||||
this.$store
|
||||
.dispatch('abandonAllDrafts')
|
||||
.then(() => this.hideConfirmDialog())
|
||||
},
|
||||
hideConfirmDialog() {
|
||||
this.showingConfirmDialog = false
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default Drafts
|
||||
|
|
|
|||
|
|
@ -13,36 +13,66 @@
|
|||
>
|
||||
{{ $t('drafts.no_drafts') }}
|
||||
</div>
|
||||
<List
|
||||
v-else
|
||||
:items="drafts"
|
||||
:non-interactive="true"
|
||||
>
|
||||
<template #item="{ item: draft }">
|
||||
<Draft
|
||||
class="draft"
|
||||
:draft="draft"
|
||||
/>
|
||||
</template>
|
||||
</List>
|
||||
<template v-else>
|
||||
<List
|
||||
:items="drafts"
|
||||
:non-interactive="true"
|
||||
>
|
||||
<template #item="{ item: draft }">
|
||||
<Draft
|
||||
class="draft"
|
||||
:draft="draft"
|
||||
/>
|
||||
</template>
|
||||
</List>
|
||||
<div class="remove-all">
|
||||
<button
|
||||
class="btn -danger button-default"
|
||||
@click="abandonAll"
|
||||
>
|
||||
{{ $t('drafts.clean_drafts') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<teleport to="#modal">
|
||||
<ConfirmModal
|
||||
v-if="showingConfirmDialog"
|
||||
:confirm-danger="true"
|
||||
:title="$t('drafts.abandon_confirm_title')"
|
||||
:confirm-text="$t('drafts.abandon_confirm_accept_button')"
|
||||
:cancel-text="$t('drafts.abandon_confirm_cancel_button')"
|
||||
@accepted="doAbandonAll"
|
||||
@cancelled="hideConfirmDialog"
|
||||
>
|
||||
{{ $t('drafts.abandon_all_confirm') }}
|
||||
</ConfirmModal>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./drafts.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.draft {
|
||||
margin: 1em 0;
|
||||
}
|
||||
.Drafts {
|
||||
.draft {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.empty-drafs-list-alert {
|
||||
padding: 3em;
|
||||
font-size: 1.2em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: var(--textFaint);
|
||||
.remove-all {
|
||||
margin: 1em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty-drafs-list-alert {
|
||||
padding: 3em;
|
||||
font-size: 1.2em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: var(--textFaint);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -333,7 +333,6 @@ const EmojiInput = {
|
|||
if (!this.pickerShown) {
|
||||
this.scrollIntoView()
|
||||
this.$refs.picker.showPicker()
|
||||
this.$refs.picker.startEmojiLoad()
|
||||
} else {
|
||||
this.$refs.picker.hidePicker()
|
||||
}
|
||||
|
|
@ -590,7 +589,7 @@ const EmojiInput = {
|
|||
setCaret({ target: { selectionStart } }) {
|
||||
this.caret = selectionStart
|
||||
this.$nextTick(() => {
|
||||
this.$refs.suggestorPopover.updateStyles()
|
||||
this.$refs.suggestorPopover?.updateStyles()
|
||||
})
|
||||
},
|
||||
autoCompleteItemLabel(suggestion) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div
|
||||
ref="root"
|
||||
class="input emoji-input"
|
||||
:class="{ 'with-picker': !hideEmojiButton }"
|
||||
:class="{ '-with-picker': !hideEmojiButton, '-textarea': input?.tagName === 'TEXTAREA' }"
|
||||
>
|
||||
<slot
|
||||
:id="'textbox-' + randomSeed"
|
||||
|
|
@ -118,9 +118,10 @@
|
|||
|
||||
.emoji-picker-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 0.2em 0.25em;
|
||||
height: 100%;
|
||||
padding: 0 0.2em;
|
||||
font-size: 1.3em;
|
||||
cursor: pointer;
|
||||
line-height: 1.2em;
|
||||
|
|
@ -130,6 +131,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.-textarea {
|
||||
.emoji-picker-icon {
|
||||
height: auto;
|
||||
padding: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-picker-panel {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
|
|
@ -151,8 +159,11 @@
|
|||
outline: none;
|
||||
}
|
||||
|
||||
&.with-picker input {
|
||||
padding-right: 2em;
|
||||
&.-with-picker {
|
||||
textarea,
|
||||
input {
|
||||
padding-right: 2.4em;
|
||||
}
|
||||
}
|
||||
|
||||
.hidden-overlay {
|
||||
|
|
|
|||
|
|
@ -131,10 +131,11 @@ export const suggestUsers = ({ dispatch, state }) => {
|
|||
const diff = (bScore - aScore) * 10
|
||||
|
||||
// Then sort alphabetically
|
||||
const activity = a.last_status_at < b.last_status_at ? 100 : -100
|
||||
const nameAlphabetically = a.name > b.name ? 1 : -1
|
||||
const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1
|
||||
|
||||
return diff + nameAlphabetically + screenNameAlphabetically
|
||||
return diff + nameAlphabetically + screenNameAlphabetically + activity
|
||||
})
|
||||
.map((user) => ({
|
||||
user,
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ const EmojiPicker = {
|
|||
hideCustomEmojiInPicker: false,
|
||||
// Lazy-load only after the first time `showing` becomes true.
|
||||
contentLoaded: false,
|
||||
popoverShown: false,
|
||||
groupRefs: {},
|
||||
emojiRefs: {},
|
||||
filteredEmojiGroups: [],
|
||||
|
|
@ -176,6 +177,13 @@ const EmojiPicker = {
|
|||
const fullEmojiSize = emojiSizeReal + 2 * 0.2 * fontSizeMultiplier * 14
|
||||
this.emojiSize = fullEmojiSize
|
||||
},
|
||||
togglePicker() {
|
||||
if (this.popoverShown) {
|
||||
this.hidePicker()
|
||||
} else {
|
||||
this.showPicker()
|
||||
}
|
||||
},
|
||||
showPicker() {
|
||||
this.$refs.popover.showPopover()
|
||||
this.$nextTick(() => {
|
||||
|
|
@ -194,10 +202,10 @@ const EmojiPicker = {
|
|||
}
|
||||
},
|
||||
onPopoverShown() {
|
||||
this.$emit('show')
|
||||
this.popoverShown = true
|
||||
},
|
||||
onPopoverClosed() {
|
||||
this.$emit('close')
|
||||
this.popoverShown = false
|
||||
},
|
||||
onStickerUploaded(e) {
|
||||
this.$emit('sticker-uploaded', e)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
trigger="click"
|
||||
popover-class="emoji-picker popover-default"
|
||||
:hide-trigger="true"
|
||||
placement="bottom"
|
||||
@show="onPopoverShown"
|
||||
@close="onPopoverClosed"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import UserAvatar from '../user_avatar/user_avatar.vue'
|
|||
import UserListPopover from '../user_list_popover/user_list_popover.vue'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faCheck, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
|
|
@ -48,6 +49,9 @@ const EmojiReactions = {
|
|||
statusId: this.status.id,
|
||||
})
|
||||
},
|
||||
allowNonSquareEmoji() {
|
||||
return useMergedConfigStore().mergedConfig.nonSquareEmoji
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleShowAll() {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,12 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&.-wide {
|
||||
width: auto;
|
||||
min-width: var(--emoji-size);
|
||||
max-width: calc(var(--emoji-size) * 3);
|
||||
}
|
||||
|
||||
--_still_image-label-scale: 0.3;
|
||||
}
|
||||
|
||||
|
|
@ -62,6 +68,12 @@
|
|||
font-size: calc(var(--emoji-size) * 0.8);
|
||||
margin: 0;
|
||||
|
||||
&.-wide {
|
||||
width: auto;
|
||||
min-width: var(--emoji-size);
|
||||
max-width: calc(var(--emoji-size) * 3);
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,13 @@
|
|||
>
|
||||
<span
|
||||
class="reaction-emoji"
|
||||
:class="{ ['-wide']: allowNonSquareEmoji }"
|
||||
>
|
||||
<StillImage
|
||||
v-if="reaction.url"
|
||||
:src="reaction.url"
|
||||
class="reaction-emoji-content"
|
||||
:class="{ ['-wide']: allowNonSquareEmoji }"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
{{ ' ' }}
|
||||
<div
|
||||
v-if="modelValue?.family"
|
||||
v-if="modelValue"
|
||||
class="font-input setting-item"
|
||||
>
|
||||
<label
|
||||
|
|
@ -67,10 +67,10 @@
|
|||
</button>
|
||||
<input
|
||||
:id="name"
|
||||
:model-value="modelValue.family"
|
||||
:model-value="modelValue"
|
||||
class="input custom-font"
|
||||
type="text"
|
||||
@update:modelValue="$emit('update:modelValue', { ...(modelValue || {}), family: $event.target.value })"
|
||||
@update:modelValue="$emit('update:modelValue', $event.target.value)"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
|
|
@ -89,9 +89,9 @@
|
|||
</button>
|
||||
<Select
|
||||
:id="name + '-local-font-switcher'"
|
||||
:model-value="modelValue?.family"
|
||||
:model-value="modelValue"
|
||||
class="custom-font"
|
||||
@update:model-value="v => $emit('update:modelValue', { ...(modelValue || {}), family: v })"
|
||||
@update:model-value="v => $emit('update:modelValue', v)"
|
||||
>
|
||||
<optgroup
|
||||
:label="$t('settings.style.themes3.font.group-builtin')"
|
||||
|
|
|
|||
|
|
@ -47,6 +47,11 @@ const Gallery = {
|
|||
: attachments
|
||||
.reduce(
|
||||
(acc, attachment, i) => {
|
||||
const peek = attachments[i + 1]
|
||||
const nextEnd = peek == null
|
||||
const nextWide = !nextEnd && !displayTypes.has(peek?.type)
|
||||
|
||||
// Inserting new row
|
||||
if (attachment.type === 'audio') {
|
||||
return [
|
||||
...acc,
|
||||
|
|
@ -61,18 +66,27 @@ const Gallery = {
|
|||
{ items: [] },
|
||||
]
|
||||
}
|
||||
|
||||
const maxPerRow = 3
|
||||
const attachmentsRemaining = this.attachments.length - i + 1
|
||||
const currentRow = acc[acc.length - 1].items
|
||||
currentRow.push(attachment)
|
||||
if (
|
||||
currentRow.length >= maxPerRow &&
|
||||
attachmentsRemaining > maxPerRow
|
||||
) {
|
||||
return [...acc, { items: [] }]
|
||||
const currentRow = acc[acc.length - 1]
|
||||
const previousRow = acc[acc.length - 2]
|
||||
|
||||
if (currentRow.items.length >= maxPerRow) {
|
||||
if (nextWide || nextEnd) {
|
||||
if (previousRow?.items.length > 1) {
|
||||
currentRow.items.push(attachment)
|
||||
return [...acc, { items: [] }]
|
||||
} else {
|
||||
const last = currentRow.items.splice(-1)[0]
|
||||
return [...acc, { items: [last, attachment] }]
|
||||
}
|
||||
} else {
|
||||
return [...acc, { items: [attachment] }]
|
||||
}
|
||||
} else {
|
||||
return acc
|
||||
currentRow.items.push(attachment)
|
||||
}
|
||||
return acc
|
||||
},
|
||||
[{ items: [] }],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@
|
|||
|
||||
.gallery-item {
|
||||
margin: 0;
|
||||
height: 15em;
|
||||
height: 20em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,3 +11,9 @@
|
|||
</template>
|
||||
|
||||
<script src="./instance_specific_panel.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.instance-specific-panel .panel-body {
|
||||
border-radius: var(--roundness);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@
|
|||
</tab-switcher>
|
||||
<Notifications
|
||||
ref="notifications"
|
||||
:no-heading="true"
|
||||
:no-extra="true"
|
||||
:minimal-mode="true"
|
||||
:filter-mode="filterMode"
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 10em;
|
||||
object-fit: cover;
|
||||
border-radius: var(--roundness);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,13 +89,16 @@
|
|||
/>
|
||||
</button>
|
||||
|
||||
<span
|
||||
<details
|
||||
v-if="description"
|
||||
open
|
||||
class="description"
|
||||
>
|
||||
{{ description }}
|
||||
</span>
|
||||
<summary>{{ $t('status.attachment_description') }}</summary>
|
||||
<span>{{ description }}</span>
|
||||
</details>
|
||||
<span
|
||||
v-if="media.length > 1"
|
||||
class="counter"
|
||||
>
|
||||
{{ $t('media_modal.counter', { current: currentIndex + 1, total: media.length }, currentIndex + 1) }}
|
||||
|
|
@ -159,19 +162,43 @@ $modal-view-button-icon-margin: 0.5em;
|
|||
.counter {
|
||||
/* Hardcoded since background is also hardcoded */
|
||||
color: white;
|
||||
margin-top: 1em;
|
||||
text-shadow: 0 0 10px black, 0 0 10px black;
|
||||
padding: 0.2em 2em;
|
||||
text-shadow: 0 0 1em black, 0 0 1em black, 0 0 1em black;
|
||||
margin: 1em 2em;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
|
||||
.description + .counter {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
flex: 0 0 auto;
|
||||
overflow-y: auto;
|
||||
min-height: 1em;
|
||||
max-width: 35.8em;
|
||||
max-width: 80ch;
|
||||
max-height: 9.5em;
|
||||
overflow-wrap: break-word;
|
||||
text-wrap: pretty;
|
||||
|
||||
summary {
|
||||
margin-bottom: 0.5em;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
overflow-y: auto;
|
||||
min-height: 1em;
|
||||
text-wrap: pretty;
|
||||
max-height: 10.5em;
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.5;
|
||||
scrollbar-color: white transparent;
|
||||
|
||||
&::-webkit-scrollbar-button,
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@
|
|||
<confirm-modal
|
||||
v-if="showingConfirmLogout"
|
||||
:title="$t('login.logout_confirm_title')"
|
||||
:confirm-danger="true"
|
||||
:confirm-text="$t('login.logout_confirm_accept_button')"
|
||||
:cancel-text="$t('login.logout_confirm_cancel_button')"
|
||||
@accepted="doLogout"
|
||||
|
|
|
|||
|
|
@ -144,7 +144,6 @@ const Notification = {
|
|||
}
|
||||
},
|
||||
doApprove() {
|
||||
this.$emit('interacted')
|
||||
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
|
||||
this.$store.dispatch('removeFollowRequest', this.user)
|
||||
this.$store.dispatch('markSingleNotificationAsSeen', {
|
||||
|
|
@ -166,7 +165,6 @@ const Notification = {
|
|||
}
|
||||
},
|
||||
doDeny() {
|
||||
this.$emit('interacted')
|
||||
this.$store.state.api.backendInteractor
|
||||
.denyUser({ id: this.user.id })
|
||||
.then(() => {
|
||||
|
|
@ -212,6 +210,9 @@ const Notification = {
|
|||
mergedConfig() {
|
||||
return useMergedConfigStore().mergedConfig
|
||||
},
|
||||
allowNonSquareEmoji() {
|
||||
return this.mergedConfig.nonSquareEmoji
|
||||
},
|
||||
shouldConfirmApprove() {
|
||||
return this.mergedConfig.modalOnApproveFollow
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
class="Notification"
|
||||
:compact="true"
|
||||
:statusoid="notification.status"
|
||||
@interacted="interacted"
|
||||
@click="interacted"
|
||||
/>
|
||||
</article>
|
||||
<article
|
||||
|
|
@ -71,6 +71,7 @@
|
|||
:title="'@'+notification.from_profile.screen_name_ui"
|
||||
:html="notification.from_profile.name_html"
|
||||
:emoji="notification.from_profile.emoji"
|
||||
:allow-non-square-emoji="allowNonSquareEmoji"
|
||||
:is-local="notification.from_profile.is_local"
|
||||
/>
|
||||
</bdi>
|
||||
|
|
@ -136,6 +137,7 @@
|
|||
:src="notification.emoji_url"
|
||||
:alt="notification.emoji"
|
||||
:title="notification.emoji"
|
||||
:class="{ ['-wide']: allowNonSquareEmoji }"
|
||||
>
|
||||
<span
|
||||
v-else
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ const Notifications = {
|
|||
return unseenNotificationsFromStore(
|
||||
this.$store,
|
||||
useMergedConfigStore().mergedConfig.notificationVisibility,
|
||||
useMergedConfigStore().mergedConfig.ignoreInactionableSeen,
|
||||
)
|
||||
},
|
||||
filteredNotifications() {
|
||||
|
|
|
|||
|
|
@ -119,6 +119,10 @@
|
|||
max-width: 1.25em;
|
||||
height: 1.25em;
|
||||
width: auto;
|
||||
|
||||
&.-wide {
|
||||
max-width: 3.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-reaction-emoji-image {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ export default {
|
|||
return this.expired ? 'polls.expired' : 'polls.expires_in'
|
||||
}
|
||||
},
|
||||
allowNonSquareEmoji() {
|
||||
return useMergedConfigStore().mergedConfig.nonSquareEmoji
|
||||
},
|
||||
loggedIn() {
|
||||
return this.$store.state.users.currentUser
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
:html="option.title_html"
|
||||
:handle-links="false"
|
||||
:emoji="emoji"
|
||||
:allow-non-square-emoji="allowNonSquareEmoji"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
type="text"
|
||||
:placeholder="$t('polls.option')"
|
||||
:maxlength="maxLength"
|
||||
@change="updatePollToParent"
|
||||
@keydown.enter.stop.prevent="nextOption(index)"
|
||||
>
|
||||
</div>
|
||||
|
|
@ -50,7 +49,6 @@
|
|||
v-model="pollType"
|
||||
class="poll-type-select"
|
||||
unstyled="true"
|
||||
@change="updatePollToParent"
|
||||
>
|
||||
<option value="single">
|
||||
{{ $t('polls.single_choice') }}
|
||||
|
|
|
|||
|
|
@ -304,10 +304,13 @@ const Popover = {
|
|||
}
|
||||
this.scrollable.addEventListener('scroll', this.onScroll)
|
||||
this.scrollable.addEventListener('resize', this.onResize)
|
||||
this.$nextTick(() => {
|
||||
// My assumption is that upon showing popover initially has different size
|
||||
// as its contents are getting populating, so logic uses those incorrect
|
||||
// sizes as basis
|
||||
setTimeout(() => {
|
||||
if (wasHidden) this.$emit('show')
|
||||
this.updateStyles()
|
||||
})
|
||||
}, 1)
|
||||
},
|
||||
hidePopover() {
|
||||
if (this.disabled) return
|
||||
|
|
|
|||
|
|
@ -152,9 +152,6 @@ const PostStatusForm = {
|
|||
DraftCloser,
|
||||
Popover,
|
||||
},
|
||||
created() {
|
||||
this.initQuote()
|
||||
},
|
||||
mounted() {
|
||||
this.updateIdempotencyKey()
|
||||
this.resize(this.$refs.textarea)
|
||||
|
|
@ -214,7 +211,11 @@ const PostStatusForm = {
|
|||
poll: {},
|
||||
hasPoll: false,
|
||||
hasQuote: false,
|
||||
quote: {},
|
||||
quote: {
|
||||
id: '',
|
||||
url: '',
|
||||
thread: false,
|
||||
},
|
||||
mediaDescriptions: {},
|
||||
visibility: scope,
|
||||
contentType,
|
||||
|
|
@ -233,7 +234,11 @@ const PostStatusForm = {
|
|||
poll: this.statusPoll || {},
|
||||
hasPoll: false,
|
||||
hasQuote: false,
|
||||
quote: {},
|
||||
quote: {
|
||||
id: '',
|
||||
url: '',
|
||||
thread: false,
|
||||
},
|
||||
mediaDescriptions: this.statusMediaDescriptions || {},
|
||||
visibility: this.statusScope || scope,
|
||||
contentType: statusContentType,
|
||||
|
|
@ -373,8 +378,15 @@ const PostStatusForm = {
|
|||
quotable() {
|
||||
return this.quotingAvailable && this.replyTo
|
||||
},
|
||||
quoteThreadToggled() {
|
||||
return this.newStatus.hasQuote && this.newStatus.quote.thread
|
||||
quoteThreadToggled: {
|
||||
get() {
|
||||
return this.newStatus.hasQuote && this.newStatus.quote.thread
|
||||
},
|
||||
set(value) {
|
||||
this.newStatus.hasQuote = value
|
||||
this.newStatus.quote.thread = value
|
||||
this.newStatus.quote.id = value ? this.replyTo : ''
|
||||
},
|
||||
},
|
||||
defaultQuotable() {
|
||||
if (
|
||||
|
|
@ -855,24 +867,6 @@ const PostStatusForm = {
|
|||
this.$refs.pollForm.clear()
|
||||
}
|
||||
},
|
||||
initQuote() {
|
||||
const quote = this.newStatus.quote
|
||||
|
||||
if (Object.keys(quote).length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const quotable = this.defaultQuotable
|
||||
|
||||
quote.id = quotable ? this.replyTo : ''
|
||||
quote.url = ''
|
||||
quote.thread = quotable
|
||||
},
|
||||
setQuoteThread(v) {
|
||||
this.newStatus.hasQuote = v
|
||||
this.newStatus.quote.thread = v
|
||||
this.newStatus.quote.id = v ? this.replyTo : ''
|
||||
},
|
||||
clearQuoteForm() {
|
||||
if (this.$refs.quoteForm) {
|
||||
this.$refs.quoteForm.clear()
|
||||
|
|
@ -880,6 +874,11 @@ const PostStatusForm = {
|
|||
},
|
||||
toggleQuoteForm() {
|
||||
this.newStatus.hasQuote = !this.newStatus.hasQuote
|
||||
|
||||
this.newStatus.quote = {}
|
||||
this.newStatus.quote.thread = false
|
||||
this.newStatus.quote.id = null
|
||||
this.newStatus.quote.url = ''
|
||||
},
|
||||
dismissScopeNotice() {
|
||||
useSyncConfigStore().setSimplePrefAndSave({
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
.form-bottom-left {
|
||||
display: flex;
|
||||
gap: 1.5em;
|
||||
margin-right: 1em;
|
||||
|
||||
button {
|
||||
padding: 0.5em;
|
||||
|
|
@ -52,13 +53,13 @@
|
|||
.preview-heading {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
|
||||
.preview-toggle {
|
||||
flex: 10 0 auto;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
padding-left: 0.5em;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
|
|
@ -89,9 +90,10 @@
|
|||
}
|
||||
|
||||
.reply-or-quote-selector {
|
||||
flex: 1 0 auto;
|
||||
margin-bottom: 0.5em;
|
||||
display: grid;
|
||||
gap: 0 1em;
|
||||
display: flex;
|
||||
flex-wrap: wrap-reverse;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +107,6 @@
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-left: -0.5em;
|
||||
}
|
||||
|
||||
.visibility-notice {
|
||||
|
|
@ -144,10 +145,7 @@
|
|||
justify-content: right;
|
||||
}
|
||||
|
||||
.media-upload-icon,
|
||||
.poll-icon,
|
||||
.quote-icon,
|
||||
.emoji-icon {
|
||||
.bottom-left-button {
|
||||
font-size: 1.85em;
|
||||
line-height: 1.1;
|
||||
flex: 1;
|
||||
|
|
@ -210,14 +208,14 @@
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.input.form-post-body {
|
||||
textarea.input.form-post-body {
|
||||
// TODO: make a resizable textarea component?
|
||||
box-sizing: content-box; // needed for easier computation of dynamic size
|
||||
overflow: hidden;
|
||||
transition: min-height 200ms 100ms;
|
||||
// stock padding + 1 line of text (for counter)
|
||||
padding-bottom: calc(var(--_padding) + var(--post-line-height) * 1em);
|
||||
padding-right: 1.5em;
|
||||
padding-right: 0.5em;
|
||||
// two lines of text
|
||||
height: calc(var(--post-line-height) * 1em);
|
||||
min-height: calc(var(--post-line-height) * 1em);
|
||||
|
|
@ -241,9 +239,11 @@
|
|||
.character-counter {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
right: 2.2em;
|
||||
padding: 0;
|
||||
margin: 0 0.5em;
|
||||
margin: 0;
|
||||
line-height: 2.2em;
|
||||
height: 2.2em;
|
||||
|
||||
&.error {
|
||||
color: var(--cRed);
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@
|
|||
</div>
|
||||
<div
|
||||
v-if="!disablePreview"
|
||||
class="preview-heading faint"
|
||||
class="preview-heading"
|
||||
>
|
||||
<a
|
||||
class="preview-toggle faint"
|
||||
|
|
@ -110,34 +110,24 @@
|
|||
<div
|
||||
v-if="quotable"
|
||||
role="radiogroup"
|
||||
class="btn-group reply-or-quote-selector"
|
||||
class="reply-or-quote-selector"
|
||||
>
|
||||
<button
|
||||
:id="`reply-or-quote-option-${randomSeed}-reply`"
|
||||
class="btn button-default reply-or-quote-option"
|
||||
:class="{ toggled: !quoteThreadToggled }"
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
<Checkbox
|
||||
v-model="quoteThreadToggled"
|
||||
:radio="true"
|
||||
:disabled="quoteFormVisible"
|
||||
:aria-labelledby="`reply-or-quote-option-${randomSeed}-reply`"
|
||||
:aria-checked="!newStatus.quote.thread"
|
||||
@click="setQuoteThread(false)"
|
||||
>
|
||||
{{ $t('post_status.reply_option') }}
|
||||
</button>
|
||||
<button
|
||||
:id="`reply-or-quote-option-${randomSeed}-quote`"
|
||||
class="btn button-default reply-or-quote-option"
|
||||
:class="{ toggled: quoteThreadToggled }"
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
:disabled="quoteFormVisible"
|
||||
:aria-labelledby="`reply-or-quote-option-${randomSeed}-quote`"
|
||||
:aria-checked="newStatus.quote.thread"
|
||||
@click="setQuoteThread(true)"
|
||||
>
|
||||
{{ $t('post_status.quote_option') }}
|
||||
</button>
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
role="radio"
|
||||
:radio="true"
|
||||
:model-value="!quoteThreadToggled"
|
||||
:disabled="quoteFormVisible"
|
||||
@update:model-value="e => quoteThreadToggled = !e"
|
||||
>
|
||||
{{ $t('post_status.reply_option') }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -266,18 +256,20 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<poll-form
|
||||
<PollForm
|
||||
v-if="pollsAvailable"
|
||||
ref="pollForm"
|
||||
:visible="pollFormVisible"
|
||||
:params="newStatus.poll"
|
||||
/>
|
||||
<quote-form
|
||||
<QuoteForm
|
||||
v-if="quotingAvailable"
|
||||
:id="newStatus.quote.id"
|
||||
ref="quoteForm"
|
||||
:visible="quoteFormVisible"
|
||||
:reply="isReply"
|
||||
:params="newStatus.quote"
|
||||
:url="newStatus.quote.url"
|
||||
@update:url="url => newStatus.quote.url = url"
|
||||
@update:id="id => newStatus.quote.id = id"
|
||||
/>
|
||||
<span
|
||||
v-if="!disableDraft && shouldAutoSaveDraft"
|
||||
|
|
@ -292,7 +284,7 @@
|
|||
<div class="form-bottom-left">
|
||||
<media-upload
|
||||
ref="mediaUpload"
|
||||
class="media-upload-icon"
|
||||
class="bottom-left-button media-upload-icon"
|
||||
:drop-files="dropFiles"
|
||||
:disabled="uploadFileLimitReached"
|
||||
@uploading="startedUploadingFiles"
|
||||
|
|
@ -302,8 +294,8 @@
|
|||
/>
|
||||
<button
|
||||
v-if="pollsAvailable"
|
||||
class="poll-icon button-unstyled"
|
||||
:class="{ selected: pollFormVisible }"
|
||||
class="bottom-left-button poll-icon button-unstyled"
|
||||
:class="{ toggled: pollFormVisible }"
|
||||
:title="$t('polls.add_poll')"
|
||||
@click="togglePollForm"
|
||||
>
|
||||
|
|
@ -311,9 +303,9 @@
|
|||
</button>
|
||||
<button
|
||||
v-if="quotingAvailable"
|
||||
class="quote-icon button-unstyled"
|
||||
:disabled="newStatus.quote.thread"
|
||||
:class="{ selected: quoteFormVisible }"
|
||||
class="bottom-left-button quote-icon button-unstyled"
|
||||
:disabled="quoteThreadToggled"
|
||||
:class="{ toggled: quoteFormVisible }"
|
||||
:title="$t('tool_tip.add_quote')"
|
||||
@click="toggleQuoteForm"
|
||||
>
|
||||
|
|
@ -389,9 +381,11 @@
|
|||
</div>
|
||||
<div
|
||||
v-if="error"
|
||||
class="alert error"
|
||||
class="alert error -dismissible"
|
||||
>
|
||||
Error: {{ error }}
|
||||
<span>
|
||||
{{ error }}
|
||||
</span>
|
||||
<button
|
||||
class="button-unstyled"
|
||||
@click="clearError"
|
||||
|
|
|
|||
|
|
@ -15,17 +15,20 @@ export default {
|
|||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
reply: {
|
||||
type: Boolean,
|
||||
url: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
required: true,
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
text: this.params.url,
|
||||
text: this.url,
|
||||
loading: false,
|
||||
error: false,
|
||||
debounceSetQuote: debounce((value) => {
|
||||
|
|
@ -34,16 +37,15 @@ export default {
|
|||
}
|
||||
},
|
||||
created() {
|
||||
if (this.params.url && !this.params.id) {
|
||||
this.fetchStatus(this.params.url)
|
||||
} else if (this.params.id) {
|
||||
if (this.url && !this.id) {
|
||||
this.fetchStatus(this.url)
|
||||
} else if (this.id) {
|
||||
this.text =
|
||||
window.location.protocol +
|
||||
'//' +
|
||||
this.instanceHost +
|
||||
'/notice/' +
|
||||
this.params.id
|
||||
this.params.url = this.text
|
||||
this.id
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -56,22 +58,23 @@ export default {
|
|||
)
|
||||
},
|
||||
quoteVisible() {
|
||||
return (!!this.params.id || this.loading) && !this.error
|
||||
return (!!this.id || this.loading) && !this.error
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
text(value) {
|
||||
this.debounceSetQuote(value)
|
||||
this.$emit('update:url', value)
|
||||
},
|
||||
visible(value) {
|
||||
if (value && this.params.url) {
|
||||
this.fetchStatus(this.params.url)
|
||||
if (value && this.url) {
|
||||
this.fetchStatus(this.url)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clear() {
|
||||
this.text = this.params.url
|
||||
this.text = this.url
|
||||
this.loading = false
|
||||
this.error = false
|
||||
},
|
||||
|
|
@ -79,16 +82,15 @@ export default {
|
|||
this.loading = value
|
||||
},
|
||||
handleError(error) {
|
||||
this.params.id = null
|
||||
this.id = null
|
||||
this.error = !!error
|
||||
},
|
||||
fetchStatus(value) {
|
||||
this.params.url = value
|
||||
this.error = false
|
||||
|
||||
const notice = this.noticeRegex.exec(value)
|
||||
if (notice && notice.length === 4) {
|
||||
this.params.id = notice[3]
|
||||
this.$emit('update:id', notice[3])
|
||||
} else if (value) {
|
||||
this.loading = true
|
||||
this.$store
|
||||
|
|
@ -101,7 +103,7 @@ export default {
|
|||
})
|
||||
.then((data) => {
|
||||
if (data && data.statuses && data.statuses.length === 1) {
|
||||
this.params.id = data.statuses[0].id
|
||||
this.$emit('update:id', data.statuses[0].id)
|
||||
} else {
|
||||
this.handleError(true)
|
||||
}
|
||||
|
|
@ -111,7 +113,7 @@ export default {
|
|||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
this.params.id = null
|
||||
this.$emit('update:id', null)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
>
|
||||
</div>
|
||||
<Quote
|
||||
:status-id="params.id"
|
||||
:status-url="params.url"
|
||||
:status-id="id"
|
||||
:status-url="url"
|
||||
:status-visible="quoteVisible"
|
||||
:initially-expanded="true"
|
||||
:loading="loading"
|
||||
|
|
|
|||
|
|
@ -112,6 +112,12 @@ export default {
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
// Allow wide emoji (max 3:1 ratio)
|
||||
allowNonSquareEmoji: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
// NEVER EVER TOUCH DATA INSIDE RENDER
|
||||
render() {
|
||||
|
|
@ -322,7 +328,13 @@ export default {
|
|||
// slots updated -> rerender -> emit -> update up the tree -> rerender -> ...
|
||||
// at least until vue3?
|
||||
const result = (
|
||||
<span class={['RichContent', this.faint ? '-faint' : '']}>
|
||||
<span
|
||||
class={[
|
||||
'RichContent',
|
||||
this.faint ? '-faint' : '',
|
||||
this.allowNonSquareEmoji ? '-allow-non-square-emoji' : '',
|
||||
]}
|
||||
>
|
||||
{this.collapse
|
||||
? pass2.map((x) => {
|
||||
if (!Array.isArray(x)) return x.replace(/\n/g, ' ')
|
||||
|
|
|
|||
|
|
@ -77,6 +77,14 @@
|
|||
height: var(--emoji-size, 32px);
|
||||
}
|
||||
|
||||
&.-allow-non-square-emoji {
|
||||
.emoji {
|
||||
width: auto;
|
||||
max-width: calc(var(--emoji-size, 32px) * 3);
|
||||
min-width: var(--emoji-size, 32px);
|
||||
}
|
||||
}
|
||||
|
||||
.img,
|
||||
video {
|
||||
max-width: 100%;
|
||||
|
|
|
|||
|
|
@ -51,8 +51,6 @@
|
|||
class="cancel-icon fa-scale-110 fa-old-padding"
|
||||
/>
|
||||
</button>
|
||||
<span class="spacer" />
|
||||
<span class="spacer" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -61,18 +59,14 @@
|
|||
|
||||
<style lang="scss">
|
||||
.SearchBar {
|
||||
display: inline-flex;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
vertical-align: baseline;
|
||||
justify-content: flex-end;
|
||||
|
||||
&.-expanded {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-bar-input,
|
||||
.search-button {
|
||||
height: 29px;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.search-bar-input {
|
||||
|
|
|
|||
|
|
@ -80,10 +80,16 @@ const present = computed(() => props.modelValue[props.selectedId] != null)
|
|||
|
||||
const moveUp = async () => {
|
||||
const newModel = [...props.modelValue]
|
||||
const movable = newModel.splice(props.selectedId, 1)[0]
|
||||
newModel.splice(props.selectedId - 1, 0, movable)
|
||||
const movableId = Number(props.selectedId)
|
||||
|
||||
emit('update:modelValue', newModel)
|
||||
const movable = newModel.slice(movableId, movableId + 1)[0]
|
||||
const before = newModel.slice(0, movableId)
|
||||
const after = newModel.slice(movableId + 1)
|
||||
|
||||
const newBefore = before.slice(0, -1)
|
||||
const newAfter = [before.slice(-1)[0], ...after]
|
||||
|
||||
emit('update:modelValue', [...newBefore, movable, ...newAfter])
|
||||
await nextTick()
|
||||
emit('update:selectedId', props.selectedId - 1)
|
||||
}
|
||||
|
|
@ -94,12 +100,18 @@ const moveDnValid = computed(() => {
|
|||
|
||||
const moveDn = async () => {
|
||||
const newModel = [...props.modelValue]
|
||||
const movable = newModel.splice(props.selectedId.value, 1)[0]
|
||||
newModel.splice(props.selectedId + 1, 0, movable)
|
||||
const movableId = Number(props.selectedId)
|
||||
|
||||
emit('update:modelValue', newModel)
|
||||
const movable = newModel.slice(movableId, movableId + 1)[0]
|
||||
const before = newModel.slice(0, movableId)
|
||||
const after = newModel.slice(movableId + 1)
|
||||
|
||||
const newBefore = [...before, after.slice(0, 1)[0]]
|
||||
const newAfter = after.slice(1)
|
||||
|
||||
emit('update:modelValue', [...newBefore, movable, ...newAfter])
|
||||
await nextTick()
|
||||
emit('update:selectedId', props.selectedId + 1)
|
||||
emit('update:selectedId', movableId + 1)
|
||||
}
|
||||
|
||||
const add = async () => {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import ModifiedIndicator from '../helpers/modified_indicator.vue'
|
|||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import StringSetting from '../helpers/string_setting.vue'
|
||||
|
||||
import { useEmojiStore } from 'src/stores/emoji.js'
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
|
||||
|
|
@ -174,63 +175,25 @@ const EmojiTab = {
|
|||
this.sortPackFiles(packName)
|
||||
},
|
||||
|
||||
loadPacksPaginated(listFunction) {
|
||||
const pageSize = 25
|
||||
const allPacks = {}
|
||||
|
||||
return listFunction({
|
||||
instance: this.remotePackInstance,
|
||||
page: 1,
|
||||
pageSize: 0,
|
||||
})
|
||||
.then((data) => data.json())
|
||||
.then((data) => {
|
||||
if (data.error !== undefined) {
|
||||
return Promise.reject(data.error)
|
||||
}
|
||||
|
||||
let resultingPromise = Promise.resolve({})
|
||||
for (let i = 0; i < Math.ceil(data.count / pageSize); i++) {
|
||||
resultingPromise = resultingPromise
|
||||
.then(() =>
|
||||
listFunction({
|
||||
instance: this.remotePackInstance,
|
||||
page: i,
|
||||
pageSize,
|
||||
}),
|
||||
)
|
||||
.then((data) => data.json())
|
||||
.then((pageData) => {
|
||||
if (pageData.error !== undefined) {
|
||||
return Promise.reject(pageData.error)
|
||||
}
|
||||
|
||||
assign(allPacks, pageData.packs)
|
||||
})
|
||||
}
|
||||
|
||||
return resultingPromise
|
||||
})
|
||||
.then(() => allPacks)
|
||||
.catch((data) => {
|
||||
this.displayError(data)
|
||||
})
|
||||
},
|
||||
|
||||
refreshPackList() {
|
||||
this.loadPacksPaginated(
|
||||
this.$store.state.api.backendInteractor.listEmojiPacks,
|
||||
).then((allPacks) => {
|
||||
this.knownLocalPacks = allPacks
|
||||
for (const name of Object.keys(this.knownLocalPacks)) {
|
||||
this.sortPackFiles(name)
|
||||
}
|
||||
})
|
||||
useEmojiStore()
|
||||
.getAdminPacks(
|
||||
this.remotePackInstance,
|
||||
this.$store.state.api.backendInteractor.listEmojiPacks,
|
||||
)
|
||||
.then((allPacks) => {
|
||||
this.knownLocalPacks = allPacks
|
||||
for (const name of Object.keys(this.knownLocalPacks)) {
|
||||
this.sortPackFiles(name)
|
||||
}
|
||||
})
|
||||
},
|
||||
listRemotePacks() {
|
||||
this.loadPacksPaginated(
|
||||
this.$store.state.api.backendInteractor.listRemoteEmojiPacks,
|
||||
)
|
||||
useEmojiStore()
|
||||
.getAdminPacks(
|
||||
this.remotePackInstance,
|
||||
this.$store.state.api.backendInteractor.listRemoteEmojiPacks,
|
||||
)
|
||||
.then((allPacks) => {
|
||||
let inst = this.remotePackInstance
|
||||
if (!inst.startsWith('http')) {
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@
|
|||
</ul>
|
||||
<h3>{{ $t('admin_dash.federation.activitypub') }}</h3>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting path=":pleroma.:instance.:allow_relay" />
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path=":pleroma.:activitypub.:unfollow_blocked" />
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
:trigger-attrs="{ 'aria-label': $t('settings.setting_changed') }"
|
||||
>
|
||||
<template #trigger>
|
||||
|
||||
<FAIcon icon="circle-question" />
|
||||
</template>
|
||||
<template #content>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
trigger="hover"
|
||||
>
|
||||
<template #trigger>
|
||||
|
||||
<FAIcon
|
||||
icon="desktop"
|
||||
:aria-label="$t('settings.setting_local_side')"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
:trigger-attrs="{ 'aria-label': $t('settings.setting_changed') }"
|
||||
>
|
||||
<template #trigger>
|
||||
|
||||
<FAIcon
|
||||
icon="wrench"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@
|
|||
class="setting-label"
|
||||
:class="{ 'faint': shouldBeDisabled }"
|
||||
>
|
||||
<ModifiedIndicator
|
||||
:changed="isChanged"
|
||||
:onclick="reset"
|
||||
/>
|
||||
<LocalSettingIndicator :is-local="isLocalSetting" />
|
||||
{{ ' ' }}
|
||||
<DraftButtons v-if="!hideDraftButtons" />
|
||||
<ModifiedIndicator
|
||||
:changed="isChanged"
|
||||
:onclick="reset"
|
||||
/>
|
||||
<LocalSettingIndicator :is-local="isLocalSetting" />
|
||||
{{ ' ' }}
|
||||
<DraftButtons v-if="!hideDraftButtons" />
|
||||
<template v-if="backendDescriptionLabel">
|
||||
{{ backendDescriptionLabel + ' ' }}
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
.tab-slot-wrapper {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
padding: 0 1em;
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(1em, 1fr) minmax(min-content, 45em) minmax(1em, 1fr);
|
||||
|
|
@ -65,6 +66,8 @@
|
|||
.tab-content-wrapper {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.-hidden {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -38,10 +38,7 @@
|
|||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.suboptions {
|
||||
margin-left: 1em;
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.sidenote {
|
||||
|
|
@ -53,6 +50,7 @@
|
|||
.setting-description {
|
||||
margin-top: 0.2em;
|
||||
margin-bottom: 0;
|
||||
margin-left: 0;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +64,7 @@
|
|||
column-gap: 0.5em;
|
||||
align-items: baseline;
|
||||
padding: 0.5em 0;
|
||||
line-height: 1.5em;
|
||||
|
||||
.setting-label {
|
||||
grid-area: label;
|
||||
|
|
@ -96,6 +95,9 @@
|
|||
|
||||
.checkbox-indicator {
|
||||
grid-area: control;
|
||||
height: 1.5em;
|
||||
line-height: 1.5em;
|
||||
align-self: baseline;
|
||||
}
|
||||
|
||||
.-mobile & {
|
||||
|
|
@ -130,6 +132,13 @@
|
|||
padding-left: 0;
|
||||
margin: 0;
|
||||
|
||||
&.suboptions {
|
||||
margin-left: 2em;
|
||||
border-top: 1px dotted var(--border);
|
||||
border-bottom: 1px dotted var(--border);
|
||||
}
|
||||
|
||||
|
||||
.btn:not(.dropdown-button) {
|
||||
padding: 0 2em;
|
||||
}
|
||||
|
|
@ -207,6 +216,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
li {
|
||||
.sidenote {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
.setting-item {
|
||||
grid-template-columns: 1fr min-content;
|
||||
|
|
@ -221,9 +236,14 @@
|
|||
.checkbox {
|
||||
.label {
|
||||
text-align: left;
|
||||
margin-left: 0;
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.checkbox-indicator {
|
||||
order: 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ul {
|
||||
|
|
@ -236,14 +256,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.setting-list:not(.suboptions),
|
||||
.option-list {
|
||||
&.two-column {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.UnitSetting {
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.peek {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ const AppearanceTab = {
|
|||
})),
|
||||
backgroundUploading: false,
|
||||
background: null,
|
||||
backgroundError: null,
|
||||
backgroundPreview: null,
|
||||
}
|
||||
},
|
||||
|
|
@ -474,6 +475,9 @@ const AppearanceTab = {
|
|||
resetUploadedBackground() {
|
||||
this.backgroundPreview = null
|
||||
},
|
||||
clearBackgroundError() {
|
||||
this.backgroundError = null
|
||||
},
|
||||
submitBackground(background) {
|
||||
if (!this.backgroundPreview && background !== '') {
|
||||
return
|
||||
|
|
@ -486,8 +490,11 @@ const AppearanceTab = {
|
|||
this.$store.commit('addNewUsers', [data])
|
||||
this.$store.commit('setCurrentUser', data)
|
||||
this.backgroundPreview = null
|
||||
this.backgroundError = null
|
||||
})
|
||||
.catch((e) => {
|
||||
this.backgroundError = e
|
||||
})
|
||||
.catch(this.displayUploadError)
|
||||
.finally(() => {
|
||||
this.backgroundUploading = false
|
||||
})
|
||||
|
|
|
|||
|
|
@ -208,6 +208,23 @@
|
|||
{{ $t('settings.reset') }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="backgroundError"
|
||||
class="alert error -dismissible"
|
||||
>
|
||||
<span>
|
||||
{{ backgroundError }}
|
||||
</span>
|
||||
<button
|
||||
class="button-unstyled"
|
||||
@click="clearBackgroundError"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="times"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="!isDefaultBackground"
|
||||
class="btn button-default reset-button"
|
||||
|
|
@ -246,6 +263,11 @@
|
|||
{{ $t('settings.hide_wallpaper') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="compactProfiles">
|
||||
{{ $t('settings.compact_profiles') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,16 @@
|
|||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="userCardHidePersonalMarks">
|
||||
{{ $t('settings.user_card_hide_personal_marks') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li v-if="shoutAvailable">
|
||||
<BooleanSetting path="hideShoutbox">
|
||||
{{ $t('settings.hide_shoutbox') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>{{ $t('settings.attachments') }}</h3>
|
||||
<ul class="setting-list">
|
||||
|
|
@ -77,16 +87,6 @@
|
|||
{{ $t('settings.hide_attachments_in_convo') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="userCardHidePersonalMarks">
|
||||
{{ $t('settings.user_card_hide_personal_marks') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li v-if="shoutAvailable">
|
||||
<BooleanSetting path="hideShoutbox">
|
||||
{{ $t('settings.hide_shoutbox') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { mapState } from 'pinia'
|
||||
|
||||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
|
||||
|
|
@ -9,14 +11,6 @@ const pleromaFeCommitUrl =
|
|||
'https://git.pleroma.social/pleroma/pleroma-fe/commit/'
|
||||
|
||||
const VersionTab = {
|
||||
data() {
|
||||
const instance = useInstanceStore()
|
||||
return {
|
||||
backendVersion: instance.backendVersion,
|
||||
backendRepository: instance.backendRepository,
|
||||
frontendVersion: instance.frontendVersion,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
BooleanSetting,
|
||||
},
|
||||
|
|
@ -24,6 +18,11 @@ const VersionTab = {
|
|||
frontendVersionLink() {
|
||||
return pleromaFeCommitUrl + this.frontendVersion
|
||||
},
|
||||
...mapState(useInstanceStore, [
|
||||
'backendVersion',
|
||||
'backendRepository',
|
||||
'frontendVersion',
|
||||
]),
|
||||
...SharedComputedObject(),
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ const FilteringTab = {
|
|||
hide = false,
|
||||
name = '',
|
||||
value = '',
|
||||
caseSensitive = false,
|
||||
} = data
|
||||
|
||||
this.createFilter({
|
||||
|
|
@ -66,6 +67,7 @@ const FilteringTab = {
|
|||
hide,
|
||||
name,
|
||||
value,
|
||||
caseSensitive,
|
||||
})
|
||||
},
|
||||
onImportFailure(result) {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,16 @@
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
> label.checkbox {
|
||||
display: grid;
|
||||
grid-template-columns: subgrid;
|
||||
grid-template-rows: subgrid;
|
||||
grid-column: 1 / span 2;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.filter-field-value {
|
||||
display: flex;
|
||||
grid-column: 2 / span 2;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,6 +254,20 @@
|
|||
:value="filter[1].value"
|
||||
@input="updateFilter(filter[0], 'value', $event.target.value)"
|
||||
>
|
||||
{{ ' ' }}
|
||||
</div>
|
||||
<div class="filter-value filter-field">
|
||||
<Checkbox
|
||||
:id="'filterCaseSensitive' + filter[0]"
|
||||
:model-value="filter[1].caseSensitive"
|
||||
:name="'filterCaseSensitive' + filter[0]"
|
||||
class="input-inset input-boolean case-sensitive"
|
||||
@update:model-value="updateFilter(filter[0], 'caseSensitive', $event)"
|
||||
>
|
||||
<template #before>
|
||||
{{ $t('settings.filter.case_sensitive') }}
|
||||
</template>
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div class="filter-expires filter-field">
|
||||
<label
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
class="lang-selector"
|
||||
@update="val => language = val"
|
||||
/>
|
||||
<h5>{{ $t('settings.email_language') }}</h5>
|
||||
<interface-language-switcher
|
||||
v-model="emailLanguage"
|
||||
class="lang-selector"
|
||||
|
|
|
|||
|
|
@ -117,7 +117,6 @@
|
|||
:key="column"
|
||||
:local="true"
|
||||
:path="column + 'ColumnWidth'"
|
||||
:units="horizontalUnits"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.column_sizes_' + column) }}
|
||||
|
|
|
|||
|
|
@ -509,22 +509,14 @@ export default {
|
|||
}
|
||||
},
|
||||
setCustomTheme() {
|
||||
useInterfaceStore().setThemeV2({
|
||||
customTheme: {
|
||||
ignore: true,
|
||||
themeFileVersion: this.selectedVersion,
|
||||
themeEngineVersion: CURRENT_VERSION,
|
||||
...this.previewTheme,
|
||||
},
|
||||
customThemeSource: {
|
||||
themeFileVersion: this.selectedVersion,
|
||||
themeEngineVersion: CURRENT_VERSION,
|
||||
shadows: this.shadowsLocal,
|
||||
fonts: this.fontsLocal,
|
||||
opacity: this.currentOpacity,
|
||||
colors: this.currentColors,
|
||||
radii: this.currentRadii,
|
||||
},
|
||||
useInterfaceStore().setTheme({
|
||||
themeFileVersion: this.selectedVersion,
|
||||
themeEngineVersion: CURRENT_VERSION,
|
||||
shadows: this.shadowsLocal,
|
||||
fonts: this.fontsLocal,
|
||||
opacity: this.currentOpacity,
|
||||
colors: this.currentColors,
|
||||
radii: this.currentRadii,
|
||||
})
|
||||
},
|
||||
updatePreviewColors() {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import BooleanSetting from '../helpers/boolean_setting.vue'
|
|||
import ChoiceSetting from '../helpers/choice_setting.vue'
|
||||
import IntegerSetting from '../helpers/integer_setting.vue'
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import UnitSetting from '../helpers/unit_setting.vue'
|
||||
|
||||
import { useLocalConfigStore } from 'src/stores/local_config.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
|
|
@ -62,6 +63,7 @@ const PostsTab = {
|
|||
ChoiceSetting,
|
||||
IntegerSetting,
|
||||
FontControl,
|
||||
UnitSetting,
|
||||
},
|
||||
computed: {
|
||||
...SharedComputedObject(),
|
||||
|
|
|
|||
|
|
@ -169,6 +169,11 @@
|
|||
{{ $t('settings.stop_gifs') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="nonSquareEmoji">
|
||||
{{ $t('settings.non_square_emoji') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
:local="true"
|
||||
|
|
|
|||
|
|
@ -492,7 +492,7 @@ export default {
|
|||
try {
|
||||
return deserializeShadow(shadow)
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
console.warn('Failed to deserialize shadow', e)
|
||||
return shadow
|
||||
}
|
||||
}
|
||||
|
|
@ -652,7 +652,7 @@ export default {
|
|||
return rgb2hex(computedColor)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
console.warn('failed to get computed color', e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ export default {
|
|||
if (computedColor) return rgb2hex(computedColor)
|
||||
return null
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
console.warn('Failed to get fallback color', e)
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ const SideDrawer = {
|
|||
return unseenNotificationsFromStore(
|
||||
this.$store,
|
||||
useMergedConfigStore().mergedConfig.notificationVisibility,
|
||||
useMergedConfigStore().mergedConfig.ignoreInactionableSeen,
|
||||
)
|
||||
},
|
||||
unseenNotificationsCount() {
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ const Status = {
|
|||
'simpleTree',
|
||||
'showOtherRepliesAsButton',
|
||||
'dive',
|
||||
'ignoreMute',
|
||||
|
||||
'controlledThreadDisplayStatus',
|
||||
'controlledToggleThreadDisplay',
|
||||
|
|
@ -166,7 +167,7 @@ const Status = {
|
|||
'controlledMediaPlaying',
|
||||
'controlledSetMediaPlaying',
|
||||
],
|
||||
emits: ['interacted', 'goto', 'toggleExpanded'],
|
||||
emits: ['goto', 'toggleExpanded'],
|
||||
data() {
|
||||
return {
|
||||
uncontrolledReplying: false,
|
||||
|
|
@ -187,6 +188,9 @@ const Status = {
|
|||
!this.inConversation
|
||||
)
|
||||
},
|
||||
allowNonSquareEmoji() {
|
||||
return this.mergedConfig.nonSquareEmoji
|
||||
},
|
||||
repeaterClass() {
|
||||
const user = this.statusoid.user
|
||||
return highlightClass(user)
|
||||
|
|
@ -345,6 +349,7 @@ const Status = {
|
|||
}
|
||||
},
|
||||
muted() {
|
||||
if (this.ignoreMute) return false
|
||||
if (this.statusoid.user.id === this.currentUser.id) return false
|
||||
return !this.unmuted && !this.shouldNotMute && this.muteReasons.length > 0
|
||||
},
|
||||
|
|
@ -366,6 +371,7 @@ const Status = {
|
|||
)
|
||||
},
|
||||
shouldNotMute() {
|
||||
if (this.ignoreMute) return true
|
||||
if (this.isFocused) return true
|
||||
const { status } = this
|
||||
const { reblog } = status
|
||||
|
|
@ -557,11 +563,9 @@ const Status = {
|
|||
this.error = error
|
||||
},
|
||||
clearError() {
|
||||
this.$emit('interacted')
|
||||
this.error = undefined
|
||||
},
|
||||
toggleReplying() {
|
||||
this.$emit('interacted')
|
||||
if (this.replying) {
|
||||
this.$refs.postStatusForm.requestClose()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -23,21 +23,18 @@
|
|||
.status-container {
|
||||
display: flex;
|
||||
padding: var(--status-margin);
|
||||
gap: var(--status-margin);
|
||||
|
||||
> * {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&.-repeat {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.pin {
|
||||
padding: var(--status-margin) var(--status-margin) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
._misclick-prevention & {
|
||||
|
|
@ -50,12 +47,11 @@
|
|||
}
|
||||
|
||||
.left-side {
|
||||
margin-right: var(--status-margin);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.right-side {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.usercard {
|
||||
|
|
@ -230,29 +226,47 @@
|
|||
}
|
||||
|
||||
.repeat-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.4em var(--status-margin);
|
||||
|
||||
.repeat-icon {
|
||||
color: var(--cGreen);
|
||||
.repeater-avatar {
|
||||
flex: 0 0 1.5em;
|
||||
border-radius: var(--roundness);
|
||||
margin-left: 2em; // 3.5 (poster avatar size) - 1.5 (repeater avatar size)
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.repeater-avatar {
|
||||
border-radius: var(--roundness);
|
||||
margin-left: 2em; // 3.5 (poster avatar size) - 1.5 (repeater avatar size)
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
}
|
||||
.right-side {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 0;
|
||||
gap: 0.5em;
|
||||
|
||||
.repeater-name {
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 0;
|
||||
.repeater-name {
|
||||
flex: 0 1 auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
object-fit: contain;
|
||||
.repeat-label {
|
||||
white-space: nowrap;
|
||||
flex: 0 0 auto;
|
||||
|
||||
.repeat-icon {
|
||||
vertical-align: middle;
|
||||
color: var(--cGreen);
|
||||
}
|
||||
}
|
||||
|
||||
.emoji {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -371,21 +385,4 @@
|
|||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (width <= 800px) {
|
||||
.repeater-avatar {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.post-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
// TODO define those other way somehow?
|
||||
&.-compact {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,16 +46,6 @@
|
|||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="showPinned"
|
||||
class="pin"
|
||||
>
|
||||
<FAIcon
|
||||
icon="thumbtack"
|
||||
class="faint"
|
||||
/>
|
||||
<span class="faint">{{ $t('status.pinned') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="retweet && !noHeading && !inConversation"
|
||||
:class="[repeaterClass, { highlighted: repeaterStyle }]"
|
||||
|
|
@ -80,6 +70,7 @@
|
|||
<RichContent
|
||||
:html="retweeterHtml"
|
||||
:emoji="retweeterUser.emoji"
|
||||
:allow-non-square-emoji="allowNonSquareEmoji"
|
||||
:is-local="retweeterUser.is_local"
|
||||
/>
|
||||
</router-link>
|
||||
|
|
@ -88,13 +79,14 @@
|
|||
:to="retweeterProfileLink"
|
||||
>{{ retweeter }}</router-link>
|
||||
</bdi>
|
||||
{{ ' ' }}
|
||||
<FAIcon
|
||||
icon="retweet"
|
||||
class="repeat-icon"
|
||||
:title="$t('tool_tip.repeat')"
|
||||
/>
|
||||
{{ $t('timeline.repeated') }}
|
||||
<div class="repeat-label">
|
||||
<FAIcon
|
||||
icon="retweet"
|
||||
class="repeat-icon"
|
||||
:title="$t('tool_tip.repeat')"
|
||||
/>
|
||||
{{ $t('timeline.repeated') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -152,6 +144,7 @@
|
|||
<RichContent
|
||||
:html="status.user.name"
|
||||
:emoji="status.user.emoji"
|
||||
:allow-non-square-emoji="allowNonSquareEmoji"
|
||||
:is-local="status.user.is_local"
|
||||
/>
|
||||
</h4>
|
||||
|
|
@ -176,6 +169,16 @@
|
|||
</div>
|
||||
|
||||
<span class="heading-right">
|
||||
<span
|
||||
v-if="showPinned"
|
||||
class="pin"
|
||||
>
|
||||
<FAIcon
|
||||
icon="thumbtack"
|
||||
class="faint"
|
||||
/>
|
||||
<span class="faint">{{ $t('status.pinned') }}</span>
|
||||
</span>
|
||||
<router-link
|
||||
class="timeago faint"
|
||||
:to="{ name: 'conversation', params: { id: status.id } }"
|
||||
|
|
@ -519,7 +522,6 @@
|
|||
:status="status"
|
||||
:replying="replying"
|
||||
@toggle-replying="toggleReplying"
|
||||
@interacted="e => $emit('interacted')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import StatusBookmarkFolderMenu from 'src/components/status_bookmark_folder_menu
|
|||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
|
|
@ -13,8 +14,8 @@ import {
|
|||
import {
|
||||
faBookmark,
|
||||
faCheck,
|
||||
faChevronDown,
|
||||
faChevronRight,
|
||||
faChevronUp,
|
||||
faExternalLinkAlt,
|
||||
faEyeSlash,
|
||||
faHistory,
|
||||
|
|
@ -38,7 +39,7 @@ library.add(
|
|||
faWrench,
|
||||
|
||||
faChevronRight,
|
||||
faChevronUp,
|
||||
faChevronDown,
|
||||
|
||||
faReply,
|
||||
faRetweet,
|
||||
|
|
@ -67,7 +68,6 @@ export default {
|
|||
'doAction',
|
||||
'outerClose',
|
||||
],
|
||||
emits: ['interacted'],
|
||||
components: {
|
||||
StatusBookmarkFolderMenu,
|
||||
EmojiPicker,
|
||||
|
|
@ -97,6 +97,9 @@ export default {
|
|||
return !useInstanceCapabilitiesStore()
|
||||
.pleromaCustomEmojiReactionsAvailable
|
||||
},
|
||||
hidePostStats() {
|
||||
return useMergedConfigStore().mergedConfig.hidePostStats
|
||||
},
|
||||
buttonInnerClass() {
|
||||
return [
|
||||
this.button.name + '-button',
|
||||
|
|
@ -128,6 +131,12 @@ export default {
|
|||
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
|
||||
}
|
||||
},
|
||||
onShowEmojiPicker() {
|
||||
this.$emit('emojiPickerShown', true)
|
||||
},
|
||||
onHideEmojiPicker() {
|
||||
this.$emit('emojiPickerShown', false)
|
||||
},
|
||||
doActionWrap(
|
||||
button,
|
||||
close = () => {
|
||||
|
|
@ -138,9 +147,8 @@ export default {
|
|||
this.button.interactive ? !this.button.interactive(this.funcArg) : false
|
||||
)
|
||||
return
|
||||
this.$emit('interacted')
|
||||
if (button.name === 'emoji') {
|
||||
this.$refs.picker.showPicker()
|
||||
this.$refs.picker.togglePicker()
|
||||
} else {
|
||||
this.animationState = true
|
||||
this.getComponent(button) === 'button' && this.doAction(button)
|
||||
|
|
|
|||
|
|
@ -6,19 +6,24 @@
|
|||
display: flex;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
height: 1.5em;
|
||||
border: 2px solid transparent;
|
||||
|
||||
.chevron-popover {
|
||||
.popover-trigger-button {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.action-counter {
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.action-button-inner,
|
||||
.extra-button {
|
||||
margin: -0.5em;
|
||||
padding: 0.5em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.separator {
|
||||
|
|
@ -26,27 +31,13 @@
|
|||
align-self: stretch;
|
||||
width: 1px;
|
||||
background-color: var(--icon);
|
||||
margin-left: 0.75em;
|
||||
margin-right: 0.125em;
|
||||
}
|
||||
|
||||
&.-pin {
|
||||
margin: calc(-2px - 0.25em);
|
||||
padding: 0.25em;
|
||||
border: 2px dashed var(--icon);
|
||||
border-radius: var(--roundness);
|
||||
grid-template-columns: minmax(max-content, 1fr) auto;
|
||||
|
||||
.chevron-icon,
|
||||
.extra-button,
|
||||
.separator {
|
||||
display: none;
|
||||
}
|
||||
opacity: 0.75;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.action-button-inner {
|
||||
display: grid;
|
||||
grid-gap: 1em;
|
||||
grid-gap: 0.125em;
|
||||
grid-template-columns: max-content;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: max-content;
|
||||
|
|
@ -72,6 +63,32 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.-pin {
|
||||
border: 2px dashed var(--icon);
|
||||
border-radius: var(--roundness);
|
||||
grid-template-columns: minmax(max-content, 1fr) auto;
|
||||
|
||||
.action-button-inner {
|
||||
opacity: 0.8;
|
||||
padding-right: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chevron-icon,
|
||||
.extra-button,
|
||||
.separator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.-with-extra {
|
||||
.action-button-inner,
|
||||
.extra-button {
|
||||
padding-left: 0.25em;
|
||||
padding-right: 0.25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-button {
|
||||
|
|
@ -106,7 +123,7 @@
|
|||
&.-extra {
|
||||
.action-counter {
|
||||
justify-self: end;
|
||||
margin-right: 1em;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.chevron-icon {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
:class="buttonInnerClass"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
placement="bottom"
|
||||
:title="$t(button.label(funcArg))"
|
||||
target="_blank"
|
||||
:tabindex="0"
|
||||
|
|
@ -26,21 +27,21 @@
|
|||
/>
|
||||
<template v-if="!buttonClass.disabled && (!button.interactive || button?.interactive(funcArg)) && button.toggleable?.(funcArg) && button.active">
|
||||
<FAIcon
|
||||
v-if="button.active(funcArg)"
|
||||
v-if="button.active(funcArg) && button.activeIndicator?.() !== null"
|
||||
class="active-marker"
|
||||
transform="shrink-6 up-9 right-15"
|
||||
transform="shrink-6 up-9 left-12"
|
||||
:icon="button.activeIndicator?.(funcArg) || 'check'"
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="!button.active(funcArg)"
|
||||
class="focus-marker"
|
||||
transform="shrink-6 up-9 right-15"
|
||||
transform="shrink-6 up-9 left-12"
|
||||
:icon="button.openIndicator?.(funcArg) || 'plus'"
|
||||
/>
|
||||
<FAIcon
|
||||
v-else
|
||||
class="focus-marker"
|
||||
transform="shrink-6 up-9 right-15"
|
||||
transform="shrink-6 up-9 left-12"
|
||||
:icon="button.closeIndicator?.(funcArg) || 'minus'"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -54,13 +55,12 @@
|
|||
<FAIcon
|
||||
v-if="button.dropdown?.()"
|
||||
class="chevron-icon"
|
||||
size="lg"
|
||||
:icon="extra ? 'chevron-right' : 'chevron-up'"
|
||||
:icon="extra ? 'chevron-right' : 'chevron-down'"
|
||||
fixed-width
|
||||
/>
|
||||
</component>
|
||||
<span
|
||||
v-if="button.counter?.(funcArg) > 0"
|
||||
v-if="!hidePostStats && button.counter?.(funcArg) > 0"
|
||||
class="action-counter"
|
||||
>
|
||||
{{ button.counter?.(funcArg) }}
|
||||
|
|
@ -71,16 +71,16 @@
|
|||
/>
|
||||
<Popover
|
||||
v-if="button.name === 'bookmark'"
|
||||
class="chevron-popover"
|
||||
:trigger="extra ? 'hover' : 'click'"
|
||||
:placement="extra ? 'right' : 'top'"
|
||||
:placement="extra ? 'right' : 'bottom'"
|
||||
:offset="extra ? { x: 10 } : { y: 10 }"
|
||||
:trigger-attrs="{ class: 'extra-button' }"
|
||||
>
|
||||
<template #trigger>
|
||||
<FAIcon
|
||||
class="chevron-icon"
|
||||
size="lg"
|
||||
:icon="extra ? 'chevron-right' : 'chevron-up'"
|
||||
:icon="extra ? 'chevron-right' : 'chevron-down'"
|
||||
fixed-width
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -100,6 +100,8 @@
|
|||
:hide-custom-emoji="hideCustomEmoji"
|
||||
class="emoji-picker-panel"
|
||||
@emoji="addReaction"
|
||||
@show="onShowEmojiPicker"
|
||||
@close="onHideEmojiPicker"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default {
|
|||
UserTimedFilterModal,
|
||||
},
|
||||
props: ['button', 'status'],
|
||||
emits: ['interacted'],
|
||||
emits: ['emojiPickerShown'],
|
||||
mounted() {
|
||||
if (this.button.name === 'mute') {
|
||||
this.$store.dispatch('fetchDomainMutes')
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
v-if="button.dropdown?.()"
|
||||
:trigger="$attrs.extra ? 'hover' : 'click'"
|
||||
:offset="{ y: 5 }"
|
||||
:placement="$attrs.extra ? 'right' : 'top'"
|
||||
:placement="$attrs.extra ? 'right' : 'bottom'"
|
||||
>
|
||||
<template #trigger>
|
||||
<ActionButton
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
:button="button"
|
||||
:status="status"
|
||||
v-bind="$attrs"
|
||||
@interacted="e => $emit('interacted')"
|
||||
@emojiPickerShown="e => $emit('emojiPickerShown', e)"
|
||||
/>
|
||||
<teleport to="#modal">
|
||||
<MuteConfirm
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export const BUTTONS = [
|
|||
anonLink: true,
|
||||
toggleable: true,
|
||||
closeIndicator: 'times',
|
||||
activeIndicator: 'none',
|
||||
activeIndicator: null,
|
||||
action({ emit }) {
|
||||
emit('toggleReplying')
|
||||
return Promise.resolve()
|
||||
|
|
@ -97,6 +97,9 @@ export const BUTTONS = [
|
|||
name: 'emoji',
|
||||
label: 'tool_tip.add_reaction',
|
||||
icon: ['far', 'smile-beam'],
|
||||
interactive: () => true,
|
||||
active: ({ emojiPickerShown }) => emojiPickerShown,
|
||||
toggleable: true,
|
||||
anonLink: true,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ library.add(faEllipsisH)
|
|||
|
||||
const StatusActionButtons = {
|
||||
props: ['status', 'replying'],
|
||||
emits: ['toggleReplying', 'interacted', 'onSuccess', 'onError'],
|
||||
emits: ['toggleReplying', 'onSuccess', 'onError'],
|
||||
data() {
|
||||
return {
|
||||
showPin: false,
|
||||
|
|
@ -28,6 +28,7 @@ const StatusActionButtons = {
|
|||
/* no-op */
|
||||
},
|
||||
randomSeed: genRandomSeed(),
|
||||
emojiPickerShown: false,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
|
@ -56,6 +57,7 @@ const StatusActionButtons = {
|
|||
return {
|
||||
status: this.status,
|
||||
replying: this.replying,
|
||||
emojiPickerShown: this.emojiPickerShown,
|
||||
emit: this.$emit,
|
||||
dispatch: this.$store.dispatch,
|
||||
state: this.$store.state,
|
||||
|
|
@ -107,6 +109,9 @@ const StatusActionButtons = {
|
|||
onExtraClose() {
|
||||
this.showPin = false
|
||||
},
|
||||
onEmojiPickerShown(state) {
|
||||
this.emojiPickerShown = state
|
||||
},
|
||||
isPinned(button) {
|
||||
return this.pinnedItems.has(button.name)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,16 +3,19 @@
|
|||
.StatusActionButtons {
|
||||
.quick-action-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10%, 3em));
|
||||
margin-left: -0.5em;
|
||||
grid-template-columns: repeat(auto-fill, minmax(3.75em, 10%));
|
||||
grid-auto-flow: row dense;
|
||||
grid-auto-rows: 1fr;
|
||||
grid-gap: 1.25em 0;
|
||||
grid-gap: 0.5em 0.1em;
|
||||
margin-top: var(--status-margin);
|
||||
}
|
||||
|
||||
.pin-action-button {
|
||||
margin: -0.5em;
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
padding: 0.5em;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
// popover
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
<template>
|
||||
<div class="StatusActionButtons">
|
||||
<span class="quick-action-buttons">
|
||||
<span
|
||||
class="quick-action-buttons"
|
||||
:class="{ '-pin': showPin }"
|
||||
>
|
||||
<span
|
||||
v-for="button in quickButtons"
|
||||
:key="button.name"
|
||||
class="quick-action"
|
||||
:class="{ '-pin': showPin, '-toggle': button.dropdown?.() }"
|
||||
:class="{ '-pin': showPin, '-toggle': button.dropdown?.(), '-with-extra': button.name === 'bookmark' }"
|
||||
>
|
||||
<ActionButtonContainer
|
||||
:class="{ '-pin': showPin }"
|
||||
|
|
@ -17,7 +20,7 @@
|
|||
:get-component="getComponent"
|
||||
:close="() => { /* no-op */ }"
|
||||
:do-action="doAction"
|
||||
@interacted="e => $emit('interacted')"
|
||||
@emojiPickerShown="onEmojiPickerShown"
|
||||
/>
|
||||
<button
|
||||
v-if="showPin && currentUser"
|
||||
|
|
@ -30,7 +33,6 @@
|
|||
<FAIcon
|
||||
v-if="showPin && currentUser"
|
||||
fixed-width
|
||||
class="fa-scale-110"
|
||||
icon="thumbtack"
|
||||
/>
|
||||
</button>
|
||||
|
|
@ -38,15 +40,16 @@
|
|||
<Popover
|
||||
trigger="click"
|
||||
:trigger-attrs="triggerAttrs"
|
||||
class="quick-action"
|
||||
:tabindex="0"
|
||||
placement="top"
|
||||
placement="bottom"
|
||||
:offset="{ y: 5 }"
|
||||
remove-padding
|
||||
@close="onExtraClose"
|
||||
>
|
||||
<template #trigger>
|
||||
<FAIcon
|
||||
class="fa-scale-110 "
|
||||
class="action-button-inner"
|
||||
icon="ellipsis-h"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -56,23 +59,6 @@
|
|||
class="dropdown-menu extra-action-buttons"
|
||||
role="menu"
|
||||
>
|
||||
<div
|
||||
v-if="currentUser"
|
||||
class="menu-item dropdown-item extra-action -icon"
|
||||
>
|
||||
<button
|
||||
class="main-button"
|
||||
role="menuitem"
|
||||
:tabindex="0"
|
||||
@click.stop="() => { resize(); showPin = !showPin }"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110"
|
||||
fixed-width
|
||||
icon="wrench"
|
||||
/><span>{{ $t('nav.edit_pinned') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-for="button in extraButtons"
|
||||
:key="button.name"
|
||||
|
|
@ -89,7 +75,6 @@
|
|||
:get-component="getComponent"
|
||||
:outer-close="close"
|
||||
:do-action="doAction"
|
||||
@interacted="e => $emit('interacted')"
|
||||
/>
|
||||
<button
|
||||
v-if="showPin && currentUser"
|
||||
|
|
@ -108,6 +93,23 @@
|
|||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="currentUser"
|
||||
class="menu-item dropdown-item extra-action -icon"
|
||||
>
|
||||
<button
|
||||
class="main-button"
|
||||
role="menuitem"
|
||||
:tabindex="0"
|
||||
@click.stop="() => { resize(); showPin = !showPin }"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110"
|
||||
fixed-width
|
||||
icon="wrench"
|
||||
/><span>{{ $t('nav.edit_pinned') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ const StatusBody = {
|
|||
localCollapseSubjectDefault() {
|
||||
return this.mergedConfig.collapseMessageWithSubject
|
||||
},
|
||||
allowNonSquareEmoji() {
|
||||
return this.mergedConfig.nonSquareEmoji
|
||||
},
|
||||
// This is a bit hacky, but we want to approximate post height before rendering
|
||||
// so we count newlines (masto uses <p> for paragraphs, GS uses <br> between them)
|
||||
// as well as approximate line count by counting characters and approximating ~80
|
||||
|
|
|
|||
|
|
@ -138,8 +138,6 @@
|
|||
align-items: start;
|
||||
flex-direction: row;
|
||||
|
||||
--emoji-size: calc(var(--emojiSize, 32px) / 2);
|
||||
|
||||
& .body,
|
||||
& .attachments {
|
||||
max-height: 3.25em;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
:html="status.summary_raw_html"
|
||||
:emoji="status.emojis"
|
||||
:is-local="status.isLocal"
|
||||
:allow-non-square-emoji="allowNonSquareEmoji"
|
||||
/>
|
||||
<button
|
||||
v-show="longSubject && showingLongSubject"
|
||||
|
|
@ -47,6 +48,7 @@
|
|||
:greentext="mergedConfig.greentext"
|
||||
:attentions="status.attentions"
|
||||
:is-local="status.is_local"
|
||||
:allow-non-square-emoji="allowNonSquareEmoji"
|
||||
@parse-ready="onParseReady"
|
||||
/>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
:is-preview="true"
|
||||
:statusoid="status"
|
||||
:compact="true"
|
||||
:ignore-mute="true"
|
||||
/>
|
||||
<div
|
||||
v-else-if="error"
|
||||
|
|
|
|||
74
src/components/still-image/still-image-emoji-popover.js
Normal file
74
src/components/still-image/still-image-emoji-popover.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import Popover from 'components/popover/popover.vue'
|
||||
import SelectComponent from 'components/select/select.vue'
|
||||
import { mapState } from 'pinia'
|
||||
|
||||
import StillImage from './still-image.vue'
|
||||
|
||||
import { useEmojiStore } from 'src/stores/emoji'
|
||||
import { useInterfaceStore } from 'src/stores/interface'
|
||||
|
||||
export default {
|
||||
components: { StillImage, Popover, SelectComponent },
|
||||
props: {
|
||||
shortcode: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isLocal: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
packName: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isUserAdmin() {
|
||||
return this.$store.state.users.currentUser?.rights.admin
|
||||
},
|
||||
...mapState(useEmojiStore, ['adminPacksLocal', 'adminPacksLocalLoading']),
|
||||
},
|
||||
methods: {
|
||||
displayError(msg) {
|
||||
useInterfaceStore().pushGlobalNotice({
|
||||
messageKey: 'admin_dash.emoji.error',
|
||||
messageArgs: [msg],
|
||||
level: 'error',
|
||||
})
|
||||
},
|
||||
copyToLocalPack() {
|
||||
this.$store.state.api.backendInteractor
|
||||
.addNewEmojiFile({
|
||||
packName: this.packName,
|
||||
file: this.$attrs.src,
|
||||
shortcode: this.shortcode,
|
||||
filename: '',
|
||||
})
|
||||
.then((resp) => resp.json())
|
||||
.then((resp) => {
|
||||
if (resp.error !== undefined) {
|
||||
this.displayError(resp.error)
|
||||
return
|
||||
}
|
||||
useInterfaceStore().pushGlobalNotice({
|
||||
messageKey: 'admin_dash.emoji.copied_successfully',
|
||||
messageArgs: [this.shortcode, this.packName],
|
||||
level: 'success',
|
||||
})
|
||||
|
||||
this.$refs.emojiPopover.hidePopover()
|
||||
this.packName = ''
|
||||
})
|
||||
},
|
||||
|
||||
fetchEmojiPacksIfAdmin() {
|
||||
useEmojiStore()
|
||||
.getAdminPacksLocal()
|
||||
.then(() => {
|
||||
this.$refs.emojiPopover.updateStyles()
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue