aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/account_actions/account_actions.js10
-rw-r--r--src/components/account_actions/account_actions.vue50
-rw-r--r--src/components/async_component_error/async_component_error.vue2
-rw-r--r--src/components/attachment/attachment.js26
-rw-r--r--src/components/attachment/attachment.vue46
-rw-r--r--src/components/block_card/block_card.vue4
-rw-r--r--src/components/chat/chat.js111
-rw-r--r--src/components/chat/chat.scss24
-rw-r--r--src/components/chat/chat.vue11
-rw-r--r--src/components/chat/chat_layout_utils.js7
-rw-r--r--src/components/chat_list/chat_list.vue5
-rw-r--r--src/components/chat_list_item/chat_list_item.vue12
-rw-r--r--src/components/chat_message/chat_message.js10
-rw-r--r--src/components/chat_message/chat_message.scss18
-rw-r--r--src/components/chat_message/chat_message.vue9
-rw-r--r--src/components/chat_message_date/chat_message_date.vue4
-rw-r--r--src/components/chat_new/chat_new.js10
-rw-r--r--src/components/chat_new/chat_new.scss8
-rw-r--r--src/components/chat_new/chat_new.vue10
-rw-r--r--src/components/chat_panel/chat_panel.js10
-rw-r--r--src/components/chat_panel/chat_panel.vue12
-rw-r--r--src/components/contrast_ratio/contrast_ratio.vue26
-rw-r--r--src/components/conversation/conversation.vue20
-rw-r--r--src/components/desktop_nav/desktop_nav.js89
-rw-r--r--src/components/desktop_nav/desktop_nav.scss117
-rw-r--r--src/components/desktop_nav/desktop_nav.vue81
-rw-r--r--src/components/domain_mute_card/domain_mute_card.vue4
-rw-r--r--src/components/emoji_input/emoji_input.js44
-rw-r--r--src/components/emoji_input/emoji_input.vue10
-rw-r--r--src/components/emoji_input/suggestor.js140
-rw-r--r--src/components/emoji_picker/emoji_picker.js16
-rw-r--r--src/components/emoji_picker/emoji_picker.scss2
-rw-r--r--src/components/emoji_picker/emoji_picker.vue10
-rw-r--r--src/components/emoji_reactions/emoji_reactions.vue2
-rw-r--r--src/components/export_import/export_import.vue4
-rw-r--r--src/components/exporter/exporter.js7
-rw-r--r--src/components/exporter/exporter.vue10
-rw-r--r--src/components/extra_buttons/extra_buttons.js27
-rw-r--r--src/components/extra_buttons/extra_buttons.vue108
-rw-r--r--src/components/favorite_button/favorite_button.js17
-rw-r--r--src/components/favorite_button/favorite_button.vue70
-rw-r--r--src/components/features_panel/features_panel.js5
-rw-r--r--src/components/features_panel/features_panel.vue1
-rw-r--r--src/components/follow_button/follow_button.vue2
-rw-r--r--src/components/follow_request_card/follow_request_card.vue4
-rw-r--r--src/components/font_control/font_control.js8
-rw-r--r--src/components/font_control/font_control.vue5
-rw-r--r--src/components/global_notice_list/global_notice_list.js8
-rw-r--r--src/components/global_notice_list/global_notice_list.vue24
-rw-r--r--src/components/image_cropper/image_cropper.js21
-rw-r--r--src/components/image_cropper/image_cropper.vue21
-rw-r--r--src/components/importer/importer.js11
-rw-r--r--src/components/importer/importer.vue16
-rw-r--r--src/components/interface_language_switcher/interface_language_switcher.vue40
-rw-r--r--src/components/link-preview/link-preview.js15
-rw-r--r--src/components/link-preview/link-preview.vue17
-rw-r--r--src/components/login_form/login_form.js8
-rw-r--r--src/components/login_form/login_form.vue7
-rw-r--r--src/components/media_modal/media_modal.js10
-rw-r--r--src/components/media_modal/media_modal.vue10
-rw-r--r--src/components/media_upload/media_upload.js8
-rw-r--r--src/components/media_upload/media_upload.vue61
-rw-r--r--src/components/mfa_form/recovery_form.js8
-rw-r--r--src/components/mfa_form/recovery_form.vue19
-rw-r--r--src/components/mfa_form/totp_form.js9
-rw-r--r--src/components/mfa_form/totp_form.vue20
-rw-r--r--src/components/mobile_nav/mobile_nav.js12
-rw-r--r--src/components/mobile_nav/mobile_nav.vue280
-rw-r--r--src/components/mobile_post_status_button/mobile_post_status_button.js8
-rw-r--r--src/components/mobile_post_status_button/mobile_post_status_button.vue6
-rw-r--r--src/components/moderation_tools/moderation_tools.vue28
-rw-r--r--src/components/mute_card/mute_card.vue4
-rw-r--r--src/components/nav_panel/nav_panel.js23
-rw-r--r--src/components/nav_panel/nav_panel.vue170
-rw-r--r--src/components/notification/notification.js22
-rw-r--r--src/components/notification/notification.scss32
-rw-r--r--src/components/notification/notification.vue64
-rw-r--r--src/components/notifications/notifications.js9
-rw-r--r--src/components/notifications/notifications.scss31
-rw-r--r--src/components/notifications/notifications.vue21
-rw-r--r--src/components/panel_loading/panel_loading.vue20
-rw-r--r--src/components/password_reset/password_reset.js8
-rw-r--r--src/components/password_reset/password_reset.vue8
-rw-r--r--src/components/poll/poll.vue5
-rw-r--r--src/components/poll/poll_form.js12
-rw-r--r--src/components/poll/poll_form.vue43
-rw-r--r--src/components/popover/popover.js13
-rw-r--r--src/components/popover/popover.vue16
-rw-r--r--src/components/post_status_form/post_status_form.js33
-rw-r--r--src/components/post_status_form/post_status_form.vue146
-rw-r--r--src/components/react_button/react_button.js14
-rw-r--r--src/components/react_button/react_button.vue30
-rw-r--r--src/components/registration/registration.vue2
-rw-r--r--src/components/remote_follow/remote_follow.vue2
-rw-r--r--src/components/reply_button/reply_button.js4
-rw-r--r--src/components/reply_button/reply_button.vue59
-rw-r--r--src/components/retweet_button/retweet_button.js11
-rw-r--r--src/components/retweet_button/retweet_button.vue87
-rw-r--r--src/components/scope_selector/scope_selector.js15
-rw-r--r--src/components/scope_selector/scope_selector.vue58
-rw-r--r--src/components/search/search.js10
-rw-r--r--src/components/search/search.vue10
-rw-r--r--src/components/search_bar/search_bar.js14
-rw-r--r--src/components/search_bar/search_bar.vue96
-rw-r--r--src/components/settings_modal/settings_modal.vue4
-rw-r--r--src/components/settings_modal/settings_modal_content.js23
-rw-r--r--src/components/settings_modal/settings_modal_content.scss2
-rw-r--r--src/components/settings_modal/settings_modal_content.vue8
-rw-r--r--src/components/settings_modal/tabs/filtering_tab.js8
-rw-r--r--src/components/settings_modal/tabs/filtering_tab.vue6
-rw-r--r--src/components/settings_modal/tabs/general_tab.js14
-rw-r--r--src/components/settings_modal/tabs/general_tab.vue17
-rw-r--r--src/components/settings_modal/tabs/mutes_and_blocks_tab.vue10
-rw-r--r--src/components/settings_modal/tabs/notifications_tab.vue2
-rw-r--r--src/components/settings_modal/tabs/profile_tab.js68
-rw-r--r--src/components/settings_modal/tabs/profile_tab.scss13
-rw-r--r--src/components/settings_modal/tabs/profile_tab.vue79
-rw-r--r--src/components/settings_modal/tabs/security_tab/confirm.vue4
-rw-r--r--src/components/settings_modal/tabs/security_tab/mfa.vue10
-rw-r--r--src/components/settings_modal/tabs/security_tab/mfa_totp.vue4
-rw-r--r--src/components/settings_modal/tabs/security_tab/security_tab.js3
-rw-r--r--src/components/settings_modal/tabs/security_tab/security_tab.vue10
-rw-r--r--src/components/settings_modal/tabs/theme_tab/preview.vue47
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.js8
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.scss3
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.vue47
-rw-r--r--src/components/shadow_control/shadow_control.js14
-rw-r--r--src/components/shadow_control/shadow_control.vue33
-rw-r--r--src/components/side_drawer/side_drawer.js28
-rw-r--r--src/components/side_drawer/side_drawer.vue104
-rw-r--r--src/components/status/status.js44
-rw-r--r--src/components/status/status.scss61
-rw-r--r--src/components/status/status.vue112
-rw-r--r--src/components/status_content/status_content.js18
-rw-r--r--src/components/status_content/status_content.vue65
-rw-r--r--src/components/status_popover/status_popover.js6
-rw-r--r--src/components/status_popover/status_popover.vue6
-rw-r--r--src/components/still-image/still-image.vue11
-rw-r--r--src/components/tab_switcher/tab_switcher.js5
-rw-r--r--src/components/tab_switcher/tab_switcher.scss4
-rw-r--r--src/components/timeago/timeago.vue6
-rw-r--r--src/components/timeline/timeline.js37
-rw-r--r--src/components/timeline/timeline.vue51
-rw-r--r--src/components/timeline_menu/timeline_menu.js31
-rw-r--r--src/components/timeline_menu/timeline_menu.vue71
-rw-r--r--src/components/user_avatar/user_avatar.vue11
-rw-r--r--src/components/user_card/user_card.js16
-rw-r--r--src/components/user_card/user_card.vue93
-rw-r--r--src/components/user_list_popover/user_list_popover.js6
-rw-r--r--src/components/user_list_popover/user_list_popover.vue6
-rw-r--r--src/components/user_profile/user_profile.js8
-rw-r--r--src/components/user_profile/user_profile.vue13
-rw-r--r--src/components/user_reporting_modal/user_reporting_modal.js10
-rw-r--r--src/components/user_reporting_modal/user_reporting_modal.vue2
-rw-r--r--src/components/video_attachment/video_attachment.vue1
155 files changed, 2910 insertions, 1340 deletions
diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js
index 6d345bc7..e53c4f77 100644
--- a/src/components/account_actions/account_actions.js
+++ b/src/components/account_actions/account_actions.js
@@ -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({
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
index 987e94b7..ab5d1d29 100644
--- a/src/components/account_actions/account_actions.vue
+++ b/src/components/account_actions/account_actions.vue
@@ -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>
diff --git a/src/components/async_component_error/async_component_error.vue b/src/components/async_component_error/async_component_error.vue
index b68b98f9..b1b59638 100644
--- a/src/components/async_component_error/async_component_error.vue
+++ b/src/components/async_component_error/async_component_error.vue
@@ -8,7 +8,7 @@
{{ $t('general.error_retry') }}
</p>
<button
- class="btn"
+ class="btn button-default"
@click="retry"
>
{{ $t('general.retry') }}
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
index cb31020d..5f5779a0 100644
--- a/src/components/attachment/attachment.js
+++ b/src/components/attachment/attachment.js
@@ -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'
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
index 19c713d5..2c1c1682 100644
--- a/src/components/attachment/attachment.vue
+++ b/src/components/attachment/attachment.vue
@@ -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 {
diff --git a/src/components/block_card/block_card.vue b/src/components/block_card/block_card.vue
index 5b00b738..2fe66d4c 100644
--- a/src/components/block_card/block_card.vue
+++ b/src/components/block_card/block_card.vue
@@ -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"
>
diff --git a/src/components/chat/chat.js b/src/components/chat/chat.js
index 34e723d0..e57fcb91 100644
--- a/src/components/chat/chat.js
+++ b/src/components/chat/chat.js
@@ -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 } })
diff --git a/src/components/chat/chat.scss b/src/components/chat/chat.scss
index 012a1b1d..aef58495 100644
--- a/src/components/chat/chat.scss
+++ b/src/components/chat/chat.scss
@@ -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 {
diff --git a/src/components/chat/chat.vue b/src/components/chat/chat.vue
index 2e4538c8..94a0097c 100644
--- a/src/components/chat/chat.vue
+++ b/src/components/chat/chat.vue
@@ -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"
diff --git a/src/components/chat/chat_layout_utils.js b/src/components/chat/chat_layout_utils.js
index 609dc0c9..50a933ac 100644
--- a/src/components/chat/chat_layout_utils.js
+++ b/src/components/chat/chat_layout_utils.js
@@ -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
+}
diff --git a/src/components/chat_list/chat_list.vue b/src/components/chat_list/chat_list.vue
index 17e2f795..e23eec13 100644
--- a/src/components/chat_list/chat_list.vue
+++ b/src/components/chat_list/chat_list.vue
@@ -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>
diff --git a/src/components/chat_list_item/chat_list_item.vue b/src/components/chat_list_item/chat_list_item.vue
index 1f8ecdf6..cd3f436e 100644
--- a/src/components/chat_list_item/chat_list_item.vue
+++ b/src/components/chat_list_item/chat_list_item.vue
@@ -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>
diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js
index be4a7c89..bb380f87 100644
--- a/src/components/chat_message/chat_message.js
+++ b/src/components/chat_message/chat_message.js
@@ -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',
diff --git a/src/components/chat_message/chat_message.scss b/src/components/chat_message/chat_message.scss
index 7d4ff60c..e4351d3b 100644
--- a/src/components/chat_message/chat_message.scss
+++ b/src/components/chat_message/chat_message.scss
@@ -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);
diff --git a/src/components/chat_message/chat_message.vue b/src/components/chat_message/chat_message.vue
index e923d694..0777f880 100644
--- a/src/components/chat_message/chat_message.vue
+++ b/src/components/chat_message/chat_message.vue
@@ -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>
diff --git a/src/components/chat_message_date/chat_message_date.vue b/src/components/chat_message_date/chat_message_date.vue
index 79c346b6..98349b75 100644
--- a/src/components/chat_message_date/chat_message_date.vue
+++ b/src/components/chat_message_date/chat_message_date.vue
@@ -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' })
}
}
}
diff --git a/src/components/chat_new/chat_new.js b/src/components/chat_new/chat_new.js
index d023efc0..71585995 100644
--- a/src/components/chat_new/chat_new.js
+++ b/src/components/chat_new/chat_new.js
@@ -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: {
diff --git a/src/components/chat_new/chat_new.scss b/src/components/chat_new/chat_new.scss
index 11305444..5506143d 100644
--- a/src/components/chat_new/chat_new.scss
+++ b/src/components/chat_new/chat_new.scss
@@ -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;
}
}
diff --git a/src/components/chat_new/chat_new.vue b/src/components/chat_new/chat_new.vue
index 3333dbf9..f3894a3a 100644
--- a/src/components/chat_new/chat_new.vue
+++ b/src/components/chat_new/chat_new.vue
@@ -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"
diff --git a/src/components/chat_panel/chat_panel.js b/src/components/chat_panel/chat_panel.js
index f2e3adf0..c3887098 100644
--- a/src/components/chat_panel/chat_panel.js
+++ b/src/components/chat_panel/chat_panel.js
@@ -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' ],
diff --git a/src/components/chat_panel/chat_panel.vue b/src/components/chat_panel/chat_panel.vue
index 570435e7..7993c94d 100644
--- a/src/components/chat_panel/chat_panel.vue
+++ b/src/components/chat_panel/chat_panel.vue
@@ -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);
}
diff --git a/src/components/contrast_ratio/contrast_ratio.vue b/src/components/contrast_ratio/contrast_ratio.vue
index 9dc871b6..374cb9ba 100644
--- a/src/components/contrast_ratio/contrast_ratio.vue
+++ b/src/components/contrast_ratio/contrast_ratio.vue
@@ -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>
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index e0b9fcc5..353859b8 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -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;
diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js
new file mode 100644
index 00000000..e048f53d
--- /dev/null
+++ b/src/components/desktop_nav/desktop_nav.js
@@ -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')
+ }
+ }
+}
diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss
new file mode 100644
index 00000000..2d468588
--- /dev/null
+++ b/src/components/desktop_nav/desktop_nav.scss
@@ -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;
+ }
+ }
+}
diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue
new file mode 100644
index 00000000..762aa610
--- /dev/null
+++ b/src/components/desktop_nav/desktop_nav.vue
@@ -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>
diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue
index 97aee243..3b5aec14 100644
--- a/src/components/domain_mute_card/domain_mute_card.vue
+++ b/src/components/domain_mute_card/domain_mute_card.vue
@@ -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">
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
index f0123447..2068a598 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -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: {
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index b9a74572..4becdc41 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -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">
diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
index 8330345b..14a2b41e 100644
--- a/src/components/emoji_input/suggestor.js
+++ b/src/components/emoji_input/suggestor.js
@@ -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)
- }
- if (firstChar === '@' && data.users) {
- return suggestUsers(data)(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 []
}
- 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
-
- 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)
+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 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 */
}
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
index 3ad80df3..2716d93f 100644
--- a/src/components/emoji_picker/emoji_picker.js
+++ b/src/components/emoji_picker/emoji_picker.js
@@ -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)
}
]
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index 8bd07e45..ec711758 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -82,7 +82,7 @@
&.active {
border-bottom: 4px solid;
- i {
+ svg {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index 191b9fa1..3262a3d9 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -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>
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 2f14b5b2..51d50359 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -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()"
diff --git a/src/components/export_import/export_import.vue b/src/components/export_import/export_import.vue
index ae00487f..8ffe34f8 100644
--- a/src/components/export_import/export_import.vue
+++ b/src/components/export_import/export_import.vue
@@ -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 }}
diff --git a/src/components/exporter/exporter.js b/src/components/exporter/exporter.js
index 8f507416..51912ac3 100644
--- a/src/components/exporter/exporter.js
+++ b/src/components/exporter/exporter.js
@@ -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: {
diff --git a/src/components/exporter/exporter.vue b/src/components/exporter/exporter.vue
index f5126dc1..d6a03088 100644
--- a/src/components/exporter/exporter.vue
+++ b/src/components/exporter/exporter.vue
@@ -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;
}
}
diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js
index 5e0c36bb..dd45b6b9 100644
--- a/src/components/extra_buttons/extra_buttons.js
+++ b/src/components/extra_buttons/extra_buttons.js
@@ -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: {
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index 7a4e8642..e845d8fc 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -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>
diff --git a/src/components/favorite_button/favorite_button.js b/src/components/favorite_button/favorite_button.js
index 5014d84f..5cd05f73 100644
--- a/src/components/favorite_button/favorite_button.js
+++ b/src/components/favorite_button/favorite_button.js
@@ -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'])
}
}
diff --git a/src/components/favorite_button/favorite_button.vue b/src/components/favorite_button/favorite_button.vue
index fbc90f84..dce25e24 100644
--- a/src/components/favorite_button/favorite_button.vue
+++ b/src/components/favorite_button/favorite_button.vue
@@ -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;
}
-}
-.favorite-button.icon-star {
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+ .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);
+ }
+ }
}
</style>
diff --git a/src/components/features_panel/features_panel.js b/src/components/features_panel/features_panel.js
index 620a85ea..8b142d08 100644
--- a/src/components/features_panel/features_panel.js
+++ b/src/components/features_panel/features_panel.js
@@ -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) }
}
}
diff --git a/src/components/features_panel/features_panel.vue b/src/components/features_panel/features_panel.vue
index 608b11c8..9605d09d 100644
--- a/src/components/features_panel/features_panel.vue
+++ b/src/components/features_panel/features_panel.vue
@@ -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>
diff --git a/src/components/follow_button/follow_button.vue b/src/components/follow_button/follow_button.vue
index bfdc137b..7f85f1d7 100644
--- a/src/components/follow_button/follow_button.vue
+++ b/src/components/follow_button/follow_button.vue
@@ -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"
diff --git a/src/components/follow_request_card/follow_request_card.vue b/src/components/follow_request_card/follow_request_card.vue
index b217b8ed..1b12ba4b 100644
--- a/src/components/follow_request_card/follow_request_card.vue
+++ b/src/components/follow_request_card/follow_request_card.vue
@@ -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') }}
diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js
index 8e2b0e45..6274780b 100644
--- a/src/components/font_control/font_control.js
+++ b/src/components/font_control/font_control.js
@@ -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: [
diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue
index 61f0384b..dd117ec0 100644
--- a/src/components/font_control/font_control.vue
+++ b/src/components/font_control/font_control.vue
@@ -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"
diff --git a/src/components/global_notice_list/global_notice_list.js b/src/components/global_notice_list/global_notice_list.js
index 3af29c23..e93fba75 100644
--- a/src/components/global_notice_list/global_notice_list.js
+++ b/src/components/global_notice_list/global_notice_list.js
@@ -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: {
diff --git a/src/components/global_notice_list/global_notice_list.vue b/src/components/global_notice_list/global_notice_list.vue
index 0e4285cc..049e23db 100644
--- a/src/components/global_notice_list/global_notice_list.vue
+++ b/src/components/global_notice_list/global_notice_list.vue
@@ -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>
diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js
index 01361e25..e8d5ec6d 100644
--- a/src/components/image_cropper/image_cropper.js
+++ b/src/components/image_cropper/image_cropper.js
@@ -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 () {
diff --git a/src/components/image_cropper/image_cropper.vue b/src/components/image_cropper/image_cropper.vue
index 4e1b5927..8c48a387 100644
--- a/src/components/image_cropper/image_cropper.vue
+++ b/src/components/image_cropper/image_cropper.vue
@@ -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>
diff --git a/src/components/importer/importer.js b/src/components/importer/importer.js
index c5f9e4d2..59f9beb1 100644
--- a/src/components/importer/importer.js
+++ b/src/components/importer/importer.js
@@ -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: {
diff --git a/src/components/importer/importer.vue b/src/components/importer/importer.vue
index ed923d59..210823f5 100644
--- a/src/components/importer/importer.vue
+++ b/src/components/importer/importer.vue
@@ -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>
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index dd6800a3..dc3bd408 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -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)
}
}
}
diff --git a/src/components/link-preview/link-preview.js b/src/components/link-preview/link-preview.js
index 444aafbe..add7c563 100644
--- a/src/components/link-preview/link-preview.js
+++ b/src/components/link-preview/link-preview.js
@@ -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) {
diff --git a/src/components/link-preview/link-preview.vue b/src/components/link-preview/link-preview.vue
index 69171977..d3ca39b8 100644
--- a/src/components/link-preview/link-preview.vue
+++ b/src/components/link-preview/link-preview.vue
@@ -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;
diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js
index 0d8f1da6..638bd812 100644
--- a/src/components/login_form/login_form.js
+++ b/src/components/login_form/login_form.js
@@ -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: () => ({
diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue
index b4fdcefb..bfabb946 100644
--- a/src/components/login_form/login_form.vue
+++ b/src/components/login_form/login_form.vue
@@ -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>
diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index 24764e80..e7384c93 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -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: {
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 46931667..ea7f7a7f 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -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>
diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
index 7b8a76cc..669d8190 100644
--- a/src/components/media_upload/media_upload.js
+++ b/src/components/media_upload/media_upload.js
@@ -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 {
diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue
index c8865d77..e955aa72 100644
--- a/src/components/media_upload/media_upload.vue
+++ b/src/components/media_upload/media_upload.vue
@@ -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>
diff --git a/src/components/mfa_form/recovery_form.js b/src/components/mfa_form/recovery_form.js
index b25c65dd..01a62a50 100644
--- a/src/components/mfa_form/recovery_form.js
+++ b/src/components/mfa_form/recovery_form.js
@@ -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: () => ({
diff --git a/src/components/mfa_form/recovery_form.vue b/src/components/mfa_form/recovery_form.vue
index 57294630..0bf68e27 100644
--- a/src/components/mfa_form/recovery_form.vue
+++ b/src/components/mfa_form/recovery_form.vue
@@ -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>
diff --git a/src/components/mfa_form/totp_form.js b/src/components/mfa_form/totp_form.js
index b774f2d0..6ee823ed 100644
--- a/src/components/mfa_form/totp_form.js
+++ b/src/components/mfa_form/totp_form.js
@@ -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,
diff --git a/src/components/mfa_form/totp_form.vue b/src/components/mfa_form/totp_form.vue
index a344b395..79230148 100644
--- a/src/components/mfa_form/totp_form.vue
+++ b/src/components/mfa_form/totp_form.vue
@@ -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>
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index b2b5d264..9e736cfb 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -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: {
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index abd95f09..0f0ea457 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -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;
-}
+.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-nav-button {
- display: flex;
- justify-content: center;
- width: 50px;
- position: relative;
- cursor: pointer;
-}
+ .mobile-inner-nav {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ }
-.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-nav-button {
+ display: inline-block;
+ text-align: center;
+ padding: 0 1em;
+ position: relative;
+ cursor: pointer;
+ }
-.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%);
+ .site-name {
+ padding: 0 .3em;
+ display: inline-block;
}
-}
-.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;
+ .item {
+ /* moslty just to get rid of extra whitespaces */
+ display: flex;
}
-}
-.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;
+ .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;
+ }
}
}
}
diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.js b/src/components/mobile_post_status_button/mobile_post_status_button.js
index 6348277b..366ea89c 100644
--- a/src/components/mobile_post_status_button/mobile_post_status_button.js
+++ b/src/components/mobile_post_status_button/mobile_post_status_button.js
@@ -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',
diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.vue b/src/components/mobile_post_status_button/mobile_post_status_button.vue
index 9cf45de3..767f8244 100644
--- a/src/components/mobile_post_status_button/mobile_post_status_button.vue
+++ b/src/components/mobile_post_status_button/mobile_post_status_button.vue
@@ -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);
diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue
index 60fa6ceb..5c7b82ec 100644
--- a/src/components/moderation_tools/moderation_tools.vue
+++ b/src/components/moderation_tools/moderation_tools.vue
@@ -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(&quot;admin&quot;)"
>
{{ $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(&quot;moderator&quot;)"
>
{{ $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') }}
diff --git a/src/components/mute_card/mute_card.vue b/src/components/mute_card/mute_card.vue
index 9611fb82..ca33c6c5 100644
--- a/src/components/mute_card/mute_card.vue
+++ b/src/components/mute_card/mute_card.vue
@@ -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"
>
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index 623dfaec..81d49cc2 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -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) {
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 4f944c95..0c83d0fe 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -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);
+ ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
}
-}
-.nav-panel li:last-child {
- border: none;
-}
+ li {
+ position: relative;
+ border-bottom: 1px solid;
+ border-color: $fallback--border;
+ border-color: var(--border, $fallback--border);
+ padding: 0;
-.nav-panel a {
- display: block;
- padding: 0.8em 0.85em;
+ &: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);
+ }
- &: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);
+ &: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);
+ }
}
- &.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:last-child {
+ border: none;
+ }
+
+ a {
+ display: block;
+ box-sizing: border-box;
+ align-items: stretch;
+ height: 3.5em;
+ line-height: 3.5em;
+ padding: 0 1em;
&:hover {
- text-decoration: underline;
+ 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;
+ }
}
}
-}
-.nav-panel .button-icon {
- margin-right: 0.5em;
-}
+ .fa-scale-110 {
+ margin-right: 0.8em;
+ }
-.nav-panel .button-icon:before {
- width: 1.1em;
+ .badge {
+ position: absolute;
+ right: 0.6rem;
+ top: 1.25em;
+ }
}
</style>
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index bb906b50..4aa9affd 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -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 () {
diff --git a/src/components/notification/notification.scss b/src/components/notification/notification.scss
index d0e63d81..f5905560 100644
--- a/src/components/notification/notification.scss
+++ b/src/components/notification/notification.scss
@@ -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);
+ }
}
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 7fac3840..f56aa977 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -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()"
/>
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index d951e2a8..49258563 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -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', '')
}
}
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index c6b2a5b5..682ae127 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -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;
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index d477a41b..725d1ad4 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -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>
diff --git a/src/components/panel_loading/panel_loading.vue b/src/components/panel_loading/panel_loading.vue
index 4efebb3c..d916d8a6 100644
--- a/src/components/panel_loading/panel_loading.vue
+++ b/src/components/panel_loading/panel_loading.vue
@@ -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;
diff --git a/src/components/password_reset/password_reset.js b/src/components/password_reset/password_reset.js
index 5d21d720..3d94f5e7 100644
--- a/src/components/password_reset/password_reset.js
+++ b/src/components/password_reset/password_reset.js
@@ -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: () => ({
diff --git a/src/components/password_reset/password_reset.vue b/src/components/password_reset/password_reset.vue
index 713c9dce..a931cb5a 100644
--- a/src/components/password_reset/password_reset.vue
+++ b/src/components/password_reset/password_reset.vue
@@ -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;
}
}
diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue
index 5f54b416..42819c19 100644
--- a/src/components/poll/poll.vue
+++ b/src/components/poll/poll.vue
@@ -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"
diff --git a/src/components/poll/poll_form.js b/src/components/poll/poll_form.js
index df93f038..1f8df3f9 100644
--- a/src/components/poll/poll_form.js
+++ b/src/components/poll/poll_form.js
@@ -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',
diff --git a/src/components/poll/poll_form.vue b/src/components/poll/poll_form.vue
index d53f3837..09496105 100644
--- a/src/components/poll/poll_form.vue
+++ b/src/components/poll/poll_form.vue
@@ -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;
}
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
index 695f73b9..5e417fa0 100644
--- a/src/components/popover/popover.js
+++ b/src/components/popover/popover.js
@@ -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
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
index 5c99c509..2252c68f 100644
--- a/src/components/popover/popover.vue
+++ b/src/components/popover/popover.vue
@@ -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);
}
}
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index ad149506..4148381c 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -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)
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index d67d9ae9..73f6a4f1 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -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; }
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index dd71e546..5e7b7580 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -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])) {
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index 0b34add1..ac940b98 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -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);
}
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index a83ca1e5..100df0d6 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -211,7 +211,7 @@
<button
:disabled="isPending"
type="submit"
- class="btn btn-default"
+ class="btn button-default"
>
{{ $t('general.submit') }}
</button>
diff --git a/src/components/remote_follow/remote_follow.vue b/src/components/remote_follow/remote_follow.vue
index cb1c2a1b..be827400 100644
--- a/src/components/remote_follow/remote_follow.vue
+++ b/src/components/remote_follow/remote_follow.vue
@@ -16,7 +16,7 @@
>
<button
click="submit"
- class="remote-button"
+ class="button-default remote-button"
>
{{ $t('user_card.remote_follow') }}
</button>
diff --git a/src/components/reply_button/reply_button.js b/src/components/reply_button/reply_button.js
index 22957650..c7bd2a2b 100644
--- a/src/components/reply_button/reply_button.js
+++ b/src/components/reply_button/reply_button.js
@@ -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',
diff --git a/src/components/reply_button/reply_button.vue b/src/components/reply_button/reply_button.vue
index b2904b5c..c17041da 100644
--- a/src/components/reply_button/reply_button.vue
+++ b/src/components/reply_button/reply_button.vue
@@ -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>
diff --git a/src/components/retweet_button/retweet_button.js b/src/components/retweet_button/retweet_button.js
index 5a41f22d..2103fd0b 100644
--- a/src/components/retweet_button/retweet_button.js
+++ b/src/components/retweet_button/retweet_button.js
@@ -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
}
diff --git a/src/components/retweet_button/retweet_button.vue b/src/components/retweet_button/retweet_button.vue
index 074f7747..859ce499 100644
--- a/src/components/retweet_button/retweet_button.vue
+++ b/src/components/retweet_button/retweet_button.vue
@@ -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>
diff --git a/src/components/scope_selector/scope_selector.js b/src/components/scope_selector/scope_selector.js
index e9ccdefc..74bf7284 100644
--- a/src/components/scope_selector/scope_selector.js
+++ b/src/components/scope_selector/scope_selector.js
@@ -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',
diff --git a/src/components/scope_selector/scope_selector.vue b/src/components/scope_selector/scope_selector.vue
index 291236f2..66ac612e 100644
--- a/src/components/scope_selector/scope_selector.vue
+++ b/src/components/scope_selector/scope_selector.vue
@@ -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;
+.ScopeSelector {
+
+ .scope {
+ display: inline-block;
cursor: pointer;
+ min-width: 1.3em;
+ min-height: 1.3em;
+ text-align: center;
- &.selected {
+ &.selected svg {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
diff --git a/src/components/search/search.js b/src/components/search/search.js
index 8e903052..b62bc2c5 100644
--- a/src/components/search/search.js
+++ b/src/components/search/search.js
@@ -2,6 +2,16 @@ import FollowCard from '../follow_card/follow_card.vue'
import Conversation from '../conversation/conversation.vue'
import Status from '../status/status.vue'
import map from 'lodash/map'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faCircleNotch,
+ faSearch
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faCircleNotch,
+ faSearch
+)
const Search = {
components: {
diff --git a/src/components/search/search.vue b/src/components/search/search.vue
index 746bbaa2..a6503c9f 100644
--- a/src/components/search/search.vue
+++ b/src/components/search/search.vue
@@ -14,17 +14,21 @@
@keyup.enter="newQuery(searchTerm)"
>
<button
- class="btn search-button"
+ class="btn button-default search-button"
@click="newQuery(searchTerm)"
>
- <i class="icon-search" />
+ <FAIcon icon="search" />
</button>
</div>
<div
v-if="loading"
class="text-center loading-icon"
>
- <i class="icon-spin3 animate-spin" />
+ <FAIcon
+ icon="circle-notch"
+ spin
+ size="lg"
+ />
</div>
<div v-else-if="loaded">
<div class="search-nav-heading">
diff --git a/src/components/search_bar/search_bar.js b/src/components/search_bar/search_bar.js
index d7d85676..551649c7 100644
--- a/src/components/search_bar/search_bar.js
+++ b/src/components/search_bar/search_bar.js
@@ -1,9 +1,19 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faTimes,
+ faSearch
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faTimes,
+ faSearch
+)
+
const SearchBar = {
data: () => ({
searchTerm: undefined,
hidden: true,
- error: false,
- loading: false
+ error: false
}),
watch: {
'$route': function (route) {
diff --git a/src/components/search_bar/search_bar.vue b/src/components/search_bar/search_bar.vue
index 4d5a1aec..6cf9179e 100644
--- a/src/components/search_bar/search_bar.vue
+++ b/src/components/search_bar/search_bar.vue
@@ -1,40 +1,50 @@
<template>
- <div>
- <div class="search-bar-container">
- <i
- v-if="loading"
- class="icon-spin4 finder-icon animate-spin-slow"
+ <div
+ class="SearchBar"
+ :class="{ '-expanded': !hidden }"
+ >
+ <button
+ v-if="hidden"
+ class="button-unstyled nav-icon"
+ :title="$t('nav.search')"
+ @click.prevent.stop="toggleHidden"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="search"
/>
- <a
- v-if="hidden"
- href="#"
- :title="$t('nav.search')"
- ><i
- class="button-icon icon-search"
+ </button>
+ <template v-else>
+ <input
+ id="search-bar-input"
+ ref="searchInput"
+ v-model="searchTerm"
+ class="search-bar-input"
+ :placeholder="$t('nav.search')"
+ type="text"
+ @keyup.enter="find(searchTerm)"
+ >
+ <button
+ class="button-default search-button"
+ @click="find(searchTerm)"
+ >
+ <FAIcon
+ fixed-width
+ icon="search"
+ />
+ </button>
+ <button
+ class="button-unstyled cancel-search"
@click.prevent.stop="toggleHidden"
- /></a>
- <template v-else>
- <input
- id="search-bar-input"
- ref="searchInput"
- v-model="searchTerm"
- class="search-bar-input"
- :placeholder="$t('nav.search')"
- type="text"
- @keyup.enter="find(searchTerm)"
- >
- <button
- class="btn search-button"
- @click="find(searchTerm)"
- >
- <i class="icon-search" />
- </button>
- <i
- class="button-icon icon-cancel"
- @click.prevent.stop="toggleHidden"
+ >
+ <FAIcon
+ fixed-width
+ icon="times"
+ class="cancel-icon fa-scale-110 fa-old-padding"
/>
- </template>
- </div>
+ </button>
+ </template>
</div>
</template>
@@ -43,30 +53,32 @@
<style lang="scss">
@import '../../_variables.scss';
-.search-bar-container {
- max-width: 100%;
+.SearchBar {
display: inline-flex;
align-items: baseline;
vertical-align: baseline;
justify-content: flex-end;
+ &.-expanded {
+ width: 100%;
+ }
+
.search-bar-input,
.search-button {
height: 29px;
}
.search-bar-input {
- // TODO: do this properly without a rough guesstimate of 2 icons + paddings
- max-width: calc(100% - 30px - 30px - 20px);
+ flex: 1 0 auto;
}
- .search-button {
- margin-left: .5em;
- margin-right: .5em;
+ .cancel-search {
+ height: 50px;
}
- .icon-cancel {
- cursor: pointer;
+ .cancel-icon {
+ color: $fallback--text;
+ color: var(--btnTopBarText, $fallback--text);
}
}
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
index 6bc64ed0..552ca41f 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -30,13 +30,13 @@
</template>
</transition>
<button
- class="btn"
+ class="btn button-default"
@click="peekModal"
>
{{ $t('general.peek') }}
</button>
<button
- class="btn"
+ class="btn button-default"
@click="closeModal"
>
{{ $t('general.close') }}
diff --git a/src/components/settings_modal/settings_modal_content.js b/src/components/settings_modal/settings_modal_content.js
index ef1a5ffa..9dcf1b5a 100644
--- a/src/components/settings_modal/settings_modal_content.js
+++ b/src/components/settings_modal/settings_modal_content.js
@@ -10,6 +10,29 @@ import GeneralTab from './tabs/general_tab.vue'
import VersionTab from './tabs/version_tab.vue'
import ThemeTab from './tabs/theme_tab/theme_tab.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faWrench,
+ faUser,
+ faFilter,
+ faPaintBrush,
+ faBell,
+ faDownload,
+ faEyeSlash,
+ faInfo
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faWrench,
+ faUser,
+ faFilter,
+ faPaintBrush,
+ faBell,
+ faDownload,
+ faEyeSlash,
+ faInfo
+)
+
const SettingsModalContent = {
components: {
TabSwitcher,
diff --git a/src/components/settings_modal/settings_modal_content.scss b/src/components/settings_modal/settings_modal_content.scss
index a3fef1cf..f066234c 100644
--- a/src/components/settings_modal/settings_modal_content.scss
+++ b/src/components/settings_modal/settings_modal_content.scss
@@ -31,7 +31,7 @@
}
.unavailable,
- .unavailable i {
+ .unavailable svg {
color: var(--cRed, $fallback--cRed);
color: $fallback--cRed;
}
diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_content.vue
index bc30a0ff..c9ed2a38 100644
--- a/src/components/settings_modal/settings_modal_content.vue
+++ b/src/components/settings_modal/settings_modal_content.vue
@@ -37,7 +37,7 @@
</div>
<div
:label="$t('settings.theme')"
- icon="brush"
+ icon="paint-brush"
data-tab-name="theme"
>
<ThemeTab />
@@ -45,7 +45,7 @@
<div
v-if="isLoggedIn"
:label="$t('settings.notifications')"
- icon="bell-ringing-o"
+ icon="bell"
data-tab-name="notifications"
>
<NotificationsTab />
@@ -62,14 +62,14 @@
v-if="isLoggedIn"
:label="$t('settings.mutes_and_blocks')"
:fullHeight="true"
- icon="eye-off"
+ icon="eye-slash"
data-tab-name="mutesAndBlocks"
>
<MutesAndBlocksTab />
</div>
<div
:label="$t('settings.version.title')"
- icon="info-circled"
+ icon="info"
data-tab-name="version"
>
<VersionTab />
diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js
index 04273211..6e95f7af 100644
--- a/src/components/settings_modal/tabs/filtering_tab.js
+++ b/src/components/settings_modal/tabs/filtering_tab.js
@@ -2,6 +2,14 @@ import { filter, trim } from 'lodash'
import BooleanSetting from '../helpers/boolean_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faChevronDown
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faChevronDown
+)
const FilteringTab = {
data () {
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
index 6c42718b..18dd4be9 100644
--- a/src/components/settings_modal/tabs/filtering_tab.vue
+++ b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -53,7 +53,10 @@
<option value="following">{{ $t('settings.reply_visibility_following') }}</option>
<option value="self">{{ $t('settings.reply_visibility_self') }}</option>
</select>
- <i class="icon-down-open" />
+ <FAIcon
+ class="select-down-icon"
+ icon="chevron-down"
+ />
</label>
</div>
<div>
@@ -72,6 +75,7 @@
<p>{{ $t('settings.filtering_explanation') }}</p>
<textarea
id="muteWords"
+ class="resize-height"
v-model="muteWordsString"
/>
</div>
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index 679ef684..2db523be 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -2,6 +2,16 @@ import BooleanSetting from '../helpers/boolean_setting.vue'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faChevronDown,
+ faGlobe
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faChevronDown,
+ faGlobe
+)
const GeneralTab = {
data () {
@@ -24,6 +34,10 @@ const GeneralTab = {
return this.$store.state.instance.postFormats || []
},
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
+ instanceWallpaperUsed () {
+ return this.$store.state.instance.background &&
+ !this.$store.state.users.currentUser.background_image
+ },
...SharedComputedObject()
}
}
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index f794ad6b..92cd2069 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -11,6 +11,11 @@
{{ $t('settings.hide_isp') }}
</BooleanSetting>
</li>
+ <li v-if="instanceWallpaperUsed">
+ <Checkbox v-model="hideInstanceWallpaper">
+ {{ $t('settings.hide_wallpaper') }}
+ </Checkbox>
+ </li>
</ul>
</div>
<div class="setting-item">
@@ -103,7 +108,10 @@
{{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }}
</option>
</select>
- <i class="icon-down-open" />
+ <FAIcon
+ class="select-down-icon"
+ icon="chevron-down"
+ />
</label>
</div>
</li>
@@ -127,7 +135,10 @@
{{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }}
</option>
</select>
- <i class="icon-down-open" />
+ <FAIcon
+ class="select-down-icon"
+ icon="chevron-down"
+ />
</label>
</div>
</li>
@@ -222,7 +233,7 @@
v-if="!loopSilentAvailable"
class="unavailable"
>
- <i class="icon-globe" />! {{ $t('settings.limited_availability') }}
+ <FAIcon icon="globe" />! {{ $t('settings.limited_availability') }}
</div>
</li>
</ul>
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
index 5a1cf2c0..63d36bf9 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
@@ -27,7 +27,7 @@
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
- class="btn btn-default bulk-action-button"
+ class="btn button-default bulk-action-button"
:click="() => blockUsers(selected)"
>
{{ $t('user_card.block') }}
@@ -37,7 +37,7 @@
</ProgressButton>
<ProgressButton
v-if="selected.length > 0"
- class="btn btn-default"
+ class="btn button-default"
:click="() => unblockUsers(selected)"
>
{{ $t('user_card.unblock') }}
@@ -85,7 +85,7 @@
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
- class="btn btn-default"
+ class="btn button-default"
:click="() => muteUsers(selected)"
>
{{ $t('user_card.mute') }}
@@ -95,7 +95,7 @@
</ProgressButton>
<ProgressButton
v-if="selected.length > 0"
- class="btn btn-default"
+ class="btn button-default"
:click="() => unmuteUsers(selected)"
>
{{ $t('user_card.unmute') }}
@@ -141,7 +141,7 @@
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
- class="btn btn-default"
+ class="btn button-default"
:click="() => unmuteDomains(selected)"
>
{{ $t('domain_mute_card.unmute') }}
diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue
index 86eed3f5..8f8fe48e 100644
--- a/src/components/settings_modal/tabs/notifications_tab.vue
+++ b/src/components/settings_modal/tabs/notifications_tab.vue
@@ -21,7 +21,7 @@
<p>{{ $t('settings.notification_mutes') }}</p>
<p>{{ $t('settings.notification_blocks') }}</p>
<button
- class="btn btn-default"
+ class="btn button-default"
@click="updateNotificationSettings"
>
{{ $t('general.submit') }}
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
index bd6bef6a..9709424c 100644
--- a/src/components/settings_modal/tabs/profile_tab.js
+++ b/src/components/settings_modal/tabs/profile_tab.js
@@ -8,6 +8,18 @@ import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
import suggestor from 'src/components/emoji_input/suggestor.js'
import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faTimes,
+ faPlus,
+ faCircleNotch
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faTimes,
+ faPlus,
+ faCircleNotch
+)
const ProfileTab = {
data () {
@@ -33,9 +45,7 @@ const ProfileTab = {
banner: null,
bannerPreview: null,
background: null,
- backgroundPreview: null,
- bannerUploadError: null,
- backgroundUploadError: null
+ backgroundPreview: null
}
},
components: {
@@ -56,8 +66,7 @@ const ProfileTab = {
...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 () {
@@ -67,10 +76,7 @@ const ProfileTab = {
] })
},
userSuggestor () {
- return suggestor({
- users: this.$store.state.users.users,
- updateUsersList: (query) => this.$store.dispatch('searchUsers', { query })
- })
+ return suggestor({ store: this.$store })
},
fieldsLimits () {
return this.$store.state.instance.fieldsLimits
@@ -154,18 +160,18 @@ const ProfileTab = {
if (file.size > this.$store.state.instance[slot + 'limit']) {
const filesize = fileSizeFormatService.fileSizeFormat(file.size)
const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit'])
- this[slot + 'UploadError'] = [
- this.$t('upload.error.base'),
- this.$t(
- 'upload.error.file_too_big',
- {
+ this.$store.dispatch('pushGlobalNotice', {
+ messageKey: 'upload.error.message',
+ messageArgs: [
+ this.$t('upload.error.file_too_big', {
filesize: filesize.num,
filesizeunit: filesize.unit,
allowedsize: allowedsize.num,
allowedsizeunit: allowedsize.unit
- }
- )
- ].join(' ')
+ })
+ ],
+ level: 'error'
+ })
return
}
// eslint-disable-next-line no-undef
@@ -205,8 +211,9 @@ const ProfileTab = {
that.$store.commit('setCurrentUser', user)
resolve()
})
- .catch((err) => {
- reject(new Error(that.$t('upload.error.base') + ' ' + err.message))
+ .catch((error) => {
+ that.displayUploadError(error)
+ reject(error)
})
}
@@ -227,24 +234,27 @@ const ProfileTab = {
this.$store.commit('setCurrentUser', user)
this.bannerPreview = null
})
- .catch((err) => {
- this.bannerUploadError = this.$t('upload.error.base') + ' ' + err.message
- })
- .then(() => { this.bannerUploading = false })
+ .catch(this.displayUploadError)
+ .finally(() => { this.bannerUploading = false })
},
submitBackground (background) {
if (!this.backgroundPreview && background !== '') { return }
this.backgroundUploading = true
- this.$store.state.api.backendInteractor.updateProfileImages({ background }).then((data) => {
- if (!data.error) {
+ this.$store.state.api.backendInteractor.updateProfileImages({ background })
+ .then((data) => {
this.$store.commit('addNewUsers', [data])
this.$store.commit('setCurrentUser', data)
this.backgroundPreview = null
- } else {
- this.backgroundUploadError = this.$t('upload.error.base') + data.error
- }
- this.backgroundUploading = false
+ })
+ .catch(this.displayUploadError)
+ .finally(() => { this.backgroundUploading = false })
+ },
+ displayUploadError (error) {
+ this.$store.dispatch('pushGlobalNotice', {
+ messageKey: 'upload.error.message',
+ messageArgs: [error.message],
+ level: 'error'
})
}
}
diff --git a/src/components/settings_modal/tabs/profile_tab.scss b/src/components/settings_modal/tabs/profile_tab.scss
index e14cf054..111eaed3 100644
--- a/src/components/settings_modal/tabs/profile_tab.scss
+++ b/src/components/settings_modal/tabs/profile_tab.scss
@@ -111,18 +111,17 @@
.profile-fields {
display: flex;
- &>.emoji-input {
+ & > .emoji-input {
flex: 1 1 auto;
- margin: 0 .2em .5em;
+ margin: 0 0.2em 0.5em;
min-width: 0;
}
- &>.icon-container {
+ .delete-field {
width: 20px;
-
- &>.icon-cancel {
- vertical-align: sub;
- }
+ align-self: center;
+ margin: 0 0.2em 0.5em;
+ padding: 0 0.5em;
}
}
}
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
index cf88c4e4..175a0219 100644
--- a/src/components/settings_modal/tabs/profile_tab.vue
+++ b/src/components/settings_modal/tabs/profile_tab.vue
@@ -11,7 +11,7 @@
<input
id="username"
v-model="newName"
- classname="name-changer"
+ class="name-changer"
>
</EmojiInput>
<p>{{ $t('settings.bio') }}</p>
@@ -22,7 +22,7 @@
>
<textarea
v-model="newBio"
- classname="bio"
+ class="bio resize-height"
/>
</EmojiInput>
<p>
@@ -124,24 +124,24 @@
:placeholder="$t('settings.profile_fields.value')"
>
</EmojiInput>
- <div
- class="icon-container"
+ <button
+ class="delete-field button-unstyled -hover-highlight"
+ @click="deleteField(i)"
>
- <i
+ <FAIcon
v-show="newFields.length > 1"
- class="icon-cancel"
- @click="deleteField(i)"
+ icon="times"
/>
- </div>
+ </button>
</div>
- <a
+ <button
v-if="newFields.length < maxFields"
- class="add-field faint"
+ class="add-field faint button-unstyled -hover-highlight"
@click="addField"
>
- <i class="icon-plus" />
+ <FAIcon icon="plus" />
{{ $t("settings.profile_fields.add_field") }}
- </a>
+ </button>
</div>
<p>
<Checkbox v-model="bot">
@@ -150,7 +150,7 @@
</p>
<button
:disabled="newName && newName.length === 0"
- class="btn btn-default"
+ class="btn button-default"
@click="updateProfile"
>
{{ $t('general.submit') }}
@@ -166,10 +166,11 @@
:src="user.profile_image_url_original"
class="current-avatar"
>
- <i
+ <FAIcon
v-if="!isDefaultAvatar && pickAvatarBtnVisible"
:title="$t('settings.reset_avatar')"
- class="reset-button icon-cancel"
+ class="reset-button"
+ icon="times"
type="button"
@click="resetAvatar"
/>
@@ -178,7 +179,7 @@
<button
v-show="pickAvatarBtnVisible"
id="pick-avatar"
- class="btn"
+ class="button-default btn"
type="button"
>
{{ $t('settings.upload_a_photo') }}
@@ -194,10 +195,11 @@
<h2>{{ $t('settings.profile_banner') }}</h2>
<div class="banner-background-preview">
<img :src="user.cover_photo">
- <i
+ <FAIcon
v-if="!isDefaultBanner"
:title="$t('settings.reset_profile_banner')"
- class="reset-button icon-cancel"
+ class="reset-button"
+ icon="times"
type="button"
@click="resetBanner"
/>
@@ -214,36 +216,29 @@
@change="uploadFile('banner', $event)"
>
</div>
- <i
+ <FAIcon
v-if="bannerUploading"
- class=" icon-spin4 animate-spin uploading"
+ class="uploading"
+ spin
+ icon="circle-notch"
/>
<button
v-else-if="bannerPreview"
- class="btn btn-default"
+ class="btn button-default"
@click="submitBanner(banner)"
>
{{ $t('general.submit') }}
</button>
- <div
- v-if="bannerUploadError"
- class="alert error"
- >
- Error: {{ bannerUploadError }}
- <i
- class="button-icon icon-cancel"
- @click="clearUploadError('banner')"
- />
- </div>
</div>
<div class="setting-item">
<h2>{{ $t('settings.profile_background') }}</h2>
<div class="banner-background-preview">
<img :src="user.background_image">
- <i
+ <FAIcon
v-if="!isDefaultBackground"
:title="$t('settings.reset_profile_background')"
- class="reset-button icon-cancel"
+ class="reset-button"
+ icon="times"
type="button"
@click="resetBackground"
/>
@@ -260,27 +255,19 @@
@change="uploadFile('background', $event)"
>
</div>
- <i
+ <FAIcon
v-if="backgroundUploading"
- class=" icon-spin4 animate-spin uploading"
+ class="uploading"
+ spin
+ icon="circle-notch"
/>
<button
v-else-if="backgroundPreview"
- class="btn btn-default"
+ class="btn button-default"
@click="submitBackground(background)"
>
{{ $t('general.submit') }}
</button>
- <div
- v-if="backgroundUploadError"
- class="alert error"
- >
- Error: {{ backgroundUploadError }}
- <i
- class="button-icon icon-cancel"
- @click="clearUploadError('background')"
- />
- </div>
</div>
</div>
</template>
diff --git a/src/components/settings_modal/tabs/security_tab/confirm.vue b/src/components/settings_modal/tabs/security_tab/confirm.vue
index 69b3811b..38c2a610 100644
--- a/src/components/settings_modal/tabs/security_tab/confirm.vue
+++ b/src/components/settings_modal/tabs/security_tab/confirm.vue
@@ -2,14 +2,14 @@
<div>
<slot />
<button
- class="btn btn-default"
+ class="btn button-default"
:disabled="disabled"
@click="confirm"
>
{{ $t('general.confirm') }}
</button>
<button
- class="btn btn-default"
+ class="btn button-default"
:disabled="disabled"
@click="cancel"
>
diff --git a/src/components/settings_modal/tabs/security_tab/mfa.vue b/src/components/settings_modal/tabs/security_tab/mfa.vue
index 7aca3c8d..455d17b6 100644
--- a/src/components/settings_modal/tabs/security_tab/mfa.vue
+++ b/src/components/settings_modal/tabs/security_tab/mfa.vue
@@ -29,7 +29,7 @@
/>
<button
v-if="!confirmNewBackupCodes"
- class="btn btn-default"
+ class="btn button-default"
@click="getBackupCodes"
>
{{ $t('settings.mfa.generate_new_recovery_codes') }}
@@ -61,7 +61,7 @@
<button
v-if="canSetupOTP"
- class="btn btn-default"
+ class="btn button-default"
@click="cancelSetup"
>
{{ $t('general.cancel') }}
@@ -69,7 +69,7 @@
<button
v-if="canSetupOTP"
- class="btn btn-default"
+ class="btn button-default"
@click="setupOTP"
>
{{ $t('settings.mfa.setup_otp') }}
@@ -108,13 +108,13 @@
>
<div class="confirm-otp-actions">
<button
- class="btn btn-default"
+ class="btn button-default"
@click="doConfirmOTP"
>
{{ $t('settings.mfa.confirm_and_enable') }}
</button>
<button
- class="btn btn-default"
+ class="btn button-default"
@click="cancelSetup"
>
{{ $t('general.cancel') }}
diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.vue b/src/components/settings_modal/tabs/security_tab/mfa_totp.vue
index c6f2cc7b..8e767bd0 100644
--- a/src/components/settings_modal/tabs/security_tab/mfa_totp.vue
+++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.vue
@@ -4,7 +4,7 @@
<strong>{{ $t('settings.mfa.otp') }}</strong>
<button
v-if="!isActivated"
- class="btn btn-default"
+ class="btn button-default"
@click="doActivate"
>
{{ $t('general.enable') }}
@@ -12,7 +12,7 @@
<button
v-if="isActivated"
- class="btn btn-default"
+ class="btn button-default"
:disabled="deactivate"
@click="doDeactivate"
>
diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.js b/src/components/settings_modal/tabs/security_tab/security_tab.js
index 811161a5..65d20fc0 100644
--- a/src/components/settings_modal/tabs/security_tab/security_tab.js
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.js
@@ -1,6 +1,7 @@
import ProgressButton from 'src/components/progress_button/progress_button.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Mfa from './mfa.vue'
+import localeService from 'src/services/locale/locale.service.js'
const SecurityTab = {
data () {
@@ -37,7 +38,7 @@ const SecurityTab = {
return {
id: oauthToken.id,
appName: oauthToken.app_name,
- validUntil: new Date(oauthToken.valid_until).toLocaleDateString()
+ validUntil: new Date(oauthToken.valid_until).toLocaleDateString(localeService.internalToBrowserLocale(this.$i18n.locale))
}
})
}
diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.vue b/src/components/settings_modal/tabs/security_tab/security_tab.vue
index 3d32d73d..56bea1f4 100644
--- a/src/components/settings_modal/tabs/security_tab/security_tab.vue
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.vue
@@ -19,7 +19,7 @@
>
</div>
<button
- class="btn btn-default"
+ class="btn button-default"
@click="changeEmail"
>
{{ $t('general.submit') }}
@@ -57,7 +57,7 @@
>
</div>
<button
- class="btn btn-default"
+ class="btn button-default"
@click="changePassword"
>
{{ $t('general.submit') }}
@@ -92,7 +92,7 @@
<td>{{ oauthToken.validUntil }}</td>
<td class="actions">
<button
- class="btn btn-default"
+ class="btn button-default"
@click="revokeToken(oauthToken.id)"
>
{{ $t('settings.revoke_token') }}
@@ -116,7 +116,7 @@
type="password"
>
<button
- class="btn btn-default"
+ class="btn button-default"
@click="deleteAccount"
>
{{ $t('settings.delete_account') }}
@@ -130,7 +130,7 @@
</p>
<button
v-if="!deletingAccount"
- class="btn btn-default"
+ class="btn button-default"
@click="confirmDelete"
>
{{ $t('general.submit') }}
diff --git a/src/components/settings_modal/tabs/theme_tab/preview.vue b/src/components/settings_modal/tabs/theme_tab/preview.vue
index 9d984659..7ac7b9d3 100644
--- a/src/components/settings_modal/tabs/theme_tab/preview.vue
+++ b/src/components/settings_modal/tabs/theme_tab/preview.vue
@@ -15,7 +15,7 @@
<span class="alert error">
{{ $t('settings.style.preview.error') }}
</span>
- <button class="btn">
+ <button class="btn button-default">
{{ $t('settings.style.preview.button') }}
</button>
</div>
@@ -39,21 +39,29 @@
</i18n>
<div class="icons">
- <i
+ <FAIcon
+ fixed-width
style="color: var(--cBlue)"
- class="button-icon icon-reply"
+ class="fa-scale-110 fa-old-padding"
+ icon="reply"
/>
- <i
+ <FAIcon
+ fixed-width
style="color: var(--cGreen)"
- class="button-icon icon-retweet"
+ class="fa-scale-110 fa-old-padding"
+ icon="retweet"
/>
- <i
+ <FAIcon
+ fixed-width
style="color: var(--cOrange)"
- class="button-icon icon-star"
+ class="fa-scale-110 fa-old-padding"
+ icon="star"
/>
- <i
+ <FAIcon
+ fixed-width
style="color: var(--cRed)"
- class="button-icon icon-cancel"
+ class="fa-scale-110 fa-old-padding"
+ icon="times"
/>
</div>
</div>
@@ -94,7 +102,7 @@
>
<label for="preview_checkbox">{{ $t('settings.style.preview.checkbox') }}</label>
</span>
- <button class="btn">
+ <button class="btn button-default">
{{ $t('settings.style.preview.button') }}
</button>
</div>
@@ -103,6 +111,25 @@
</div>
</template>
+<script>
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faTimes,
+ faStar,
+ faRetweet,
+ faReply
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faTimes,
+ faStar,
+ faRetweet,
+ faReply
+)
+
+export default {}
+</script>
+
<style lang="scss">
.preview-container {
position: relative;
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
index e3c5e80a..6cf75fe7 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.js
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
@@ -35,6 +35,14 @@ import ExportImport from 'src/components/export_import/export_import.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Preview from './preview.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faChevronDown
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faChevronDown
+)
// List of color values used in v1
const v1OnlyNames = [
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
index 926eceff..1b7d9f31 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
@@ -165,7 +165,8 @@
border-color: var(--border, $fallback--border);
margin: 1em 0;
padding: 1em;
- background: var(--body-background-image);
+ background-color: var(--wallpaper);
+ background-image: var(--body-background-image);
background-size: cover;
background-position: 50% 50%;
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
index 5328c350..b8add42f 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
@@ -12,13 +12,13 @@
<div class="buttons">
<template v-if="themeWarning.type === 'snapshot_source_mismatch'">
<button
- class="btn"
+ class="btn button-default"
@click="forceLoad"
>
{{ $t('settings.style.switcher.use_source') }}
</button>
<button
- class="btn"
+ class="btn button-default"
@click="forceSnapshot"
>
{{ $t('settings.style.switcher.use_snapshot') }}
@@ -26,7 +26,7 @@
</template>
<template v-else-if="themeWarning.noActionsPossible">
<button
- class="btn"
+ class="btn button-default"
@click="dismissWarning"
>
{{ $t('general.dismiss') }}
@@ -34,13 +34,13 @@
</template>
<template v-else>
<button
- class="btn"
+ class="btn button-default"
@click="forceLoad"
>
{{ $t('settings.style.switcher.load_theme') }}
</button>
<button
- class="btn"
+ class="btn button-default"
@click="dismissWarning"
>
{{ $t('settings.style.switcher.keep_as_is') }}
@@ -80,7 +80,10 @@
{{ style[0] || style.name }}
</option>
</select>
- <i class="icon-down-open" />
+ <FAIcon
+ class="select-down-icon"
+ icon="chevron-down"
+ />
</label>
</div>
</template>
@@ -128,13 +131,13 @@
<p>{{ $t('settings.theme_help') }}</p>
<div class="tab-header-buttons">
<button
- class="btn"
+ class="btn button-default"
@click="clearOpacity"
>
{{ $t('settings.style.switcher.clear_opacity') }}
</button>
<button
- class="btn"
+ class="btn button-default"
@click="clearV1"
>
{{ $t('settings.style.switcher.clear_all') }}
@@ -235,13 +238,13 @@
<div class="tab-header">
<p>{{ $t('settings.theme_help') }}</p>
<button
- class="btn"
+ class="btn button-default"
@click="clearOpacity"
>
{{ $t('settings.style.switcher.clear_opacity') }}
</button>
<button
- class="btn"
+ class="btn button-default"
@click="clearV1"
>
{{ $t('settings.style.switcher.clear_all') }}
@@ -614,6 +617,15 @@
/>
</div>
<div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.wallpaper') }}</h4>
+ <ColorInput
+ v-model="wallpaperColorLocal"
+ name="wallpaper"
+ :label="$t('settings.style.advanced_colors.wallpaper')"
+ :fallback="previewTheme.colors.wallpaper"
+ />
+ </div>
+ <div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.poll') }}</h4>
<ColorInput
v-model="pollColorLocal"
@@ -803,7 +815,7 @@
<div class="tab-header">
<p>{{ $t('settings.radii_help') }}</p>
<button
- class="btn"
+ class="btn button-default"
@click="clearRoundness"
>
{{ $t('settings.style.switcher.clear_all') }}
@@ -907,7 +919,10 @@
{{ $t('settings.style.shadows.components.' + shadow) }}
</option>
</select>
- <i class="icon-down-open" />
+ <FAIcon
+ class="select-down-icon"
+ icon="chevron-down"
+ />
</label>
</div>
<div class="override">
@@ -930,7 +945,7 @@
/>
</div>
<button
- class="btn"
+ class="btn button-default"
@click="clearShadows"
>
{{ $t('settings.style.switcher.clear_all') }}
@@ -974,7 +989,7 @@
<div class="tab-header">
<p>{{ $t('settings.style.fonts.help') }}</p>
<button
- class="btn"
+ class="btn button-default"
@click="clearFonts"
>
{{ $t('settings.style.switcher.clear_all') }}
@@ -1011,14 +1026,14 @@
<div class="apply-container">
<button
- class="btn submit"
+ class="btn button-default submit"
:disabled="!themeValid"
@click="setCustomTheme"
>
{{ $t('general.apply') }}
</button>
<button
- class="btn"
+ class="btn button-default"
@click="clearAll"
>
{{ $t('settings.style.switcher.reset') }}
diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js
index f9e7b985..800c39d5 100644
--- a/src/components/shadow_control/shadow_control.js
+++ b/src/components/shadow_control/shadow_control.js
@@ -2,6 +2,20 @@ import ColorInput from '../color_input/color_input.vue'
import OpacityInput from '../opacity_input/opacity_input.vue'
import { getCssShadow } from '../../services/style_setter/style_setter.js'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faTimes,
+ faChevronDown,
+ faChevronUp,
+ faPlus
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faChevronDown,
+ faChevronUp,
+ faTimes,
+ faPlus
+)
const toModel = (object = {}) => ({
x: 0,
diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue
index 815a9e59..37d491f0 100644
--- a/src/components/shadow_control/shadow_control.vue
+++ b/src/components/shadow_control/shadow_control.vue
@@ -78,35 +78,50 @@
{{ $t('settings.style.shadows.shadow_id', { value: index }) }}
</option>
</select>
- <i class="icon-down-open" />
+ <FAIcon
+ icon="chevron-down"
+ class="select-down-icon"
+ />
</label>
<button
- class="btn btn-default"
+ class="btn button-default"
:disabled="!ready || !present"
@click="del"
>
- <i class="icon-cancel" />
+ <FAIcon
+ fixed-width
+ icon="times"
+ />
</button>
<button
- class="btn btn-default"
+ class="btn button-default"
:disabled="!moveUpValid"
@click="moveUp"
>
- <i class="icon-up-open" />
+ <FAIcon
+ fixed-width
+ icon="chevron-up"
+ />
</button>
<button
- class="btn btn-default"
+ class="btn button-default"
:disabled="!moveDnValid"
@click="moveDn"
>
- <i class="icon-down-open" />
+ <FAIcon
+ fixed-width
+ icon="chevron-down"
+ />
</button>
<button
- class="btn btn-default"
+ class="btn button-default"
:disabled="usingFallback"
@click="add"
>
- <i class="icon-plus" />
+ <FAIcon
+ fixed-width
+ icon="plus"
+ />
</button>
</div>
<div
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index 281052e5..fe736168 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -2,6 +2,34 @@ import { mapState, mapGetters } from 'vuex'
import UserCard from '../user_card/user_card.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
+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
+)
const SideDrawer = {
props: [ 'logout' ],
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index eda5a68c..223b1632 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -36,7 +36,11 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'login' }">
- <i class="button-icon icon-login" /> {{ $t("login.login") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="sign-in-alt"
+ /> {{ $t("login.login") }}
</router-link>
</li>
<li
@@ -44,7 +48,11 @@
@click="toggleDrawer"
>
<router-link :to="{ name: timelinesRoute }">
- <i class="button-icon icon-home-2" /> {{ $t("nav.timelines") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="home"
+ /> {{ $t("nav.timelines") }}
</router-link>
</li>
<li
@@ -55,10 +63,14 @@
:to="{ name: 'chats', params: { username: currentUser.screen_name } }"
style="position: relative"
>
- <i class="button-icon icon-chat" /> {{ $t("nav.chats") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="comments"
+ /> {{ $t("nav.chats") }}
<span
v-if="unreadChatCount"
- class="badge badge-notification unread-chat-count"
+ class="badge badge-notification"
>
{{ unreadChatCount }}
</span>
@@ -68,7 +80,11 @@
<ul v-if="currentUser">
<li @click="toggleDrawer">
<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 fa-old-padding"
+ icon="bell"
+ /> {{ $t("nav.interactions") }}
</router-link>
</li>
<li
@@ -76,10 +92,14 @@
@click="toggleDrawer"
>
<router-link to="/friend-requests">
- <i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="user-plus"
+ /> {{ $t("nav.friend_requests") }}
<span
v-if="followRequestCount > 0"
- class="badge follow-request-count"
+ class="badge badge-notification"
>
{{ followRequestCount }}
</span>
@@ -89,8 +109,12 @@
v-if="chat"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'chat' }">
- <i class="button-icon icon-megaphone" /> {{ $t("shoutbox.title") }}
+ <router-link :to="{ name: 'chat-panel' }">
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="bullhorn"
+ /> {{ $t("shoutbox.title") }}
</router-link>
</li>
</ul>
@@ -100,7 +124,11 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'search' }">
- <i class="button-icon icon-search" /> {{ $t("nav.search") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="search"
+ /> {{ $t("nav.search") }}
</router-link>
</li>
<li
@@ -108,20 +136,32 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'who-to-follow' }">
- <i class="button-icon icon-user-plus" /> {{ $t("nav.who_to_follow") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="user-plus"
+ /> {{ $t("nav.who_to_follow") }}
</router-link>
</li>
<li @click="toggleDrawer">
- <a
- href="#"
+ <button
+ class="button-unstyled -link -fullwidth"
@click="openSettingsModal"
>
- <i class="button-icon icon-cog" /> {{ $t("settings.settings") }}
- </a>
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="cog"
+ /> {{ $t("settings.settings") }}
+ </button>
</li>
<li @click="toggleDrawer">
<router-link :to="{ name: 'about'}">
- <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="info-circle"
+ /> {{ $t("nav.about") }}
</router-link>
</li>
<li
@@ -132,19 +172,27 @@
href="/pleroma/admin/#/login-pleroma"
target="_blank"
>
- <i class="button-icon icon-gauge" /> {{ $t("nav.administration") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="tachometer-alt"
+ /> {{ $t("nav.administration") }}
</a>
</li>
<li
v-if="currentUser"
@click="toggleDrawer"
>
- <a
- href="#"
+ <button
+ class="button-unstyled -link -fullwidth"
@click="doLogout"
>
- <i class="button-icon icon-logout" /> {{ $t("login.logout") }}
- </a>
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="sign-out-alt"
+ /> {{ $t("login.logout") }}
+ </button>
</li>
</ul>
</div>
@@ -224,8 +272,10 @@
--lightText: var(--popoverLightText, $fallback--lightText);
--icon: var(--popoverIcon, $fallback--icon);
- .button-icon:before {
- width: 1.1em;
+ .badge {
+ position: absolute;
+ right: 0.7rem;
+ top: 1em;
}
}
@@ -272,7 +322,6 @@
border-bottom: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
- margin: 0.2em 0;
}
.side-drawer ul:last-child {
@@ -282,9 +331,12 @@
.side-drawer li {
padding: 0;
- a {
+ a, button {
+ box-sizing: border-box;
display: block;
- padding: 0.5em 0.85em;
+ height: 3em;
+ line-height: 3em;
+ padding: 0 0.7em;
&:hover {
background-color: $fallback--lightBg;
diff --git a/src/components/status/status.js b/src/components/status/status.js
index e48b2eb8..2bf93a9e 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -17,6 +17,41 @@ import { highlightClass, highlightStyle } from '../../services/user_highlighter/
import { muteWordHits } from '../../services/status_parser/status_parser.js'
import { unescape, uniqBy } from 'lodash'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faEnvelope,
+ faLock,
+ faLockOpen,
+ faGlobe,
+ faTimes,
+ faRetweet,
+ faReply,
+ faPlusSquare,
+ faSmileBeam,
+ faEllipsisH,
+ faStar,
+ faEyeSlash,
+ faEye,
+ faThumbtack
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faEnvelope,
+ faGlobe,
+ faLock,
+ faLockOpen,
+ faTimes,
+ faRetweet,
+ faReply,
+ faPlusSquare,
+ faStar,
+ faSmileBeam,
+ faEllipsisH,
+ faEyeSlash,
+ faEye,
+ faThumbtack
+)
+
const Status = {
name: 'Status',
components: {
@@ -122,6 +157,7 @@ const Status = {
return muteWordHits(this.status, this.muteWords)
},
muted () {
+ if (this.statusoid.user.id === this.currentUser.id) return false
const { status } = this
const { reblog } = status
const relationship = this.$store.getters.relationship(status.user.id)
@@ -227,13 +263,13 @@ const Status = {
visibilityIcon (visibility) {
switch (visibility) {
case 'private':
- return 'icon-lock'
+ return 'lock'
case 'unlisted':
- return 'icon-lock-open-alt'
+ return 'lock-open'
case 'direct':
- return 'icon-mail-alt'
+ return 'envelope'
default:
- return 'icon-globe'
+ return 'globe'
}
},
showError (error) {
diff --git a/src/components/status/status.scss b/src/components/status/status.scss
index 66a91c1e..58b55bc8 100644
--- a/src/components/status/status.scss
+++ b/src/components/status/status.scss
@@ -7,8 +7,9 @@ $status-margin: 0.75em;
min-width: 0;
&:hover {
- --still-image-img: visible;
- --still-image-canvas: hidden;
+ --_still-image-img-visibility: visible;
+ --_still-image-canvas-visibility: hidden;
+ --_still-image-label-visibility: hidden;
}
&.-focused {
@@ -28,6 +29,8 @@ $status-margin: 0.75em;
&.-conversation {
border-left-width: 4px;
border-left-style: solid;
+ border-left-color: $fallback--cRed;
+ border-left-color: var(--cRed, $fallback--cRed);
}
.gravestone {
@@ -58,6 +61,15 @@ $status-margin: 0.75em;
justify-content: flex-end;
}
+ ._misclick-prevention & {
+ pointer-events: none;
+
+ .attachments {
+ pointer-events: initial;
+ cursor: initial;
+ }
+ }
+
.left-side {
margin-right: $status-margin;
}
@@ -127,6 +139,20 @@ $status-margin: 0.75em;
.heading-right {
display: flex;
flex-shrink: 0;
+
+ .button-unstyled {
+ padding: 5px;
+ margin: -5px;
+
+ &:hover svg {
+ color: $fallback--lightText;
+ color: var(--lightText, $fallback--lightText);
+ }
+ }
+
+ .svg-inline--fa {
+ margin-left: 0.25em;
+ }
}
.timeago {
@@ -156,11 +182,6 @@ $status-margin: 0.75em;
text-overflow: ellipsis;
overflow-x: hidden;
}
-
- .icon-reply {
- // mirror the icon
- transform: scaleX(-1);
- }
}
& .reply-to-popover,
@@ -200,7 +221,6 @@ $status-margin: 0.75em;
}
.reply-to {
- display: flex;
position: relative;
}
@@ -208,7 +228,6 @@ $status-margin: 0.75em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
- margin-left: 0.2em;
}
.replies-separator {
@@ -232,16 +251,10 @@ $status-margin: 0.75em;
.repeat-info {
padding: 0.4em $status-margin;
- line-height: 22px;
-
- .right-side {
- display: flex;
- align-content: center;
- flex-wrap: wrap;
- }
- i {
- padding: 0 0.2em;
+ .repeat-icon {
+ color: $fallback--cGreen;
+ color: var(--cGreen, $fallback--cGreen);
}
}
@@ -291,18 +304,6 @@ $status-margin: 0.75em;
}
}
- .button-reply {
- &:not(.-disabled) {
- cursor: pointer;
- }
-
- &:not(.-disabled):hover,
- &.-active {
- color: $fallback--cBlue;
- color: var(--cBlue, $fallback--cBlue);
- }
- }
-
.muted {
padding: 0.25em 0.6em;
height: 1.2em;
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index ffae32fc..6ee8117f 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -10,17 +10,20 @@
class="alert error"
>
{{ error }}
- <i
- class="button-icon icon-cancel"
+ <span
+ class="fa-scale-110 fa-old-padding"
@click="clearError"
- />
+ >
+ <FAIcon icon="times" />
+ </span>
</div>
<template v-if="muted && !isPreview">
<div class="status-container muted">
<small class="status-username">
- <i
+ <FAIcon
v-if="muted && retweet"
- class="button-icon icon-retweet"
+ class="fa-scale-110 fa-old-padding repeat-icon"
+ icon="retweet"
/>
<router-link :to="userProfileLink">
{{ status.user.screen_name }}
@@ -44,11 +47,15 @@
>
{{ muteWordHits.join(', ') }}
</small>
- <a
- href="#"
- class="unmute"
+ <button
+ class="unmute button-unstyled"
@click.prevent="toggleMute"
- ><i class="button-icon icon-eye-off" /></a>
+ >
+ <FAIcon
+ icon="eye-slash"
+ class="fa-scale-110 fa-old-padding"
+ />
+ </button>
</div>
</template>
<template v-else>
@@ -56,7 +63,10 @@
v-if="showPinned"
class="pin"
>
- <i class="fa icon-pin faint" />
+ <FAIcon
+ icon="thumbtack"
+ class="faint"
+ />
<span class="faint">{{ $t('status.pinned') }}</span>
</div>
<div
@@ -86,8 +96,9 @@
:to="retweeterProfileLink"
>{{ retweeter }}</router-link>
</span>
- <i
- class="fa icon-retweet retweeted"
+ <FAIcon
+ icon="retweet"
+ class="repeat-icon"
:title="$t('tool_tip.repeat')"
/>
{{ $t('timeline.repeated') }}
@@ -167,38 +178,40 @@
:auto-update="60"
/>
</router-link>
- <div
+ <span
v-if="status.visibility"
- class="button-icon visibility-icon"
+ class="visibility-icon"
+ :title="status.visibility | capitalize"
>
- <i
- :class="visibilityIcon(status.visibility)"
- :title="status.visibility | capitalize"
+ <FAIcon
+ fixed-width
+ class="fa-scale-110"
+ :icon="visibilityIcon(status.visibility)"
/>
- </div>
- <a
- v-if="!status.is_local && !isPreview"
- :href="status.external_url"
- target="_blank"
- class="source_url"
- title="Source"
+ </span>
+ <button
+ v-if="expandable && !isPreview"
+ class="button-unstyled"
+ :title="$t('status.expand')"
+ @click.prevent="toggleExpanded"
>
- <i class="button-icon icon-link-ext-alt" />
- </a>
- <template v-if="expandable && !isPreview">
- <a
- href="#"
- title="Expand"
- @click.prevent="toggleExpanded"
- >
- <i class="button-icon icon-plus-squared" />
- </a>
- </template>
- <a
+ <FAIcon
+ fixed-width
+ class="fa-scale-110"
+ icon="plus-square"
+ />
+ </button>
+ <button
v-if="unmuted"
- href="#"
+ class="button-unstyled"
@click.prevent="toggleMute"
- ><i class="button-icon icon-eye-off" /></a>
+ >
+ <FAIcon
+ fixed-width
+ icon="eye-slash"
+ class="fa-scale-110"
+ />
+ </button>
</span>
</div>
@@ -214,19 +227,22 @@
style="min-width: 0"
:class="{ '-strikethrough': !status.parent_visible }"
>
- <a
- class="reply-to"
- href="#"
+ <button
+ class="button-unstyled reply-to"
:aria-label="$t('tool_tip.reply')"
@click.prevent="gotoOriginal(status.in_reply_to_status_id)"
>
- <i class="button-icon reply-button icon-reply" />
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="reply"
+ flip="horizontal"
+ />
<span
class="faint-link reply-to-text"
>
{{ $t('status.reply_to') }}
</span>
- </a>
+ </button>
</StatusPopover>
<span
@@ -259,11 +275,12 @@
:key="reply.id"
:status-id="reply.id"
>
- <a
- href="#"
- class="reply-link"
+ <button
+ class="button-unstyled -link reply-link"
@click.prevent="gotoOriginal(reply.id)"
- >{{ reply.name }}</a>
+ >
+ {{ reply.name }}
+ </button>
</StatusPopover>
</div>
</div>
@@ -348,7 +365,6 @@
@onSuccess="clearError"
/>
</div>
-
</div>
</div>
<div
diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js
index df095de3..a6f79d76 100644
--- a/src/components/status_content/status_content.js
+++ b/src/components/status_content/status_content.js
@@ -7,6 +7,24 @@ import fileType from 'src/services/file_type/file_type.service'
import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
import { mapGetters, mapState } from 'vuex'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faCircleNotch,
+ faFile,
+ faMusic,
+ faImage,
+ faLink,
+ faPollH
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faCircleNotch,
+ faFile,
+ faMusic,
+ faImage,
+ faLink,
+ faPollH
+)
const StatusContent = {
name: 'StatusContent',
diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue
index f7fb5ee2..90bfaf40 100644
--- a/src/components/status_content/status_content.vue
+++ b/src/components/status_content/status_content.vue
@@ -12,35 +12,34 @@
@click.prevent="linkClicked"
v-html="status.summary_html"
/>
- <a
+ <button
v-if="longSubject && showingLongSubject"
- href="#"
- class="tall-subject-hider"
+ class="button-unstyled -link tall-subject-hider"
@click.prevent="showingLongSubject=false"
- >{{ $t("status.hide_full_subject") }}</a>
- <a
+ >
+ {{ $t("status.hide_full_subject") }}
+ </button>
+ <button
v-else-if="longSubject"
- class="tall-subject-hider"
+ class="button-unstyled -link tall-subject-hider"
:class="{ 'tall-subject-hider_focused': focused }"
- href="#"
@click.prevent="showingLongSubject=true"
>
{{ $t("status.show_full_subject") }}
- </a>
+ </button>
</div>
<div
:class="{'tall-status': hideTallStatus}"
class="status-content-wrapper"
>
- <a
+ <button
v-if="hideTallStatus"
- class="tall-status-hider"
+ class="button-unstyled -link tall-status-hider"
:class="{ 'tall-status-hider_focused': focused }"
- href="#"
@click.prevent="toggleShowMore"
>
{{ $t("general.show_more") }}
- </a>
+ </button>
<div
v-if="!hideSubjectStatus"
:class="{ 'single-line': singleLine }"
@@ -48,46 +47,44 @@
@click.prevent="linkClicked"
v-html="postBodyHtml"
/>
- <a
+ <button
v-if="hideSubjectStatus"
- href="#"
- class="cw-status-hider"
+ class="button-unstyled -link cw-status-hider"
@click.prevent="toggleShowMore"
>
{{ $t("status.show_content") }}
- <span
+ <FAIcon
v-if="attachmentTypes.includes('image')"
- class="icon-picture"
+ icon="image"
/>
- <span
+ <FAIcon
v-if="attachmentTypes.includes('video')"
- class="icon-video"
+ icon="video"
/>
- <span
+ <FAIcon
v-if="attachmentTypes.includes('audio')"
- class="icon-music"
+ icon="music"
/>
- <span
+ <FAIcon
v-if="attachmentTypes.includes('unknown')"
- class="icon-doc"
+ icon="file"
/>
- <span
+ <FAIcon
v-if="status.poll && status.poll.options"
- class="icon-chart-bar"
+ icon="poll-h"
/>
- <span
+ <FAIcon
v-if="status.card"
- class="icon-link"
+ icon="link"
/>
- </a>
- <a
+ </button>
+ <button
v-if="showingMore && !fullContent"
- href="#"
- class="status-unhider"
+ class="button-unstyled -link status-unhider"
@click.prevent="toggleShowMore"
>
{{ tallStatus ? $t("general.show_less") : $t("status.hide_content") }}
- </a>
+ </button>
</div>
<div v-if="status.poll && status.poll.options && !hideSubjectStatus">
@@ -182,6 +179,10 @@ $status-margin: 0.75em;
text-align: center;
display: inline-block;
word-break: break-all;
+
+ svg {
+ color: inherit;
+ }
}
img, video {
diff --git a/src/components/status_popover/status_popover.js b/src/components/status_popover/status_popover.js
index 51e7680c..c47f5631 100644
--- a/src/components/status_popover/status_popover.js
+++ b/src/components/status_popover/status_popover.js
@@ -1,4 +1,10 @@
import { find } from 'lodash'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faCircleNotch
+)
const StatusPopover = {
name: 'StatusPopover',
diff --git a/src/components/status_popover/status_popover.vue b/src/components/status_popover/status_popover.vue
index 162eb210..8237ce00 100644
--- a/src/components/status_popover/status_popover.vue
+++ b/src/components/status_popover/status_popover.vue
@@ -27,7 +27,11 @@
v-else
class="status-preview-no-content"
>
- <i class="icon-spin4 animate-spin" />
+ <FAIcon
+ icon="circle-notch"
+ spin
+ size="2x"
+ />
</div>
</div>
</Popover>
diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
index ad82210d..d3eb5925 100644
--- a/src/components/still-image/still-image.vue
+++ b/src/components/still-image/still-image.vue
@@ -42,7 +42,7 @@
width: 100%;
height: 100%;
object-fit: contain;
- visibility: var(--still-image-canvas, visible);
+ visibility: var(--_still-image-canvas-visibility, visible);
}
img {
@@ -66,16 +66,19 @@
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
z-index: 2;
- visibility: var(--still-image-label-visibility, visible);
+ visibility: var(--_still-image-label-visibility, visible);
}
&:hover canvas {
display: none;
}
- &:hover::before,
+ &:hover::before {
+ visibility: var(--_still-image-label-visibility, hidden);
+ }
+
img {
- visibility: var(--still-image-img, hidden);
+ visibility: var(--_still-image-img-visibility, hidden);
}
&:hover img {
diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js
index 9c1da354..76e7ef03 100644
--- a/src/components/tab_switcher/tab_switcher.js
+++ b/src/components/tab_switcher/tab_switcher.js
@@ -1,5 +1,6 @@
import Vue from 'vue'
import { mapState } from 'vuex'
+import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
import './tab_switcher.scss'
@@ -80,7 +81,7 @@ export default Vue.component('tab-switcher', {
const tabs = this.$slots.default
.map((slot, index) => {
if (!slot.tag) return
- const classesTab = ['tab']
+ const classesTab = ['tab', 'button-default']
const classesWrapper = ['tab-wrapper']
if (this.activeIndex === index) {
classesTab.push('active')
@@ -107,7 +108,7 @@ export default Vue.component('tab-switcher', {
class={classesTab.join(' ')}
type="button"
>
- {!slot.data.attrs.icon ? '' : (<i class={'tab-icon icon-' + slot.data.attrs.icon}/>)}
+ {!slot.data.attrs.icon ? '' : (<FAIcon class="tab-icon" size="2x" fixed-width icon={slot.data.attrs.icon}/>)}
<span class="text">
{slot.data.attrs.label}
</span>
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index d2ef4857..0ed614b7 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -4,7 +4,7 @@
display: flex;
.tab-icon {
- font-size: 2em;
+ margin: 0.2em auto;
display: block;
}
@@ -91,7 +91,7 @@
flex-direction: column;
@media all and (max-width: 800px) {
- min-width: 1em;
+ min-width: 4em;
}
&:not(.active)::after {
diff --git a/src/components/timeago/timeago.vue b/src/components/timeago/timeago.vue
index 6df0524d..55a2dd94 100644
--- a/src/components/timeago/timeago.vue
+++ b/src/components/timeago/timeago.vue
@@ -9,6 +9,7 @@
<script>
import * as DateUtils from 'src/services/date_utils/date_utils.js'
+import localeService from 'src/services/locale/locale.service.js'
export default {
name: 'Timeago',
@@ -21,9 +22,10 @@ export default {
},
computed: {
localeDateString () {
+ const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
return typeof this.time === 'string'
- ? new Date(Date.parse(this.time)).toLocaleString()
- : this.time.toLocaleString()
+ ? new Date(Date.parse(this.time)).toLocaleString(browserLocale)
+ : this.time.toLocaleString(browserLocale)
}
},
created () {
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 17680542..665d195e 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -2,7 +2,13 @@ import Status from '../status/status.vue'
import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js'
import Conversation from '../conversation/conversation.vue'
import TimelineMenu from '../timeline_menu/timeline_menu.vue'
-import { throttle, keyBy } from 'lodash'
+import { debounce, throttle, keyBy } from 'lodash'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faCircleNotch
+)
export const getExcludedStatusIdsByPinning = (statuses, pinnedStatusIds) => {
const ids = []
@@ -34,7 +40,8 @@ const Timeline = {
paused: false,
unfocused: false,
bottomedOut: false,
- virtualScrollIndex: 0
+ virtualScrollIndex: 0,
+ blockingClicks: false
}
},
components: {
@@ -43,17 +50,10 @@ const Timeline = {
TimelineMenu
},
computed: {
- timelineError () {
- return this.$store.state.statuses.error
- },
- errorData () {
- return this.$store.state.statuses.errorData
- },
newStatusCount () {
return this.timeline.newStatusCount
},
showLoadButton () {
- if (this.timelineError || this.errorData) return false
return this.timeline.newStatusCount > 0 || this.timeline.flushMarker !== 0
},
loadButtonString () {
@@ -64,8 +64,10 @@ const Timeline = {
}
},
classes () {
+ let rootClasses = !this.embedded ? ['panel', 'panel-default'] : []
+ if (this.blockingClicks) rootClasses = rootClasses.concat(['-blocked', '_misclick-prevention'])
return {
- root: ['timeline'].concat(!this.embedded ? ['panel', 'panel-default'] : []),
+ root: rootClasses,
header: ['timeline-heading'].concat(!this.embedded ? ['panel-heading'] : []),
body: ['timeline-body'].concat(!this.embedded ? ['panel-body'] : []),
footer: ['timeline-footer'].concat(!this.embedded ? ['panel-footer'] : [])
@@ -124,6 +126,15 @@ const Timeline = {
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
},
methods: {
+ stopBlockingClicks: debounce(function () {
+ this.blockingClicks = false
+ }, 1000),
+ blockClicksTemporarily () {
+ if (!this.blockingClicks) {
+ this.blockingClicks = true
+ }
+ this.stopBlockingClicks()
+ },
handleShortKey (e) {
// Ignore when input fields are focused
if (['textarea', 'input'].includes(e.target.tagName.toLowerCase())) return
@@ -135,6 +146,7 @@ const Timeline = {
this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 })
this.fetchOlderStatuses()
} else {
+ this.blockClicksTemporarily()
this.$store.commit('showNewStatuses', { timeline: this.timelineName })
this.paused = false
}
@@ -152,11 +164,12 @@ const Timeline = {
userId: this.userId,
tag: this.tag
}).then(({ statuses }) => {
- store.commit('setLoading', { timeline: this.timelineName, value: false })
if (statuses && statuses.length === 0) {
this.bottomedOut = true
}
- })
+ }).finally(() =>
+ store.commit('setLoading', { timeline: this.timelineName, value: false })
+ )
}, 1000, this),
determineVisibleStatuses () {
if (!this.$refs.timeline) return
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index c1e2f44b..4c43fe5c 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -1,24 +1,10 @@
<template>
- <div :class="[classes.root, 'timeline']">
+ <div :class="[classes.root, 'Timeline']">
<div :class="classes.header">
<TimelineMenu v-if="!embedded" />
- <div
- v-if="timelineError"
- class="loadmore-error alert error"
- @click.prevent
- >
- {{ $t('timeline.error_fetching') }}
- </div>
- <div
- v-else-if="errorData"
- class="loadmore-error alert error"
- @click.prevent
- >
- {{ errorData.statusText }}
- </div>
<button
- v-else-if="showLoadButton"
- class="loadmore-button"
+ v-if="showLoadButton"
+ class="button-default loadmore-button"
@click.prevent="showNewStatuses"
>
{{ loadButtonString }}
@@ -75,24 +61,24 @@
>
{{ $t('timeline.no_more_statuses') }}
</div>
- <a
- v-else-if="!timeline.loading && !errorData"
- href="#"
+ <button
+ v-else-if="!timeline.loading"
+ class="button-unstyled -link -fullwidth"
@click.prevent="fetchOlderStatuses()"
>
- <div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div>
- </a>
- <a
- v-else-if="errorData"
- href="#"
- >
- <div class="new-status-notification text-center panel-footer">{{ errorData.error }}</div>
- </a>
+ <div class="new-status-notification text-center panel-footer">
+ {{ $t('timeline.load_older') }}
+ </div>
+ </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>
@@ -103,15 +89,20 @@
<style lang="scss">
@import '../../_variables.scss';
-.timeline {
+.Timeline {
.loadmore-text {
opacity: 1;
}
+
+ &.-blocked {
+ cursor: progress;
+ }
}
.timeline-heading {
max-width: 100%;
flex-wrap: nowrap;
+ align-items: center;
.loadmore-button {
flex-shrink: 0;
}
diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js
index 2be75b06..8d6a58b1 100644
--- a/src/components/timeline_menu/timeline_menu.js
+++ b/src/components/timeline_menu/timeline_menu.js
@@ -1,7 +1,25 @@
import Popover from '../popover/popover.vue'
import { mapState } from 'vuex'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faUsers,
+ faGlobe,
+ faBookmark,
+ faEnvelope,
+ faHome,
+ faChevronDown
+} from '@fortawesome/free-solid-svg-icons'
-// Route -> i18n key mapping, exported andnot in the computed
+library.add(
+ faUsers,
+ faGlobe,
+ faBookmark,
+ faEnvelope,
+ faHome,
+ faChevronDown
+)
+
+// Route -> i18n key mapping, exported and not in the computed
// because nav panel benefits from the same information.
export const timelineNames = () => {
return {
@@ -9,8 +27,7 @@ export const timelineNames = () => {
'bookmarks': 'nav.bookmarks',
'dms': 'nav.dms',
'public-timeline': 'nav.public_tl',
- 'public-external-timeline': 'nav.twkn',
- 'tag-timeline': 'tag'
+ 'public-external-timeline': 'nav.twkn'
}
}
@@ -42,6 +59,14 @@ const TimelineMenu = {
this.isOpen = true
}, 25)
},
+ blockOpen (event) {
+ // For the blank area inside the button element.
+ // Just setting @click.stop="" makes unintuitive behavior when
+ // menu is open and clicking on the blank area doesn't close it.
+ if (!this.isOpen) {
+ event.stopPropagation()
+ }
+ },
timelineName () {
const route = this.$route.name
if (route === 'tag-timeline') {
diff --git a/src/components/timeline_menu/timeline_menu.vue b/src/components/timeline_menu/timeline_menu.vue
index b7e5f2da..3c86842b 100644
--- a/src/components/timeline_menu/timeline_menu.vue
+++ b/src/components/timeline_menu/timeline_menu.vue
@@ -1,7 +1,7 @@
<template>
<Popover
trigger="click"
- class="timeline-menu"
+ class="TimelineMenu"
:class="{ 'open': isOpen }"
:margin="{ left: -15, right: -200 }"
:bound-to="{ x: 'container' }"
@@ -16,27 +16,47 @@
<ul>
<li v-if="currentUser">
<router-link :to="{ name: 'friends' }">
- <i class="button-icon icon-home-2" />{{ $t("nav.timeline") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding "
+ icon="home"
+ />{{ $t("nav.timeline") }}
</router-link>
</li>
<li v-if="currentUser">
<router-link :to="{ name: 'bookmarks'}">
- <i class="button-icon icon-bookmark" />{{ $t("nav.bookmarks") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding "
+ icon="bookmark"
+ />{{ $t("nav.bookmarks") }}
</router-link>
</li>
<li v-if="currentUser">
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
- <i class="button-icon icon-mail-alt" />{{ $t("nav.dms") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding "
+ icon="envelope"
+ />{{ $t("nav.dms") }}
</router-link>
</li>
<li v-if="currentUser || !privateMode">
<router-link :to="{ name: 'public-timeline' }">
- <i class="button-icon icon-users" />{{ $t("nav.public_tl") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding "
+ icon="users"
+ />{{ $t("nav.public_tl") }}
</router-link>
</li>
<li v-if="federating && (currentUser || !privateMode)">
<router-link :to="{ name: 'public-external-timeline' }">
- <i class="button-icon icon-globe" />{{ $t("nav.twkn") }}
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding "
+ icon="globe"
+ />{{ $t("nav.twkn") }}
</router-link>
</li>
</ul>
@@ -45,8 +65,17 @@
slot="trigger"
class="title timeline-menu-title"
>
- <span>{{ timelineName() }}</span>
- <i class="icon-down-open" />
+ <span class="timeline-title">{{ timelineName() }}</span>
+ <span>
+ <FAIcon
+ size="sm"
+ icon="chevron-down"
+ />
+ </span>
+ <span
+ class="click-blocker"
+ @click="blockOpen"
+ />
</div>
</Popover>
</template>
@@ -56,17 +85,19 @@
<style lang="scss">
@import '../../_variables.scss';
-.timeline-menu {
+.TimelineMenu {
flex-shrink: 1;
margin-right: auto;
min-width: 0;
width: 24rem;
+
.timeline-menu-popover-wrap {
overflow: hidden;
// Match panel heading padding to line up menu with bottom of heading
margin-top: 0.6rem;
padding: 0 15px 15px 15px;
}
+
.timeline-menu-popover {
width: 24rem;
max-width: 100vw;
@@ -77,10 +108,12 @@
transform: translateY(-100%);
transition: transform 100ms;
}
+
.panel::after {
border-top-right-radius: 0;
border-top-left-radius: 0;
}
+
&.open .timeline-menu-popover {
transform: translateY(0);
}
@@ -88,25 +121,28 @@
.timeline-menu-title {
margin: 0;
cursor: pointer;
- display: flex;
user-select: none;
width: 100%;
+ display: flex;
- span {
+ .timeline-menu-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
- i {
+ svg {
margin-left: 0.6em;
- flex-shrink: 0;
- font-size: 1rem;
transition: transform 100ms;
}
+
+ .click-blocker {
+ cursor: default;
+ flex-grow: 1;
+ }
}
- &.open .timeline-menu-title i {
+ &.open .timeline-menu-title svg {
color: $fallback--text;
color: var(--panelText, $fallback--text);
transform: rotate(180deg);
@@ -171,8 +207,9 @@
}
}
- i {
- margin-right: 0.5em;
+ svg {
+ margin-right: 0.4em;
+ margin-left: -0.2em;
}
}
}
diff --git a/src/components/user_avatar/user_avatar.vue b/src/components/user_avatar/user_avatar.vue
index eb3d375e..0f7c584b 100644
--- a/src/components/user_avatar/user_avatar.vue
+++ b/src/components/user_avatar/user_avatar.vue
@@ -20,11 +20,14 @@
@import '../../_variables.scss';
.Avatar {
- --still-image-label-visibility: hidden;
+ --_avatarShadowBox: var(--avatarStatusShadow);
+ --_avatarShadowFilter: var(--avatarStatusShadowFilter);
+ --_avatarShadowInset: var(--avatarStatusShadowInset);
+ --_still-image-label-visibility: hidden;
width: 48px;
height: 48px;
- box-shadow: var(--avatarStatusShadow);
+ box-shadow: var(--_avatarShadowBox);
border-radius: $fallback--avatarRadius;
border-radius: var(--avatarRadius, $fallback--avatarRadius);
@@ -34,8 +37,8 @@
}
&.better-shadow {
- box-shadow: var(--avatarStatusShadowInset);
- filter: var(--avatarStatusShadowFilter)
+ box-shadow: var(--_avatarShadowInset);
+ filter: var(--_avatarShadowFilter);
}
&.animated::before {
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 8e6b9d7f..3a8efafc 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -6,6 +6,22 @@ import ModerationTools from '../moderation_tools/moderation_tools.vue'
import AccountActions from '../account_actions/account_actions.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { mapGetters } from 'vuex'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faBell,
+ faRss,
+ faChevronDown,
+ faSearchPlus,
+ faExternalLinkAlt
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faRss,
+ faBell,
+ faChevronDown,
+ faSearchPlus,
+ faExternalLinkAlt
+)
export default {
props: [
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 041bb80f..773f764a 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -21,7 +21,10 @@
:user="user"
/>
<div class="user-info-avatar-link-overlay">
- <i class="button-icon icon-zoom-in" />
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="search-plus"
+ />
</div>
</a>
<router-link
@@ -54,8 +57,12 @@
v-if="isOtherUser && !user.is_local"
:href="user.statusnet_profile_url"
target="_blank"
+ class="external-link-button"
>
- <i class="icon-link-ext usersettings" />
+ <FAIcon
+ class="icon"
+ icon="external-link-alt"
+ />
</a>
<AccountActions
v-if="isOtherUser && loggedIn"
@@ -76,7 +83,7 @@
v-if="!!visibleRole"
class="alert user-role"
>
- {{ visibleRole }}
+ {{ $t(`user_card.roles.${visibleRole}`) }}
</span>
<span
v-if="user.bot"
@@ -85,7 +92,13 @@
bot
</span>
</template>
- <span v-if="user.locked"><i class="icon icon-lock" /></span>
+ <span v-if="user.locked">
+ <FAIcon
+ class="lock-icon"
+ icon="lock"
+ size="sm"
+ />
+ </span>
<span
v-if="!mergedConfig.hideUserStats && !hideBio"
class="dailyAvg"
@@ -133,7 +146,10 @@
<option value="striped">Striped bg</option>
<option value="side">Side stripe</option>
</select>
- <i class="icon-down-open" />
+ <FAIcon
+ class="select-down-icon"
+ icon="chevron-down"
+ />
</label>
</div>
</div>
@@ -146,33 +162,44 @@
<template v-if="relationship.following">
<ProgressButton
v-if="!relationship.subscribing"
- class="btn btn-default"
+ class="btn button-default"
:click="subscribeUser"
:title="$t('user_card.subscribe')"
>
- <i class="icon-bell-alt" />
+ <FAIcon icon="bell" />
</ProgressButton>
<ProgressButton
v-else
- class="btn btn-default toggled"
+ class="btn button-default toggled"
:click="unsubscribeUser"
:title="$t('user_card.unsubscribe')"
>
- <i class="icon-bell-ringing-o" />
+ <FALayers>
+ <FAIcon
+ icon="rss"
+ transform="left-5 shrink-6 up-3 rotate-20"
+ flip="horizontal"
+ />
+ <FAIcon
+ icon="rss"
+ transform="right-5 shrink-6 up-3 rotate-20"
+ />
+ <FAIcon icon="bell" />
+ </FALayers>
</ProgressButton>
</template>
</div>
<div>
<button
v-if="relationship.muting"
- class="btn btn-default btn-block toggled"
+ class="btn button-default btn-block toggled"
@click="unmuteUser"
>
{{ $t('user_card.muted') }}
</button>
<button
v-else
- class="btn btn-default btn-block"
+ class="btn button-default btn-block"
@click="muteUser"
>
{{ $t('user_card.mute') }}
@@ -180,7 +207,7 @@
</div>
<div>
<button
- class="btn btn-default btn-block"
+ class="btn button-default btn-block"
@click="mentionUser"
>
{{ $t('user_card.mention') }}
@@ -255,6 +282,11 @@
.user-card {
position: relative;
+ &:hover .Avatar {
+ --_still-image-img-visibility: visible;
+ --_still-image-canvas-visibility: hidden;
+ }
+
.panel-heading {
padding: .5em 0;
text-align: center;
@@ -283,7 +315,7 @@
mask: linear-gradient(to top, white, transparent) bottom no-repeat,
linear-gradient(to top, white, white);
// Autoprefixed seem to ignore this one, and also syntax is different
- -webkit-mask-composite: xor;
+ -webkit-mask-composite: xor;
mask-composite: exclude;
background-size: cover;
mask-size: 100% 60%;
@@ -355,20 +387,17 @@
max-height: 56px;
.Avatar {
+ --_avatarShadowBox: var(--avatarShadow);
+ --_avatarShadowFilter: var(--avatarShadowFilter);
+ --_avatarShadowInset: var(--avatarShadowInset);
+
flex: 1 0 100%;
width: 56px;
height: 56px;
- box-shadow: 0px 1px 8px rgba(0,0,0,0.75);
- box-shadow: var(--avatarShadow);
object-fit: cover;
}
}
- &:hover .Avatar {
- --still-image-img: visible;
- --still-image-canvas: hidden;
- }
-
&-avatar-link {
position: relative;
cursor: pointer;
@@ -388,7 +417,7 @@
opacity: 0;
transition: opacity .2s ease;
- i {
+ svg {
color: #FFF;
}
}
@@ -398,10 +427,17 @@
}
}
- .usersettings {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- opacity: .8;
+ .external-link-button {
+ cursor: pointer;
+ width: 2.5em;
+ text-align: center;
+ margin: -0.5em 0;
+ padding: 0.5em 0;
+
+ &:not(:hover) .icon {
+ color: $fallback--lightText;
+ color: var(--lightText, $fallback--lightText);
+ }
}
.user-summary {
@@ -447,6 +483,10 @@
font-weight: light;
font-size: 15px;
+ .lock-icon {
+ margin-left: 0.5em;
+ }
+
.user-screen-name {
min-width: 1px;
flex: 0 1 auto;
@@ -467,7 +507,6 @@
.user-role {
flex: none;
- text-transform: capitalize;
color: $fallback--text;
color: var(--alertNeutralText, $fallback--text);
background-color: $fallback--fg;
@@ -508,7 +547,7 @@
padding-bottom: 0;
flex: 1 0 auto;
}
- .userHighlightSel.select i {
+ .userHighlightSel.select svg {
line-height: 22px;
}
diff --git a/src/components/user_list_popover/user_list_popover.js b/src/components/user_list_popover/user_list_popover.js
index b60f2c4c..32ca2b8d 100644
--- a/src/components/user_list_popover/user_list_popover.js
+++ b/src/components/user_list_popover/user_list_popover.js
@@ -1,3 +1,9 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faCircleNotch
+)
const UserListPopover = {
name: 'UserListPopover',
diff --git a/src/components/user_list_popover/user_list_popover.vue b/src/components/user_list_popover/user_list_popover.vue
index 185c73ca..95673733 100644
--- a/src/components/user_list_popover/user_list_popover.vue
+++ b/src/components/user_list_popover/user_list_popover.vue
@@ -31,7 +31,11 @@
</div>
</div>
<div v-else>
- <i class="icon-spin4 animate-spin" />
+ <FAIcon
+ icon="circle-notch"
+ spin
+ size="3x"
+ />
</div>
</div>
</Popover>
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 201727d4..c0b55a6c 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -6,6 +6,14 @@ import Conversation from '../conversation/conversation.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
import List from '../list/list.vue'
import withLoadMore from '../../hocs/with_load_more/with_load_more'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faCircleNotch
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faCircleNotch
+)
const FollowerList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchFollowers', props.userId),
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index b26499b4..745e795d 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -20,14 +20,13 @@
:key="index"
class="user-profile-field"
>
+ <!-- eslint-disable vue/no-v-html -->
<dt
:title="user.fields_text[index].name"
class="user-profile-field-name"
@click.prevent="linkClicked"
- >
- {{ field.name }}
- </dt>
- <!-- eslint-disable vue/no-v-html -->
+ v-html="field.name"
+ />
<dd
:title="user.fields_text[index].value"
class="user-profile-field-value"
@@ -122,9 +121,10 @@
</div>
<div class="panel-body">
<span v-if="error">{{ error }}</span>
- <i
+ <FAIcon
v-else
- class="icon-spin3 animate-spin"
+ spin
+ icon="circle-notch"
/>
</div>
</div>
@@ -142,6 +142,7 @@
.user-profile-fields {
margin: 0 0.5em;
+
img {
object-fit: contain;
vertical-align: middle;
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
index 38cf117b..8d171b2d 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.js
+++ b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -38,17 +38,23 @@ const UserReportingModal = {
},
statuses () {
return this.$store.state.reports.statuses
+ },
+ preTickedIds () {
+ return this.$store.state.reports.preTickedIds
}
},
watch: {
- userId: 'resetState'
+ userId: 'resetState',
+ preTickedIds (newValue) {
+ this.statusIdsToReport = newValue
+ }
},
methods: {
resetState () {
// Reset state
this.comment = ''
this.forward = false
- this.statusIdsToReport = []
+ this.statusIdsToReport = this.preTickedIds
this.processing = false
this.error = false
},
diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue
index 2a8d8d48..fb43094f 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.vue
+++ b/src/components/user_reporting_modal/user_reporting_modal.vue
@@ -29,7 +29,7 @@
</div>
<div>
<button
- class="btn btn-default"
+ class="btn button-default"
:disabled="processing"
@click="reportUser"
>
diff --git a/src/components/video_attachment/video_attachment.vue b/src/components/video_attachment/video_attachment.vue
index a4bf01e8..8a3ea1e3 100644
--- a/src/components/video_attachment/video_attachment.vue
+++ b/src/components/video_attachment/video_attachment.vue
@@ -1,6 +1,7 @@
<template>
<video
class="video"
+ preload="metadata"
:src="attachment.url"
:loop="loopVideo"
:controls="controls"