Merge remote-tracking branch 'origin/develop' into settings-changed
* origin/develop: (306 commits) fallback if shadows aren't defined Translated using Weblate (Chinese (Traditional)) Translated using Weblate (Ukrainian) Translated using Weblate (Italian) Translated using Weblate (Ukrainian) Translated using Weblate (Portuguese) Translated using Weblate (Italian) Translated using Weblate (Russian) Translated using Weblate (Portuguese) Translated using Weblate (Russian) Translated using Weblate (Portuguese) Translated using Weblate (Portuguese) Translated using Weblate (Portuguese) Translated using Weblate (Portuguese) Translated using Weblate (Portuguese) Translated using Weblate (Portuguese) Translated using Weblate (Portuguese) Translated using Weblate (Portuguese) Translated using Weblate (Portuguese) Translated using Weblate (Portuguese) ...
This commit is contained in:
commit
8958f386be
222 changed files with 11463 additions and 4132 deletions
|
@ -1,6 +1,14 @@
|
|||
import { mapState } from 'vuex'
|
||||
import ProgressButton from '../progress_button/progress_button.vue'
|
||||
import Popover from '../popover/popover.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faEllipsisV
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faEllipsisV
|
||||
)
|
||||
|
||||
const AccountActions = {
|
||||
props: [
|
||||
|
@ -27,7 +35,7 @@ const AccountActions = {
|
|||
this.$store.dispatch('unblockUser', this.user.id)
|
||||
},
|
||||
reportUser () {
|
||||
this.$store.dispatch('openUserReportingModal', this.user.id)
|
||||
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
|
||||
},
|
||||
openChat () {
|
||||
this.$router.push({
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<template>
|
||||
<div class="account-actions">
|
||||
<div class="AccountActions">
|
||||
<Popover
|
||||
trigger="click"
|
||||
placement="bottom"
|
||||
:bound-to="{ x: 'container' }"
|
||||
remove-padding
|
||||
>
|
||||
<div
|
||||
slot="content"
|
||||
|
@ -13,14 +14,14 @@
|
|||
<template v-if="relationship.following">
|
||||
<button
|
||||
v-if="relationship.showing_reblogs"
|
||||
class="btn btn-default dropdown-item"
|
||||
class="btn button-default dropdown-item"
|
||||
@click="hideRepeats"
|
||||
>
|
||||
{{ $t('user_card.hide_repeats') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="!relationship.showing_reblogs"
|
||||
class="btn btn-default dropdown-item"
|
||||
class="btn button-default dropdown-item"
|
||||
@click="showRepeats"
|
||||
>
|
||||
{{ $t('user_card.show_repeats') }}
|
||||
|
@ -32,27 +33,27 @@
|
|||
</template>
|
||||
<button
|
||||
v-if="relationship.blocking"
|
||||
class="btn btn-default btn-block dropdown-item"
|
||||
class="btn button-default btn-block dropdown-item"
|
||||
@click="unblockUser"
|
||||
>
|
||||
{{ $t('user_card.unblock') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-default btn-block dropdown-item"
|
||||
class="btn button-default btn-block dropdown-item"
|
||||
@click="blockUser"
|
||||
>
|
||||
{{ $t('user_card.block') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-default btn-block dropdown-item"
|
||||
class="btn button-default btn-block dropdown-item"
|
||||
@click="reportUser"
|
||||
>
|
||||
{{ $t('user_card.report') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="pleromaChatMessagesAvailable"
|
||||
class="btn btn-default btn-block dropdown-item"
|
||||
class="btn button-default btn-block dropdown-item"
|
||||
@click="openChat"
|
||||
>
|
||||
{{ $t('user_card.message') }}
|
||||
|
@ -61,9 +62,12 @@
|
|||
</div>
|
||||
<div
|
||||
slot="trigger"
|
||||
class="btn btn-default ellipsis-button"
|
||||
class="ellipsis-button"
|
||||
>
|
||||
<i class="icon-ellipsis trigger-button" />
|
||||
<FAIcon
|
||||
class="icon"
|
||||
icon="ellipsis-v"
|
||||
/>
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
|
@ -73,22 +77,22 @@
|
|||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
.account-actions {
|
||||
margin: 0 .8em;
|
||||
}
|
||||
.AccountActions {
|
||||
button.dropdown-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.account-actions button.dropdown-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
.ellipsis-button {
|
||||
cursor: pointer;
|
||||
width: 2.5em;
|
||||
margin: -0.5em 0;
|
||||
padding: 0.5em 0;
|
||||
text-align: center;
|
||||
|
||||
.account-actions .trigger-button {
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
opacity: .8;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
&:not(:hover) .icon {
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
{{ $t('general.error_retry') }}
|
||||
</p>
|
||||
<button
|
||||
class="btn"
|
||||
class="btn button-default"
|
||||
@click="retry"
|
||||
>
|
||||
{{ $t('general.retry') }}
|
||||
|
|
|
@ -3,6 +3,24 @@ import VideoAttachment from '../video_attachment/video_attachment.vue'
|
|||
import nsfwImage from '../../assets/nsfw.png'
|
||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faFile,
|
||||
faMusic,
|
||||
faImage,
|
||||
faVideo,
|
||||
faPlayCircle,
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faFile,
|
||||
faMusic,
|
||||
faImage,
|
||||
faVideo,
|
||||
faPlayCircle,
|
||||
faTimes
|
||||
)
|
||||
|
||||
const Attachment = {
|
||||
props: [
|
||||
|
@ -39,10 +57,10 @@ const Attachment = {
|
|||
return this.attachment.description
|
||||
},
|
||||
placeholderIconClass () {
|
||||
if (this.type === 'image') return 'icon-picture'
|
||||
if (this.type === 'video') return 'icon-video'
|
||||
if (this.type === 'audio') return 'icon-music'
|
||||
return 'icon-doc'
|
||||
if (this.type === 'image') return 'image'
|
||||
if (this.type === 'video') return 'video'
|
||||
if (this.type === 'audio') return 'music'
|
||||
return 'file'
|
||||
},
|
||||
referrerpolicy () {
|
||||
return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer'
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
:alt="attachment.description"
|
||||
:title="attachment.description"
|
||||
>
|
||||
<span :class="placeholderIconClass" />
|
||||
<FAIcon :icon="placeholderIconClass" />
|
||||
<b>{{ nsfw ? "NSFW / " : "" }}</b>{{ placeholderName }}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -36,20 +36,19 @@
|
|||
:src="nsfwImage"
|
||||
:class="{'small': isSmall}"
|
||||
>
|
||||
<i
|
||||
<FAIcon
|
||||
v-if="type === 'video'"
|
||||
class="play-icon icon-play-circled"
|
||||
class="play-icon"
|
||||
icon="play-circle"
|
||||
/>
|
||||
</a>
|
||||
<div
|
||||
<button
|
||||
v-if="nsfw && hideNsfwLocal && !hidden"
|
||||
class="hider"
|
||||
class="button-unstyled hider"
|
||||
@click.prevent="toggleHidden"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
@click.prevent="toggleHidden"
|
||||
>Hide</a>
|
||||
</div>
|
||||
<FAIcon icon="times" />
|
||||
</button>
|
||||
|
||||
<a
|
||||
v-if="type === 'image' && (!hidden || preloadImage)"
|
||||
|
@ -83,9 +82,10 @@
|
|||
@play="$emit('play')"
|
||||
@pause="$emit('pause')"
|
||||
/>
|
||||
<i
|
||||
<FAIcon
|
||||
v-if="!allowPlay"
|
||||
class="play-icon icon-play-circled"
|
||||
class="play-icon"
|
||||
icon="play-circle"
|
||||
/>
|
||||
</a>
|
||||
|
||||
|
@ -142,6 +142,10 @@
|
|||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
|
||||
svg {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.nsfw-placeholder {
|
||||
|
@ -228,15 +232,23 @@
|
|||
.hider {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
white-space: nowrap;
|
||||
margin: 10px;
|
||||
padding: 5px;
|
||||
background: rgba(230,230,230,0.6);
|
||||
font-weight: bold;
|
||||
padding: 0;
|
||||
z-index: 4;
|
||||
line-height: 1;
|
||||
border-radius: $fallback--tooltipRadius;
|
||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
||||
text-align: center;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
font-size: 1.25em;
|
||||
// TODO: theming? hard to theme with unknown background image color
|
||||
background: rgba(230, 230, 230, 0.7);
|
||||
.svg-inline--fa {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
&:hover .svg-inline--fa {
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
video {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="block-card-content-container">
|
||||
<button
|
||||
v-if="blocked"
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
:disabled="progress"
|
||||
@click="unblockUser"
|
||||
>
|
||||
|
@ -16,7 +16,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
:disabled="progress"
|
||||
@click="blockUser"
|
||||
>
|
||||
|
|
|
@ -6,11 +6,24 @@ import PostStatusForm from '../post_status_form/post_status_form.vue'
|
|||
import ChatTitle from '../chat_title/chat_title.vue'
|
||||
import chatService from '../../services/chat_service/chat_service.js'
|
||||
import { promiseInterval } from '../../services/promise_interval/promise_interval.js'
|
||||
import { getScrollPosition, getNewTopPosition, isBottomedOut, scrollableContainerHeight } from './chat_layout_utils.js'
|
||||
import { getScrollPosition, getNewTopPosition, isBottomedOut, scrollableContainerHeight, isScrollable } from './chat_layout_utils.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faChevronDown,
|
||||
faChevronLeft
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js'
|
||||
|
||||
library.add(
|
||||
faChevronDown,
|
||||
faChevronLeft
|
||||
)
|
||||
|
||||
const BOTTOMED_OUT_OFFSET = 10
|
||||
const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 150
|
||||
const SAFE_RESIZE_TIME_OFFSET = 100
|
||||
const MARK_AS_READ_DELAY = 1500
|
||||
const MAX_RETRIES = 10
|
||||
|
||||
const Chat = {
|
||||
components: {
|
||||
|
@ -24,7 +37,8 @@ const Chat = {
|
|||
hoveredMessageChainId: undefined,
|
||||
lastScrollPosition: {},
|
||||
scrollableContainerHeight: '100%',
|
||||
errorLoadingChat: false
|
||||
errorLoadingChat: false,
|
||||
messageRetriers: {}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
@ -94,7 +108,7 @@ const Chat = {
|
|||
const bottomedOutBeforeUpdate = this.bottomedOut(BOTTOMED_OUT_OFFSET)
|
||||
this.$nextTick(() => {
|
||||
if (bottomedOutBeforeUpdate) {
|
||||
this.scrollDown({ forceRead: !document.hidden })
|
||||
this.scrollDown()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
@ -200,7 +214,7 @@ const Chat = {
|
|||
this.$nextTick(() => {
|
||||
scrollable.scrollTo({ top: scrollable.scrollHeight, left: 0, behavior })
|
||||
})
|
||||
if (forceRead || this.newMessageCount > 0) {
|
||||
if (forceRead) {
|
||||
this.readChat()
|
||||
}
|
||||
},
|
||||
|
@ -208,7 +222,10 @@ const Chat = {
|
|||
if (!(this.currentChatMessageService && this.currentChatMessageService.maxId)) { return }
|
||||
if (document.hidden) { return }
|
||||
const lastReadId = this.currentChatMessageService.maxId
|
||||
this.$store.dispatch('readChat', { id: this.currentChat.id, lastReadId })
|
||||
this.$store.dispatch('readChat', {
|
||||
id: this.currentChat.id,
|
||||
lastReadId
|
||||
})
|
||||
},
|
||||
bottomedOut (offset) {
|
||||
return isBottomedOut(this.$refs.scrollable, offset)
|
||||
|
@ -225,12 +242,18 @@ const Chat = {
|
|||
} else if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
|
||||
this.jumpToBottomButtonVisible = false
|
||||
if (this.newMessageCount > 0) {
|
||||
this.readChat()
|
||||
// Use a delay before marking as read to prevent situation where new messages
|
||||
// arrive just as you're leaving the view and messages that you didn't actually
|
||||
// get to see get marked as read.
|
||||
window.setTimeout(() => {
|
||||
// Don't mark as read if the element doesn't exist, user has left chat view
|
||||
if (this.$el) this.readChat()
|
||||
}, MARK_AS_READ_DELAY)
|
||||
}
|
||||
} else {
|
||||
this.jumpToBottomButtonVisible = true
|
||||
}
|
||||
}, 100),
|
||||
}, 200),
|
||||
handleScrollUp (positionBeforeLoading) {
|
||||
const positionAfterLoading = getScrollPosition(this.$refs.scrollable)
|
||||
this.$refs.scrollable.scrollTo({
|
||||
|
@ -264,6 +287,14 @@ const Chat = {
|
|||
if (isFirstFetch) {
|
||||
this.updateScrollableContainerHeight()
|
||||
}
|
||||
|
||||
// In vertical screens, the first batch of fetched messages may not always take the
|
||||
// full height of the scrollable container.
|
||||
// If this is the case, we want to fetch the messages until the scrollable container
|
||||
// is fully populated so that the user has the ability to scroll up and load the history.
|
||||
if (!isScrollable(this.$refs.scrollable) && messages.length > 0) {
|
||||
this.fetchChat({ maxId: this.currentChatMessageService.minId })
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -292,42 +323,74 @@ const Chat = {
|
|||
})
|
||||
this.fetchChat({ isFirstFetch: true })
|
||||
},
|
||||
sendMessage ({ status, media }) {
|
||||
handleAttachmentPosting () {
|
||||
this.$nextTick(() => {
|
||||
this.handleResize()
|
||||
// When the posting form size changes because of a media attachment, we need an extra resize
|
||||
// to account for the potential delay in the DOM update.
|
||||
setTimeout(() => {
|
||||
this.updateScrollableContainerHeight()
|
||||
}, SAFE_RESIZE_TIME_OFFSET)
|
||||
this.scrollDown({ forceRead: true })
|
||||
})
|
||||
},
|
||||
sendMessage ({ status, media, idempotencyKey }) {
|
||||
const params = {
|
||||
id: this.currentChat.id,
|
||||
content: status
|
||||
content: status,
|
||||
idempotencyKey
|
||||
}
|
||||
|
||||
if (media[0]) {
|
||||
params.mediaId = media[0].id
|
||||
}
|
||||
|
||||
return this.backendInteractor.sendChatMessage(params)
|
||||
const fakeMessage = buildFakeMessage({
|
||||
attachments: media,
|
||||
chatId: this.currentChat.id,
|
||||
content: status,
|
||||
userId: this.currentUser.id,
|
||||
idempotencyKey
|
||||
})
|
||||
|
||||
this.$store.dispatch('addChatMessages', {
|
||||
chatId: this.currentChat.id,
|
||||
messages: [fakeMessage]
|
||||
}).then(() => {
|
||||
this.handleAttachmentPosting()
|
||||
})
|
||||
|
||||
return this.doSendMessage({ params, fakeMessage, retriesLeft: MAX_RETRIES })
|
||||
},
|
||||
doSendMessage ({ params, fakeMessage, retriesLeft = MAX_RETRIES }) {
|
||||
if (retriesLeft <= 0) return
|
||||
|
||||
this.backendInteractor.sendChatMessage(params)
|
||||
.then(data => {
|
||||
this.$store.dispatch('addChatMessages', {
|
||||
chatId: this.currentChat.id,
|
||||
messages: [data],
|
||||
updateMaxId: false
|
||||
}).then(() => {
|
||||
this.$nextTick(() => {
|
||||
this.handleResize()
|
||||
// When the posting form size changes because of a media attachment, we need an extra resize
|
||||
// to account for the potential delay in the DOM update.
|
||||
setTimeout(() => {
|
||||
this.updateScrollableContainerHeight()
|
||||
}, SAFE_RESIZE_TIME_OFFSET)
|
||||
this.scrollDown({ forceRead: true })
|
||||
})
|
||||
updateMaxId: false,
|
||||
messages: [{ ...data, fakeId: fakeMessage.id }]
|
||||
})
|
||||
|
||||
return data
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error sending message', error)
|
||||
return {
|
||||
error: this.$t('chats.error_sending_message')
|
||||
this.$store.dispatch('handleMessageError', {
|
||||
chatId: this.currentChat.id,
|
||||
fakeId: fakeMessage.id,
|
||||
isRetry: retriesLeft !== MAX_RETRIES
|
||||
})
|
||||
if ((error.statusCode >= 500 && error.statusCode < 600) || error.message === 'Failed to fetch') {
|
||||
this.messageRetriers[fakeMessage.id] = setTimeout(() => {
|
||||
this.doSendMessage({ params, fakeMessage, retriesLeft: retriesLeft - 1 })
|
||||
}, 1000 * (2 ** (MAX_RETRIES - retriesLeft)))
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
return Promise.resolve(fakeMessage)
|
||||
},
|
||||
goBack () {
|
||||
this.$router.push({ name: 'chats', params: { username: this.currentUser.screen_name } })
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
min-height: 100%;
|
||||
margin: 0 0 0 0;
|
||||
border-radius: 10px 10px 0 0;
|
||||
border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0 ;
|
||||
border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;
|
||||
|
||||
&::after {
|
||||
border-radius: 0;
|
||||
|
@ -58,12 +58,10 @@
|
|||
|
||||
.go-back-button {
|
||||
cursor: pointer;
|
||||
margin-right: 1.4em;
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
width: 28px;
|
||||
text-align: center;
|
||||
padding: 0.6em;
|
||||
margin: -0.6em 0.6em -0.6em -0.6em;
|
||||
}
|
||||
|
||||
.jump-to-bottom-button {
|
||||
|
@ -78,7 +76,7 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3), 0px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
z-index: 10;
|
||||
transition: 0.35s all;
|
||||
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
|
@ -140,11 +138,21 @@
|
|||
}
|
||||
|
||||
.chat-view-heading {
|
||||
box-sizing: border-box;
|
||||
position: static;
|
||||
z-index: 9999;
|
||||
top: 0;
|
||||
margin-top: 0;
|
||||
border-radius: 0;
|
||||
|
||||
/* This practically overlays the panel heading color over panel background
|
||||
* color. This is needed because we allow transparent panel background and
|
||||
* it doesn't work well in this "disjointed panel header" case
|
||||
*/
|
||||
background:
|
||||
linear-gradient(to top, var(--panel), var(--panel)),
|
||||
linear-gradient(to top, var(--bg), var(--bg));
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.scrollable-message-list {
|
||||
|
|
|
@ -14,7 +14,10 @@
|
|||
class="go-back-button"
|
||||
@click="goBack"
|
||||
>
|
||||
<i class="button-icon icon-left-open" />
|
||||
<FAIcon
|
||||
size="lg"
|
||||
icon="chevron-left"
|
||||
/>
|
||||
</a>
|
||||
<div class="title text-center">
|
||||
<ChatTitle
|
||||
|
@ -58,14 +61,15 @@
|
|||
:class="{ 'visible': jumpToBottomButtonVisible }"
|
||||
@click="scrollDown({ behavior: 'smooth' })"
|
||||
>
|
||||
<i class="icon-down-open">
|
||||
<span>
|
||||
<FAIcon icon="chevron-down" />
|
||||
<div
|
||||
v-if="newMessageCount"
|
||||
class="badge badge-notification unread-chat-count unread-message-count"
|
||||
>
|
||||
{{ newMessageCount }}
|
||||
</div>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
<PostStatusForm
|
||||
:disable-subject="true"
|
||||
|
@ -76,6 +80,7 @@
|
|||
:disable-sensitivity-checkbox="true"
|
||||
:disable-submit="errorLoadingChat || !currentChat"
|
||||
:disable-preview="true"
|
||||
:optimistic-posting="true"
|
||||
:post-handler="sendMessage"
|
||||
:submit-on-enter="!mobileLayout"
|
||||
:preserve-focus="!mobileLayout"
|
||||
|
|
|
@ -24,3 +24,10 @@ export const isBottomedOut = (el, offset = 0) => {
|
|||
export const scrollableContainerHeight = (inner, header, footer) => {
|
||||
return inner.offsetHeight - header.clientHeight - footer.clientHeight
|
||||
}
|
||||
|
||||
// Returns whether or not the scrollbar is visible.
|
||||
export const isScrollable = (el) => {
|
||||
if (!el) return
|
||||
|
||||
return el.scrollHeight > el.clientHeight
|
||||
}
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
<span class="title">
|
||||
{{ $t("chats.chats") }}
|
||||
</span>
|
||||
<button @click="newChat">
|
||||
<button
|
||||
class="button-default"
|
||||
@click="newChat"
|
||||
>
|
||||
{{ $t("chats.new") }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -21,6 +21,12 @@
|
|||
/>
|
||||
</span>
|
||||
<span class="heading-right" />
|
||||
<div class="time-wrapper">
|
||||
<Timeago
|
||||
:time="chat.updated_at"
|
||||
:auto-update="60"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-preview">
|
||||
<StatusContent
|
||||
|
@ -35,12 +41,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="time-wrapper">
|
||||
<Timeago
|
||||
:time="chat.updated_at"
|
||||
:auto-update="60"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -7,6 +7,16 @@ import LinkPreview from '../link-preview/link-preview.vue'
|
|||
import StatusContent from '../status_content/status_content.vue'
|
||||
import ChatMessageDate from '../chat_message_date/chat_message_date.vue'
|
||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faTimes,
|
||||
faEllipsisH
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faTimes,
|
||||
faEllipsisH
|
||||
)
|
||||
|
||||
const ChatMessage = {
|
||||
name: 'ChatMessage',
|
||||
|
|
|
@ -24,16 +24,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.icon-ellipsis {
|
||||
.menu-icon {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover, .extra-button-popover.open & {
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
}
|
||||
|
||||
border-radius: $fallback--chatMessageRadius;
|
||||
border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius);
|
||||
}
|
||||
|
||||
.popover {
|
||||
|
@ -101,6 +98,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
.pending {
|
||||
.status-content.media-body, .created-at {
|
||||
color: var(--faint);
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
.status-content.media-body, .created-at {
|
||||
color: $fallback--cRed;
|
||||
color: var(--badgeNotification, $fallback--cRed);
|
||||
}
|
||||
}
|
||||
|
||||
.incoming {
|
||||
a {
|
||||
color: var(--chatMessageIncomingLink, $fallback--link);
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
>
|
||||
<div
|
||||
class="media status"
|
||||
:class="{ 'without-attachment': !hasAttachment }"
|
||||
:class="{ 'without-attachment': !hasAttachment, 'pending': chatViewItem.data.pending, 'error': chatViewItem.data.error }"
|
||||
style="position: relative"
|
||||
@mouseenter="hovered = true"
|
||||
@mouseleave="hovered = false"
|
||||
|
@ -53,18 +53,19 @@
|
|||
<div slot="content">
|
||||
<div class="dropdown-menu">
|
||||
<button
|
||||
class="dropdown-item dropdown-item-icon"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click="deleteMessage"
|
||||
>
|
||||
<i class="icon-cancel" /> {{ $t("chats.delete") }}
|
||||
<FAIcon icon="times" /> {{ $t("chats.delete") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
slot="trigger"
|
||||
class="button-default menu-icon"
|
||||
:title="$t('chats.more')"
|
||||
>
|
||||
<i class="icon-ellipsis" />
|
||||
<FAIcon icon="ellipsis-h" />
|
||||
</button>
|
||||
</Popover>
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import localeService from 'src/services/locale/locale.service.js'
|
||||
|
||||
export default {
|
||||
name: 'Timeago',
|
||||
props: ['date'],
|
||||
|
@ -16,7 +18,7 @@ export default {
|
|||
if (this.date.getTime() === today.getTime()) {
|
||||
return this.$t('display_date.today')
|
||||
} else {
|
||||
return this.date.toLocaleDateString('en', { day: 'numeric', month: 'long' })
|
||||
return this.date.toLocaleDateString(localeService.internalToBrowserLocale(this.$i18n.locale), { day: 'numeric', month: 'long' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
import { mapState, mapGetters } from 'vuex'
|
||||
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faSearch,
|
||||
faChevronLeft
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faSearch,
|
||||
faChevronLeft
|
||||
)
|
||||
|
||||
const chatNew = {
|
||||
components: {
|
||||
|
|
|
@ -8,9 +8,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.icon-search {
|
||||
font-size: 1.5em;
|
||||
float: right;
|
||||
.search-icon {
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
|
||||
|
@ -25,5 +23,9 @@
|
|||
|
||||
.go-back-button {
|
||||
cursor: pointer;
|
||||
width: 28px;
|
||||
text-align: center;
|
||||
padding: 0.6em;
|
||||
margin: -0.6em 0.6em -0.6em -0.6em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,18 @@
|
|||
class="go-back-button"
|
||||
@click="goBack"
|
||||
>
|
||||
<i class="button-icon icon-left-open" />
|
||||
<FAIcon
|
||||
size="lg"
|
||||
icon="chevron-left"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="input-wrap">
|
||||
<div class="input-search">
|
||||
<i class="button-icon icon-search" />
|
||||
<FAIcon
|
||||
class="search-icon fa-scale-110 fa-old-padding"
|
||||
icon="search"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
ref="search"
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faBullhorn,
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faBullhorn,
|
||||
faTimes
|
||||
)
|
||||
|
||||
const chatPanel = {
|
||||
props: [ 'floating' ],
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
>
|
||||
<div class="title">
|
||||
<span>{{ $t('shoutbox.title') }}</span>
|
||||
<i
|
||||
<FAIcon
|
||||
v-if="floating"
|
||||
class="icon-cancel"
|
||||
icon="times"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -63,7 +63,10 @@
|
|||
@click.stop.prevent="togglePanel"
|
||||
>
|
||||
<div class="title">
|
||||
<i class="icon-megaphone" />
|
||||
<FAIcon
|
||||
class="icon"
|
||||
icon="bullhorn"
|
||||
/>
|
||||
{{ $t('shoutbox.title') }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -87,7 +90,8 @@
|
|||
.chat-panel {
|
||||
.chat-heading {
|
||||
cursor: pointer;
|
||||
.icon-comment-empty {
|
||||
|
||||
.icon {
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
}
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
class="rating"
|
||||
>
|
||||
<span v-if="contrast.aaa">
|
||||
<i class="icon-thumbs-up-alt" />
|
||||
<FAIcon icon="thumbs-up" />
|
||||
</span>
|
||||
<span v-if="!contrast.aaa && contrast.aa">
|
||||
<i class="icon-adjust" />
|
||||
<FAIcon icon="adjust" />
|
||||
</span>
|
||||
<span v-if="!contrast.aaa && !contrast.aa">
|
||||
<i class="icon-attention" />
|
||||
<FAIcon icon="exclamation-triangle" />
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
|
@ -23,19 +23,32 @@
|
|||
:title="hint_18pt"
|
||||
>
|
||||
<span v-if="contrast.laaa">
|
||||
<i class="icon-thumbs-up-alt" />
|
||||
<FAIcon icon="thumbs-up" />
|
||||
</span>
|
||||
<span v-if="!contrast.laaa && contrast.laa">
|
||||
<i class="icon-adjust" />
|
||||
<FAIcon icon="adjust" />
|
||||
</span>
|
||||
<span v-if="!contrast.laaa && !contrast.laa">
|
||||
<i class="icon-attention" />
|
||||
<FAIcon icon="exclamation-triangle" />
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faAdjust,
|
||||
faExclamationTriangle,
|
||||
faThumbsUp
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faAdjust,
|
||||
faExclamationTriangle,
|
||||
faThumbsUp
|
||||
)
|
||||
|
||||
export default {
|
||||
props: {
|
||||
large: {
|
||||
|
@ -85,6 +98,7 @@ export default {
|
|||
.rating {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -10,12 +10,13 @@
|
|||
class="panel-heading conversation-heading"
|
||||
>
|
||||
<span class="title"> {{ $t('timeline.conversation') }} </span>
|
||||
<span v-if="collapsable">
|
||||
<a
|
||||
href="#"
|
||||
@click.prevent="toggleExpanded"
|
||||
>{{ $t('timeline.collapse') }}</a>
|
||||
</span>
|
||||
<button
|
||||
v-if="collapsable"
|
||||
class="button-unstyled -link"
|
||||
@click.prevent="toggleExpanded"
|
||||
>
|
||||
{{ $t('timeline.collapse') }}
|
||||
</button>
|
||||
</div>
|
||||
<status
|
||||
v-for="status in conversation"
|
||||
|
@ -57,13 +58,6 @@
|
|||
}
|
||||
|
||||
&.-expanded {
|
||||
.conversation-status {
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
border-left-color: $fallback--cRed;
|
||||
border-left-color: var(--cRed, $fallback--cRed);
|
||||
}
|
||||
|
||||
.conversation-status:last-child {
|
||||
border-bottom: none;
|
||||
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
|
||||
|
|
89
src/components/desktop_nav/desktop_nav.js
Normal file
89
src/components/desktop_nav/desktop_nav.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
import SearchBar from 'components/search_bar/search_bar.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faSignInAlt,
|
||||
faSignOutAlt,
|
||||
faHome,
|
||||
faComments,
|
||||
faBell,
|
||||
faUserPlus,
|
||||
faBullhorn,
|
||||
faSearch,
|
||||
faTachometerAlt,
|
||||
faCog,
|
||||
faInfoCircle
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faSignInAlt,
|
||||
faSignOutAlt,
|
||||
faHome,
|
||||
faComments,
|
||||
faBell,
|
||||
faUserPlus,
|
||||
faBullhorn,
|
||||
faSearch,
|
||||
faTachometerAlt,
|
||||
faCog,
|
||||
faInfoCircle
|
||||
)
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SearchBar
|
||||
},
|
||||
data: () => ({
|
||||
searchBarHidden: true,
|
||||
supportsMask: window.CSS && window.CSS.supports && (
|
||||
window.CSS.supports('mask-size', 'contain') ||
|
||||
window.CSS.supports('-webkit-mask-size', 'contain') ||
|
||||
window.CSS.supports('-moz-mask-size', 'contain') ||
|
||||
window.CSS.supports('-ms-mask-size', 'contain') ||
|
||||
window.CSS.supports('-o-mask-size', 'contain')
|
||||
)
|
||||
}),
|
||||
computed: {
|
||||
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
|
||||
logoStyle () {
|
||||
return {
|
||||
'visibility': this.enableMask ? 'hidden' : 'visible'
|
||||
}
|
||||
},
|
||||
logoMaskStyle () {
|
||||
return this.enableMask ? {
|
||||
'mask-image': `url(${this.$store.state.instance.logo})`
|
||||
} : {
|
||||
'background-color': this.enableMask ? '' : 'transparent'
|
||||
}
|
||||
},
|
||||
logoBgStyle () {
|
||||
return Object.assign({
|
||||
'margin': `${this.$store.state.instance.logoMargin} 0`,
|
||||
opacity: this.searchBarHidden ? 1 : 0
|
||||
}, this.enableMask ? {} : {
|
||||
'background-color': this.enableMask ? '' : 'transparent'
|
||||
})
|
||||
},
|
||||
logo () { return this.$store.state.instance.logo },
|
||||
sitename () { return this.$store.state.instance.name },
|
||||
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 }
|
||||
},
|
||||
methods: {
|
||||
scrollToTop () {
|
||||
window.scrollTo(0, 0)
|
||||
},
|
||||
logout () {
|
||||
this.$router.replace('/main/public')
|
||||
this.$store.dispatch('logout')
|
||||
},
|
||||
onSearchBarToggled (hidden) {
|
||||
this.searchBarHidden = hidden
|
||||
},
|
||||
openSettingsModal () {
|
||||
this.$store.dispatch('openSettingsModal')
|
||||
}
|
||||
}
|
||||
}
|
117
src/components/desktop_nav/desktop_nav.scss
Normal file
117
src/components/desktop_nav/desktop_nav.scss
Normal file
|
@ -0,0 +1,117 @@
|
|||
@import '../../_variables.scss';
|
||||
|
||||
.DesktopNav {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
|
||||
a {
|
||||
color: var(--topBarLink, $fallback--link);
|
||||
}
|
||||
|
||||
.inner-nav {
|
||||
display: grid;
|
||||
grid-template-rows: 50px;
|
||||
grid-template-columns: 2fr auto 2fr;
|
||||
grid-template-areas: "sitename logo actions";
|
||||
box-sizing: border-box;
|
||||
padding: 0 1.2em;
|
||||
margin: auto;
|
||||
max-width: 980px;
|
||||
}
|
||||
|
||||
&.-logoLeft {
|
||||
grid-template-columns: auto 2fr 2fr;
|
||||
grid-template-areas: "logo sitename actions";
|
||||
}
|
||||
|
||||
.button-default {
|
||||
&, svg {
|
||||
color: $fallback--text;
|
||||
color: var(--btnTopBarText, $fallback--text);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--btnPressedTopBar, $fallback--fg);
|
||||
color: $fallback--text;
|
||||
color: var(--btnPressedTopBarText, $fallback--text);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: $fallback--text;
|
||||
color: var(--btnDisabledTopBarText, $fallback--text);
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
color: $fallback--text;
|
||||
color: var(--btnToggledTopBarText, $fallback--text);
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--btnToggledTopBar, $fallback--fg)
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
grid-area: logo;
|
||||
position: relative;
|
||||
transition: opacity;
|
||||
transition-timing-function: ease-out;
|
||||
transition-duration: 100ms;
|
||||
|
||||
@media all and (min-width: 800px) {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mask {
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--topBarText, $fallback--fg);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
display: inline-block;
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
margin-left: 0.2em;
|
||||
width: 2em;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
|
||||
.svg-inline--fa {
|
||||
color: $fallback--link;
|
||||
color: var(--topBarLink, $fallback--link);
|
||||
}
|
||||
}
|
||||
|
||||
.sitename {
|
||||
grid-area: sitename;
|
||||
}
|
||||
|
||||
.actions {
|
||||
grid-area: actions;
|
||||
}
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
line-height: 50px;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&.right {
|
||||
justify-content: flex-end;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
81
src/components/desktop_nav/desktop_nav.vue
Normal file
81
src/components/desktop_nav/desktop_nav.vue
Normal file
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<nav
|
||||
id="nav"
|
||||
class="DesktopNav"
|
||||
:class="{ '-logoLeft': logoLeft }"
|
||||
@click="scrollToTop()"
|
||||
>
|
||||
<div class="inner-nav">
|
||||
<div class="item sitename">
|
||||
<router-link
|
||||
v-if="!hideSitename"
|
||||
class="site-name"
|
||||
:to="{ name: 'root' }"
|
||||
active-class="home"
|
||||
>
|
||||
{{ sitename }}
|
||||
</router-link>
|
||||
</div>
|
||||
<router-link
|
||||
class="logo"
|
||||
:to="{ name: 'root' }"
|
||||
:style="logoBgStyle"
|
||||
>
|
||||
<div
|
||||
class="mask"
|
||||
:style="logoMaskStyle"
|
||||
/>
|
||||
<img
|
||||
:src="logo"
|
||||
:style="logoStyle"
|
||||
>
|
||||
</router-link>
|
||||
<div class="item right actions">
|
||||
<search-bar
|
||||
v-if="currentUser || !privateMode"
|
||||
@toggled="onSearchBarToggled"
|
||||
@click.stop.native
|
||||
/>
|
||||
<button
|
||||
class="button-unstyled nav-icon"
|
||||
@click.stop="openSettingsModal"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="cog"
|
||||
:title="$t('nav.preferences')"
|
||||
/>
|
||||
</button>
|
||||
<a
|
||||
v-if="currentUser && currentUser.role === 'admin'"
|
||||
href="/pleroma/admin/#/login-pleroma"
|
||||
class="nav-icon"
|
||||
target="_blank"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="tachometer-alt"
|
||||
:title="$t('nav.administration')"
|
||||
/>
|
||||
</a>
|
||||
<button
|
||||
v-if="currentUser"
|
||||
class="button-unstyled nav-icon"
|
||||
@click.prevent="logout"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="sign-out-alt"
|
||||
:title="$t('login.logout')"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
<script src="./desktop_nav.js"></script>
|
||||
|
||||
<style src="./desktop_nav.scss" lang="scss"></style>
|
|
@ -6,7 +6,7 @@
|
|||
<ProgressButton
|
||||
v-if="muted"
|
||||
:click="unmuteDomain"
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
>
|
||||
{{ $t('domain_mute_card.unmute') }}
|
||||
<template slot="progress">
|
||||
|
@ -16,7 +16,7 @@
|
|||
<ProgressButton
|
||||
v-else
|
||||
:click="muteDomain"
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
>
|
||||
{{ $t('domain_mute_card.mute') }}
|
||||
<template slot="progress">
|
||||
|
|
|
@ -3,6 +3,15 @@ import EmojiPicker from '../emoji_picker/emoji_picker.vue'
|
|||
import { take } from 'lodash'
|
||||
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faSmileBeam
|
||||
} from '@fortawesome/free-regular-svg-icons'
|
||||
|
||||
library.add(
|
||||
faSmileBeam
|
||||
)
|
||||
|
||||
/**
|
||||
* EmojiInput - augmented inputs for emoji and autocomplete support in inputs
|
||||
* without having to give up the comfort of <input/> and <textarea/> elements
|
||||
|
@ -105,7 +114,8 @@ const EmojiInput = {
|
|||
showPicker: false,
|
||||
temporarilyHideSuggestions: false,
|
||||
keepOpen: false,
|
||||
disableClickOutside: false
|
||||
disableClickOutside: false,
|
||||
suggestions: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -115,21 +125,6 @@ const EmojiInput = {
|
|||
padEmoji () {
|
||||
return this.$store.getters.mergedConfig.padEmoji
|
||||
},
|
||||
suggestions () {
|
||||
const firstchar = this.textAtCaret.charAt(0)
|
||||
if (this.textAtCaret === firstchar) { return [] }
|
||||
const matchedSuggestions = this.suggest(this.textAtCaret)
|
||||
if (matchedSuggestions.length <= 0) {
|
||||
return []
|
||||
}
|
||||
return take(matchedSuggestions, 5)
|
||||
.map(({ imageUrl, ...rest }, index) => ({
|
||||
...rest,
|
||||
// eslint-disable-next-line camelcase
|
||||
img: imageUrl || '',
|
||||
highlighted: index === this.highlighted
|
||||
}))
|
||||
},
|
||||
showSuggestions () {
|
||||
return this.focused &&
|
||||
this.suggestions &&
|
||||
|
@ -179,6 +174,23 @@ const EmojiInput = {
|
|||
watch: {
|
||||
showSuggestions: function (newValue) {
|
||||
this.$emit('shown', newValue)
|
||||
},
|
||||
textAtCaret: async function (newWord) {
|
||||
const firstchar = newWord.charAt(0)
|
||||
this.suggestions = []
|
||||
if (newWord === firstchar) return
|
||||
const matchedSuggestions = await this.suggest(newWord)
|
||||
// Async: cancel if textAtCaret has changed during wait
|
||||
if (this.textAtCaret !== newWord) return
|
||||
if (matchedSuggestions.length <= 0) return
|
||||
this.suggestions = take(matchedSuggestions, 5)
|
||||
.map(({ imageUrl, ...rest }) => ({
|
||||
...rest,
|
||||
img: imageUrl || ''
|
||||
}))
|
||||
},
|
||||
suggestions (newValue) {
|
||||
this.$nextTick(this.resize)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
>
|
||||
<slot />
|
||||
<template v-if="enableEmojiPicker">
|
||||
<div
|
||||
<button
|
||||
v-if="!hideEmojiButton"
|
||||
class="emoji-picker-icon"
|
||||
class="button-unstyled emoji-picker-icon"
|
||||
@click.prevent="togglePicker"
|
||||
>
|
||||
<i class="icon-smile" />
|
||||
</div>
|
||||
<FAIcon :icon="['far', 'smile-beam']" />
|
||||
</button>
|
||||
<EmojiPicker
|
||||
v-if="enableEmojiPicker"
|
||||
ref="picker"
|
||||
|
@ -37,7 +37,7 @@
|
|||
v-for="(suggestion, index) in suggestions"
|
||||
:key="index"
|
||||
class="autocomplete-item"
|
||||
:class="{ highlighted: suggestion.highlighted }"
|
||||
:class="{ highlighted: index === highlighted }"
|
||||
@click.stop.prevent="onClick($event, suggestion)"
|
||||
>
|
||||
<span class="image">
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { debounce } from 'lodash'
|
||||
/**
|
||||
* suggest - generates a suggestor function to be used by emoji-input
|
||||
* data: object providing source information for specific types of suggestions:
|
||||
|
@ -11,19 +10,19 @@ import { debounce } from 'lodash'
|
|||
* doesn't support user linking you can just provide only emoji.
|
||||
*/
|
||||
|
||||
const debounceUserSearch = debounce((data, input) => {
|
||||
data.updateUsersList(input)
|
||||
}, 500)
|
||||
|
||||
export default data => input => {
|
||||
const firstChar = input[0]
|
||||
if (firstChar === ':' && data.emoji) {
|
||||
return suggestEmoji(data.emoji)(input)
|
||||
export default data => {
|
||||
const emojiCurry = suggestEmoji(data.emoji)
|
||||
const usersCurry = data.store && suggestUsers(data.store)
|
||||
return input => {
|
||||
const firstChar = input[0]
|
||||
if (firstChar === ':' && data.emoji) {
|
||||
return emojiCurry(input)
|
||||
}
|
||||
if (firstChar === '@' && usersCurry) {
|
||||
return usersCurry(input)
|
||||
}
|
||||
return []
|
||||
}
|
||||
if (firstChar === '@' && data.users) {
|
||||
return suggestUsers(data)(input)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export const suggestEmoji = emojis => input => {
|
||||
|
@ -57,50 +56,75 @@ export const suggestEmoji = emojis => input => {
|
|||
})
|
||||
}
|
||||
|
||||
export const suggestUsers = data => input => {
|
||||
const noPrefix = input.toLowerCase().substr(1)
|
||||
const users = data.users
|
||||
export const suggestUsers = ({ dispatch, state }) => {
|
||||
// Keep some persistent values in closure, most importantly for the
|
||||
// custom debounce to work. Lodash debounce does not return a promise.
|
||||
let suggestions = []
|
||||
let previousQuery = ''
|
||||
let timeout = null
|
||||
let cancelUserSearch = null
|
||||
|
||||
const newUsers = users.filter(
|
||||
user =>
|
||||
user.screen_name.toLowerCase().startsWith(noPrefix) ||
|
||||
user.name.toLowerCase().startsWith(noPrefix)
|
||||
|
||||
/* taking only 20 results so that sorting is a bit cheaper, we display
|
||||
* only 5 anyway. could be inaccurate, but we ideally we should query
|
||||
* backend anyway
|
||||
*/
|
||||
).slice(0, 20).sort((a, b) => {
|
||||
let aScore = 0
|
||||
let bScore = 0
|
||||
|
||||
// Matches on screen name (i.e. user@instance) makes a priority
|
||||
aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0
|
||||
bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0
|
||||
|
||||
// Matches on name takes second priority
|
||||
aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0
|
||||
bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0
|
||||
|
||||
const diff = (bScore - aScore) * 10
|
||||
|
||||
// Then sort alphabetically
|
||||
const nameAlphabetically = a.name > b.name ? 1 : -1
|
||||
const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1
|
||||
|
||||
return diff + nameAlphabetically + screenNameAlphabetically
|
||||
/* eslint-disable camelcase */
|
||||
}).map(({ screen_name, name, profile_image_url_original }) => ({
|
||||
displayText: screen_name,
|
||||
detailText: name,
|
||||
imageUrl: profile_image_url_original,
|
||||
replacement: '@' + screen_name + ' '
|
||||
}))
|
||||
|
||||
// BE search users to get more comprehensive results
|
||||
if (data.updateUsersList) {
|
||||
debounceUserSearch(data, noPrefix)
|
||||
const userSearch = (query) => dispatch('searchUsers', { query })
|
||||
const debounceUserSearch = (query) => {
|
||||
cancelUserSearch && cancelUserSearch()
|
||||
return new Promise((resolve, reject) => {
|
||||
timeout = setTimeout(() => {
|
||||
userSearch(query).then(resolve).catch(reject)
|
||||
}, 300)
|
||||
cancelUserSearch = () => {
|
||||
clearTimeout(timeout)
|
||||
resolve([])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return async input => {
|
||||
const noPrefix = input.toLowerCase().substr(1)
|
||||
if (previousQuery === noPrefix) return suggestions
|
||||
|
||||
suggestions = []
|
||||
previousQuery = noPrefix
|
||||
// Fetch more and wait, don't fetch if there's the 2nd @ because
|
||||
// the backend user search can't deal with it.
|
||||
// Reference semantics make it so that we get the updated data after
|
||||
// the await.
|
||||
if (!noPrefix.includes('@')) {
|
||||
await debounceUserSearch(noPrefix)
|
||||
}
|
||||
|
||||
const newSuggestions = state.users.users.filter(
|
||||
user =>
|
||||
user.screen_name.toLowerCase().startsWith(noPrefix) ||
|
||||
user.name.toLowerCase().startsWith(noPrefix)
|
||||
).slice(0, 20).sort((a, b) => {
|
||||
let aScore = 0
|
||||
let bScore = 0
|
||||
|
||||
// Matches on screen name (i.e. user@instance) makes a priority
|
||||
aScore += a.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0
|
||||
bScore += b.screen_name.toLowerCase().startsWith(noPrefix) ? 2 : 0
|
||||
|
||||
// Matches on name takes second priority
|
||||
aScore += a.name.toLowerCase().startsWith(noPrefix) ? 1 : 0
|
||||
bScore += b.name.toLowerCase().startsWith(noPrefix) ? 1 : 0
|
||||
|
||||
const diff = (bScore - aScore) * 10
|
||||
|
||||
// Then sort alphabetically
|
||||
const nameAlphabetically = a.name > b.name ? 1 : -1
|
||||
const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1
|
||||
|
||||
return diff + nameAlphabetically + screenNameAlphabetically
|
||||
/* eslint-disable camelcase */
|
||||
}).map(({ screen_name, name, profile_image_url_original }) => ({
|
||||
displayText: screen_name,
|
||||
detailText: name,
|
||||
imageUrl: profile_image_url_original,
|
||||
replacement: '@' + screen_name + ' '
|
||||
}))
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
suggestions = newSuggestions || []
|
||||
return suggestions
|
||||
}
|
||||
return newUsers
|
||||
/* eslint-enable camelcase */
|
||||
}
|
||||
|
|
|
@ -1,4 +1,16 @@
|
|||
import Checkbox from '../checkbox/checkbox.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faBoxOpen,
|
||||
faStickyNote,
|
||||
faSmileBeam
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faBoxOpen,
|
||||
faStickyNote,
|
||||
faSmileBeam
|
||||
)
|
||||
|
||||
// At widest, approximately 20 emoji are visible in a row,
|
||||
// loading 3 rows, could be overkill for narrow picker
|
||||
|
@ -177,13 +189,13 @@ const EmojiPicker = {
|
|||
{
|
||||
id: 'custom',
|
||||
text: this.$t('emoji.custom'),
|
||||
icon: 'icon-smile',
|
||||
icon: 'smile-beam',
|
||||
emojis: customEmojis
|
||||
},
|
||||
{
|
||||
id: 'standard',
|
||||
text: this.$t('emoji.unicode'),
|
||||
icon: 'icon-picture',
|
||||
icon: 'box-open',
|
||||
emojis: filterByKeyword(standardEmojis, this.keyword)
|
||||
}
|
||||
]
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
&.active {
|
||||
border-bottom: 4px solid;
|
||||
|
||||
i {
|
||||
svg {
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,10 @@
|
|||
:title="group.text"
|
||||
@click.prevent="highlight(group.id)"
|
||||
>
|
||||
<i :class="group.icon" />
|
||||
<FAIcon
|
||||
:icon="group.icon"
|
||||
fixed-width
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
|
@ -26,7 +29,10 @@
|
|||
:title="$t('emoji.stickers')"
|
||||
@click.prevent="toggleStickers"
|
||||
>
|
||||
<i class="icon-star" />
|
||||
<FAIcon
|
||||
icon="sticky-note"
|
||||
fixed-width
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
:users="accountsForEmoji[reaction.name]"
|
||||
>
|
||||
<button
|
||||
class="emoji-reaction btn btn-default"
|
||||
class="emoji-reaction btn button-default"
|
||||
:class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
|
||||
@click="emojiOnClick(reaction.name, $event)"
|
||||
@mouseenter="fetchEmojiReactionsByIfMissing()"
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
<div class="import-export-container">
|
||||
<slot name="before" />
|
||||
<button
|
||||
class="btn"
|
||||
class="btn button-default"
|
||||
@click="exportData"
|
||||
>
|
||||
{{ exportLabel }}
|
||||
</button>
|
||||
<button
|
||||
class="btn"
|
||||
class="btn button-default"
|
||||
@click="importData"
|
||||
>
|
||||
{{ importLabel }}
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faCircleNotch
|
||||
)
|
||||
|
||||
const Exporter = {
|
||||
props: {
|
||||
getContent: {
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
<template>
|
||||
<div class="exporter">
|
||||
<div v-if="processing">
|
||||
<i class="icon-spin4 animate-spin exporter-processing" />
|
||||
<FAIcon
|
||||
icon="circle-notch"
|
||||
size="lg"
|
||||
spin
|
||||
/>
|
||||
|
||||
<span>{{ processingMessage }}</span>
|
||||
</div>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
@click="process"
|
||||
>
|
||||
{{ exportButtonLabel }}
|
||||
|
@ -19,7 +24,6 @@
|
|||
<style lang="scss">
|
||||
.exporter {
|
||||
&-processing {
|
||||
font-size: 1.5em;
|
||||
margin: 0.25em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,28 @@
|
|||
import Popover from '../popover/popover.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faEllipsisH,
|
||||
faBookmark,
|
||||
faEyeSlash,
|
||||
faThumbtack,
|
||||
faShareAlt,
|
||||
faExternalLinkAlt
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
faBookmark as faBookmarkReg,
|
||||
faFlag
|
||||
} from '@fortawesome/free-regular-svg-icons'
|
||||
|
||||
library.add(
|
||||
faEllipsisH,
|
||||
faBookmark,
|
||||
faBookmarkReg,
|
||||
faEyeSlash,
|
||||
faThumbtack,
|
||||
faShareAlt,
|
||||
faExternalLinkAlt,
|
||||
faFlag
|
||||
)
|
||||
|
||||
const ExtraButtons = {
|
||||
props: [ 'status' ],
|
||||
|
@ -44,6 +68,9 @@ const ExtraButtons = {
|
|||
this.$store.dispatch('unbookmark', { id: this.status.id })
|
||||
.then(() => this.$emit('onSuccess'))
|
||||
.catch(err => this.$emit('onError', err.error.error))
|
||||
},
|
||||
reportStatus () {
|
||||
this.$store.dispatch('openUserReportingModal', { userId: this.status.user.id, statusIds: [this.status.id] })
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<template>
|
||||
<Popover
|
||||
class="ExtraButtons"
|
||||
trigger="click"
|
||||
placement="top"
|
||||
class="extra-button-popover"
|
||||
:offset="{ y: 5 }"
|
||||
:bound-to="{ x: 'container' }"
|
||||
remove-padding
|
||||
>
|
||||
<div
|
||||
slot="content"
|
||||
|
@ -12,71 +14,122 @@
|
|||
<div class="dropdown-menu">
|
||||
<button
|
||||
v-if="canMute && !status.thread_muted"
|
||||
class="dropdown-item dropdown-item-icon"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="muteConversation"
|
||||
>
|
||||
<i class="icon-eye-off" /><span>{{ $t("status.mute_conversation") }}</span>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="eye-slash"
|
||||
/><span>{{ $t("status.mute_conversation") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="canMute && status.thread_muted"
|
||||
class="dropdown-item dropdown-item-icon"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="unmuteConversation"
|
||||
>
|
||||
<i class="icon-eye-off" /><span>{{ $t("status.unmute_conversation") }}</span>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="eye-slash"
|
||||
/><span>{{ $t("status.unmute_conversation") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="!status.pinned && canPin"
|
||||
class="dropdown-item dropdown-item-icon"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="pinStatus"
|
||||
@click="close"
|
||||
>
|
||||
<i class="icon-pin" /><span>{{ $t("status.pin") }}</span>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="thumbtack"
|
||||
/><span>{{ $t("status.pin") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="status.pinned && canPin"
|
||||
class="dropdown-item dropdown-item-icon"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="unpinStatus"
|
||||
@click="close"
|
||||
>
|
||||
<i class="icon-pin" /><span>{{ $t("status.unpin") }}</span>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="thumbtack"
|
||||
/><span>{{ $t("status.unpin") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="!status.bookmarked"
|
||||
class="dropdown-item dropdown-item-icon"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="bookmarkStatus"
|
||||
@click="close"
|
||||
>
|
||||
<i class="icon-bookmark-empty" /><span>{{ $t("status.bookmark") }}</span>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
:icon="['far', 'bookmark']"
|
||||
/><span>{{ $t("status.bookmark") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="status.bookmarked"
|
||||
class="dropdown-item dropdown-item-icon"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="unbookmarkStatus"
|
||||
@click="close"
|
||||
>
|
||||
<i class="icon-bookmark" /><span>{{ $t("status.unbookmark") }}</span>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="bookmark"
|
||||
/><span>{{ $t("status.unbookmark") }}</span>
|
||||
</button>
|
||||
<button
|
||||
v-if="canDelete"
|
||||
class="dropdown-item dropdown-item-icon"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="deleteStatus"
|
||||
@click="close"
|
||||
>
|
||||
<i class="icon-cancel" /><span>{{ $t("status.delete") }}</span>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="times"
|
||||
/><span>{{ $t("status.delete") }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="dropdown-item dropdown-item-icon"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="copyLink"
|
||||
@click="close"
|
||||
>
|
||||
<i class="icon-share" /><span>{{ $t("status.copy_link") }}</span>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="share-alt"
|
||||
/><span>{{ $t("status.copy_link") }}</span>
|
||||
</button>
|
||||
<a
|
||||
v-if="!status.is_local"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
title="Source"
|
||||
:href="status.external_url"
|
||||
target="_blank"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
icon="external-link-alt"
|
||||
/><span>{{ $t("status.external_source") }}</span>
|
||||
</a>
|
||||
<button
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click.prevent="reportStatus"
|
||||
@click="close"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
:icon="['far', 'flag']"
|
||||
/><span>{{ $t("user_card.report") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<i
|
||||
<span
|
||||
slot="trigger"
|
||||
class="icon-ellipsis button-icon"
|
||||
/>
|
||||
class="popover-trigger"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="ellipsis-h"
|
||||
/>
|
||||
</span>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
|
@ -85,13 +138,16 @@
|
|||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.icon-ellipsis {
|
||||
cursor: pointer;
|
||||
.ExtraButtons {
|
||||
.popover-trigger {
|
||||
position: static;
|
||||
padding: 10px;
|
||||
margin: -10px;
|
||||
|
||||
&:hover,
|
||||
.extra-button-popover.open & {
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
&:hover .svg-inline--fa {
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
import { mapGetters } from 'vuex'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faStar } from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
faStar as faStarRegular
|
||||
} from '@fortawesome/free-regular-svg-icons'
|
||||
|
||||
library.add(
|
||||
faStar,
|
||||
faStarRegular
|
||||
)
|
||||
|
||||
const FavoriteButton = {
|
||||
props: ['status', 'loggedIn'],
|
||||
|
@ -21,13 +31,6 @@ const FavoriteButton = {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
classes () {
|
||||
return {
|
||||
'icon-star-empty': !this.status.favorited,
|
||||
'icon-star': this.status.favorited,
|
||||
'animate-spin': this.animated
|
||||
}
|
||||
},
|
||||
...mapGetters(['mergedConfig'])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,31 @@
|
|||
<template>
|
||||
<div v-if="loggedIn">
|
||||
<i
|
||||
:class="classes"
|
||||
class="button-icon favorite-button fav-active"
|
||||
<div class="FavoriteButton">
|
||||
<button
|
||||
v-if="loggedIn"
|
||||
class="button-unstyled interactive"
|
||||
:class="status.favorited && '-favorited'"
|
||||
:title="$t('tool_tip.favorite')"
|
||||
@click.prevent="favorite()"
|
||||
/>
|
||||
<span v-if="!mergedConfig.hidePostStats && status.fave_num > 0">{{ status.fave_num }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<i
|
||||
:class="classes"
|
||||
class="button-icon favorite-button"
|
||||
:title="$t('tool_tip.favorite')"
|
||||
/>
|
||||
<span v-if="!mergedConfig.hidePostStats && status.fave_num > 0">{{ status.fave_num }}</span>
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
:icon="[status.favorited ? 'fas' : 'far', 'star']"
|
||||
:spin="animated"
|
||||
/>
|
||||
</button>
|
||||
<span v-else>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
:title="$t('tool_tip.favorite')"
|
||||
:icon="['far', 'star']"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="!mergedConfig.hidePostStats && status.fave_num > 0"
|
||||
class="action-counter"
|
||||
>
|
||||
{{ status.fave_num }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -23,18 +34,29 @@
|
|||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.fav-active {
|
||||
cursor: pointer;
|
||||
animation-duration: 0.6s;
|
||||
.FavoriteButton {
|
||||
display: flex;
|
||||
|
||||
&:hover {
|
||||
color: $fallback--cOrange;
|
||||
color: var(--cOrange, $fallback--cOrange);
|
||||
> :first-child {
|
||||
padding: 10px;
|
||||
margin: -10px -8px -10px -10px;
|
||||
}
|
||||
|
||||
.action-counter {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.interactive {
|
||||
.svg-inline--fa {
|
||||
animation-duration: 0.6s;
|
||||
}
|
||||
|
||||
&:hover .svg-inline--fa,
|
||||
&.-favorited .svg-inline--fa {
|
||||
color: $fallback--cOrange;
|
||||
color: var(--cOrange, $fallback--cOrange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.favorite-button.icon-star {
|
||||
color: $fallback--cOrange;
|
||||
color: var(--cOrange, $fallback--cOrange);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
|
||||
|
||||
const FeaturesPanel = {
|
||||
computed: {
|
||||
chat: function () { return this.$store.state.instance.chatAvailable },
|
||||
|
@ -6,7 +8,8 @@ const FeaturesPanel = {
|
|||
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
|
||||
mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable },
|
||||
minimalScopesMode: function () { return this.$store.state.instance.minimalScopesMode },
|
||||
textlimit: function () { return this.$store.state.instance.textlimit }
|
||||
textlimit: function () { return this.$store.state.instance.textlimit },
|
||||
uploadlimit: function () { return fileSizeFormatService.fileSizeFormat(this.$store.state.instance.uploadlimit) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
</li>
|
||||
<li>{{ $t('features_panel.scope_options') }}</li>
|
||||
<li>{{ $t('features_panel.text_limit') }} = {{ textlimit }}</li>
|
||||
<li>{{ $t('features_panel.upload_limit') }} = {{ uploadlimit.num }} {{ $t('upload.file_size_units.' + uploadlimit.unit) }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<button
|
||||
class="btn btn-default follow-button"
|
||||
class="btn button-default follow-button"
|
||||
:class="{ toggled: isPressed }"
|
||||
:disabled="inProgress"
|
||||
:title="title"
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
<basic-user-card :user="user">
|
||||
<div class="follow-request-card-content-container">
|
||||
<button
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
@click="approveUser"
|
||||
>
|
||||
{{ $t('user_card.approve') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
@click="denyUser"
|
||||
>
|
||||
{{ $t('user_card.deny') }}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
import { set } from 'vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faChevronDown
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faChevronDown
|
||||
)
|
||||
|
||||
export default {
|
||||
props: [
|
||||
|
|
|
@ -41,7 +41,10 @@
|
|||
{{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
|
||||
</option>
|
||||
</select>
|
||||
<i class="icon-down-open" />
|
||||
<FAIcon
|
||||
class="select-down-icon"
|
||||
icon="chevron-down"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
v-if="isCustom"
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faTimes
|
||||
)
|
||||
|
||||
const GlobalNoticeList = {
|
||||
computed: {
|
||||
|
|
|
@ -9,10 +9,15 @@
|
|||
<div class="notice-message">
|
||||
{{ $t(notice.messageKey, notice.messageArgs) }}
|
||||
</div>
|
||||
<i
|
||||
class="button-icon icon-cancel"
|
||||
<button
|
||||
class="button-unstyled close-notice"
|
||||
@click="closeNotice(notice)"
|
||||
/>
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="times"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -53,7 +58,7 @@
|
|||
.global-error {
|
||||
background-color: var(--alertPopupError, $fallback--cRed);
|
||||
color: var(--alertPopupErrorText, $fallback--text);
|
||||
i {
|
||||
.svg-inline--fa {
|
||||
color: var(--alertPopupErrorText, $fallback--text);
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +66,7 @@
|
|||
.global-warning {
|
||||
background-color: var(--alertPopupWarning, $fallback--cOrange);
|
||||
color: var(--alertPopupWarningText, $fallback--text);
|
||||
i {
|
||||
.svg-inline--fa {
|
||||
color: var(--alertPopupWarningText, $fallback--text);
|
||||
}
|
||||
}
|
||||
|
@ -69,9 +74,16 @@
|
|||
.global-info {
|
||||
background-color: var(--alertPopupNeutral, $fallback--fg);
|
||||
color: var(--alertPopupNeutralText, $fallback--text);
|
||||
i {
|
||||
.svg-inline--fa {
|
||||
color: var(--alertPopupNeutralText, $fallback--text);
|
||||
}
|
||||
}
|
||||
|
||||
.close-notice {
|
||||
padding-right: 0.2em;
|
||||
.svg-inline--fa:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import Cropper from 'cropperjs'
|
||||
import 'cropperjs/dist/cropper.css'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faCircleNotch
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faCircleNotch
|
||||
)
|
||||
|
||||
const ImageCropper = {
|
||||
props: {
|
||||
|
@ -43,8 +51,7 @@ const ImageCropper = {
|
|||
cropper: undefined,
|
||||
dataUrl: undefined,
|
||||
filename: undefined,
|
||||
submitting: false,
|
||||
submitError: null
|
||||
submitting: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -56,9 +63,6 @@ const ImageCropper = {
|
|||
},
|
||||
cancelText () {
|
||||
return this.cancelButtonLabel || this.$t('image_cropper.cancel')
|
||||
},
|
||||
submitErrorMsg () {
|
||||
return this.submitError && this.submitError instanceof Error ? this.submitError.toString() : this.submitError
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -72,12 +76,8 @@ const ImageCropper = {
|
|||
},
|
||||
submit (cropping = true) {
|
||||
this.submitting = true
|
||||
this.avatarUploadError = null
|
||||
this.submitHandler(cropping && this.cropper, this.file)
|
||||
.then(() => this.destroy())
|
||||
.catch((err) => {
|
||||
this.submitError = err
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
|
@ -103,9 +103,6 @@ const ImageCropper = {
|
|||
reader.readAsDataURL(this.file)
|
||||
this.$emit('changed', this.file, reader)
|
||||
}
|
||||
},
|
||||
clearError () {
|
||||
this.submitError = null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
@ -11,39 +11,30 @@
|
|||
</div>
|
||||
<div class="image-cropper-buttons-wrapper">
|
||||
<button
|
||||
class="btn"
|
||||
class="button-default btn"
|
||||
type="button"
|
||||
:disabled="submitting"
|
||||
@click="submit()"
|
||||
v-text="saveText"
|
||||
/>
|
||||
<button
|
||||
class="btn"
|
||||
class="button-default btn"
|
||||
type="button"
|
||||
:disabled="submitting"
|
||||
@click="destroy"
|
||||
v-text="cancelText"
|
||||
/>
|
||||
<button
|
||||
class="btn"
|
||||
class="button-default btn"
|
||||
type="button"
|
||||
:disabled="submitting"
|
||||
@click="submit(false)"
|
||||
v-text="saveWithoutCroppingText"
|
||||
/>
|
||||
<i
|
||||
<FAIcon
|
||||
v-if="submitting"
|
||||
class="icon-spin4 animate-spin"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="submitError"
|
||||
class="alert error"
|
||||
>
|
||||
{{ submitErrorMsg }}
|
||||
<i
|
||||
class="button-icon icon-cancel"
|
||||
@click="clearError"
|
||||
spin
|
||||
icon="circle-notch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faCircleNotch,
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faCircleNotch,
|
||||
faTimes
|
||||
)
|
||||
|
||||
const Importer = {
|
||||
props: {
|
||||
submitHandler: {
|
||||
|
|
|
@ -7,27 +7,29 @@
|
|||
@change="change"
|
||||
>
|
||||
</form>
|
||||
<i
|
||||
<FAIcon
|
||||
v-if="submitting"
|
||||
class="icon-spin4 animate-spin importer-uploading"
|
||||
class="importer-uploading"
|
||||
spin
|
||||
icon="circle-notch"
|
||||
/>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
@click="submit"
|
||||
>
|
||||
{{ submitButtonLabel }}
|
||||
</button>
|
||||
<div v-if="success">
|
||||
<i
|
||||
class="icon-cross"
|
||||
<FAIcon
|
||||
icon="times"
|
||||
@click="dismiss"
|
||||
/>
|
||||
<p>{{ successMessage }}</p>
|
||||
</div>
|
||||
<div v-else-if="error">
|
||||
<i
|
||||
class="icon-cross"
|
||||
<FAIcon
|
||||
icon="times"
|
||||
@click="dismiss"
|
||||
/>
|
||||
<p>{{ errorMessage }}</p>
|
||||
|
|
|
@ -12,31 +12,39 @@
|
|||
v-model="language"
|
||||
>
|
||||
<option
|
||||
v-for="(langCode, i) in languageCodes"
|
||||
:key="langCode"
|
||||
:value="langCode"
|
||||
v-for="lang in languages"
|
||||
:key="lang.code"
|
||||
:value="lang.code"
|
||||
>
|
||||
{{ languageNames[i] }}
|
||||
{{ lang.name }}
|
||||
</option>
|
||||
</select>
|
||||
<i class="icon-down-open" />
|
||||
<FAIcon
|
||||
class="select-down-icon"
|
||||
icon="chevron-down"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import languagesObject from '../../i18n/messages'
|
||||
import localeService from '../../services/locale/locale.service.js'
|
||||
import ISO6391 from 'iso-639-1'
|
||||
import _ from 'lodash'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faChevronDown
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faChevronDown
|
||||
)
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
languageCodes () {
|
||||
return languagesObject.languages
|
||||
},
|
||||
|
||||
languageNames () {
|
||||
return _.map(this.languageCodes, this.getLanguageName)
|
||||
languages () {
|
||||
return _.map(languagesObject.languages, (code) => ({ code: code, name: this.getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name))
|
||||
},
|
||||
|
||||
language: {
|
||||
|
@ -50,11 +58,13 @@ export default {
|
|||
methods: {
|
||||
getLanguageName (code) {
|
||||
const specialLanguageNames = {
|
||||
'ja': 'Japanese (日本語)',
|
||||
'ja_easy': 'Japanese (やさしいにほんご)',
|
||||
'zh': 'Chinese (简体中文)'
|
||||
'ja_easy': 'やさしいにほんご',
|
||||
'zh': '简体中文',
|
||||
'zh_Hant': '繁體中文'
|
||||
}
|
||||
return specialLanguageNames[code] || ISO6391.getName(code)
|
||||
const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code)
|
||||
const browserLocale = localeService.internalToBrowserLocale(code)
|
||||
return languageName.charAt(0).toLocaleUpperCase(browserLocale) + languageName.slice(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { mapGetters } from 'vuex'
|
||||
|
||||
const LinkPreview = {
|
||||
name: 'LinkPreview',
|
||||
props: [
|
||||
|
@ -15,11 +17,20 @@ const LinkPreview = {
|
|||
// Currently BE shoudn't give cards if tagged NSFW, this is a bit paranoid
|
||||
// as it makes sure to hide the image if somehow NSFW tagged preview can
|
||||
// exist.
|
||||
return this.card.image && !this.nsfw && this.size !== 'hide'
|
||||
return this.card.image && !this.censored && this.size !== 'hide'
|
||||
},
|
||||
censored () {
|
||||
return this.nsfw && this.hideNsfwConfig
|
||||
},
|
||||
useDescription () {
|
||||
return this.card.description && /\S/.test(this.card.description)
|
||||
}
|
||||
},
|
||||
hideNsfwConfig () {
|
||||
return this.mergedConfig.hideNsfw
|
||||
},
|
||||
...mapGetters([
|
||||
'mergedConfig'
|
||||
])
|
||||
},
|
||||
created () {
|
||||
if (this.useImage) {
|
||||
|
|
|
@ -9,12 +9,17 @@
|
|||
<div
|
||||
v-if="useImage && imageLoaded"
|
||||
class="card-image"
|
||||
:class="{ 'small-image': size === 'small' }"
|
||||
>
|
||||
<img :src="card.image">
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<span class="card-host faint">{{ card.provider_name }}</span>
|
||||
<span class="card-host faint">
|
||||
<span
|
||||
v-if="censored"
|
||||
class="nsfw-alert alert warning"
|
||||
>{{ $t('status.nsfw') }}</span>
|
||||
{{ card.provider_name }}
|
||||
</span>
|
||||
<h4 class="card-title">{{ card.title }}</h4>
|
||||
<p
|
||||
v-if="useDescription"
|
||||
|
@ -50,10 +55,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.small-image {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
max-height: 100%;
|
||||
margin: 0.5em;
|
||||
|
@ -76,6 +77,10 @@
|
|||
max-height: calc(1.2em * 3 - 1px);
|
||||
}
|
||||
|
||||
.nsfw-alert {
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
border-style: solid;
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
|
||||
import oauthApi from '../../services/new_api/oauth.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faTimes
|
||||
)
|
||||
|
||||
const LoginForm = {
|
||||
data: () => ({
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<button
|
||||
:disabled="loggingIn"
|
||||
type="submit"
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
>
|
||||
{{ $t('login.login') }}
|
||||
</button>
|
||||
|
@ -76,8 +76,9 @@
|
|||
>
|
||||
<div class="alert error">
|
||||
{{ error }}
|
||||
<i
|
||||
class="button-icon icon-cancel"
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="times"
|
||||
@click="clearError"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,16 @@ import VideoAttachment from '../video_attachment/video_attachment.vue'
|
|||
import Modal from '../modal/modal.vue'
|
||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||
import GestureService from '../../services/gesture_service/gesture_service'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faChevronLeft,
|
||||
faChevronRight
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faChevronLeft,
|
||||
faChevronRight
|
||||
)
|
||||
|
||||
const MediaModal = {
|
||||
components: {
|
||||
|
|
|
@ -34,7 +34,10 @@
|
|||
class="modal-view-button-arrow modal-view-button-arrow--prev"
|
||||
@click.stop.prevent="goPrev"
|
||||
>
|
||||
<i class="icon-left-open arrow-icon" />
|
||||
<FAIcon
|
||||
class="arrow-icon"
|
||||
icon="chevron-left"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="canNavigate"
|
||||
|
@ -42,7 +45,10 @@
|
|||
class="modal-view-button-arrow modal-view-button-arrow--next"
|
||||
@click.stop.prevent="goNext"
|
||||
>
|
||||
<i class="icon-right-open arrow-icon" />
|
||||
<FAIcon
|
||||
class="arrow-icon"
|
||||
icon="chevron-right"
|
||||
/>
|
||||
</button>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
import statusPosterService from '../../services/status_poster/status_poster.service.js'
|
||||
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faUpload, faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faUpload,
|
||||
faCircleNotch
|
||||
)
|
||||
|
||||
const mediaUpload = {
|
||||
data () {
|
||||
return {
|
||||
|
|
|
@ -1,30 +1,29 @@
|
|||
<template>
|
||||
<div
|
||||
<label
|
||||
class="media-upload"
|
||||
:class="{ disabled: disabled }"
|
||||
:title="$t('tool_tip.media_upload')"
|
||||
>
|
||||
<label
|
||||
class="label"
|
||||
:title="$t('tool_tip.media_upload')"
|
||||
<FAIcon
|
||||
v-if="uploading"
|
||||
class="progress-icon"
|
||||
icon="circle-notch"
|
||||
spin
|
||||
/>
|
||||
<FAIcon
|
||||
v-if="!uploading"
|
||||
class="new-icon"
|
||||
icon="upload"
|
||||
/>
|
||||
<input
|
||||
v-if="uploadReady"
|
||||
:disabled="disabled"
|
||||
type="file"
|
||||
style="position: fixed; top: -100em"
|
||||
multiple="true"
|
||||
@change="change"
|
||||
>
|
||||
<i
|
||||
v-if="uploading"
|
||||
class="progress-icon icon-spin4 animate-spin"
|
||||
/>
|
||||
<i
|
||||
v-if="!uploading"
|
||||
class="new-icon icon-upload"
|
||||
/>
|
||||
<input
|
||||
v-if="uploadReady"
|
||||
:disabled="disabled"
|
||||
type="file"
|
||||
style="position: fixed; top: -100em"
|
||||
multiple="true"
|
||||
@change="change"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script src="./media_upload.js" ></script>
|
||||
|
@ -33,22 +32,6 @@
|
|||
@import '../../_variables.scss';
|
||||
|
||||
.media-upload {
|
||||
.label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.new-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.progress-icon {
|
||||
display: inline-block;
|
||||
line-height: 0;
|
||||
&::before {
|
||||
/* Overriding fontello to achieve the perfect speeeen */
|
||||
margin: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import mfaApi from '../../services/new_api/mfa.js'
|
||||
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faTimes
|
||||
)
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
|
|
|
@ -23,23 +23,23 @@
|
|||
<div class="form-group">
|
||||
<div class="login-bottom">
|
||||
<div>
|
||||
<a
|
||||
href="#"
|
||||
<button
|
||||
class="button-unstyled -link"
|
||||
@click.prevent="requireTOTP"
|
||||
>
|
||||
{{ $t('login.enter_two_factor_code') }}
|
||||
</a>
|
||||
</button>
|
||||
<br>
|
||||
<a
|
||||
href="#"
|
||||
<button
|
||||
class="button-unstyled -link"
|
||||
@click.prevent="abortMFA"
|
||||
>
|
||||
{{ $t('general.cancel') }}
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
>
|
||||
{{ $t('general.verify') }}
|
||||
</button>
|
||||
|
@ -54,8 +54,9 @@
|
|||
>
|
||||
<div class="alert error">
|
||||
{{ error }}
|
||||
<i
|
||||
class="button-icon icon-cancel"
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="times"
|
||||
@click="clearError"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
import mfaApi from '../../services/new_api/mfa.js'
|
||||
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faTimes
|
||||
)
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
code: null,
|
||||
|
|
|
@ -25,23 +25,23 @@
|
|||
<div class="form-group">
|
||||
<div class="login-bottom">
|
||||
<div>
|
||||
<a
|
||||
href="#"
|
||||
<button
|
||||
class="button-unstyled -link"
|
||||
@click.prevent="requireRecovery"
|
||||
>
|
||||
{{ $t('login.enter_recovery_code') }}
|
||||
</a>
|
||||
</button>
|
||||
<br>
|
||||
<a
|
||||
href="#"
|
||||
<button
|
||||
class="button-unstyled -link"
|
||||
@click.prevent="abortMFA"
|
||||
>
|
||||
{{ $t('general.cancel') }}
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
>
|
||||
{{ $t('general.verify') }}
|
||||
</button>
|
||||
|
@ -56,8 +56,10 @@
|
|||
>
|
||||
<div class="alert error">
|
||||
{{ error }}
|
||||
<i
|
||||
class="button-icon icon-cancel"
|
||||
<FAIcon
|
||||
size="lg"
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="times"
|
||||
@click="clearError"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -3,6 +3,18 @@ import Notifications from '../notifications/notifications.vue'
|
|||
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
|
||||
import GestureService from '../../services/gesture_service/gesture_service'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faTimes,
|
||||
faBell,
|
||||
faBars
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faTimes,
|
||||
faBell,
|
||||
faBars
|
||||
)
|
||||
|
||||
const MobileNav = {
|
||||
components: {
|
||||
|
|
|
@ -1,49 +1,51 @@
|
|||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="MobileNav"
|
||||
>
|
||||
<nav
|
||||
id="nav"
|
||||
class="nav-bar container"
|
||||
class="mobile-nav"
|
||||
:class="{ 'mobile-hidden': isChat }"
|
||||
@click="scrollToTop()"
|
||||
>
|
||||
<div
|
||||
class="mobile-inner-nav"
|
||||
@click="scrollToTop()"
|
||||
>
|
||||
<div class="item">
|
||||
<a
|
||||
href="#"
|
||||
class="mobile-nav-button"
|
||||
@click.stop.prevent="toggleMobileSidebar()"
|
||||
>
|
||||
<i class="button-icon icon-menu" />
|
||||
<div
|
||||
v-if="unreadChatCount"
|
||||
class="alert-dot"
|
||||
/>
|
||||
</a>
|
||||
<router-link
|
||||
v-if="!hideSitename"
|
||||
class="site-name"
|
||||
:to="{ name: 'root' }"
|
||||
active-class="home"
|
||||
>
|
||||
{{ sitename }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="item right">
|
||||
<a
|
||||
v-if="currentUser"
|
||||
class="mobile-nav-button"
|
||||
href="#"
|
||||
@click.stop.prevent="openMobileNotifications()"
|
||||
>
|
||||
<i class="button-icon icon-bell-alt" />
|
||||
<div
|
||||
v-if="unseenNotificationsCount"
|
||||
class="alert-dot"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<button
|
||||
class="button-unstyled mobile-nav-button"
|
||||
@click.stop.prevent="toggleMobileSidebar()"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="bars"
|
||||
/>
|
||||
<div
|
||||
v-if="unreadChatCount"
|
||||
class="alert-dot"
|
||||
/>
|
||||
</button>
|
||||
<router-link
|
||||
v-if="!hideSitename"
|
||||
class="site-name"
|
||||
:to="{ name: 'root' }"
|
||||
active-class="home"
|
||||
>
|
||||
{{ sitename }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="item right">
|
||||
<button
|
||||
v-if="currentUser"
|
||||
class="button-unstyled mobile-nav-button"
|
||||
@click.stop.prevent="openMobileNotifications()"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="bell"
|
||||
/>
|
||||
<div
|
||||
v-if="unseenNotificationsCount"
|
||||
class="alert-dot"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div
|
||||
|
@ -59,7 +61,10 @@
|
|||
class="mobile-nav-button"
|
||||
@click.stop.prevent="closeMobileNotifications()"
|
||||
>
|
||||
<i class="button-icon icon-cancel" />
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="times"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
|
@ -84,101 +89,124 @@
|
|||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.mobile-inner-nav {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mobile-nav-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.alert-dot {
|
||||
border-radius: 100%;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
position: absolute;
|
||||
left: calc(50% - 4px);
|
||||
top: calc(50% - 4px);
|
||||
margin-left: 6px;
|
||||
margin-top: -6px;
|
||||
background-color: $fallback--cRed;
|
||||
background-color: var(--badgeNotification, $fallback--cRed);
|
||||
}
|
||||
|
||||
.mobile-notifications-drawer {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow-x: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-shadow: 1px 1px 4px rgba(0,0,0,.6);
|
||||
box-shadow: var(--panelShadow);
|
||||
transition-property: transform;
|
||||
transition-duration: 0.25s;
|
||||
transform: translateX(0);
|
||||
z-index: 1001;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
&.closed {
|
||||
transform: translateX(100%);
|
||||
.MobileNav {
|
||||
.mobile-nav {
|
||||
display: grid;
|
||||
line-height: 50px;
|
||||
height: 50px;
|
||||
grid-template-rows: 50px;
|
||||
grid-template-columns: 2fr auto;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-notifications-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
position: absolute;
|
||||
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: var(--topBarShadow);
|
||||
|
||||
.title {
|
||||
font-size: 1.3em;
|
||||
margin-left: 0.6em;
|
||||
.mobile-inner-nav {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-notifications {
|
||||
margin-top: 50px;
|
||||
width: 100vw;
|
||||
height: calc(100vh - 50px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
.mobile-nav-button {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
padding: 0 1em;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
background-color: $fallback--bg;
|
||||
background-color: var(--bg, $fallback--bg);
|
||||
.site-name {
|
||||
padding: 0 .3em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.notifications {
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
.panel {
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
.item {
|
||||
/* moslty just to get rid of extra whitespaces */
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.alert-dot {
|
||||
border-radius: 100%;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
position: absolute;
|
||||
left: calc(50% - 4px);
|
||||
top: calc(50% - 4px);
|
||||
margin-left: 6px;
|
||||
margin-top: -6px;
|
||||
background-color: $fallback--cRed;
|
||||
background-color: var(--badgeNotification, $fallback--cRed);
|
||||
}
|
||||
|
||||
.mobile-notifications-drawer {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow-x: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-shadow: 1px 1px 4px rgba(0,0,0,.6);
|
||||
box-shadow: var(--panelShadow);
|
||||
transition-property: transform;
|
||||
transition-duration: 0.25s;
|
||||
transform: translateX(0);
|
||||
z-index: 1001;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
&.closed {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
.panel:after {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.mobile-notifications-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
position: absolute;
|
||||
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: var(--topBarShadow);
|
||||
|
||||
.title {
|
||||
font-size: 1.3em;
|
||||
margin-left: 0.6em;
|
||||
}
|
||||
.panel .panel-heading {
|
||||
}
|
||||
|
||||
.mobile-notifications {
|
||||
margin-top: 50px;
|
||||
width: 100vw;
|
||||
height: calc(100vh - 50px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
background-color: $fallback--bg;
|
||||
background-color: var(--bg, $fallback--bg);
|
||||
|
||||
.notifications {
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
.panel {
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
.panel:after {
|
||||
border-radius: 0;
|
||||
}
|
||||
.panel .panel-heading {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
import { debounce } from 'lodash'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faPen
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faPen
|
||||
)
|
||||
|
||||
const HIDDEN_FOR_PAGES = new Set([
|
||||
'chats',
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<div v-if="isLoggedIn">
|
||||
<button
|
||||
class="new-status-button"
|
||||
class="button-default new-status-button"
|
||||
:class="{ 'hidden': isHidden }"
|
||||
@click="openPostForm"
|
||||
>
|
||||
<i class="icon-edit" />
|
||||
<FAIcon icon="pen" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -39,7 +39,7 @@
|
|||
transform: translateY(150%);
|
||||
}
|
||||
|
||||
i {
|
||||
svg {
|
||||
font-size: 1.5em;
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
|
|
|
@ -12,13 +12,13 @@
|
|||
<div class="dropdown-menu">
|
||||
<span v-if="user.is_local">
|
||||
<button
|
||||
class="dropdown-item"
|
||||
class="button-default dropdown-item"
|
||||
@click="toggleRight("admin")"
|
||||
>
|
||||
{{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }}
|
||||
</button>
|
||||
<button
|
||||
class="dropdown-item"
|
||||
class="button-default dropdown-item"
|
||||
@click="toggleRight("moderator")"
|
||||
>
|
||||
{{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }}
|
||||
|
@ -29,13 +29,13 @@
|
|||
/>
|
||||
</span>
|
||||
<button
|
||||
class="dropdown-item"
|
||||
class="button-default dropdown-item"
|
||||
@click="toggleActivationStatus()"
|
||||
>
|
||||
{{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }}
|
||||
</button>
|
||||
<button
|
||||
class="dropdown-item"
|
||||
class="button-default dropdown-item"
|
||||
@click="deleteUserDialog(true)"
|
||||
>
|
||||
{{ $t('user_card.admin_menu.delete_account') }}
|
||||
|
@ -47,7 +47,7 @@
|
|||
/>
|
||||
<span v-if="hasTagPolicy">
|
||||
<button
|
||||
class="dropdown-item"
|
||||
class="button-default dropdown-item"
|
||||
@click="toggleTag(tags.FORCE_NSFW)"
|
||||
>
|
||||
{{ $t('user_card.admin_menu.force_nsfw') }}
|
||||
|
@ -57,7 +57,7 @@
|
|||
/>
|
||||
</button>
|
||||
<button
|
||||
class="dropdown-item"
|
||||
class="button-default dropdown-item"
|
||||
@click="toggleTag(tags.STRIP_MEDIA)"
|
||||
>
|
||||
{{ $t('user_card.admin_menu.strip_media') }}
|
||||
|
@ -67,7 +67,7 @@
|
|||
/>
|
||||
</button>
|
||||
<button
|
||||
class="dropdown-item"
|
||||
class="button-default dropdown-item"
|
||||
@click="toggleTag(tags.FORCE_UNLISTED)"
|
||||
>
|
||||
{{ $t('user_card.admin_menu.force_unlisted') }}
|
||||
|
@ -77,7 +77,7 @@
|
|||
/>
|
||||
</button>
|
||||
<button
|
||||
class="dropdown-item"
|
||||
class="button-default dropdown-item"
|
||||
@click="toggleTag(tags.SANDBOX)"
|
||||
>
|
||||
{{ $t('user_card.admin_menu.sandbox') }}
|
||||
|
@ -88,7 +88,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-if="user.is_local"
|
||||
class="dropdown-item"
|
||||
class="button-default dropdown-item"
|
||||
@click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)"
|
||||
>
|
||||
{{ $t('user_card.admin_menu.disable_remote_subscription') }}
|
||||
|
@ -99,7 +99,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-if="user.is_local"
|
||||
class="dropdown-item"
|
||||
class="button-default dropdown-item"
|
||||
@click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)"
|
||||
>
|
||||
{{ $t('user_card.admin_menu.disable_any_subscription') }}
|
||||
|
@ -110,7 +110,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-if="user.is_local"
|
||||
class="dropdown-item"
|
||||
class="button-default dropdown-item"
|
||||
@click="toggleTag(tags.QUARANTINE)"
|
||||
>
|
||||
{{ $t('user_card.admin_menu.quarantine') }}
|
||||
|
@ -124,7 +124,7 @@
|
|||
</div>
|
||||
<button
|
||||
slot="trigger"
|
||||
class="btn btn-default btn-block"
|
||||
class="btn button-default btn-block"
|
||||
:class="{ toggled }"
|
||||
>
|
||||
{{ $t('user_card.admin_menu.moderation') }}
|
||||
|
@ -141,13 +141,13 @@
|
|||
<p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p>
|
||||
<template slot="footer">
|
||||
<button
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
@click="deleteUserDialog(false)"
|
||||
>
|
||||
{{ $t('general.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-default danger"
|
||||
class="btn button-default danger"
|
||||
@click="deleteUser()"
|
||||
>
|
||||
{{ $t('user_card.admin_menu.delete_user') }}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="mute-card-content-container">
|
||||
<button
|
||||
v-if="muted"
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
:disabled="progress"
|
||||
@click="unmuteUser"
|
||||
>
|
||||
|
@ -16,7 +16,7 @@
|
|||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
:disabled="progress"
|
||||
@click="muteUser"
|
||||
>
|
||||
|
|
|
@ -1,6 +1,29 @@
|
|||
import { timelineNames } from '../timeline_menu/timeline_menu.js'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faUsers,
|
||||
faGlobe,
|
||||
faBookmark,
|
||||
faEnvelope,
|
||||
faHome,
|
||||
faComments,
|
||||
faBell,
|
||||
faInfoCircle
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faUsers,
|
||||
faGlobe,
|
||||
faBookmark,
|
||||
faEnvelope,
|
||||
faHome,
|
||||
faComments,
|
||||
faBell,
|
||||
faInfoCircle
|
||||
)
|
||||
|
||||
const NavPanel = {
|
||||
created () {
|
||||
if (this.currentUser && this.currentUser.locked) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="nav-panel">
|
||||
<div class="NavPanel">
|
||||
<div class="panel panel-default">
|
||||
<ul>
|
||||
<li v-if="currentUser || !privateMode">
|
||||
|
@ -7,31 +7,47 @@
|
|||
:to="{ name: timelinesRoute }"
|
||||
:class="onTimelineRoute && 'router-link-active'"
|
||||
>
|
||||
<i class="button-icon icon-home-2" />{{ $t("nav.timelines") }}
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110"
|
||||
icon="home"
|
||||
/>{{ $t("nav.timelines") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser">
|
||||
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
|
||||
<i class="button-icon icon-bell-alt" />{{ $t("nav.interactions") }}
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110"
|
||||
icon="bell"
|
||||
/>{{ $t("nav.interactions") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser && pleromaChatMessagesAvailable">
|
||||
<router-link :to="{ name: 'chats', params: { username: currentUser.screen_name } }">
|
||||
<div
|
||||
v-if="unreadChatCount"
|
||||
class="badge badge-notification unread-chat-count"
|
||||
class="badge badge-notification"
|
||||
>
|
||||
{{ unreadChatCount }}
|
||||
</div>
|
||||
<i class="button-icon icon-chat" />{{ $t("nav.chats") }}
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110"
|
||||
icon="comments"
|
||||
/>{{ $t("nav.chats") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="currentUser && currentUser.locked">
|
||||
<router-link :to="{ name: 'friend-requests' }">
|
||||
<i class="button-icon icon-user-plus" />{{ $t("nav.friend_requests") }}
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110"
|
||||
icon="user-plus"
|
||||
/>{{ $t("nav.friend_requests") }}
|
||||
<span
|
||||
v-if="followRequestCount > 0"
|
||||
class="badge follow-request-count"
|
||||
class="badge badge-notification"
|
||||
>
|
||||
{{ followRequestCount }}
|
||||
</span>
|
||||
|
@ -39,7 +55,11 @@
|
|||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'about' }">
|
||||
<i class="button-icon icon-info-circled" />{{ $t("nav.about") }}
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="fa-scale-110"
|
||||
icon="info-circle"
|
||||
/>{{ $t("nav.about") }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -52,84 +72,88 @@
|
|||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.nav-panel .panel {
|
||||
overflow: hidden;
|
||||
box-shadow: var(--panelShadow);
|
||||
}
|
||||
.nav-panel ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.follow-request-count {
|
||||
margin: -6px 10px;
|
||||
background-color: $fallback--bg;
|
||||
background-color: var(--input, $fallback--faint);
|
||||
}
|
||||
|
||||
.nav-panel li {
|
||||
border-bottom: 1px solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
padding: 0;
|
||||
|
||||
&:first-child a {
|
||||
border-top-right-radius: $fallback--panelRadius;
|
||||
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
border-top-left-radius: $fallback--panelRadius;
|
||||
border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
.NavPanel {
|
||||
.panel {
|
||||
overflow: hidden;
|
||||
box-shadow: var(--panelShadow);
|
||||
}
|
||||
|
||||
&:last-child a {
|
||||
border-bottom-right-radius: $fallback--panelRadius;
|
||||
border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
border-bottom-left-radius: $fallback--panelRadius;
|
||||
border-bottom-left-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-panel li:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.nav-panel a {
|
||||
display: block;
|
||||
padding: 0.8em 0.85em;
|
||||
|
||||
&:hover {
|
||||
background-color: $fallback--lightBg;
|
||||
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);
|
||||
--icon: var(--selectedMenuIcon, $fallback--icon);
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.router-link-active {
|
||||
font-weight: bolder;
|
||||
background-color: $fallback--lightBg;
|
||||
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);
|
||||
--icon: var(--selectedMenuIcon, $fallback--icon);
|
||||
li {
|
||||
position: relative;
|
||||
border-bottom: 1px solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
&:first-child a {
|
||||
border-top-right-radius: $fallback--panelRadius;
|
||||
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
border-top-left-radius: $fallback--panelRadius;
|
||||
border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
}
|
||||
|
||||
&:last-child a {
|
||||
border-bottom-right-radius: $fallback--panelRadius;
|
||||
border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
border-bottom-left-radius: $fallback--panelRadius;
|
||||
border-bottom-left-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-panel .button-icon {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
li:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.nav-panel .button-icon:before {
|
||||
width: 1.1em;
|
||||
a {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
align-items: stretch;
|
||||
height: 3.5em;
|
||||
line-height: 3.5em;
|
||||
padding: 0 1em;
|
||||
|
||||
&:hover {
|
||||
background-color: $fallback--lightBg;
|
||||
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);
|
||||
--icon: var(--selectedMenuIcon, $fallback--icon);
|
||||
}
|
||||
|
||||
&.router-link-active {
|
||||
font-weight: bolder;
|
||||
background-color: $fallback--lightBg;
|
||||
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);
|
||||
--icon: var(--selectedMenuIcon, $fallback--icon);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fa-scale-110 {
|
||||
margin-right: 0.8em;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
right: 0.6rem;
|
||||
top: 1.25em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,6 +7,28 @@ import Timeago from '../timeago/timeago.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'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faCheck,
|
||||
faTimes,
|
||||
faStar,
|
||||
faRetweet,
|
||||
faUserPlus,
|
||||
faEyeSlash,
|
||||
faUser,
|
||||
faSuitcaseRolling
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faCheck,
|
||||
faTimes,
|
||||
faStar,
|
||||
faRetweet,
|
||||
faUserPlus,
|
||||
faUser,
|
||||
faEyeSlash,
|
||||
faSuitcaseRolling
|
||||
)
|
||||
|
||||
const Notification = {
|
||||
data () {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import '../../_variables.scss';
|
||||
|
||||
// TODO Copypaste from Status, should unify it somehow
|
||||
.Notification {
|
||||
&.-muted {
|
||||
|
@ -49,4 +51,34 @@
|
|||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.type-icon {
|
||||
margin: 0 0.1em;
|
||||
}
|
||||
|
||||
&.-type--repeat .type-icon {
|
||||
color: $fallback--cGreen;
|
||||
color: var(--cGreen, $fallback--cGreen);
|
||||
}
|
||||
|
||||
&.-type--follow .type-icon {
|
||||
color: $fallback--cBlue;
|
||||
color: var(--cBlue, $fallback--cBlue);
|
||||
}
|
||||
|
||||
&.-type--follow-request .type-icon {
|
||||
color: $fallback--cBlue;
|
||||
color: var(--cBlue, $fallback--cBlue);
|
||||
}
|
||||
|
||||
&.-type--like .type-icon {
|
||||
color: orange;
|
||||
color: $fallback--cOrange;
|
||||
color: var(--cOrange, $fallback--cOrange);
|
||||
}
|
||||
|
||||
&.-type--move .type-icon {
|
||||
color: $fallback--cBlue;
|
||||
color: var(--cBlue, $fallback--cBlue);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<status
|
||||
<Status
|
||||
v-if="notification.type === 'mention'"
|
||||
:compact="true"
|
||||
:statusoid="notification.status"
|
||||
|
@ -14,16 +14,20 @@
|
|||
{{ notification.from_profile.screen_name }}
|
||||
</router-link>
|
||||
</small>
|
||||
<a
|
||||
href="#"
|
||||
class="unmute"
|
||||
<button
|
||||
class="button-unstyled unmute"
|
||||
@click.prevent="toggleMute"
|
||||
><i class="button-icon icon-eye-off" /></a>
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="eye-slash"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="non-mention"
|
||||
:class="[userClass, { highlighted: userStyle }]"
|
||||
class="Notification non-mention"
|
||||
:class="[userClass, { highlighted: userStyle }, '-type--' + notification.type]"
|
||||
:style="[ userStyle ]"
|
||||
>
|
||||
<a
|
||||
|
@ -60,26 +64,39 @@
|
|||
:title="'@'+notification.from_profile.screen_name"
|
||||
>{{ notification.from_profile.name }}</span>
|
||||
<span v-if="notification.type === 'like'">
|
||||
<i class="fa icon-star lit" />
|
||||
<FAIcon
|
||||
class="type-icon"
|
||||
icon="star"
|
||||
/>
|
||||
<small>{{ $t('notifications.favorited_you') }}</small>
|
||||
</span>
|
||||
<span v-if="notification.type === 'repeat'">
|
||||
<i
|
||||
class="fa icon-retweet lit"
|
||||
<FAIcon
|
||||
class="type-icon"
|
||||
icon="retweet"
|
||||
:title="$t('tool_tip.repeat')"
|
||||
/>
|
||||
<small>{{ $t('notifications.repeated_you') }}</small>
|
||||
</span>
|
||||
<span v-if="notification.type === 'follow'">
|
||||
<i class="fa icon-user-plus lit" />
|
||||
<FAIcon
|
||||
class="type-icon"
|
||||
icon="user-plus"
|
||||
/>
|
||||
<small>{{ $t('notifications.followed_you') }}</small>
|
||||
</span>
|
||||
<span v-if="notification.type === 'follow_request'">
|
||||
<i class="fa icon-user lit" />
|
||||
<FAIcon
|
||||
class="type-icon"
|
||||
icon="user"
|
||||
/>
|
||||
<small>{{ $t('notifications.follow_request') }}</small>
|
||||
</span>
|
||||
<span v-if="notification.type === 'move'">
|
||||
<i class="fa icon-arrow-curved lit" />
|
||||
<FAIcon
|
||||
class="type-icon"
|
||||
icon="suitcase-rolling"
|
||||
/>
|
||||
<small>{{ $t('notifications.migrated_to') }}</small>
|
||||
</span>
|
||||
<span v-if="notification.type === 'pleroma:emoji_reaction'">
|
||||
|
@ -116,11 +133,16 @@
|
|||
/>
|
||||
</span>
|
||||
</div>
|
||||
<a
|
||||
<button
|
||||
v-if="needMute"
|
||||
href="#"
|
||||
class="button-unstyled"
|
||||
@click.prevent="toggleMute"
|
||||
><i class="button-icon icon-eye-off" /></a>
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="eye-slash"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
<div
|
||||
v-if="notification.type === 'follow' || notification.type === 'follow_request'"
|
||||
|
@ -136,13 +158,15 @@
|
|||
v-if="notification.type === 'follow_request'"
|
||||
style="white-space: nowrap;"
|
||||
>
|
||||
<i
|
||||
class="icon-ok button-icon follow-request-accept"
|
||||
<FAIcon
|
||||
icon="check"
|
||||
class="fa-scale-110 fa-old-padding follow-request-accept"
|
||||
:title="$t('tool_tip.accept_follow_request')"
|
||||
@click="approveUser()"
|
||||
/>
|
||||
<i
|
||||
class="icon-cancel button-icon follow-request-reject"
|
||||
<FAIcon
|
||||
icon="times"
|
||||
class="fa-scale-110 fa-old-padding follow-request-reject"
|
||||
:title="$t('tool_tip.reject_follow_request')"
|
||||
@click="denyUser()"
|
||||
/>
|
||||
|
|
|
@ -6,6 +6,13 @@ import {
|
|||
filteredNotificationsFromStore,
|
||||
unseenNotificationsFromStore
|
||||
} from '../../services/notification_utils/notification_utils.js'
|
||||
import FaviconService from '../../services/favicon_service/favicon_service.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faCircleNotch
|
||||
)
|
||||
|
||||
const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
|
||||
|
||||
|
@ -69,8 +76,10 @@ const Notifications = {
|
|||
watch: {
|
||||
unseenCountTitle (count) {
|
||||
if (count > 0) {
|
||||
FaviconService.drawFaviconBadge()
|
||||
this.$store.dispatch('setPageTitle', `(${count})`)
|
||||
} else {
|
||||
FaviconService.clearFaviconBadge()
|
||||
this.$store.dispatch('setPageTitle', '')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,37 +158,6 @@
|
|||
margin-right: .2em;
|
||||
}
|
||||
|
||||
.icon-retweet.lit {
|
||||
color: $fallback--cGreen;
|
||||
color: var(--cGreen, $fallback--cGreen);
|
||||
}
|
||||
|
||||
.icon-user.lit {
|
||||
color: $fallback--cBlue;
|
||||
color: var(--cBlue, $fallback--cBlue);
|
||||
}
|
||||
|
||||
.icon-user-plus.lit {
|
||||
color: $fallback--cBlue;
|
||||
color: var(--cBlue, $fallback--cBlue);
|
||||
}
|
||||
|
||||
.icon-reply.lit {
|
||||
color: $fallback--cBlue;
|
||||
color: var(--cBlue, $fallback--cBlue);
|
||||
}
|
||||
|
||||
.icon-star.lit {
|
||||
color: orange;
|
||||
color: $fallback--cOrange;
|
||||
color: var(--cOrange, $fallback--cOrange);
|
||||
}
|
||||
|
||||
.icon-arrow-curved.lit {
|
||||
color: $fallback--cBlue;
|
||||
color: var(--cBlue, $fallback--cBlue);
|
||||
}
|
||||
|
||||
.status-content {
|
||||
margin: 0;
|
||||
max-height: 300px;
|
||||
|
|
|
@ -15,16 +15,9 @@
|
|||
class="badge badge-notification unseen-count"
|
||||
>{{ unseenCount }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="error"
|
||||
class="loadmore-error alert error"
|
||||
@click.prevent
|
||||
>
|
||||
{{ $t('timeline.error_fetching') }}
|
||||
</div>
|
||||
<button
|
||||
v-if="unseenCount"
|
||||
class="read-button"
|
||||
class="button-default read-button"
|
||||
@click.prevent="markAsSeen"
|
||||
>
|
||||
{{ $t('notifications.read') }}
|
||||
|
@ -48,20 +41,24 @@
|
|||
>
|
||||
{{ $t('notifications.no_more_notifications') }}
|
||||
</div>
|
||||
<a
|
||||
<button
|
||||
v-else-if="!loading"
|
||||
href="#"
|
||||
class="button-unstyled -link -fullwidth"
|
||||
@click.prevent="fetchOlderNotifications()"
|
||||
>
|
||||
<div class="new-status-notification text-center panel-footer">
|
||||
{{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
|
||||
</div>
|
||||
</a>
|
||||
</button>
|
||||
<div
|
||||
v-else
|
||||
class="new-status-notification text-center panel-footer"
|
||||
>
|
||||
<i class="icon-spin3 animate-spin" />
|
||||
<FAIcon
|
||||
icon="circle-notch"
|
||||
spin
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,27 @@
|
|||
<template>
|
||||
<div class="panel-loading">
|
||||
<span class="loading-text">
|
||||
<i class="icon-spin4 animate-spin" />
|
||||
<FAIcon
|
||||
icon="circle-notch"
|
||||
spin
|
||||
size="3x"
|
||||
/>
|
||||
{{ $t('general.loading') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faCircleNotch
|
||||
)
|
||||
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'src/_variables.scss';
|
||||
|
||||
|
@ -18,8 +33,7 @@
|
|||
font-size: 2em;
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
.loading-text i {
|
||||
font-size: 3em;
|
||||
.loading-text svg {
|
||||
line-height: 0;
|
||||
vertical-align: middle;
|
||||
color: $fallback--text;
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
import { mapState } from 'vuex'
|
||||
import passwordResetApi from '../../services/new_api/password_reset.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faTimes
|
||||
)
|
||||
|
||||
const passwordReset = {
|
||||
data: () => ({
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
<button
|
||||
:disabled="isPending"
|
||||
type="submit"
|
||||
class="btn btn-default btn-block"
|
||||
class="btn button-default btn-block"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
</button>
|
||||
|
@ -63,10 +63,10 @@
|
|||
>
|
||||
<span>{{ error }}</span>
|
||||
<a
|
||||
class="button-icon dismiss"
|
||||
class="fa-scale-110 fa-old-padding dismiss"
|
||||
@click.prevent="dismissError()"
|
||||
>
|
||||
<i class="icon-cancel" />
|
||||
<FAIcon icon="times" />
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -122,7 +122,7 @@
|
|||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.icon-cancel {
|
||||
.dismiss {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,14 +42,15 @@
|
|||
:value="index"
|
||||
>
|
||||
<label class="option-vote">
|
||||
<div>{{ option.title }}</div>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-html="option.title_html" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer faint">
|
||||
<button
|
||||
v-if="!showResults"
|
||||
class="btn btn-default poll-vote-button"
|
||||
class="btn button-default poll-vote-button"
|
||||
type="button"
|
||||
:disabled="isDisabled"
|
||||
@click="vote"
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
import * as DateUtils from 'src/services/date_utils/date_utils.js'
|
||||
import { uniq } from 'lodash'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faTimes,
|
||||
faChevronDown,
|
||||
faPlus
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faTimes,
|
||||
faChevronDown,
|
||||
faPlus
|
||||
)
|
||||
|
||||
export default {
|
||||
name: 'PollForm',
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<input
|
||||
:id="`poll-${index}`"
|
||||
v-model="options[index]"
|
||||
size="1"
|
||||
class="poll-option-input"
|
||||
type="text"
|
||||
:placeholder="$t('polls.option')"
|
||||
|
@ -20,24 +21,26 @@
|
|||
@keydown.enter.stop.prevent="nextOption(index)"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
<button
|
||||
v-if="options.length > 2"
|
||||
class="icon-container"
|
||||
class="delete-option button-unstyled -hover-highlight"
|
||||
@click="deleteOption(index)"
|
||||
>
|
||||
<i
|
||||
class="icon-cancel"
|
||||
@click="deleteOption(index)"
|
||||
/>
|
||||
</div>
|
||||
<FAIcon icon="times" />
|
||||
</button>
|
||||
</div>
|
||||
<a
|
||||
<button
|
||||
v-if="options.length < maxOptions"
|
||||
class="add-option faint"
|
||||
class="add-option faint button-unstyled -hover-highlight"
|
||||
@click="addOption"
|
||||
>
|
||||
<i class="icon-plus" />
|
||||
<FAIcon
|
||||
icon="plus"
|
||||
size="sm"
|
||||
/>
|
||||
|
||||
{{ $t("polls.add_option") }}
|
||||
</a>
|
||||
</button>
|
||||
<div class="poll-type-expiry">
|
||||
<div
|
||||
class="poll-type"
|
||||
|
@ -55,7 +58,10 @@
|
|||
<option value="single">{{ $t('polls.single_choice') }}</option>
|
||||
<option value="multiple">{{ $t('polls.multiple_choices') }}</option>
|
||||
</select>
|
||||
<i class="icon-down-open" />
|
||||
<FAIcon
|
||||
class="select-down-icon"
|
||||
icon="chevron-down"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
|
@ -83,7 +89,10 @@
|
|||
{{ $t(`time.${unit}_short`, ['']) }}
|
||||
</option>
|
||||
</select>
|
||||
<i class="icon-down-open" />
|
||||
<FAIcon
|
||||
class="select-down-icon"
|
||||
icon="chevron-down"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -103,7 +112,7 @@
|
|||
.add-option {
|
||||
align-self: flex-start;
|
||||
padding-top: 0.25em;
|
||||
cursor: pointer;
|
||||
padding-left: 0.1em;
|
||||
}
|
||||
|
||||
.poll-option {
|
||||
|
@ -122,10 +131,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
.delete-option {
|
||||
// Hack: Move the icon over the input box
|
||||
width: 2em;
|
||||
margin-left: -2em;
|
||||
width: 1.5em;
|
||||
margin-left: -1.5em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,10 @@ const Popover = {
|
|||
// Replaces the classes you may want for the popover container.
|
||||
// Use 'popover-default' in addition to get the default popover
|
||||
// styles with your custom class.
|
||||
popoverClass: String
|
||||
popoverClass: String,
|
||||
// If true, subtract padding when calculating position for the popover,
|
||||
// use it when popover offset looks to be different on top vs bottom.
|
||||
removePadding: Boolean
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -96,9 +99,15 @@ const Popover = {
|
|||
if (origin.y + content.offsetHeight > yBounds.max) usingTop = true
|
||||
if (origin.y - content.offsetHeight < yBounds.min) usingTop = false
|
||||
|
||||
let vPadding = 0
|
||||
if (this.removePadding && usingTop) {
|
||||
const anchorStyle = getComputedStyle(anchorEl)
|
||||
vPadding = parseFloat(anchorStyle.paddingTop) + parseFloat(anchorStyle.paddingBottom)
|
||||
}
|
||||
|
||||
const yOffset = (this.offset && this.offset.y) || 0
|
||||
const translateY = usingTop
|
||||
? -anchorEl.offsetHeight - yOffset - content.offsetHeight
|
||||
? -anchorEl.offsetHeight + vPadding - yOffset - content.offsetHeight
|
||||
: yOffset
|
||||
|
||||
const xOffset = (this.offset && this.offset.x) || 0
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
@mouseenter="onMouseenter"
|
||||
@mouseleave="onMouseleave"
|
||||
>
|
||||
<div
|
||||
<button
|
||||
ref="trigger"
|
||||
class="button-unstyled -fullwidth popover-trigger-button"
|
||||
@click="onClick"
|
||||
>
|
||||
<slot name="trigger" />
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
v-if="!hidden"
|
||||
ref="content"
|
||||
|
@ -27,9 +28,13 @@
|
|||
|
||||
<script src="./popover.js" />
|
||||
|
||||
<style lang=scss>
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.popover-trigger-button {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.popover {
|
||||
z-index: 8;
|
||||
position: absolute;
|
||||
|
@ -90,13 +95,14 @@
|
|||
box-shadow: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
--btnText: var(--popoverText, $fallback--text);
|
||||
|
||||
&-icon {
|
||||
padding-left: 0.5rem;
|
||||
|
||||
i {
|
||||
svg {
|
||||
margin-right: 0.25rem;
|
||||
color: var(--menuPopoverIcon, $fallback--icon)
|
||||
}
|
||||
|
@ -111,7 +117,7 @@
|
|||
--faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
|
||||
--lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
|
||||
--icon: var(--selectedMenuPopoverIcon, $fallback--icon);
|
||||
i {
|
||||
svg {
|
||||
color: var(--selectedMenuPopoverIcon, $fallback--icon);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,27 @@ import suggestor from '../emoji_input/suggestor.js'
|
|||
import { mapGetters, mapState } from 'vuex'
|
||||
import Checkbox from '../checkbox/checkbox.vue'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faChevronDown,
|
||||
faSmileBeam,
|
||||
faPollH,
|
||||
faUpload,
|
||||
faBan,
|
||||
faTimes,
|
||||
faCircleNotch
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faChevronDown,
|
||||
faSmileBeam,
|
||||
faPollH,
|
||||
faUpload,
|
||||
faBan,
|
||||
faTimes,
|
||||
faCircleNotch
|
||||
)
|
||||
|
||||
const buildMentionsString = ({ user, attentions = [] }, currentUser) => {
|
||||
let allAttentions = [...attentions]
|
||||
|
||||
|
@ -54,7 +75,8 @@ const PostStatusForm = {
|
|||
'autoFocus',
|
||||
'fileLimit',
|
||||
'submitOnEnter',
|
||||
'emojiPickerPlacement'
|
||||
'emojiPickerPlacement',
|
||||
'optimisticPosting'
|
||||
],
|
||||
components: {
|
||||
MediaUpload,
|
||||
|
@ -137,8 +159,7 @@ const PostStatusForm = {
|
|||
...this.$store.state.instance.emoji,
|
||||
...this.$store.state.instance.customEmoji
|
||||
],
|
||||
users: this.$store.state.users.users,
|
||||
updateUsersList: (query) => this.$store.dispatch('searchUsers', { query })
|
||||
store: this.$store
|
||||
})
|
||||
},
|
||||
emojiSuggestor () {
|
||||
|
@ -251,7 +272,7 @@ const PostStatusForm = {
|
|||
if (this.preview) this.previewStatus()
|
||||
},
|
||||
async postStatus (event, newStatus, opts = {}) {
|
||||
if (this.posting) { return }
|
||||
if (this.posting && !this.optimisticPosting) { return }
|
||||
if (this.disableSubmit) { return }
|
||||
if (this.emojiInputShown) { return }
|
||||
if (this.submitOnEnter) {
|
||||
|
@ -259,6 +280,8 @@ const PostStatusForm = {
|
|||
event.preventDefault()
|
||||
}
|
||||
|
||||
if (this.optimisticPosting && (this.emptyStatus || this.isOverLengthLimit)) { return }
|
||||
|
||||
if (this.emptyStatus) {
|
||||
this.error = this.$t('post_status.empty_status_error')
|
||||
return
|
||||
|
@ -507,7 +530,7 @@ const PostStatusForm = {
|
|||
!(isFormBiggerThanScroller &&
|
||||
this.$refs.textarea.selectionStart !== this.$refs.textarea.value.length)
|
||||
const totalDelta = shouldScrollToBottom ? bottomChangeDelta : 0
|
||||
const targetScroll = currentScroll + totalDelta
|
||||
const targetScroll = Math.round(currentScroll + totalDelta)
|
||||
|
||||
if (scrollerRef === window) {
|
||||
scrollerRef.scroll(0, targetScroll)
|
||||
|
|
|
@ -12,10 +12,11 @@
|
|||
v-show="showDropIcon !== 'hide'"
|
||||
:style="{ animation: showDropIcon === 'show' ? 'fade-in 0.25s' : 'fade-out 0.5s' }"
|
||||
class="drop-indicator"
|
||||
:class="[uploadFileLimitReached ? 'icon-block' : 'icon-upload']"
|
||||
@dragleave="fileDragStop"
|
||||
@drop.stop="fileDrop"
|
||||
/>
|
||||
>
|
||||
<FAIcon :icon="uploadFileLimitReached ? 'ban' : 'upload'" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<i18n
|
||||
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning"
|
||||
|
@ -23,12 +24,12 @@
|
|||
tag="p"
|
||||
class="visibility-notice"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
<button
|
||||
class="button-unstyled -link"
|
||||
@click="openProfileTab"
|
||||
>
|
||||
{{ $t('post_status.account_not_locked_warning_link') }}
|
||||
</a>
|
||||
</button>
|
||||
</i18n>
|
||||
<p
|
||||
v-if="!hideScopeNotice && newStatus.visibility === 'public'"
|
||||
|
@ -36,10 +37,10 @@
|
|||
>
|
||||
<span>{{ $t('post_status.scope_notice.public') }}</span>
|
||||
<a
|
||||
class="button-icon dismiss"
|
||||
class="fa-scale-110 fa-old-padding dismiss"
|
||||
@click.prevent="dismissScopeNotice()"
|
||||
>
|
||||
<i class="icon-cancel" />
|
||||
<FAIcon icon="times" />
|
||||
</a>
|
||||
</p>
|
||||
<p
|
||||
|
@ -48,10 +49,10 @@
|
|||
>
|
||||
<span>{{ $t('post_status.scope_notice.unlisted') }}</span>
|
||||
<a
|
||||
class="button-icon dismiss"
|
||||
class="fa-scale-110 fa-old-padding dismiss"
|
||||
@click.prevent="dismissScopeNotice()"
|
||||
>
|
||||
<i class="icon-cancel" />
|
||||
<FAIcon icon="times" />
|
||||
</a>
|
||||
</p>
|
||||
<p
|
||||
|
@ -60,10 +61,10 @@
|
|||
>
|
||||
<span>{{ $t('post_status.scope_notice.private') }}</span>
|
||||
<a
|
||||
class="button-icon dismiss"
|
||||
class="fa-scale-110 fa-old-padding dismiss"
|
||||
@click.prevent="dismissScopeNotice()"
|
||||
>
|
||||
<i class="icon-cancel" />
|
||||
<FAIcon icon="times" />
|
||||
</a>
|
||||
</p>
|
||||
<p
|
||||
|
@ -82,12 +83,18 @@
|
|||
@click.stop.prevent="togglePreview"
|
||||
>
|
||||
{{ $t('post_status.preview') }}
|
||||
<i :class="showPreview ? 'icon-left-open' : 'icon-right-open'" />
|
||||
<FAIcon :icon="showPreview ? 'chevron-left' : 'chevron-right'" />
|
||||
</a>
|
||||
<i
|
||||
<div
|
||||
v-show="previewLoading"
|
||||
class="icon-spin3 animate-spin"
|
||||
/>
|
||||
class="preview-spinner"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-old-padding"
|
||||
spin
|
||||
icon="circle-notch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="showPreview"
|
||||
|
@ -122,7 +129,8 @@
|
|||
v-model="newStatus.spoilerText"
|
||||
type="text"
|
||||
:placeholder="$t('post_status.content_warning')"
|
||||
:disabled="posting"
|
||||
:disabled="posting && !optimisticPosting"
|
||||
size="1"
|
||||
class="form-post-subject"
|
||||
>
|
||||
</EmojiInput>
|
||||
|
@ -147,7 +155,7 @@
|
|||
:placeholder="placeholder || $t('post_status.default')"
|
||||
rows="1"
|
||||
cols="1"
|
||||
:disabled="posting"
|
||||
:disabled="posting && !optimisticPosting"
|
||||
class="form-post-body"
|
||||
:class="{ 'scrollable-form': !!maxHeight }"
|
||||
@keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
|
||||
|
@ -198,7 +206,10 @@
|
|||
{{ $t(`post_status.content_type["${postFormat}"]`) }}
|
||||
</option>
|
||||
</select>
|
||||
<i class="icon-down-open" />
|
||||
<FAIcon
|
||||
class="select-down-icon"
|
||||
icon="chevron-down"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
|
@ -232,38 +243,34 @@
|
|||
@upload-failed="uploadFailed"
|
||||
@all-uploaded="finishedUploadingFiles"
|
||||
/>
|
||||
<div
|
||||
class="emoji-icon"
|
||||
<button
|
||||
class="emoji-icon button-unstyled"
|
||||
:title="$t('emoji.add_emoji')"
|
||||
@click="showEmojiPicker"
|
||||
>
|
||||
<i
|
||||
:title="$t('emoji.add_emoji')"
|
||||
class="icon-smile btn btn-default"
|
||||
@click="showEmojiPicker"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
<FAIcon icon="smile-beam" />
|
||||
</button>
|
||||
<button
|
||||
v-if="pollsAvailable"
|
||||
class="poll-icon"
|
||||
class="poll-icon button-unstyled"
|
||||
:class="{ selected: pollFormVisible }"
|
||||
:title="$t('polls.add_poll')"
|
||||
@click="togglePollForm"
|
||||
>
|
||||
<i
|
||||
:title="$t('polls.add_poll')"
|
||||
class="icon-chart-bar btn btn-default"
|
||||
@click="togglePollForm"
|
||||
/>
|
||||
</div>
|
||||
<FAIcon icon="poll-h" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="posting"
|
||||
disabled
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
>
|
||||
{{ $t('post_status.posting') }}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="isOverLengthLimit"
|
||||
disabled
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
</button>
|
||||
|
@ -271,7 +278,7 @@
|
|||
<button
|
||||
v-else
|
||||
:disabled="uploadingFiles || disableSubmit"
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
@touchstart.stop.prevent="postStatus($event, newStatus)"
|
||||
@click.stop.prevent="postStatus($event, newStatus)"
|
||||
>
|
||||
|
@ -283,8 +290,9 @@
|
|||
class="alert error"
|
||||
>
|
||||
Error: {{ error }}
|
||||
<i
|
||||
class="button-icon icon-cancel"
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="times"
|
||||
@click="clearError"
|
||||
/>
|
||||
</div>
|
||||
|
@ -294,10 +302,12 @@
|
|||
:key="file.url"
|
||||
class="media-upload-wrapper"
|
||||
>
|
||||
<i
|
||||
class="fa button-icon icon-cancel"
|
||||
<button
|
||||
class="button-unstyled hider"
|
||||
@click="removeMediaFile(file)"
|
||||
/>
|
||||
>
|
||||
<FAIcon icon="times" />
|
||||
</button>
|
||||
<attachment
|
||||
:attachment="file"
|
||||
:set-media="() => $store.dispatch('setMedia', newStatus.files)"
|
||||
|
@ -375,24 +385,19 @@
|
|||
}
|
||||
|
||||
.preview-heading {
|
||||
padding-left: 0.5em;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.icon-spin3 {
|
||||
margin-left: auto;
|
||||
}
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.preview-toggle {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
i {
|
||||
svg, i {
|
||||
margin-left: 0.2em;
|
||||
font-size: 0.8em;
|
||||
transform: rotate(90deg);
|
||||
|
@ -434,18 +439,20 @@
|
|||
|
||||
.media-upload-icon, .poll-icon, .emoji-icon {
|
||||
font-size: 26px;
|
||||
line-height: 1.1;
|
||||
flex: 1;
|
||||
padding: 0 0.1em;
|
||||
|
||||
&.selected, &:hover {
|
||||
// needs to be specific to override icon default color
|
||||
i, label {
|
||||
svg, i, label {
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
i {
|
||||
svg, i {
|
||||
cursor: not-allowed;
|
||||
color: $fallback--icon;
|
||||
color: var(--btnDisabledText, $fallback--icon);
|
||||
|
@ -474,7 +481,7 @@
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
.icon-chart-bar {
|
||||
.poll-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
@ -487,19 +494,6 @@
|
|||
margin-bottom: .5em;
|
||||
width: 18em;
|
||||
|
||||
.icon-cancel {
|
||||
display: inline-block;
|
||||
position: static;
|
||||
margin: 0;
|
||||
padding-bottom: 0;
|
||||
margin-left: $fallback--attachmentRadius;
|
||||
margin-left: var(--attachmentRadius, $fallback--attachmentRadius);
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--btn, $fallback--fg);
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
img, video {
|
||||
object-fit: contain;
|
||||
max-height: 10em;
|
||||
|
@ -522,23 +516,12 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.media-upload-wrapper .attachments {
|
||||
padding: 0 0.5em;
|
||||
.attachments .media-upload-wrapper {
|
||||
position: relative;
|
||||
|
||||
.attachment {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
margin: 10px;
|
||||
padding: 5px;
|
||||
background: rgba(230,230,230,0.6);
|
||||
border-radius: $fallback--attachmentRadius;
|
||||
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -612,11 +595,6 @@
|
|||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.icon-cancel {
|
||||
cursor: pointer;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 0.6; }
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import Popover from '../popover/popover.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faSmileBeam } from '@fortawesome/free-regular-svg-icons'
|
||||
|
||||
library.add(faSmileBeam)
|
||||
|
||||
const ReactButton = {
|
||||
props: ['status'],
|
||||
|
@ -23,13 +27,21 @@ const ReactButton = {
|
|||
},
|
||||
computed: {
|
||||
commonEmojis () {
|
||||
return ['👍', '😠', '👀', '😂', '🔥']
|
||||
return [
|
||||
{ displayText: 'thumbsup', replacement: '👍' },
|
||||
{ displayText: 'angry', replacement: '😠' },
|
||||
{ displayText: 'eyes', replacement: '👀' },
|
||||
{ displayText: 'joy', replacement: '😂' },
|
||||
{ displayText: 'fire', replacement: '🔥' }
|
||||
]
|
||||
},
|
||||
emojis () {
|
||||
if (this.filterWord !== '') {
|
||||
const filterWordLowercase = this.filterWord.toLowerCase()
|
||||
let orderedEmojiList = []
|
||||
for (const emoji of this.$store.state.instance.emoji) {
|
||||
if (emoji.replacement === this.filterWord) return [emoji]
|
||||
|
||||
const indexOfFilterWord = emoji.displayText.toLowerCase().indexOf(filterWordLowercase)
|
||||
if (indexOfFilterWord > -1) {
|
||||
if (!Array.isArray(orderedEmojiList[indexOfFilterWord])) {
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
trigger="click"
|
||||
placement="top"
|
||||
:offset="{ y: 5 }"
|
||||
class="react-button-popover"
|
||||
:bound-to="{ x: 'container' }"
|
||||
remove-padding
|
||||
>
|
||||
<div
|
||||
slot="content"
|
||||
|
@ -12,23 +13,26 @@
|
|||
<div class="reaction-picker-filter">
|
||||
<input
|
||||
v-model="filterWord"
|
||||
size="1"
|
||||
:placeholder="$t('emoji.search_emoji')"
|
||||
>
|
||||
</div>
|
||||
<div class="reaction-picker">
|
||||
<span
|
||||
v-for="emoji in commonEmojis"
|
||||
:key="emoji"
|
||||
:key="emoji.replacement"
|
||||
class="emoji-button"
|
||||
@click="addReaction($event, emoji, close)"
|
||||
:title="emoji.displayText"
|
||||
@click="addReaction($event, emoji.replacement, close)"
|
||||
>
|
||||
{{ emoji }}
|
||||
{{ emoji.replacement }}
|
||||
</span>
|
||||
<div class="reaction-picker-divider" />
|
||||
<span
|
||||
v-for="(emoji, key) in emojis"
|
||||
:key="key"
|
||||
class="emoji-button"
|
||||
:title="emoji.displayText"
|
||||
@click="addReaction($event, emoji.replacement, close)"
|
||||
>
|
||||
{{ emoji.replacement }}
|
||||
|
@ -36,11 +40,16 @@
|
|||
<div class="reaction-bottom-fader" />
|
||||
</div>
|
||||
</div>
|
||||
<i
|
||||
<span
|
||||
slot="trigger"
|
||||
class="icon-smile button-icon add-reaction-button"
|
||||
class="ReactButton"
|
||||
:title="$t('tool_tip.add_reaction')"
|
||||
/>
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
:icon="['far', 'smile-beam']"
|
||||
/>
|
||||
</span>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
|
@ -98,10 +107,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.add-reaction-button {
|
||||
cursor: pointer;
|
||||
.ReactButton {
|
||||
padding: 10px;
|
||||
margin: -10px;
|
||||
|
||||
&:hover {
|
||||
&:hover .svg-inline--fa {
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
}
|
||||
|
|
|
@ -211,7 +211,7 @@
|
|||
<button
|
||||
:disabled="isPending"
|
||||
type="submit"
|
||||
class="btn btn-default"
|
||||
class="btn button-default"
|
||||
>
|
||||
{{ $t('general.submit') }}
|
||||
</button>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
>
|
||||
<button
|
||||
click="submit"
|
||||
class="remote-button"
|
||||
class="button-default remote-button"
|
||||
>
|
||||
{{ $t('user_card.remote_follow') }}
|
||||
</button>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faReply } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(faReply)
|
||||
|
||||
const ReplyButton = {
|
||||
name: 'ReplyButton',
|
||||
|
|
|
@ -1,21 +1,58 @@
|
|||
<template>
|
||||
<div>
|
||||
<i
|
||||
<div class="ReplyButton">
|
||||
<button
|
||||
v-if="loggedIn"
|
||||
class="button-icon button-reply icon-reply"
|
||||
:title="$t('tool_tip.reply')"
|
||||
class="button-unstyled interactive"
|
||||
:class="{'-active': replying}"
|
||||
@click.prevent="$emit('toggle')"
|
||||
/>
|
||||
<i
|
||||
v-else
|
||||
class="button-icon button-reply -disabled icon-reply"
|
||||
:title="$t('tool_tip.reply')"
|
||||
/>
|
||||
<span v-if="status.replies_count > 0">
|
||||
@click.prevent="$emit('toggle')"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="reply"
|
||||
/>
|
||||
</button>
|
||||
<span v-else>
|
||||
<FAIcon
|
||||
icon="reply"
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
:title="$t('tool_tip.reply')"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="status.replies_count > 0"
|
||||
class="action-counter"
|
||||
>
|
||||
{{ status.replies_count }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./reply_button.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.ReplyButton {
|
||||
display: flex;
|
||||
|
||||
> :first-child {
|
||||
padding: 10px;
|
||||
margin: -10px -8px -10px -10px;
|
||||
}
|
||||
|
||||
.action-counter {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.interactive {
|
||||
&:hover .svg-inline--fa,
|
||||
&.-active .svg-inline--fa {
|
||||
color: $fallback--cBlue;
|
||||
color: var(--cBlue, $fallback--cBlue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faRetweet } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(faRetweet)
|
||||
|
||||
const RetweetButton = {
|
||||
props: ['status', 'loggedIn', 'visibility'],
|
||||
|
@ -20,13 +24,6 @@ const RetweetButton = {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
classes () {
|
||||
return {
|
||||
'retweeted': this.status.repeated,
|
||||
'retweeted-empty': !this.status.repeated,
|
||||
'animate-spin': this.animated
|
||||
}
|
||||
},
|
||||
mergedConfig () {
|
||||
return this.$store.getters.mergedConfig
|
||||
}
|
||||
|
|
|
@ -1,29 +1,38 @@
|
|||
<template>
|
||||
<div v-if="loggedIn">
|
||||
<template v-if="visibility !== 'private' && visibility !== 'direct'">
|
||||
<i
|
||||
:class="classes"
|
||||
class="button-icon retweet-button icon-retweet rt-active"
|
||||
:title="$t('tool_tip.repeat')"
|
||||
@click.prevent="retweet()"
|
||||
<div class="RetweetButton">
|
||||
<button
|
||||
v-if="visibility !== 'private' && visibility !== 'direct' && loggedIn"
|
||||
class="button-unstyled interactive"
|
||||
:class="status.repeated && '-repeated'"
|
||||
:title="$t('tool_tip.repeat')"
|
||||
@click.prevent="retweet()"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="retweet"
|
||||
:spin="animated"
|
||||
/>
|
||||
<span v-if="!mergedConfig.hidePostStats && status.repeat_num > 0">{{ status.repeat_num }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<i
|
||||
:class="classes"
|
||||
class="button-icon icon-lock"
|
||||
</button>
|
||||
<span v-else-if="loggedIn">
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="lock"
|
||||
:title="$t('timeline.no_retweet_hint')"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else-if="!loggedIn">
|
||||
<i
|
||||
:class="classes"
|
||||
class="button-icon icon-retweet"
|
||||
:title="$t('tool_tip.repeat')"
|
||||
/>
|
||||
<span v-if="!mergedConfig.hidePostStats && status.repeat_num > 0">{{ status.repeat_num }}</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
<FAIcon
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
icon="retweet"
|
||||
:title="$t('tool_tip.repeat')"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="!mergedConfig.hidePostStats && status.repeat_num > 0"
|
||||
class="no-event"
|
||||
>
|
||||
{{ status.repeat_num }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -31,16 +40,30 @@
|
|||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
.rt-active {
|
||||
cursor: pointer;
|
||||
animation-duration: 0.6s;
|
||||
&:hover {
|
||||
color: $fallback--cGreen;
|
||||
color: var(--cGreen, $fallback--cGreen);
|
||||
|
||||
.RetweetButton {
|
||||
display: flex;
|
||||
|
||||
> :first-child {
|
||||
padding: 10px;
|
||||
margin: -10px -8px -10px -10px;
|
||||
}
|
||||
|
||||
.action-counter {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.interactive {
|
||||
.svg-inline--fa {
|
||||
animation-duration: 0.6s;
|
||||
}
|
||||
|
||||
&:hover .svg-inline--fa,
|
||||
&.-repeated .svg-inline--fa {
|
||||
color: $fallback--cGreen;
|
||||
color: var(--cGreen, $fallback--cGreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon-retweet.retweeted {
|
||||
color: $fallback--cGreen;
|
||||
color: var(--cGreen, $fallback--cGreen);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faEnvelope,
|
||||
faLock,
|
||||
faLockOpen,
|
||||
faGlobe
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faEnvelope,
|
||||
faGlobe,
|
||||
faLock,
|
||||
faLockOpen
|
||||
)
|
||||
|
||||
const ScopeSelector = {
|
||||
props: [
|
||||
'showAll',
|
||||
|
|
|
@ -1,36 +1,56 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="!showNothing"
|
||||
class="scope-selector"
|
||||
class="ScopeSelector"
|
||||
>
|
||||
<i
|
||||
<button
|
||||
v-if="showDirect"
|
||||
class="icon-mail-alt"
|
||||
class="button-unstyled scope"
|
||||
:class="css.direct"
|
||||
:title="$t('post_status.scope.direct')"
|
||||
@click="changeVis('direct')"
|
||||
/>
|
||||
<i
|
||||
>
|
||||
<FAIcon
|
||||
icon="envelope"
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="showPrivate"
|
||||
class="icon-lock"
|
||||
class="button-unstyled scope"
|
||||
:class="css.private"
|
||||
:title="$t('post_status.scope.private')"
|
||||
@click="changeVis('private')"
|
||||
/>
|
||||
<i
|
||||
>
|
||||
<FAIcon
|
||||
icon="lock"
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="showUnlisted"
|
||||
class="icon-lock-open-alt"
|
||||
class="button-unstyled scope"
|
||||
:class="css.unlisted"
|
||||
:title="$t('post_status.scope.unlisted')"
|
||||
@click="changeVis('unlisted')"
|
||||
/>
|
||||
<i
|
||||
>
|
||||
<FAIcon
|
||||
icon="lock-open"
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="showPublic"
|
||||
class="icon-globe"
|
||||
class="button-unstyled scope"
|
||||
:class="css.public"
|
||||
:title="$t('post_status.scope.public')"
|
||||
@click="changeVis('public')"
|
||||
/>
|
||||
>
|
||||
<FAIcon
|
||||
icon="globe"
|
||||
class="fa-scale-110 fa-old-padding"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -39,12 +59,16 @@
|
|||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.scope-selector {
|
||||
i {
|
||||
font-size: 1.2em;
|
||||
cursor: pointer;
|
||||
.ScopeSelector {
|
||||
|
||||
&.selected {
|
||||
.scope {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
min-width: 1.3em;
|
||||
min-height: 1.3em;
|
||||
text-align: center;
|
||||
|
||||
&.selected svg {
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
}
|
||||
|
|
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