Merge remote-tracking branch 'upstream/develop' into checkbox-goes-brrr

This commit is contained in:
tusooa 2023-01-28 21:39:39 -05:00
commit da58282e90
No known key found for this signature in database
GPG key ID: 7B467EDE43A08224
188 changed files with 4165 additions and 2321 deletions

View file

@ -9,6 +9,3 @@
</template>
<script src="./about.js"></script>
<style lang="scss">
</style>

View file

@ -2,6 +2,7 @@ import { mapState } from 'vuex'
import ProgressButton from '../progress_button/progress_button.vue'
import Popover from '../popover/popover.vue'
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisV
@ -16,14 +17,30 @@ const AccountActions = {
'user', 'relationship'
],
data () {
return { }
return {
showingConfirmBlock: false,
showingConfirmRemoveFollower: false
}
},
components: {
ProgressButton,
Popover,
UserListMenu
UserListMenu,
ConfirmModal
},
methods: {
showConfirmBlock () {
this.showingConfirmBlock = true
},
hideConfirmBlock () {
this.showingConfirmBlock = false
},
showConfirmRemoveUserFromFollowers () {
this.showingConfirmRemoveFollower = true
},
hideConfirmRemoveUserFromFollowers () {
this.showingConfirmRemoveFollower = false
},
showRepeats () {
this.$store.dispatch('showReblogs', this.user.id)
},
@ -31,13 +48,29 @@ const AccountActions = {
this.$store.dispatch('hideReblogs', this.user.id)
},
blockUser () {
if (!this.shouldConfirmBlock) {
this.doBlockUser()
} else {
this.showConfirmBlock()
}
},
doBlockUser () {
this.$store.dispatch('blockUser', this.user.id)
this.hideConfirmBlock()
},
unblockUser () {
this.$store.dispatch('unblockUser', this.user.id)
},
removeUserFromFollowers () {
if (!this.shouldConfirmRemoveUserFromFollowers) {
this.doRemoveUserFromFollowers()
} else {
this.showConfirmRemoveUserFromFollowers()
}
},
doRemoveUserFromFollowers () {
this.$store.dispatch('removeUserFromFollowers', this.user.id)
this.hideConfirmRemoveUserFromFollowers()
},
reportUser () {
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
@ -50,6 +83,12 @@ const AccountActions = {
}
},
computed: {
shouldConfirmBlock () {
return this.$store.getters.mergedConfig.modalOnBlock
},
shouldConfirmRemoveUserFromFollowers () {
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
},
...mapState({
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
})

View file

@ -74,13 +74,56 @@
</button>
</template>
</Popover>
<teleport to="#modal">
<confirm-modal
v-if="showingConfirmBlock"
:title="$t('user_card.block_confirm_title')"
:confirm-text="$t('user_card.block_confirm_accept_button')"
:cancel-text="$t('user_card.block_confirm_cancel_button')"
@accepted="doBlockUser"
@cancelled="hideConfirmBlock"
>
<i18n-t
keypath="user_card.block_confirm"
tag="span"
>
<template #user>
<span
v-text="user.screen_name_ui"
/>
</template>
</i18n-t>
</confirm-modal>
</teleport>
<teleport to="#modal">
<confirm-modal
v-if="showingConfirmRemoveFollower"
:title="$t('user_card.remove_follower_confirm_title')"
:confirm-text="$t('user_card.remove_follower_confirm_accept_button')"
:cancel-text="$t('user_card.remove_follower_confirm_cancel_button')"
@accepted="doRemoveUserFromFollowers"
@cancelled="hideConfirmRemoveUserFromFollowers"
>
<i18n-t
keypath="user_card.remove_follower_confirm"
tag="span"
>
<template #user>
<span
v-text="user.screen_name_ui"
/>
</template>
</i18n-t>
</confirm-modal>
</teleport>
</div>
</template>
<script src="./account_actions.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.AccountActions {
.ellipsis-button {
width: 2.5em;

View file

@ -27,6 +27,9 @@ const Announcement = {
...mapState({
currentUser: state => state.users.currentUser
}),
canEditAnnouncement () {
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
},
content () {
return this.announcement.content
},

View file

@ -45,14 +45,14 @@
{{ $t('announcements.mark_as_read_action') }}
</button>
<button
v-if="currentUser && currentUser.role === 'admin'"
v-if="canEditAnnouncement"
class="btn button-default"
@click="enterEditMode"
>
{{ $t('announcements.edit_action') }}
</button>
<button
v-if="currentUser && currentUser.role === 'admin'"
v-if="canEditAnnouncement"
class="btn button-default"
@click="deleteAnnouncement"
>
@ -102,19 +102,19 @@
@import "../../variables";
.announcement {
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--border, $fallback--border);
border-bottom: 1px solid var(--border, $fallback--border);
border-radius: 0;
padding: var(--status-margin, $status-margin);
.heading, .body {
.heading,
.body {
margin-bottom: var(--status-margin, $status-margin);
}
.footer {
display: flex;
flex-direction: column;
.times {
display: flex;
flex-direction: column;

View file

@ -28,6 +28,9 @@ const AnnouncementsPage = {
}),
announcements () {
return this.$store.state.announcements.announcements
},
canPostAnnouncement () {
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
}
},
methods: {

View file

@ -7,7 +7,7 @@
</div>
<div class="panel-body">
<section
v-if="currentUser && currentUser.role === 'admin'"
v-if="canPostAnnouncement"
>
<div class="post-form">
<div class="heading">
@ -67,7 +67,8 @@
.post-form {
padding: var(--status-margin, $status-margin);
.heading, .body {
.heading,
.body {
margin-bottom: var(--status-margin, $status-margin);
}

View file

@ -34,9 +34,10 @@ export default {
height: 100%;
align-items: center;
justify-content: center;
.btn {
margin: .5em;
padding: .5em 2em;
margin: 0.5em;
padding: 0.5em 2em;
}
}
</style>

View file

@ -36,6 +36,7 @@ library.add(
const Attachment = {
props: [
'attachment',
'compact',
'description',
'hideDescription',
'nsfw',
@ -71,7 +72,8 @@ const Attachment = {
{
'-loading': this.loading,
'-nsfw-placeholder': this.hidden,
'-editable': this.edit !== undefined
'-editable': this.edit !== undefined,
'-compact': this.compact
},
'-type-' + this.type,
this.size && '-size-' + this.size,

View file

@ -1,4 +1,4 @@
@import '../../_variables.scss';
@import "../../variables";
.Attachment {
display: inline-flex;
@ -102,14 +102,13 @@
padding-top: 0.5em;
}
.play-icon {
position: absolute;
font-size: 64px;
top: calc(50% - 32px);
left: calc(50% - 32px);
color: rgba(255, 255, 255, 0.75);
text-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
color: rgb(255 255 255 / 75%);
text-shadow: 0 0 2px rgb(0 0 0 / 40%);
&::before {
margin: 0;
@ -135,18 +134,32 @@
margin-left: 0.5em;
font-size: 1.25em;
// TODO: theming? hard to theme with unknown background image color
background: rgba(230, 230, 230, 0.7);
background: rgb(230 230 230 / 70%);
.svg-inline--fa {
color: rgba(0, 0, 0, 0.6);
color: rgb(0 0 0 / 60%);
}
&:hover .svg-inline--fa {
color: rgba(0, 0, 0, 0.9);
color: rgb(0 0 0 / 90%);
}
}
}
&.-contain-fit {
img,
canvas {
object-fit: contain;
}
}
&.-cover-fit {
img,
canvas {
object-fit: cover;
}
}
.oembed-container {
line-height: 1.2em;
flex: 1 0 100%;
@ -160,8 +173,9 @@
.image {
flex: 1;
img {
border: 0px;
border: 0;
border-radius: 5px;
height: 100%;
object-fit: cover;
@ -172,9 +186,10 @@
flex: 2;
margin: 8px;
word-break: break-all;
h1 {
font-size: 1rem;
margin: 0px;
margin: 0;
}
}
}
@ -252,17 +267,9 @@
cursor: progress;
}
&.-contain-fit {
img,
canvas {
object-fit: contain;
}
}
&.-cover-fit {
img,
canvas {
object-fit: cover;
&.-compact {
.placeholder-container {
padding-bottom: 0.5em;
}
}
}

View file

@ -162,10 +162,11 @@
target="_blank"
>
<FAIcon
size="5x"
:size="compact ? '2x' : '5x'"
:icon="placeholderIconClass"
:title="localDescription"
/>
<p>
<p v-if="!compact">
{{ localDescription }}
</p>
</a>

View file

@ -24,7 +24,7 @@
<script src="./autosuggest.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.autosuggest {
position: relative;
@ -50,7 +50,7 @@
border-radius: var(--inputRadius, $fallback--inputRadius);
border-top-left-radius: 0;
border-top-right-radius: 0;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
box-shadow: var(--panelShadow);
overflow-y: auto;
z-index: 1;

View file

@ -17,7 +17,7 @@
<script src="./avatar_list.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.avatars {
display: flex;

View file

@ -49,7 +49,7 @@
margin: 0;
padding: 0.6em 1em;
--emoji-size: 14px;
--emoji-size: 14px;
&-collapsed-content {
margin-left: 0.7em;

View file

@ -37,6 +37,7 @@
.block-card-content-container {
margin-top: 0.5em;
text-align: right;
button {
width: 10em;
}

View file

@ -17,7 +17,7 @@
width: 100%;
overflow: visible;
min-height: calc(100vh - var(--navbar-height));
margin: 0 0 0 0;
margin: 0;
border-radius: 10px 10px 0 0;
border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;
@ -66,7 +66,7 @@
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);
box-shadow: 0 1px 1px rgb(0 0 0 / 30%), 0 2px 4px rgb(0 0 0 / 30%);
z-index: 10;
transition: 0.35s all;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);

View file

@ -95,6 +95,6 @@
<script src="./chat.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import './chat.scss';
@import "../../variables";
@import "./chat";
</style>

View file

@ -45,7 +45,7 @@
<script src="./chat_list.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.chat-list {
min-height: 25em;

View file

@ -13,7 +13,7 @@
&:hover {
background-color: var(--selectedPost, $fallback--lightBg);
box-shadow: 0 0 3px 1px rgba(0, 0, 0, 0.1);
box-shadow: 0 0 3px 1px rgb(0 0 0 / 10%);
}
.chat-list-item-left {
@ -67,6 +67,7 @@
canvas {
display: none;
}
img {
visibility: visible;
}
@ -79,13 +80,11 @@
.chat-preview-body {
--emoji-size: 1.4em;
padding-right: 1em;
}
.time-wrapper {
line-height: var(--post-line-height);
}
.chat-preview-body {
padding-right: 1em;
}
}

View file

@ -48,6 +48,6 @@
<script src="./chat_list_item.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import './chat_list_item.scss';
@import "../../variables";
@import "./chat_list_item";
</style>

View file

@ -1,12 +1,12 @@
@import '../../_variables.scss';
@import "../../variables";
.chat-message-wrapper {
&.hovered-message-chain {
.animated.Avatar {
canvas {
display: none;
}
img {
visibility: visible;
}
@ -28,7 +28,8 @@
.menu-icon {
cursor: pointer;
&:hover, .extra-button-popover.open & {
&:hover,
.extra-button-popover.open & {
color: $fallback--text;
color: var(--text, $fallback--text);
}
@ -54,27 +55,11 @@
width: 32px;
}
.link-preview, .attachments {
.link-preview,
.attachments {
margin-bottom: 1em;
}
.chat-message-inner {
display: flex;
flex-direction: column;
align-items: flex-start;
max-width: 80%;
min-width: 10em;
width: 100%;
&.with-media {
width: 100%;
.status {
width: 100%;
}
}
}
.status {
border-radius: $fallback--chatMessageRadius;
border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius);
@ -86,7 +71,7 @@
position: relative;
float: right;
font-size: 0.8em;
margin: -1em 0 -0.5em 0;
margin: -1em 0 -0.5em;
font-style: italic;
opacity: 0.8;
}
@ -103,18 +88,54 @@
}
.pending {
.status-content.media-body, .created-at {
.status-content.media-body,
.created-at {
color: var(--faint);
}
}
.error {
.status-content.media-body, .created-at {
.status-content.media-body,
.created-at {
color: $fallback--cRed;
color: var(--badgeNotification, $fallback--cRed);
}
}
.chat-message-inner {
display: flex;
flex-direction: column;
align-items: flex-start;
max-width: 80%;
min-width: 10em;
width: 100%;
}
.outgoing {
display: flex;
flex-flow: row wrap;
align-content: end;
justify-content: flex-end;
a {
color: var(--chatMessageOutgoingLink, $fallback--link);
}
.status {
color: var(--chatMessageOutgoingText, $fallback--text);
background-color: var(--chatMessageOutgoingBg, $fallback--lightBg);
border: 1px solid var(--chatMessageOutgoingBorder, --lightBg);
}
.chat-message-inner {
align-items: flex-end;
}
.chat-message-menu {
right: 0.4rem;
}
}
.incoming {
a {
color: var(--chatMessageIncomingLink, $fallback--link);
@ -137,36 +158,17 @@
}
}
.outgoing {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: end;
justify-content: flex-end;
a {
color: var(--chatMessageOutgoingLink, $fallback--link);
}
.chat-message-inner.with-media {
width: 100%;
.status {
color: var(--chatMessageOutgoingText, $fallback--text);
background-color: var(--chatMessageOutgoingBg, $fallback--lightBg);
border: 1px solid var(--chatMessageOutgoingBorder, --lightBg);
}
.chat-message-inner {
align-items: flex-end;
}
.chat-message-menu {
right: 0.4rem;
width: 100%;
}
}
.visible {
opacity: 1;
}
}
.chat-message-date-separator {

View file

@ -33,7 +33,7 @@
<div
class="media status"
:class="{ 'without-attachment': !hasAttachment, 'pending': chatViewItem.data.pending, 'error': chatViewItem.data.error }"
style="position: relative"
style="position: relative;"
@mouseenter="hovered = true"
@mouseleave="hovered = false"
>
@ -98,6 +98,6 @@
<script src="./chat_message.js"></script>
<style lang="scss">
@import './chat_message.scss';
@import "./chat_message";
</style>

View file

@ -1,7 +1,7 @@
.chat-new {
.input-wrap {
display: flex;
margin: 0.7em 0.5em 0.7em 0.5em;
margin: 0.7em 0.5em;
input {
width: 100%;

View file

@ -46,6 +46,6 @@
<script src="./chat_new.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import './chat_new.scss';
@import "../../variables";
@import "./chat_new";
</style>

View file

@ -26,7 +26,7 @@
<script src="./chat_title.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.chat-title {
display: flex;

View file

@ -32,7 +32,7 @@ export default {
</script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.checkbox {
position: relative;
@ -54,11 +54,11 @@ export default {
position: absolute;
inset: 0;
display: block;
content: '✓';
content: "✓";
transition: color 200ms;
border-radius: $fallback--checkboxRadius;
border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
box-shadow: 0px 0px 2px black inset;
box-shadow: 0 0 2px black inset;
box-shadow: var(--inputShadow);
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
@ -74,15 +74,16 @@ export default {
&.disabled {
.checkbox-indicator::before,
.label {
opacity: .5;
opacity: 0.5;
}
.label {
color: $fallback--faint;
color: var(--faint, $fallback--faint);
}
}
input[type=checkbox] {
input[type="checkbox"] {
display: none;
&:checked + .checkbox-indicator::before {
@ -91,15 +92,14 @@ export default {
}
&:indeterminate + .checkbox-indicator::before {
content: '';
content: "";
color: $fallback--text;
color: var(--inputText, $fallback--text);
}
}
& > span {
margin-left: .5em;
margin-left: 0.5em;
}
}
</style>

View file

@ -1,4 +1,4 @@
@import '../../_variables.scss';
@import "../../variables";
.color-input {
display: inline-flex;
@ -8,7 +8,7 @@
flex: 0 0 0;
max-width: 9em;
align-items: stretch;
padding: .2em 8px;
padding: 0.2em 8px;
input {
background: none;
@ -31,6 +31,7 @@
min-height: 100%;
}
}
.computedIndicator,
.transparentIndicator {
flex: 0 0 2em;
@ -38,22 +39,27 @@
align-self: stretch;
min-height: 100%;
}
.transparentIndicator {
// forgot to install counter-strike source, ooops
background-color: #FF00FF;
background-color: #f0f;
position: relative;
&::before, &::after {
&::before,
&::after {
display: block;
content: '';
background-color: #000000;
content: "";
background-color: #000;
position: absolute;
height: 50%;
width: 50%;
}
&::after {
top: 0;
left: 0;
}
&::before {
bottom: 0;
right: 0;
@ -64,5 +70,4 @@
.label {
flex: 1 1 auto;
}
}

View file

@ -0,0 +1,37 @@
import DialogModal from '../dialog_modal/dialog_modal.vue'
/**
* This component emits the following events:
* cancelled, emitted when the action should not be performed;
* accepted, emitted when the action should be performed;
*
* The caller should close this dialog after receiving any of the two events.
*/
const ConfirmModal = {
components: {
DialogModal
},
props: {
title: {
type: String
},
cancelText: {
type: String
},
confirmText: {
type: String
}
},
computed: {
},
methods: {
onCancel () {
this.$emit('cancelled')
},
onAccept () {
this.$emit('accepted')
}
}
}
export default ConfirmModal

View file

@ -0,0 +1,29 @@
<template>
<dialog-modal
v-body-scroll-lock="true"
class="confirm-modal"
:on-cancel="onCancel"
>
<template #header>
<span v-text="title" />
</template>
<slot />
<template #footer>
<button
class="btn button-default"
@click.prevent="onAccept"
v-text="confirmText"
/>
<button
class="btn button-default"
@click.prevent="onCancel"
v-text="cancelText"
/>
</template>
</dialog-modal>
</template>
<script src="./confirm_modal.js"></script>

View file

@ -87,7 +87,6 @@ export default {
.contrast-ratio {
display: flex;
justify-content: flex-end;
margin-top: -4px;
margin-bottom: 5px;

View file

@ -210,17 +210,16 @@
<script src="./conversation.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.Conversation {
z-index: 1;
.conversation-dive-to-top-level-box {
padding: var(--status-margin, $status-margin);
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--border, $fallback--border);
border-bottom: 1px solid var(--border, $fallback--border);
border-radius: 0;
/* Make the button stretch along the whole row */
display: flex;
align-items: stretch;
@ -235,52 +234,48 @@
.thread-ancestor.-faded .StatusContent {
--link: var(--faintLink);
--text: var(--faint);
color: var(--text);
}
.thread-ancestor-dive-box {
padding-left: var(--status-margin, $status-margin);
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--border, $fallback--border);
border-bottom: 1px solid var(--border, $fallback--border);
border-radius: 0;
/* Make the button stretch along the whole row */
&, &-inner {
&,
&-inner {
display: flex;
align-items: stretch;
flex-direction: column;
}
}
.thread-ancestor-dive-box-inner {
padding: var(--status-margin, $status-margin);
}
.conversation-status {
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--border, $fallback--border);
border-bottom: 1px solid var(--border, $fallback--border);
border-radius: 0;
}
.thread-ancestor-has-other-replies .conversation-status,
&:last-child .conversation-status,
.thread-ancestor:last-child .conversation-status,
.thread-ancestor:last-child .thread-ancestor-dive-box,
&:last-child .conversation-status,
&.-expanded .thread-tree .conversation-status {
border-bottom: none;
}
.thread-ancestors + .thread-tree > .conversation-status {
border-top-width: 1px;
border-top-style: solid;
border-top-color: var(--border, $fallback--border);
border-top: 1px solid var(--border, $fallback--border);
}
/* expanded conversation in timeline */
&.status-fadein.-expanded .thread-body {
border-left-width: 4px;
border-left-style: solid;
border-left-color: $fallback--cRed;
border-left: 4px solid $fallback--cRed;
border-left-color: var(--cRed, $fallback--cRed);
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);

View file

@ -1,4 +1,5 @@
import SearchBar from 'components/search_bar/search_bar.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSignInAlt,
@ -30,7 +31,8 @@ library.add(
export default {
components: {
SearchBar
SearchBar,
ConfirmModal
},
data: () => ({
searchBarHidden: true,
@ -40,7 +42,8 @@ export default {
window.CSS.supports('-moz-mask-size', 'contain') ||
window.CSS.supports('-ms-mask-size', 'contain') ||
window.CSS.supports('-o-mask-size', 'contain')
)
),
showingConfirmLogout: false
}),
computed: {
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
@ -73,15 +76,32 @@ export default {
hideSitename () { return this.$store.state.instance.hideSitename },
logoLeft () { return this.$store.state.instance.logoLeft },
currentUser () { return this.$store.state.users.currentUser },
privateMode () { return this.$store.state.instance.private }
privateMode () { return this.$store.state.instance.private },
shouldConfirmLogout () {
return this.$store.getters.mergedConfig.modalOnLogout
}
},
methods: {
scrollToTop () {
window.scrollTo(0, 0)
},
showConfirmLogout () {
this.showingConfirmLogout = true
},
hideConfirmLogout () {
this.showingConfirmLogout = false
},
logout () {
if (!this.shouldConfirmLogout) {
this.doLogout()
} else {
this.showConfirmLogout()
}
},
doLogout () {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
this.hideConfirmLogout()
},
onSearchBarToggled (hidden) {
this.searchBarHidden = hidden

View file

@ -1,4 +1,4 @@
@import '../../_variables.scss';
@import "../../variables";
.DesktopNav {
width: 100%;
@ -27,20 +27,13 @@
--miniColumn: 25rem;
--maxiColumn: 45rem;
--columnGap: 1em;
max-width: calc(
var(--sidebarColumnWidth, var(--miniColumn)) +
var(--contentColumnWidth, var(--maxiColumn)) +
var(--columnGap)
);
}
&.-column-stretch.-wide .inner-nav {
max-width: calc(
var(--sidebarColumnWidth, var(--miniColumn)) +
var(--contentColumnWidth, var(--maxiColumn)) +
var(--notifsColumnWidth, var(--miniColumn)) +
var(--columnGap)
);
max-width:
calc(
var(--sidebarColumnWidth, var(--miniColumn)) +
var(--contentColumnWidth, var(--maxiColumn)) +
var(--columnGap)
);
}
&.-logoLeft .inner-nav {
@ -48,8 +41,19 @@
grid-template-areas: "logo sitename actions";
}
&.-column-stretch.-wide .inner-nav {
max-width:
calc(
var(--sidebarColumnWidth, var(--miniColumn)) +
var(--contentColumnWidth, var(--maxiColumn)) +
var(--notifsColumnWidth, var(--miniColumn)) +
var(--columnGap)
);
}
.button-default {
&, svg {
&,
svg {
color: $fallback--text;
color: var(--btnTopBarText, $fallback--text);
}
@ -70,7 +74,7 @@
color: $fallback--text;
color: var(--btnToggledTopBarText, $fallback--text);
background-color: $fallback--fg;
background-color: var(--btnToggledTopBar, $fallback--fg)
background-color: var(--btnToggledTopBar, $fallback--fg);
}
}
@ -82,6 +86,7 @@
transition-duration: 100ms;
@media all and (min-width: 800px) {
/* stylelint-disable-next-line declaration-no-important */
opacity: 1 !important;
}

View file

@ -38,7 +38,7 @@
/>
<button
class="button-unstyled nav-icon"
@click="openSettingsModal"
@click.stop="openSettingsModal"
>
<FAIcon
fixed-width
@ -65,7 +65,7 @@
<button
v-if="currentUser"
class="button-unstyled nav-icon"
@click.prevent="logout"
@click.stop.prevent="logout"
>
<FAIcon
fixed-width
@ -76,6 +76,18 @@
</button>
</div>
</div>
<teleport to="#modal">
<confirm-modal
v-if="showingConfirmLogout"
:title="$t('login.logout_confirm_title')"
:confirm-text="$t('login.logout_confirm_accept_button')"
:cancel-text="$t('login.logout_confirm_cancel_button')"
@accepted="doLogout"
@cancelled="hideConfirmLogout"
>
{{ $t('login.logout_confirm') }}
</confirm-modal>
</teleport>
</nav>
</template>
<script src="./desktop_nav.js"></script>

View file

@ -25,7 +25,7 @@
<script src="./dialog_modal.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
// TODO: unify with other modals.
.dark-overlay {
@ -38,8 +38,8 @@
position: fixed;
right: 0;
top: 0;
background: rgba(27,31,35,.5);
z-index: 99;
background: rgb(27 31 35 / 50%);
z-index: 2000;
}
}
@ -51,7 +51,7 @@
margin: 15vh auto;
position: fixed;
transform: translateX(-50%);
z-index: 999;
z-index: 2001;
cursor: default;
display: block;
background-color: $fallback--bg;
@ -65,7 +65,7 @@
.dialog-modal-content {
margin: 0;
padding: 1rem 1rem;
padding: 1rem;
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
white-space: normal;
@ -73,7 +73,7 @@
.dialog-modal-footer {
margin: 0;
padding: .5em .5em;
padding: 0.5em;
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
border-top: 1px solid $fallback--border;
@ -83,7 +83,7 @@
button {
width: auto;
margin-left: .5rem;
margin-left: 0.5rem;
}
}
}

View file

@ -26,6 +26,7 @@
.modal-view.edit-form-modal-view {
align-items: flex-start;
}
.edit-form-modal-panel {
flex-shrink: 0;
margin-top: 25%;

View file

@ -91,22 +91,18 @@
<script src="./emoji_input.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.emoji-input {
display: flex;
flex-direction: column;
position: relative;
&.with-picker input {
padding-right: 30px;
}
.emoji-picker-icon {
position: absolute;
top: 0;
right: 0;
margin: .2em .25em;
margin: 0.2em 0.25em;
font-size: 1.3em;
cursor: pointer;
line-height: 24px;
@ -123,14 +119,19 @@
margin-top: 2px;
&.hide {
display: none
display: none;
}
}
input, textarea {
input,
textarea {
flex: 1 0 auto;
}
&.with-picker input {
padding-right: 30px;
}
.hidden-overlay {
opacity: 0;
pointer-events: none;
@ -140,8 +141,10 @@
right: 0;
left: 0;
overflow: hidden;
/* DEBUG STUFF */
color: red;
/* set opacity to non-zero to see the overlay */
.caret {
@ -151,6 +154,7 @@
}
}
}
.autocomplete {
&-panel {
position: absolute;
@ -160,7 +164,7 @@
display: flex;
cursor: pointer;
padding: 0.2em 0.4em;
border-bottom: 1px solid rgba(0, 0, 0, 0.4);
border-bottom: 1px solid rgb(0 0 0 / 40%);
height: 32px;
.image {
@ -169,7 +173,6 @@
line-height: 32px;
text-align: center;
font-size: 32px;
margin-right: 4px;
img {
@ -199,6 +202,7 @@
background-color: $fallback--fg;
background-color: var(--selectedMenuPopover, $fallback--fg);
color: var(--selectedMenuPopoverText, $fallback--text);
--faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
--faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
--lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);

View file

@ -3,7 +3,6 @@ import Checkbox from '../checkbox/checkbox.vue'
import Popover from 'src/components/popover/popover.vue'
import StillImage from '../still-image/still-image.vue'
import { ensureFinalFallback } from '../../i18n/languages.js'
import lozad from 'lozad'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faBoxOpen,
@ -19,7 +18,7 @@ import {
faCode,
faFlag
} from '@fortawesome/free-solid-svg-icons'
import { debounce, trim } from 'lodash'
import { debounce, trim, chunk } from 'lodash'
library.add(
faBoxOpen,
@ -82,6 +81,17 @@ const filterByKeyword = (list, keyword = '', languages, nameLocalizer) => {
return orderedEmojiList.flat()
}
const getOffset = (elem) => {
const style = elem.style.transform
const res = /translateY\((\d+)px\)/.exec(style)
if (!res) { return 0 }
return res[1]
}
const toHeaderId = id => {
return id.replace(/^row-\d+-/, '')
}
const EmojiPicker = {
props: {
enableStickerPicker: {
@ -102,7 +112,8 @@ const EmojiPicker = {
contentLoaded: false,
groupRefs: {},
emojiRefs: {},
filteredEmojiGroups: []
filteredEmojiGroups: [],
width: 0
}
},
components: {
@ -125,9 +136,6 @@ const EmojiPicker = {
setGroupRef (name) {
return el => { this.groupRefs[name] = el }
},
setEmojiRef (name) {
return el => { this.emojiRefs[name] = el }
},
onPopoverShown () {
this.$emit('show')
},
@ -147,18 +155,21 @@ const EmojiPicker = {
}
this.$emit('emoji', { insertion: value, keepOpen: this.keepOpen })
},
onScroll (e) {
const target = (e && e.target) || this.$refs['emoji-groups']
this.updateScrolledClass(target)
this.scrolledGroup(target)
onScroll (startIndex, endIndex, visibleStartIndex, visibleEndIndex) {
const target = this.$refs['emoji-groups'].$el
this.scrolledGroup(target, visibleStartIndex, visibleEndIndex)
},
scrolledGroup (target) {
scrolledGroup (target, start, end) {
const top = target.scrollTop + 5
this.$nextTick(() => {
this.allEmojiGroups.forEach(group => {
this.emojiItems.slice(start, end + 1).forEach(group => {
const headerId = toHeaderId(group.id)
const ref = this.groupRefs['group-' + group.id]
if (ref && ref.offsetTop <= top) {
this.activeGroup = group.id
if (!ref) { return }
const elem = ref.$el.parentElement
if (!elem) { return }
if (elem && getOffset(elem) <= top) {
this.activeGroup = headerId
}
})
this.scrollHeader()
@ -181,14 +192,10 @@ const EmojiPicker = {
setScroll(right + margin - headerCont.clientWidth)
}
},
highlight (key) {
const ref = this.groupRefs['group-' + key]
const top = ref.offsetTop
highlight (groupId) {
this.setShowStickers(false)
this.activeGroup = key
this.$nextTick(() => {
this.$refs['emoji-groups'].scrollTop = top + 1
})
const indexInList = this.emojiItems.findIndex(k => k.id === groupId)
this.$refs['emoji-groups'].scrollToItem(indexInList)
},
updateScrolledClass (target) {
if (target.scrollTop <= 5) {
@ -208,43 +215,13 @@ const EmojiPicker = {
filterByKeyword (list, keyword) {
return filterByKeyword(list, keyword, this.languages, this.maybeLocalizedEmojiName)
},
initializeLazyLoad () {
this.destroyLazyLoad()
this.$nextTick(() => {
this.$lozad = lozad('.still-image.emoji-picker-emoji', {
load: el => {
const name = el.getAttribute('data-emoji-name')
const vn = this.emojiRefs[name]
if (!vn) {
return
}
vn.loadLazy()
}
})
this.$lozad.observe()
})
},
waitForDomAndInitializeLazyLoad () {
this.$nextTick(() => this.initializeLazyLoad())
},
destroyLazyLoad () {
if (this.$lozad) {
if (this.$lozad.observer) {
this.$lozad.observer.disconnect()
}
if (this.$lozad.mutationObserver) {
this.$lozad.mutationObserver.disconnect()
}
}
},
onShowing () {
const oldContentLoaded = this.contentLoaded
this.recalculateItemPerRow()
this.$nextTick(() => {
this.$refs.search.focus()
})
this.contentLoaded = true
this.waitForDomAndInitializeLazyLoad()
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
if (!oldContentLoaded) {
this.$nextTick(() => {
@ -261,6 +238,14 @@ const EmojiPicker = {
emojis: this.filterByKeyword(group.emojis, trim(this.keyword))
}))
.filter(group => group.emojis.length > 0)
},
recalculateItemPerRow () {
this.$nextTick(() => {
if (!this.$refs['emoji-groups']) {
return
}
this.width = this.$refs['emoji-groups'].$el.clientWidth
})
}
},
watch: {
@ -269,14 +254,22 @@ const EmojiPicker = {
this.debouncedHandleKeywordChange()
},
allCustomGroups () {
this.waitForDomAndInitializeLazyLoad()
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
}
},
destroyed () {
this.destroyLazyLoad()
},
computed: {
minItemSize () {
return this.emojiHeight
},
emojiHeight () {
return 32 + 4
},
emojiWidth () {
return 32 + 4
},
itemPerRow () {
return this.width ? Math.floor(this.width / this.emojiWidth - 1) : 6
},
activeGroupView () {
return this.showingStickers ? '' : this.activeGroup
},
@ -287,7 +280,11 @@ const EmojiPicker = {
return 0
},
allCustomGroups () {
return this.$store.getters.groupedCustomEmojis
const emojis = this.$store.getters.groupedCustomEmojis
if (emojis.unpacked) {
emojis.unpacked.text = this.$t('emoji.unpacked')
}
return emojis
},
defaultGroup () {
return Object.keys(this.allCustomGroups)[0]
@ -310,10 +307,20 @@ const EmojiPicker = {
},
debouncedHandleKeywordChange () {
return debounce(() => {
this.waitForDomAndInitializeLazyLoad()
this.filteredEmojiGroups = this.getFilteredEmojiGroups()
}, 500)
},
emojiItems () {
return this.filteredEmojiGroups.map(group =>
chunk(group.emojis, this.itemPerRow)
.map((items, index) => ({
...group,
id: index === 0 ? group.id : `row-${index}-${group.id}`,
emojis: items,
isFirstRow: index === 0
})))
.reduce((a, c) => a.concat(c), [])
},
languages () {
return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
},

View file

@ -1,4 +1,4 @@
@import '../../_variables.scss';
@import "../../variables";
$emoji-picker-header-height: 36px;
$emoji-picker-header-picture-width: 32px;
@ -7,14 +7,14 @@ $emoji-picker-emoji-size: 32px;
.emoji-picker {
width: 25em;
max-width: 100vw;
max-width: calc(100vw - 20px); // popover gives 10px margin from window edge
display: flex;
flex-direction: column;
background-color: $fallback--bg;
background-color: var(--popover, $fallback--bg);
color: $fallback--link;
color: var(--popoverText, $fallback--link);
--lightText: var(--popoverLightText, $fallback--faint);
--faint: var(--popoverFaintText, $fallback--faint);
--faintLink: var(--popoverFaintLink, $fallback--faint);
--lightText: var(--popoverLightText, $fallback--lightText);
@ -28,6 +28,7 @@ $emoji-picker-emoji-size: 32px;
max-width: $emoji-picker-header-picture-width;
height: $emoji-picker-header-picture-height;
max-height: $emoji-picker-header-picture-height;
.still-image {
max-width: 100%;
max-height: 100%;
@ -62,24 +63,18 @@ $emoji-picker-emoji-size: 32px;
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0px;
min-height: 0;
}
.emoji-tabs {
flex-grow: 1;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
flex-flow: row nowrap;
overflow-x: auto;
}
.emoji-groups {
min-height: 200px;
}
.additional-tabs {
display: flex;
flex: 1;
border-left: 1px solid;
border-left-color: $fallback--icon;
border-left-color: var(--icon, $fallback--icon);
@ -121,7 +116,7 @@ $emoji-picker-emoji-size: 32px;
}
.sticker-picker {
flex: 1 1 auto
flex: 1 1 auto;
}
.stickers,
@ -151,22 +146,27 @@ $emoji-picker-emoji-size: 32px;
}
&-groups {
height: 100%;
min-height: 200px;
flex: 1 1 1px;
position: relative;
overflow: auto;
user-select: none;
mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
linear-gradient(to top, white, white);
mask:
linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
linear-gradient(to top, white, white);
transition: mask-size 150ms;
mask-size: 100% 20px, 100% 20px, auto;
// Autoprefixed seem to ignore this one, and also syntax is different
-webkit-mask-composite: xor;
mask-composite: xor;
mask-composite: exclude;
&.scrolled {
&-top {
mask-size: 100% 20px, 100% 0, auto;
}
&-bottom {
mask-size: 100% 0, 100% 20px, auto;
}
@ -200,7 +200,6 @@ $emoji-picker-emoji-size: 32px;
align-items: center;
justify-content: center;
margin: 4px;
cursor: pointer;
.emoji-picker-emoji.-custom {
@ -208,12 +207,11 @@ $emoji-picker-emoji-size: 32px;
max-width: 100%;
max-height: 100%;
}
.emoji-picker-emoji.-unicode {
font-size: 24px;
overflow: hidden;
}
}
}
}

View file

@ -74,45 +74,57 @@
@input="$event.target.composing = false"
>
</div>
<div
<DynamicScroller
ref="emoji-groups"
class="emoji-groups"
:class="groupsScrolledClass"
@scroll="onScroll"
:min-item-size="minItemSize"
:items="emojiItems"
:emit-update="true"
@update="onScroll"
@visible="recalculateItemPerRow"
@resize="recalculateItemPerRow"
>
<div
v-for="group in filteredEmojiGroups"
:key="group.id"
class="emoji-group"
>
<h6
<template #default="{ item: group, index, active }">
<DynamicScrollerItem
:ref="setGroupRef('group-' + group.id)"
class="emoji-group-title"
:item="group"
:active="active"
:data-index="index"
:size-dependencies="[group.emojis.length]"
>
{{ group.text }}
</h6>
<span
v-for="emoji in group.emojis"
:key="group.id + emoji.displayText"
:title="maybeLocalizedEmojiName(emoji)"
class="emoji-item"
@click.stop.prevent="onEmoji(emoji)"
>
<span
v-if="!emoji.imageUrl"
class="emoji-picker-emoji -unicode"
>{{ emoji.replacement }}</span>
<still-image
v-else
:ref="setEmojiRef(group.id + emoji.displayText)"
class="emoji-picker-emoji -custom"
:data-src="emoji.imageUrl"
:data-emoji-name="group.id + emoji.displayText"
/>
</span>
<span :ref="setGroupRef('group-end-' + group.id)" />
</div>
</div>
<div
class="emoji-group"
>
<h6
v-if="group.isFirstRow"
class="emoji-group-title"
>
{{ group.text }}
</h6>
<span
v-for="emoji in group.emojis"
:key="group.id + emoji.displayText"
:title="maybeLocalizedEmojiName(emoji)"
class="emoji-item"
@click.stop.prevent="onEmoji(emoji)"
>
<span
v-if="!emoji.imageUrl"
class="emoji-picker-emoji -unicode"
>{{ emoji.replacement }}</span>
<still-image
v-else
class="emoji-picker-emoji -custom"
loading="lazy"
:src="emoji.imageUrl"
:data-emoji-name="group.id + emoji.displayText"
/>
</span>
</div>
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div class="keep-open">
<Checkbox v-model="keepOpen">
{{ $t('emoji.keep_open') }}

View file

@ -28,7 +28,7 @@
<script src="./emoji_reactions.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.EmojiReactions {
display: flex;
@ -55,6 +55,7 @@
&.not-clickable {
cursor: default;
&:hover {
box-shadow: $fallback--buttonShadow;
box-shadow: var(--buttonShadow);
@ -75,10 +76,10 @@
display: flex;
align-items: center;
justify-content: center;
&:hover {
text-decoration: underline;
}
}
}
</style>

View file

@ -1,4 +1,5 @@
import Popover from '../popover/popover.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisH,
@ -32,10 +33,14 @@ library.add(
const ExtraButtons = {
props: ['status'],
components: { Popover },
components: {
Popover,
ConfirmModal
},
data () {
return {
expanded: false
expanded: false,
showingDeleteDialog: false
}
},
methods: {
@ -46,11 +51,22 @@ const ExtraButtons = {
this.expanded = false
},
deleteStatus () {
const confirmed = window.confirm(this.$t('status.delete_confirm'))
if (confirmed) {
this.$store.dispatch('deleteStatus', { id: this.status.id })
if (this.shouldConfirmDelete) {
this.showDeleteStatusConfirmDialog()
} else {
this.doDeleteStatus()
}
},
doDeleteStatus () {
this.$store.dispatch('deleteStatus', { id: this.status.id })
this.hideDeleteStatusConfirmDialog()
},
showDeleteStatusConfirmDialog () {
this.showingDeleteDialog = true
},
hideDeleteStatusConfirmDialog () {
this.showingDeleteDialog = false
},
pinStatus () {
this.$store.dispatch('pinStatus', this.status.id)
.then(() => this.$emit('onSuccess'))
@ -133,7 +149,10 @@ const ExtraButtons = {
isEdited () {
return this.status.edited_at !== null
},
editingAvailable () { return this.$store.state.instance.editingAvailable }
editingAvailable () { return this.$store.state.instance.editingAvailable },
shouldConfirmDelete () {
return this.$store.getters.mergedConfig.modalOnDelete
}
}
}

View file

@ -165,6 +165,18 @@
/>
</FALayers>
</span>
<teleport to="#modal">
<ConfirmModal
v-if="showingDeleteDialog"
:title="$t('status.delete_confirm_title')"
:cancel-text="$t('status.delete_confirm_cancel_button')"
:confirm-text="$t('status.delete_confirm_accept_button')"
@cancelled="hideDeleteStatusConfirmDialog"
@accepted="doDeleteStatus"
>
{{ $t('status.delete_confirm') }}
</ConfirmModal>
</teleport>
</template>
</Popover>
</template>
@ -172,15 +184,10 @@
<script src="./extra_buttons.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import '../../_mixins.scss';
@import "../../variables";
@import "../../mixins";
.ExtraButtons {
/* override of popover internal stuff */
.popover-trigger-button {
width: auto;
}
.popover-trigger {
position: static;
padding: 10px;
@ -190,10 +197,12 @@
color: $fallback--text;
color: var(--text, $fallback--text);
}
}
.popover-trigger-button {
/* override of popover internal stuff */
width: auto;
@include unfocused-style {
.focus-marker {
visibility: hidden;

View file

@ -58,8 +58,8 @@
<script src="./favorite_button.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import '../../_mixins.scss';
@import "../../variables";
@import "../../mixins";
.FavoriteButton {
display: flex;

View file

@ -42,7 +42,8 @@
<script src="./flash.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.Flash {
display: inline-block;
width: 100%;
@ -78,7 +79,7 @@
.hidden {
display: none;
visibility: 'hidden';
visibility: "hidden";
}
}
</style>

View file

@ -1,12 +1,20 @@
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
export default {
props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
components: {
ConfirmModal
},
data () {
return {
inProgress: false
inProgress: false,
showingConfirmUnfollow: false
}
},
computed: {
shouldConfirmUnfollow () {
return this.$store.getters.mergedConfig.modalOnUnfollow
},
isPressed () {
return this.inProgress || this.relationship.following
},
@ -35,6 +43,12 @@ export default {
}
},
methods: {
showConfirmUnfollow () {
this.showingConfirmUnfollow = true
},
hideConfirmUnfollow () {
this.showingConfirmUnfollow = false
},
onClick () {
this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow()
},
@ -45,12 +59,21 @@ export default {
})
},
unfollow () {
if (this.shouldConfirmUnfollow) {
this.showConfirmUnfollow()
} else {
this.doUnfollow()
}
},
doUnfollow () {
const store = this.$store
this.inProgress = true
requestUnfollow(this.relationship.id, store).then(() => {
this.inProgress = false
store.commit('removeStatus', { timeline: 'friends', userId: this.relationship.id })
})
this.hideConfirmUnfollow()
}
}
}

View file

@ -7,6 +7,27 @@
@click="onClick"
>
{{ label }}
<teleport to="#modal">
<confirm-modal
v-if="showingConfirmUnfollow"
:title="$t('user_card.unfollow_confirm_title')"
:confirm-text="$t('user_card.unfollow_confirm_accept_button')"
:cancel-text="$t('user_card.unfollow_confirm_cancel_button')"
@accepted="doUnfollow"
@cancelled="hideConfirmUnfollow"
>
<i18n-t
keypath="user_card.unfollow_confirm"
tag="span"
>
<template #user>
<span
v-text="user.screen_name_ui"
/>
</template>
</i18n-t>
</confirm-modal>
</teleport>
</button>
</template>

View file

@ -24,6 +24,7 @@
/>
<RemoveFollowerButton
v-if="noFollowsYou && relationship.followed_by"
:user="user"
:relationship="relationship"
class="follow-card-button"
/>
@ -39,9 +40,8 @@
&-content-container {
flex-shrink: 0;
display: flex;
flex-direction: row;
flex-flow: row wrap;
justify-content: space-between;
flex-wrap: wrap;
line-height: 1.5em;
}

View file

@ -1,10 +1,18 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js'
const FollowRequestCard = {
props: ['user'],
components: {
BasicUserCard
BasicUserCard,
ConfirmModal
},
data () {
return {
showingApproveConfirmDialog: false,
showingDenyConfirmDialog: false
}
},
methods: {
findFollowRequestNotificationId () {
@ -13,7 +21,26 @@ const FollowRequestCard = {
)
return notif && notif.id
},
showApproveConfirmDialog () {
this.showingApproveConfirmDialog = true
},
hideApproveConfirmDialog () {
this.showingApproveConfirmDialog = false
},
showDenyConfirmDialog () {
this.showingDenyConfirmDialog = true
},
hideDenyConfirmDialog () {
this.showingDenyConfirmDialog = false
},
approveUser () {
if (this.shouldConfirmApprove) {
this.showApproveConfirmDialog()
} else {
this.doApprove()
}
},
doApprove () {
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
@ -25,14 +52,34 @@ const FollowRequestCard = {
notification.type = 'follow'
}
})
this.hideApproveConfirmDialog()
},
denyUser () {
if (this.shouldConfirmDeny) {
this.showDenyConfirmDialog()
} else {
this.doDeny()
}
},
doDeny () {
const notifId = this.findFollowRequestNotificationId()
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: notifId })
this.$store.dispatch('removeFollowRequest', this.user)
})
this.hideDenyConfirmDialog()
}
},
computed: {
mergedConfig () {
return this.$store.getters.mergedConfig
},
shouldConfirmApprove () {
return this.mergedConfig.modalOnApproveFollow
},
shouldConfirmDeny () {
return this.mergedConfig.modalOnDenyFollow
}
}
}

View file

@ -14,6 +14,28 @@
{{ $t('user_card.deny') }}
</button>
</div>
<teleport to="#modal">
<confirm-modal
v-if="showingApproveConfirmDialog"
:title="$t('user_card.approve_confirm_title')"
:confirm-text="$t('user_card.approve_confirm_accept_button')"
:cancel-text="$t('user_card.approve_confirm_cancel_button')"
@accepted="doApprove"
@cancelled="hideApproveConfirmDialog"
>
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
</confirm-modal>
<confirm-modal
v-if="showingDenyConfirmDialog"
:title="$t('user_card.deny_confirm_title')"
:confirm-text="$t('user_card.deny_confirm_accept_button')"
:cancel-text="$t('user_card.deny_confirm_cancel_button')"
@accepted="doDeny"
@cancelled="hideDenyConfirmDialog"
>
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
</confirm-modal>
</teleport>
</basic-user-card>
</template>
@ -22,8 +44,8 @@
<style lang="scss">
.follow-request-card-content-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
flex-flow: row wrap;
button {
margin-top: 0.5em;
margin-right: 0.5em;

View file

@ -50,17 +50,20 @@
<script src="./font_control.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.font-control {
input.custom-font {
min-width: 10em;
}
&.custom {
/* TODO Should make proper joiners... */
.font-switcher {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.custom-font {
border-top-left-radius: 0;
border-bottom-left-radius: 0;

View file

@ -4,6 +4,7 @@ import { sumBy, set } from 'lodash'
const Gallery = {
props: [
'attachments',
'compact',
'limitRows',
'descriptions',
'limit',

View file

@ -20,6 +20,7 @@
v-for="(attachment, attachmentIndex) in row.items"
:key="attachment.id"
class="gallery-item"
:compact="compact"
:nsfw="nsfw"
:attachment="attachment"
:size="size"
@ -86,7 +87,7 @@
<script src='./gallery.js'></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.Gallery {
.gallery-rows {
@ -100,6 +101,53 @@
width: 100%;
flex-grow: 1;
.gallery-row-inner {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-flow: row wrap;
align-content: stretch;
.gallery-item {
margin: 0 0.5em 0 0;
flex-grow: 1;
height: 100%;
box-sizing: border-box;
// to make failed images a bit more noticeable on chromium
min-width: 2em;
&:last-child {
margin: 0;
}
}
&.-grid {
width: 100%;
height: auto;
position: relative;
display: grid;
grid-gap: 0.5em;
grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
.gallery-item {
margin: 0;
height: 200px;
}
}
}
&.-grid,
&.-minimal {
height: auto;
.gallery-row-inner {
position: relative;
}
}
&:not(:first-child) {
margin-top: 0.5em;
}
@ -114,7 +162,7 @@
linear-gradient(to top, white, white);
/* Autoprefixed seem to ignore this one, and also syntax is different */
-webkit-mask-composite: xor;
mask-composite: xor;
mask-composite: exclude;
}
}
@ -138,54 +186,5 @@
padding: 0 2em;
}
}
.gallery-row {
&.-grid,
&.-minimal {
height: auto;
.gallery-row-inner {
position: relative;
}
}
}
.gallery-row-inner {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: stretch;
&.-grid {
width: 100%;
height: auto;
position: relative;
display: grid;
grid-column-gap: 0.5em;
grid-row-gap: 0.5em;
grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
.gallery-item {
margin: 0;
height: 200px;
}
}
}
.gallery-item {
margin: 0 0.5em 0 0;
flex-grow: 1;
height: 100%;
box-sizing: border-box;
// to make failed images a bit more noticeable on chromium
min-width: 2em;
&:last-child {
margin: 0;
}
}
}
</style>

View file

@ -25,7 +25,7 @@
<script src="./global_notice_list.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.global-notice-list {
position: fixed;
@ -73,6 +73,7 @@
.global-success {
background-color: var(--alertPopupSuccess, $fallback--cGreen);
color: var(--alertPopupSuccessText, $fallback--text);
.svg-inline--fa {
color: var(--alertPopupSuccessText, $fallback--text);
}
@ -81,6 +82,7 @@
.global-info {
background-color: var(--alertPopupNeutral, $fallback--fg);
color: var(--alertPopupNeutralText, $fallback--text);
.svg-inline--fa {
color: var(--alertPopupNeutralText, $fallback--text);
}
@ -88,6 +90,7 @@
.close-notice {
padding-right: 0.2em;
.svg-inline--fa:hover {
opacity: 0.6;
}

View file

@ -1,21 +1,44 @@
<template>
<div>
<label for="interface-language-switcher">
<div class="interface-language-switcher">
<label>
{{ promptText }}
</label>
{{ ' ' }}
<Select
id="interface-language-switcher"
v-model="controlledLanguage"
>
<option
v-for="lang in languages"
:key="lang.code"
:value="lang.code"
<ul class="setting-list">
<li
v-for="index of controlledLanguage.keys()"
:key="index"
>
{{ lang.name }}
</option>
</Select>
<label>
{{ index === 0 ? $t('settings.primary_language') : $tc('settings.fallback_language', index, { index }) }}
<Select
class="language-select"
:model-value="controlledLanguage[index]"
@update:modelValue="val => setLanguageAt(index, val)"
>
<option
v-for="lang in languages"
:key="lang.code"
:value="lang.code"
>
{{ lang.name }}
</option>
</Select>
</label>
<button
v-if="controlledLanguage.length > 1 && index !== 0"
class="button-default btn"
@click="() => removeLanguageAt(index)"
>
{{ $t('settings.remove_language') }}
</button>
</li>
<li>
<button
class="button-default btn"
@click="addLanguage"
>{{ $t('settings.add_language') }}</button>
</li>
</ul>
</div>
</template>
@ -34,7 +57,7 @@ export default {
required: true
},
language: {
type: String,
type: [Array, String],
required: true
},
setLanguage: {
@ -48,7 +71,9 @@ export default {
},
controlledLanguage: {
get: function () { return this.language },
get: function () {
return Array.isArray(this.language) ? this.language : [this.language]
},
set: function (val) {
this.setLanguage(val)
}
@ -58,7 +83,30 @@ export default {
methods: {
getLanguageName (code) {
return localeService.getLanguageName(code)
},
addLanguage () {
this.controlledLanguage = [...this.controlledLanguage, '']
},
setLanguageAt (index, val) {
const lang = [...this.controlledLanguage]
lang[index] = val
this.controlledLanguage = lang
},
removeLanguageAt (index) {
const lang = [...this.controlledLanguage]
lang.splice(index, 1)
this.controlledLanguage = lang
}
}
}
</script>
<style lang="scss">
@import "../../variables";
.interface-language-switcher {
.language-select {
margin-right: 1em;
}
}
</style>

View file

@ -33,7 +33,7 @@
<script src="./link-preview.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.link-preview-card {
display: flex;
@ -46,6 +46,7 @@
flex-shrink: 0;
width: 120px;
max-width: 25%;
img {
width: 100%;
height: 100%;
@ -67,7 +68,7 @@
}
.card-description {
margin: 0.5em 0 0 0;
margin: 0.5em 0 0;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-word;

View file

@ -35,7 +35,7 @@ export default {
</script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.list {
&-item:not(:last-child) {

View file

@ -21,12 +21,16 @@
<script src="./lists_card.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.list-card {
display: flex;
}
.list-name {
flex-grow: 1;
}
.list-name,
.button-list-edit {
margin: 0;
@ -39,13 +43,10 @@
background-color: var(--selectedMenu, $fallback--lightBg);
color: $fallback--link;
color: var(--selectedMenuText, $fallback--link);
--faint: var(--selectedMenuFaintText, $fallback--faint);
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
--lightText: var(--selectedMenuLightText, $fallback--lightText);
}
}
.list-name {
flex-grow: 1;
}
</style>

View file

@ -95,10 +95,10 @@ const ListsNew = {
return this.addedUserIds.has(user.id)
},
addUser (user) {
this.$store.dispatch('addListAccount', { accountId: this.user.id, listId: this.id })
this.$store.dispatch('addListAccount', { accountId: user.id, listId: this.id })
},
removeUser (userId) {
this.$store.dispatch('removeListAccount', { accountId: this.user.id, listId: this.id })
this.$store.dispatch('removeListAccount', { accountId: userId, listId: this.id })
},
onSearchLoading (results) {
this.searchLoading = true

View file

@ -164,7 +164,7 @@
<script src="./lists_edit.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.ListEdit {
--panel-body-padding: 0.5em;

View file

@ -27,12 +27,12 @@
<script src="./lists_user_search.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.ListsUserSearch {
.input-wrap {
display: flex;
margin: 0.7em 0.5em 0.7em 0.5em;
margin: 0.7em 0.5em;
input {
width: 100%;

View file

@ -93,7 +93,7 @@
<script src="./login_form.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.login-form {
display: flex;
@ -110,7 +110,7 @@
}
.login-bottom {
margin-top: 1.0em;
margin-top: 1em;
display: flex;
flex-direction: row;
align-items: center;
@ -121,7 +121,7 @@
display: flex;
flex-direction: column;
padding: 0.3em 0.5em 0.6em;
line-height:24px;
line-height: 24px;
}
.form-bottom {
@ -142,7 +142,6 @@
.error {
text-align: center;
animation-name: shakeError;
animation-duration: 0.4s;
animation-timing-function: ease-in-out;

View file

@ -63,6 +63,11 @@ const MediaModal = {
},
type () {
return this.currentMedia ? this.getType(this.currentMedia) : null
},
swipeDisableClickThreshold () {
// If there is only one media, allow more mouse movements to close the modal
// because there is less chance that the user wants to switch to another image
return () => this.canNavigate ? 1 : 30
}
},
methods: {

View file

@ -10,6 +10,7 @@
class="modal-image-container"
:direction="swipeDirection"
:threshold="swipeThreshold"
:disable-click-threshold="swipeDisableClickThreshold"
@preview-requested="handleSwipePreview"
@swipe-finished="handleSwipeEnd"
@swipeless-clicked="hide"
@ -120,32 +121,12 @@ $modal-view-button-icon-half-height: calc(#{$modal-view-button-icon-height} / 2)
$modal-view-button-icon-width: 3em;
$modal-view-button-icon-margin: 0.5em;
.modal-view.media-modal-view {
z-index: var(--ZI_media_modal);
flex-direction: column;
.modal-view-button-arrow,
.modal-view-button-hide {
opacity: 0.75;
&:focus,
&:hover {
outline: none;
box-shadow: none;
}
&:hover {
opacity: 1;
}
}
overflow: hidden;
}
.media-modal-view {
@keyframes media-fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
@ -226,7 +207,7 @@ $modal-view-button-icon-margin: 0.5em;
appearance: none;
overflow: visible;
cursor: pointer;
transition: opacity 333ms cubic-bezier(.4,0,.22,1);
transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
height: $modal-view-button-icon-height;
width: $modal-view-button-icon-width;
@ -236,9 +217,9 @@ $modal-view-button-icon-margin: 0.5em;
width: $modal-view-button-icon-width;
font-size: 1rem;
line-height: $modal-view-button-icon-height;
color: #FFF;
color: #fff;
text-align: center;
background-color: rgba(0,0,0,.3);
background-color: rgb(0 0 0 / 30%);
}
}
@ -254,13 +235,14 @@ $modal-view-button-icon-margin: 0.5em;
position: absolute;
top: 0;
line-height: $modal-view-button-icon-height;
color: #FFF;
color: #fff;
text-align: center;
background-color: rgba(0,0,0,.3);
background-color: rgb(0 0 0 / 30%);
}
&--prev {
left: 0;
.arrow-icon {
left: $modal-view-button-icon-margin;
}
@ -268,6 +250,7 @@ $modal-view-button-icon-margin: 0.5em;
&--next {
right: 0;
.arrow-icon {
right: $modal-view-button-icon-margin;
}
@ -278,10 +261,33 @@ $modal-view-button-icon-margin: 0.5em;
position: absolute;
top: 0;
right: 0;
.button-icon {
top: $modal-view-button-icon-margin;
right: $modal-view-button-icon-margin;
}
}
}
.modal-view.media-modal-view {
z-index: var(--ZI_media_modal);
flex-direction: column;
.modal-view-button-arrow,
.modal-view-button-hide {
opacity: 0.75;
&:focus,
&:hover {
outline: none;
box-shadow: none;
}
&:hover {
opacity: 1;
}
}
overflow: hidden;
}
</style>

View file

@ -29,7 +29,7 @@
<script src="./media_upload.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.media-upload {
cursor: pointer; // We use <label> for interactivity... i wonder if it's fine

View file

@ -1,4 +1,4 @@
@import '../../_variables.scss';
@import "../../variables";
.MentionLink {
position: relative;
@ -59,6 +59,7 @@
font-weight: 600;
}
}
&.-has-selection {
color: var(--alertNeutralText, $fallback--text);
background-color: var(--alertNeutral, $fallback--fg);
@ -100,10 +101,6 @@
}
}
.full {
pointer-events: none;
}
.serverName.-faded {
color: var(--faintLink, $fallback--link);
}

View file

@ -2,7 +2,7 @@
word-break: break-all;
.mention-link:not(:first-child)::before {
content: ' ';
content: " ";
}
.showMoreLess {

View file

@ -1,5 +1,6 @@
import SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
@ -25,12 +26,14 @@ const MobileNav = {
components: {
SideDrawer,
Notifications,
NavigationPins
NavigationPins,
ConfirmModal
},
data: () => ({
notificationsCloseGesture: undefined,
notificationsOpen: false,
notificationsAtTop: true
notificationsAtTop: true,
showingConfirmLogout: false
}),
created () {
this.notificationsCloseGesture = GestureService.swipeGesture(
@ -57,7 +60,11 @@ const MobileNav = {
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']),
chatsPinned () {
return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats')
}
},
shouldConfirmLogout () {
return this.$store.getters.mergedConfig.modalOnLogout
},
...mapGetters(['unreadChatCount'])
},
methods: {
toggleMobileSidebar () {
@ -88,9 +95,23 @@ const MobileNav = {
scrollMobileNotificationsToTop () {
this.$refs.mobileNotifications.scrollTo(0, 0)
},
showConfirmLogout () {
this.showingConfirmLogout = true
},
hideConfirmLogout () {
this.showingConfirmLogout = false
},
logout () {
if (!this.shouldConfirmLogout) {
this.doLogout()
} else {
this.showConfirmLogout()
}
},
doLogout () {
this.$router.replace('/main/public')
this.$store.dispatch('logout')
this.hideConfirmLogout()
},
markNotificationsAsSeen () {
// this.$refs.notifications.markAsSeen()

View file

@ -88,13 +88,25 @@
ref="sideDrawer"
:logout="logout"
/>
<teleport to="#modal">
<confirm-modal
v-if="showingConfirmLogout"
:title="$t('login.logout_confirm_title')"
:confirm-text="$t('login.logout_confirm_accept_button')"
:cancel-text="$t('login.logout_confirm_cancel_button')"
@accepted="doLogout"
@cancelled="hideConfirmLogout"
>
{{ $t('login.logout_confirm') }}
</confirm-modal>
</teleport>
</div>
</template>
<script src="./mobile_nav.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.MobileNav {
z-index: var(--ZI_navbar);
@ -127,7 +139,7 @@
}
.site-name {
padding: 0 .3em;
padding: 0 0.3em;
display: inline-block;
}
@ -156,7 +168,7 @@
position: fixed;
top: 0;
left: 0;
box-shadow: 1px 1px 4px rgba(0,0,0,.6);
box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
box-shadow: var(--panelShadow);
transition-property: transform;
transition-duration: 0.25s;
@ -182,7 +194,7 @@
color: var(--topBarText);
background-color: $fallback--fg;
background-color: var(--topBar, $fallback--fg);
box-shadow: 0px 0px 4px rgba(0,0,0,.6);
box-shadow: 0 0 4px rgb(0 0 0 / 60%);
box-shadow: var(--topBarShadow);
.spacer {
@ -235,6 +247,16 @@
}
}
}
.confirm-modal.dark-overlay {
&::before {
z-index: 3000;
}
.dialog-modal.panel {
z-index: 3001;
}
}
}
</style>

View file

@ -13,7 +13,7 @@
<script src="./mobile_post_status_button.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.MobilePostButton {
&.button-default {
@ -30,9 +30,8 @@
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3), 0px 4px 6px rgba(0, 0, 0, 0.3);
box-shadow: 0 2px 2px rgb(0 0 0 / 30%), 0 4px 6px rgb(0 0 0 / 30%);
z-index: 10;
transition: 0.35s transform;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
}

View file

@ -59,7 +59,7 @@ export default {
&.modal-background {
pointer-events: initial;
background-color: rgba(0, 0, 0, 0.5);
background-color: rgb(0 0 0 / 50%);
}
&.open {
@ -69,10 +69,11 @@ export default {
@keyframes modal-background-fadein {
from {
background-color: rgba(0, 0, 0, 0);
background-color: rgb(0 0 0 / 0%);
}
to {
background-color: rgba(0, 0, 0, 0.5);
background-color: rgb(0 0 0 / 50%);
}
}
</style>

View file

@ -166,18 +166,21 @@
<script src="./moderation_tools.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.moderation-tools-popover {
height: 100%;
.trigger {
/* stylelint-disable-next-line declaration-no-important */
display: flex !important;
height: 100%;
}
}
.moderation-tools-button {
svg,i {
svg,
i {
font-size: 0.8em;
}
}

View file

@ -2,19 +2,21 @@
margin: 1em;
table {
width:100%;
width: 100%;
text-align: left;
padding-left:10px;
padding-bottom:20px;
padding-left: 10px;
padding-bottom: 20px;
th, td {
th,
td {
width: 180px;
max-width: 360px;
overflow: hidden;
overflow: hidden;
vertical-align: text-top;
}
th+th, td+td {
th + th,
td + td {
width: auto;
}
}

View file

@ -227,6 +227,6 @@
<script src="./mrf_transparency_panel.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import './mrf_transparency_panel.scss';
@import "../../variables";
@import "./mrf_transparency_panel";
</style>

View file

@ -37,6 +37,7 @@
.mute-card-content-container {
margin-top: 0.5em;
text-align: right;
button {
width: 10em;
}

View file

@ -102,7 +102,7 @@
<script src="./nav_panel.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.NavPanel {
.panel {
@ -169,8 +169,9 @@
}
.nav-panel-heading {
// breaks without a unit
--panel-heading-height-padding: 0em;
// breaks without a unit
// stylelint-disable-next-line length-zero-no-unit
--panel-heading-height-padding: 0px;
}
}
</style>

View file

@ -63,7 +63,7 @@
<script src="./navigation_entry.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.NavigationEntry {
display: flex;
@ -102,6 +102,7 @@
background-color: var(--selectedMenu, $fallback--lightBg);
color: $fallback--link;
color: var(--selectedMenuText, $fallback--link);
--faint: var(--selectedMenuFaintText, $fallback--faint);
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
--lightText: var(--selectedMenuLightText, $fallback--lightText);
@ -117,6 +118,7 @@
background-color: var(--selectedMenu, $fallback--lightBg);
color: $fallback--text;
color: var(--selectedMenuText, $fallback--text);
--faint: var(--selectedMenuFaintText, $fallback--faint);
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
--lightText: var(--selectedMenuLightText, $fallback--lightText);

View file

@ -27,7 +27,8 @@
<script src="./navigation_pins.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.NavigationPins {
display: flex;
flex-wrap: wrap;

View file

@ -8,6 +8,7 @@ import Report from '../report/report.vue'
import UserLink from '../user_link/user_link.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import UserPopover from '../user_popover/user_popover.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -20,7 +21,9 @@ import {
faUserPlus,
faEyeSlash,
faUser,
faSuitcaseRolling
faSuitcaseRolling,
faExpandAlt,
faCompressAlt
} from '@fortawesome/free-solid-svg-icons'
library.add(
@ -31,15 +34,19 @@ library.add(
faUserPlus,
faUser,
faEyeSlash,
faSuitcaseRolling
faSuitcaseRolling,
faExpandAlt,
faCompressAlt
)
const Notification = {
data () {
return {
userExpanded: false,
statusExpanded: false,
betterShadow: this.$store.state.interface.browserSupport.cssFilter,
unmuted: false
unmuted: false,
showingApproveConfirmDialog: false,
showingDenyConfirmDialog: false
}
},
props: ['notification'],
@ -52,11 +59,12 @@ const Notification = {
Report,
RichContent,
UserPopover,
UserLink
UserLink,
ConfirmModal
},
methods: {
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
toggleStatusExpanded () {
this.statusExpanded = !this.statusExpanded
},
generateUserProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
@ -67,7 +75,26 @@ const Notification = {
toggleMute () {
this.unmuted = !this.unmuted
},
showApproveConfirmDialog () {
this.showingApproveConfirmDialog = true
},
hideApproveConfirmDialog () {
this.showingApproveConfirmDialog = false
},
showDenyConfirmDialog () {
this.showingDenyConfirmDialog = true
},
hideDenyConfirmDialog () {
this.showingDenyConfirmDialog = false
},
approveUser () {
if (this.shouldConfirmApprove) {
this.showApproveConfirmDialog()
} else {
this.doApprove()
}
},
doApprove () {
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id })
@ -77,13 +104,22 @@ const Notification = {
notification.type = 'follow'
}
})
this.hideApproveConfirmDialog()
},
denyUser () {
if (this.shouldConfirmDeny) {
this.showDenyConfirmDialog()
} else {
this.doDeny()
}
},
doDeny () {
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })
this.$store.dispatch('removeFollowRequest', this.user)
})
this.hideDenyConfirmDialog()
}
},
computed: {
@ -113,6 +149,15 @@ const Notification = {
isStatusNotification () {
return isStatusNotification(this.notification.type)
},
mergedConfig () {
return this.$store.getters.mergedConfig
},
shouldConfirmApprove () {
return this.mergedConfig.modalOnApproveFollow
},
shouldConfirmDeny () {
return this.mergedConfig.modalOnDenyFollow
},
...mapState({
currentUser: state => state.users.currentUser
})

View file

@ -1,13 +1,14 @@
@import '../../_variables.scss';
@import "../../variables";
// TODO Copypaste from Status, should unify it somehow
.Notification {
border-bottom: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
word-wrap: break-word;
word-break: break-word;
--emoji-size: 14px;
border-bottom: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
word-wrap: break-word;
word-break: break-word;
--emoji-size: 14px;
&:hover {
--_still-image-img-visibility: visible;
@ -54,7 +55,7 @@
margin-left: 0.2em;
&::before {
content: ' ';
content: " ";
}
}

View file

@ -144,13 +144,25 @@
<router-link
v-if="notification.status"
:to="{ name: 'conversation', params: { id: notification.status.id } }"
class="faint-link"
class="timeago-link faint-link"
>
<Timeago
:time="notification.created_at"
:auto-update="240"
/>
</router-link>
<button
class="button-unstyled expand-icon"
@click.prevent="toggleStatusExpanded"
:title="$t('tool_tip.toggle_expand')"
:aria-expanded="statusExpanded"
>
<FAIcon
class="fa-scale-110"
fixed-width
:icon="statusExpanded ? 'compress-alt' : 'expand-alt'"
/>
</button>
</div>
<div
v-else
@ -166,6 +178,8 @@
<button
v-if="needMute"
class="button-unstyled"
:title="$t('tool_tip.toggle_mute')"
:aria-expanded="!unmuted"
@click.prevent="toggleMute"
>
<FAIcon
@ -222,13 +236,35 @@
/>
<template v-else>
<StatusContent
class="faint"
:compact="true"
:class="{ faint: !statusExpanded }"
:compact="!statusExpanded"
:status="notification.action"
/>
</template>
</div>
</div>
<teleport to="#modal">
<confirm-modal
v-if="showingApproveConfirmDialog"
:title="$t('user_card.approve_confirm_title')"
:confirm-text="$t('user_card.approve_confirm_accept_button')"
:cancel-text="$t('user_card.approve_confirm_cancel_button')"
@accepted="doApprove"
@cancelled="hideApproveConfirmDialog"
>
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
</confirm-modal>
<confirm-modal
v-if="showingDenyConfirmDialog"
:title="$t('user_card.deny_confirm_title')"
:confirm-text="$t('user_card.deny_confirm_accept_button')"
:cancel-text="$t('user_card.deny_confirm_cancel_button')"
@accepted="doDeny"
@cancelled="hideDenyConfirmDialog"
>
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
</confirm-modal>
</teleport>
</article>
</template>

View file

@ -101,6 +101,9 @@ const Notifications = {
if (!this.scrollerRef) {
this.scrollerRef = this.$refs.root.closest('.mobile-notifications')
}
if (!this.scrollerRef) {
this.scrollerRef = this.$refs.root.closest('.column.main')
}
this.scrollerRef.addEventListener('scroll', this.updateScrollPosition)
},
unmounted () {

View file

@ -1,4 +1,4 @@
@import '../../_variables.scss';
@import "../../variables";
.Notifications {
&:not(.minimal) {
@ -25,12 +25,13 @@
&.unseen {
.notification-overlay {
background-image: linear-gradient(135deg, var(--badgeNotification, $fallback--cRed) 4px, transparent 10px)
background-image: linear-gradient(135deg, var(--badgeNotification, $fallback--cRed) 4px, transparent 10px);
}
}
}
}
/* stylelint-disable-next-line no-descending-specificity */
.notification {
box-sizing: border-box;
@ -38,6 +39,7 @@
canvas {
display: none;
}
img {
visibility: visible;
}
@ -79,7 +81,8 @@
}
}
.follow-text, .move-text {
.follow-text,
.move-text {
padding: 0.5em 0;
overflow-wrap: break-word;
display: flex;
@ -112,6 +115,16 @@
min-width: 3em;
text-align: right;
}
.timeago-link {
margin-right: 0.2em;
}
.expand-icon {
.svg-inline--fa {
margin-left: 0.25em;
}
}
}
.emoji-reaction-emoji {

View file

@ -23,7 +23,7 @@ export default {}
</script>
<style lang="scss">
@import 'src/_variables.scss';
@import "src/variables";
.panel-loading {
display: flex;
@ -33,6 +33,7 @@ export default {}
font-size: 2em;
color: $fallback--text;
color: var(--text, $fallback--text);
.loading-text svg {
line-height: 0;
vertical-align: middle;

View file

@ -77,7 +77,7 @@
<script src="./password_reset.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.password-reset-form {
display: flex;

View file

@ -90,7 +90,7 @@
<script src="./poll.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.poll {
.votes {
@ -98,9 +98,11 @@
flex-direction: column;
margin: 0 0 0.5em;
}
.poll-option {
margin: 0.75em 0.5em;
}
.option-result {
height: 100%;
display: flex;
@ -109,6 +111,7 @@
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
.option-result-label {
display: flex;
align-items: center;
@ -116,10 +119,12 @@
z-index: 1;
word-break: break-word;
}
.result-percentage {
width: 3.5em;
flex-shrink: 0;
}
.result-fill {
height: 100%;
position: absolute;
@ -133,20 +138,25 @@
left: 0;
transition: width 0.5s;
}
.option-vote {
display: flex;
align-items: center;
}
input {
width: 3.5em;
}
.footer {
display: flex;
align-items: center;
}
&.loading * {
cursor: progress;
}
.poll-vote-button {
padding: 0 0.5em;
margin-right: 0.5em;

View file

@ -94,19 +94,10 @@ export default {
},
convertExpiryToUnit (unit, amount) {
// Note: we want seconds and not milliseconds
switch (unit) {
case 'minutes': return (1000 * amount) / DateUtils.MINUTE
case 'hours': return (1000 * amount) / DateUtils.HOUR
case 'days': return (1000 * amount) / DateUtils.DAY
}
return DateUtils.secondsToUnit(unit, amount)
},
convertExpiryFromUnit (unit, amount) {
// Note: we want seconds and not milliseconds
switch (unit) {
case 'minutes': return 0.001 * amount * DateUtils.MINUTE
case 'hours': return 0.001 * amount * DateUtils.HOUR
case 'days': return 0.001 * amount * DateUtils.DAY
}
return DateUtils.unitToSeconds(unit, amount)
},
expiryAmountChange () {
this.expiryAmount =

View file

@ -95,7 +95,7 @@
<script src="./poll_form.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.poll-form {
display: flex;
@ -117,6 +117,7 @@
.input-container {
width: 100%;
input {
// Hack: dodge the floating X icon
padding-right: 2.5em;

View file

@ -41,7 +41,7 @@
<script src="./popover.js" />
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.popover-trigger-button {
display: inline-block;
@ -52,31 +52,31 @@
position: fixed;
min-width: 0;
max-width: calc(100vw - 20px);
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
box-shadow: 2px 2px 3px rgb(0 0 0 / 50%);
box-shadow: var(--popupShadow);
}
.popover-default {
&:after {
content: '';
&::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 3;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
box-shadow: var(--panelShadow);
pointer-events: none;
}
border-radius: $fallback--btnRadius;
border-radius: var(--btnRadius, $fallback--btnRadius);
background-color: $fallback--bg;
background-color: var(--popover, $fallback--bg);
color: $fallback--text;
color: var(--popoverText, $fallback--text);
--faint: var(--popoverFaintText, $fallback--faint);
--faintLink: var(--popoverFaintLink, $fallback--faint);
--lightText: var(--popoverLightText, $fallback--lightText);
@ -87,7 +87,7 @@
.dropdown-menu {
display: block;
padding: .5rem 0;
padding: 0.5rem 0;
font-size: 1em;
text-align: left;
list-style: none;
@ -97,7 +97,7 @@
.dropdown-divider {
height: 0;
margin: .5rem 0;
margin: 0.5rem 0;
overflow: hidden;
border-top: 1px solid $fallback--border;
border-top: 1px solid var(--border, $fallback--border);
@ -113,7 +113,7 @@
text-align: inherit;
white-space: nowrap;
border: none;
border-radius: 0px;
border-radius: 0;
background-color: transparent;
box-shadow: none;
width: 100%;
@ -126,7 +126,7 @@
svg {
width: 22px;
margin-right: 0.75rem;
color: var(--menuPopoverIcon, $fallback--icon)
color: var(--menuPopoverIcon, $fallback--icon);
}
}
@ -137,17 +137,21 @@
}
}
&:active, &:hover {
&:active,
&:hover {
background-color: $fallback--lightBg;
background-color: var(--selectedMenuPopover, $fallback--lightBg);
box-shadow: none;
--btnText: var(--selectedMenuPopoverText, $fallback--link);
--faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
--faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
--lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
--icon: var(--selectedMenuPopoverIcon, $fallback--icon);
svg {
color: var(--selectedMenuPopoverIcon, $fallback--icon);
--icon: var(--selectedMenuPopoverIcon, $fallback--icon);
}
}
@ -161,16 +165,16 @@
max-height: 22px;
line-height: 22px;
text-align: center;
border-radius: 0px;
border-radius: 0;
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
box-shadow: 0px 0px 2px black inset;
box-shadow: 0 0 2px black inset;
box-shadow: var(--inputShadow);
margin-right: 0.75em;
&.menu-checkbox-checked::after {
font-size: 1.25em;
content: '✓';
content: "✓";
}
&.-radio {
@ -178,16 +182,15 @@
&.menu-checkbox-checked::after {
font-size: 2em;
content: '•';
content: "•";
}
}
}
}
.button-default.dropdown-item {
&,
i[class*=icon-] {
i[class*="icon-"] {
color: $fallback--text;
color: var(--btnText, $fallback--text);
}

View file

@ -331,7 +331,7 @@
<script src="./post_status_form.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
.post-status-form {
position: relative;
@ -378,7 +378,9 @@
&:hover {
text-decoration: underline;
}
svg, i {
svg,
i {
margin-left: 0.2em;
font-size: 0.8em;
transform: rotate(90deg);
@ -428,36 +430,6 @@
}
}
.media-upload-icon, .poll-icon, .emoji-icon {
font-size: 1.85em;
line-height: 1.1;
flex: 1;
padding: 0 0.1em;
display: flex;
align-items: center;
&.selected, &:hover {
// needs to be specific to override icon default color
svg, i, label {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
}
&.disabled {
svg, i {
cursor: not-allowed;
color: $fallback--icon;
color: var(--btnDisabledText, $fallback--icon);
&:hover {
color: $fallback--icon;
color: var(--btnDisabledText, $fallback--icon);
}
}
}
}
// Order is not necessary but a good indicator
.media-upload-icon {
order: 1;
@ -474,16 +446,53 @@
justify-content: right;
}
.media-upload-icon,
.poll-icon,
.emoji-icon {
font-size: 1.85em;
line-height: 1.1;
flex: 1;
padding: 0 0.1em;
display: flex;
align-items: center;
&.selected,
&:hover {
// needs to be specific to override icon default color
svg,
i,
label {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
}
&.disabled {
svg,
i {
cursor: not-allowed;
color: $fallback--icon;
color: var(--btnDisabledText, $fallback--icon);
&:hover {
color: $fallback--icon;
color: var(--btnDisabledText, $fallback--icon);
}
}
}
}
.error {
text-align: center;
}
.media-upload-wrapper {
margin-right: .2em;
margin-bottom: .5em;
margin-right: 0.2em;
margin-bottom: 0.5em;
width: 18em;
img, video {
img,
video {
object-fit: contain;
max-height: 10em;
}
@ -557,18 +566,14 @@
}
}
.btn[disabled] {
cursor: not-allowed;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 0.6; }
to { opacity: 0.6; }
}
@keyframes fade-out {
from { opacity: 0.6; }
to { opacity: 0; }
to { opacity: 0; }
}
.drop-indicator {

View file

@ -41,7 +41,7 @@ const ReactButton = {
},
focusInput () {
this.$nextTick(() => {
const input = this.$el.querySelector('input')
const input = document.querySelector('.reaction-picker-filter > input')
if (input) input.focus()
})
},

View file

@ -73,8 +73,8 @@
<script src="./react_button.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import '../../_mixins.scss';
@import "../../variables";
@import "../../mixins";
.ReactButton {
.reaction-picker-filter {
@ -104,20 +104,19 @@
text-align: center;
align-content: flex-start;
user-select: none;
mask: linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
linear-gradient(to top, white, white);
mask:
linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
linear-gradient(to top, white, white);
transition: mask-size 150ms;
mask-size: 100% 20px, 100% 20px, auto;
/* Autoprefixed seem to ignore this one, and also syntax is different */
-webkit-mask-composite: xor;
mask-composite: xor;
mask-composite: exclude;
.emoji-button {
cursor: pointer;
flex-basis: 20%;
line-height: 1.5;
align-content: center;
@ -128,11 +127,6 @@
}
}
/* override of popover internal stuff */
.popover-trigger-button {
width: auto;
}
.popover-trigger {
padding: 10px;
margin: -10px;
@ -141,10 +135,12 @@
color: $fallback--text;
color: var(--text, $fallback--text);
}
}
.popover-trigger-button {
/* override of popover internal stuff */
width: auto;
@include unfocused-style {
.focus-marker {
visibility: hidden;

View file

@ -3,6 +3,7 @@ import { required, requiredIf, sameAs } from '@vuelidate/validators'
import { mapActions, mapState } from 'vuex'
import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
import localeService from '../../services/locale/locale.service.js'
import { DAY } from 'src/services/date_utils/date_utils.js'
const registration = {
setup () { return { v$: useVuelidate() } },
@ -13,6 +14,7 @@ const registration = {
username: '',
password: '',
confirm: '',
birthday: '',
reason: '',
language: ''
},
@ -32,6 +34,12 @@ const registration = {
required,
sameAs: sameAs(this.user.password)
},
birthday: {
required: requiredIf(() => this.birthdayRequired),
maxValue: value => {
return !this.birthdayRequired || new Date(value).getTime() <= this.birthdayMin.getTime()
}
},
reason: { required: requiredIf(() => this.accountApprovalRequired) },
language: {}
}
@ -52,6 +60,24 @@ const registration = {
reasonPlaceholder () {
return this.replaceNewlines(this.$t('registration.reason_placeholder'))
},
birthdayMin () {
const minAge = this.birthdayMinAge
const today = new Date()
today.setUTCMilliseconds(0)
today.setUTCSeconds(0)
today.setUTCMinutes(0)
today.setUTCHours(0)
const minDate = new Date()
minDate.setTime(today.getTime() - minAge * DAY)
return minDate
},
birthdayMinAttr () {
return this.birthdayMin.toJSON().replace(/T.+$/, '')
},
birthdayMinFormatted () {
const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
return this.user.birthday && new Date(Date.parse(this.birthdayMin)).toLocaleDateString(browserLocale, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' })
},
...mapState({
registrationOpen: (state) => state.instance.registrationOpen,
signedIn: (state) => !!state.users.currentUser,
@ -59,7 +85,9 @@ const registration = {
serverValidationErrors: (state) => state.users.signUpErrors,
termsOfService: (state) => state.instance.tos,
accountActivationRequired: (state) => state.instance.accountActivationRequired,
accountApprovalRequired: (state) => state.instance.accountApprovalRequired
accountApprovalRequired: (state) => state.instance.accountApprovalRequired,
birthdayRequired: (state) => state.instance.birthdayRequired,
birthdayMinAge: (state) => state.instance.birthdayMinAge
})
},
methods: {

View file

@ -158,15 +158,49 @@
class="form-error"
>
<ul>
<li v-if="!v$.user.confirm.required">
<li v-if="v$.user.confirm.required.$invalid">
<span>{{ $t('registration.validations.password_confirmation_required') }}</span>
</li>
<li v-if="!v$.user.confirm.sameAsPassword">
<li v-if="v$.user.confirm.sameAs.$invalid">
<span>{{ $t('registration.validations.password_confirmation_match') }}</span>
</li>
</ul>
</div>
<div
class="form-group"
:class="{ 'form-group--error': v$.user.birthday.$error }"
>
<label
class="form--label"
for="sign-up-birthday"
>
{{ birthdayRequired ? $t('registration.birthday') : $t('registration.birthday_optional') }}
</label>
<input
id="sign-up-birthday"
v-model="user.birthday"
:disabled="isPending"
class="form-control"
type="date"
:max="birthdayRequired ? birthdayMinAttr : undefined"
:aria-required="birthdayRequired"
>
</div>
<div
v-if="v$.user.birthday.$dirty"
class="form-error"
>
<ul>
<li v-if="v$.user.birthday.required.$invalid">
<span>{{ $t('registration.validations.birthday_required') }}</span>
</li>
<li v-if="v$.user.birthday.maxValue.$invalid">
<span>{{ $tc('registration.validations.birthday_min_age', { date: birthdayMinFormatted }) }}</span>
</li>
</ul>
</div>
<div
class="form-group"
:class="{ 'form-group--error': v$.user.language.$error }"
@ -277,7 +311,7 @@
<script src="./registration.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import "../../variables";
$validations-cRed: #f04124;
.registration-form {
@ -321,7 +355,7 @@ $validations-cRed: #f04124;
.form-group--error {
animation-name: shakeError;
animation-duration: .6s;
animation-duration: 0.6s;
animation-timing-function: ease-in-out;
}
@ -350,7 +384,7 @@ $validations-cRed: #f04124;
}
form textarea {
line-height:16px;
line-height: 16px;
resize: vertical;
}

View file

@ -15,6 +15,3 @@
</template>
<script src="./remote_user_resolver.js"></script>
<style lang="scss">
</style>

View file

@ -1,10 +1,16 @@
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
export default {
props: ['relationship'],
props: ['user', 'relationship'],
data () {
return {
inProgress: false
inProgress: false,
showingConfirmRemoveFollower: false
}
},
components: {
ConfirmModal
},
computed: {
label () {
if (this.inProgress) {
@ -12,14 +18,31 @@ export default {
} else {
return this.$t('user_card.remove_follower')
}
},
shouldConfirmRemoveUserFromFollowers () {
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
}
},
methods: {
showConfirmRemoveUserFromFollowers () {
this.showingConfirmRemoveFollower = true
},
hideConfirmRemoveUserFromFollowers () {
this.showingConfirmRemoveFollower = false
},
onClick () {
if (!this.shouldConfirmRemoveUserFromFollowers) {
this.doRemoveUserFromFollowers()
} else {
this.showConfirmRemoveUserFromFollowers()
}
},
doRemoveUserFromFollowers () {
this.inProgress = true
this.$store.dispatch('removeUserFromFollowers', this.relationship.id).then(() => {
this.inProgress = false
})
this.hideConfirmRemoveUserFromFollowers()
}
}
}

View file

@ -7,6 +7,27 @@
@click="onClick"
>
{{ label }}
<teleport to="#modal">
<confirm-modal
v-if="showingConfirmRemoveFollower"
:title="$t('user_card.remove_follower_confirm_title')"
:confirm-text="$t('user_card.remove_follower_confirm_accept_button')"
:cancel-text="$t('user_card.remove_follower_confirm_cancel_button')"
@accepted="doRemoveUserFromFollowers"
@cancelled="hideConfirmRemoveUserFromFollowers"
>
<i18n-t
keypath="user_card.remove_follower_confirm"
tag="span"
>
<template #user>
<span
v-text="user.screen_name_ui"
/>
</template>
</i18n-t>
</confirm-modal>
</teleport>
</button>
</template>

View file

@ -51,8 +51,8 @@
<script src="./reply_button.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@import '../../_mixins.scss';
@import "../../variables";
@import "../../mixins";
.ReplyButton {
display: flex;
@ -86,6 +86,5 @@
}
}
}
}
</style>

View file

@ -1,4 +1,4 @@
@import '../../_variables.scss';
@import "../../variables";
.Report {
.report-content {

View file

@ -1,3 +1,4 @@
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faRetweet,
@ -15,13 +16,24 @@ library.add(
const RetweetButton = {
props: ['status', 'loggedIn', 'visibility'],
components: {
ConfirmModal
},
data () {
return {
animated: false
animated: false,
showingConfirmDialog: false
}
},
methods: {
retweet () {
if (!this.status.repeated && this.shouldConfirmRepeat) {
this.showConfirmDialog()
} else {
this.doRetweet()
}
},
doRetweet () {
if (!this.status.repeated) {
this.$store.dispatch('retweet', { id: this.status.id })
} else {
@ -31,6 +43,13 @@ const RetweetButton = {
setTimeout(() => {
this.animated = false
}, 500)
this.hideConfirmDialog()
},
showConfirmDialog () {
this.showingConfirmDialog = true
},
hideConfirmDialog () {
this.showingConfirmDialog = false
}
},
computed: {
@ -39,6 +58,9 @@ const RetweetButton = {
},
remoteInteractionLink () {
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
},
shouldConfirmRepeat () {
return this.mergedConfig.modalOnRepeat
}
}
}

Some files were not shown because too many files have changed in this diff Show more