aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/account_actions/account_actions.vue25
-rw-r--r--src/components/attachment/attachment.js2
-rw-r--r--src/components/attachment/attachment.vue6
-rw-r--r--src/components/chat_list/chat_list.vue5
-rw-r--r--src/components/chat_message/chat_message.vue32
-rw-r--r--src/components/domain_mute_card/domain_mute_card.vue4
-rw-r--r--src/components/emoji_input/emoji_input.js60
-rw-r--r--src/components/emoji_input/emoji_input.vue1
-rw-r--r--src/components/export_import/export_import.vue102
-rw-r--r--src/components/extra_buttons/extra_buttons.vue24
-rw-r--r--src/components/features_panel/features_panel.js2
-rw-r--r--src/components/features_panel/features_panel.vue4
-rw-r--r--src/components/flash/flash.js52
-rw-r--r--src/components/flash/flash.vue88
-rw-r--r--src/components/font_control/font_control.js12
-rw-r--r--src/components/font_control/font_control.vue35
-rw-r--r--src/components/global_notice_list/global_notice_list.vue8
-rw-r--r--src/components/interface_language_switcher/interface_language_switcher.vue41
-rw-r--r--src/components/moderation_tools/moderation_tools.js5
-rw-r--r--src/components/moderation_tools/moderation_tools.vue63
-rw-r--r--src/components/nav_panel/nav_panel.js36
-rw-r--r--src/components/nav_panel/nav_panel.vue70
-rw-r--r--src/components/notifications/notification_filters.vue122
-rw-r--r--src/components/notifications/notifications.js13
-rw-r--r--src/components/notifications/notifications.scss7
-rw-r--r--src/components/notifications/notifications.vue11
-rw-r--r--src/components/password_reset/password_reset.vue2
-rw-r--r--src/components/poll/poll_form.js6
-rw-r--r--src/components/poll/poll_form.vue70
-rw-r--r--src/components/popover/popover.js16
-rw-r--r--src/components/popover/popover.vue35
-rw-r--r--src/components/post_status_form/post_status_form.js8
-rw-r--r--src/components/post_status_form/post_status_form.vue35
-rw-r--r--src/components/react_button/react_button.vue28
-rw-r--r--src/components/registration/registration.vue2
-rw-r--r--src/components/select/select.js21
-rw-r--r--src/components/select/select.vue62
-rw-r--r--src/components/selectable_list/selectable_list.vue7
-rw-r--r--src/components/settings_modal/helpers/boolean_setting.js38
-rw-r--r--src/components/settings_modal/helpers/boolean_setting.vue38
-rw-r--r--src/components/settings_modal/helpers/choice_setting.js39
-rw-r--r--src/components/settings_modal/helpers/choice_setting.vue29
-rw-r--r--src/components/settings_modal/helpers/modified_indicator.vue16
-rw-r--r--src/components/settings_modal/settings_modal.js124
-rw-r--r--src/components/settings_modal/settings_modal.vue68
-rw-r--r--src/components/settings_modal/settings_modal_content.scss13
-rw-r--r--src/components/settings_modal/tabs/filtering_tab.js19
-rw-r--r--src/components/settings_modal/tabs/filtering_tab.vue28
-rw-r--r--src/components/settings_modal/tabs/general_tab.js17
-rw-r--r--src/components/settings_modal/tabs/general_tab.vue81
-rw-r--r--src/components/settings_modal/tabs/mutes_and_blocks_tab.vue73
-rw-r--r--src/components/settings_modal/tabs/notifications_tab.vue2
-rw-r--r--src/components/settings_modal/tabs/profile_tab.vue6
-rw-r--r--src/components/settings_modal/tabs/security_tab/security_tab.vue6
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.js65
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.vue108
-rw-r--r--src/components/shadow_control/shadow_control.js4
-rw-r--r--src/components/shadow_control/shadow_control.vue46
-rw-r--r--src/components/shout_panel/shout_panel.js (renamed from src/components/chat_panel/chat_panel.js)20
-rw-r--r--src/components/shout_panel/shout_panel.vue (renamed from src/components/chat_panel/chat_panel.vue)57
-rw-r--r--src/components/side_drawer/side_drawer.js1
-rw-r--r--src/components/side_drawer/side_drawer.vue4
-rw-r--r--src/components/status_popover/status_popover.vue8
-rw-r--r--src/components/timeline/timeline.js9
-rw-r--r--src/components/timeline/timeline.scss31
-rw-r--r--src/components/timeline/timeline.vue36
-rw-r--r--src/components/timeline/timeline_quick_settings.js61
-rw-r--r--src/components/timeline/timeline_quick_settings.vue102
-rw-r--r--src/components/timeline_menu/timeline_menu.js31
-rw-r--r--src/components/timeline_menu/timeline_menu.vue86
-rw-r--r--src/components/timeline_menu/timeline_menu_content.js29
-rw-r--r--src/components/timeline_menu/timeline_menu_content.vue66
-rw-r--r--src/components/user_card/user_card.js15
-rw-r--r--src/components/user_card/user_card.vue69
-rw-r--r--src/components/user_list_popover/user_list_popover.vue59
-rw-r--r--src/components/user_profile/user_profile.vue10
-rw-r--r--src/components/user_reporting_modal/user_reporting_modal.vue5
77 files changed, 1682 insertions, 959 deletions
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
index ab5d1d29..1e31151c 100644
--- a/src/components/account_actions/account_actions.vue
+++ b/src/components/account_actions/account_actions.vue
@@ -6,10 +6,7 @@
:bound-to="{ x: 'container' }"
remove-padding
>
- <div
- slot="content"
- class="account-tools-popover"
- >
+ <template v-slot:content>
<div class="dropdown-menu">
<template v-if="relationship.following">
<button
@@ -59,16 +56,15 @@
{{ $t('user_card.message') }}
</button>
</div>
- </div>
- <div
- slot="trigger"
- class="ellipsis-button"
- >
- <FAIcon
- class="icon"
- icon="ellipsis-v"
- />
- </div>
+ </template>
+ <template v-slot:trigger>
+ <button class="button-unstyled ellipsis-button">
+ <FAIcon
+ class="icon"
+ icon="ellipsis-v"
+ />
+ </button>
+ </template>
</Popover>
</div>
</template>
@@ -83,7 +79,6 @@
}
.ellipsis-button {
- cursor: pointer;
width: 2.5em;
margin: -0.5em 0;
padding: 0.5em 0;
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
index 5f5779a0..8849f501 100644
--- a/src/components/attachment/attachment.js
+++ b/src/components/attachment/attachment.js
@@ -1,4 +1,5 @@
import StillImage from '../still-image/still-image.vue'
+import Flash from '../flash/flash.vue'
import VideoAttachment from '../video_attachment/video_attachment.vue'
import nsfwImage from '../../assets/nsfw.png'
import fileTypeService from '../../services/file_type/file_type.service.js'
@@ -43,6 +44,7 @@ const Attachment = {
}
},
components: {
+ Flash,
StillImage,
VideoAttachment
},
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
index 2c1c1682..f80badfd 100644
--- a/src/components/attachment/attachment.vue
+++ b/src/components/attachment/attachment.vue
@@ -117,6 +117,11 @@
<!-- eslint-enable vue/no-v-html -->
</div>
</div>
+
+ <Flash
+ v-if="type === 'flash'"
+ :src="attachment.large_thumb_url || attachment.url"
+ />
</div>
</template>
@@ -172,6 +177,7 @@
}
.non-gallery.attachment {
+ &.flash,
&.video {
flex: 1 0 40%;
}
diff --git a/src/components/chat_list/chat_list.vue b/src/components/chat_list/chat_list.vue
index e23eec13..f98b7ed2 100644
--- a/src/components/chat_list/chat_list.vue
+++ b/src/components/chat_list/chat_list.vue
@@ -23,10 +23,7 @@
class="timeline"
>
<List :items="sortedChatList">
- <template
- slot="item"
- slot-scope="{item}"
- >
+ <template v-slot:item="{item}">
<ChatListItem
:key="item.id"
:compact="false"
diff --git a/src/components/chat_message/chat_message.vue b/src/components/chat_message/chat_message.vue
index 0777f880..0f3fc97d 100644
--- a/src/components/chat_message/chat_message.vue
+++ b/src/components/chat_message/chat_message.vue
@@ -50,7 +50,7 @@
@show="menuOpened = true"
@close="menuOpened = false"
>
- <div slot="content">
+ <template v-slot:content>
<div class="dropdown-menu">
<button
class="button-default dropdown-item dropdown-item-icon"
@@ -59,26 +59,28 @@
<FAIcon icon="times" /> {{ $t("chats.delete") }}
</button>
</div>
- </div>
- <button
- slot="trigger"
- class="button-default menu-icon"
- :title="$t('chats.more')"
- >
- <FAIcon icon="ellipsis-h" />
- </button>
+ </template>
+ <template v-slot:trigger>
+ <button
+ class="button-default menu-icon"
+ :title="$t('chats.more')"
+ >
+ <FAIcon icon="ellipsis-h" />
+ </button>
+ </template>
</Popover>
</div>
<StatusContent
:status="messageForStatusContent"
:full-content="true"
>
- <span
- slot="footer"
- class="created-at"
- >
- {{ createdAt }}
- </span>
+ <template v-slot:footer>
+ <span
+ class="created-at"
+ >
+ {{ createdAt }}
+ </span>
+ </template>
</StatusContent>
</div>
</div>
diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue
index 3b5aec14..836688aa 100644
--- a/src/components/domain_mute_card/domain_mute_card.vue
+++ b/src/components/domain_mute_card/domain_mute_card.vue
@@ -9,7 +9,7 @@
class="btn button-default"
>
{{ $t('domain_mute_card.unmute') }}
- <template slot="progress">
+ <template v-slot:progress>
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
@@ -19,7 +19,7 @@
class="btn button-default"
>
{{ $t('domain_mute_card.mute') }}
- <template slot="progress">
+ <template v-slot:progress>
{{ $t('domain_mute_card.mute_progress') }}
</template>
</ProgressButton>
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
index dc03bc9f..902ec384 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -57,6 +57,7 @@ const EmojiInput = {
required: true,
type: Function
},
+ // TODO VUE3: change to modelValue, change 'input' event to 'input'
value: {
/**
* Used for v-model
@@ -143,32 +144,31 @@ const EmojiInput = {
}
},
mounted () {
- const slots = this.$slots.default
- if (!slots || slots.length === 0) return
- const input = slots.find(slot => ['input', 'textarea'].includes(slot.tag))
+ const { root } = this.$refs
+ const input = root.querySelector('.emoji-input > input') || root.querySelector('.emoji-input > textarea')
if (!input) return
this.input = input
this.resize()
- input.elm.addEventListener('blur', this.onBlur)
- input.elm.addEventListener('focus', this.onFocus)
- input.elm.addEventListener('paste', this.onPaste)
- input.elm.addEventListener('keyup', this.onKeyUp)
- input.elm.addEventListener('keydown', this.onKeyDown)
- input.elm.addEventListener('click', this.onClickInput)
- input.elm.addEventListener('transitionend', this.onTransition)
- input.elm.addEventListener('input', this.onInput)
+ input.addEventListener('blur', this.onBlur)
+ input.addEventListener('focus', this.onFocus)
+ input.addEventListener('paste', this.onPaste)
+ input.addEventListener('keyup', this.onKeyUp)
+ input.addEventListener('keydown', this.onKeyDown)
+ input.addEventListener('click', this.onClickInput)
+ input.addEventListener('transitionend', this.onTransition)
+ input.addEventListener('input', this.onInput)
},
unmounted () {
const { input } = this
if (input) {
- input.elm.removeEventListener('blur', this.onBlur)
- input.elm.removeEventListener('focus', this.onFocus)
- input.elm.removeEventListener('paste', this.onPaste)
- input.elm.removeEventListener('keyup', this.onKeyUp)
- input.elm.removeEventListener('keydown', this.onKeyDown)
- input.elm.removeEventListener('click', this.onClickInput)
- input.elm.removeEventListener('transitionend', this.onTransition)
- input.elm.removeEventListener('input', this.onInput)
+ input.removeEventListener('blur', this.onBlur)
+ input.removeEventListener('focus', this.onFocus)
+ input.removeEventListener('paste', this.onPaste)
+ input.removeEventListener('keyup', this.onKeyUp)
+ input.removeEventListener('keydown', this.onKeyDown)
+ input.removeEventListener('click', this.onClickInput)
+ input.removeEventListener('transitionend', this.onTransition)
+ input.removeEventListener('input', this.onInput)
}
},
watch: {
@@ -216,7 +216,7 @@ const EmojiInput = {
}, 0)
},
togglePicker () {
- this.input.elm.focus()
+ this.input.focus()
this.showPicker = !this.showPicker
if (this.showPicker) {
this.scrollIntoView()
@@ -262,13 +262,13 @@ const EmojiInput = {
this.$emit('input', newValue)
const position = this.caret + (insertion + spaceAfter + spaceBefore).length
if (!keepOpen) {
- this.input.elm.focus()
+ this.input.focus()
}
this.$nextTick(function () {
// Re-focus inputbox after clicking suggestion
// Set selection right after the replacement instead of the very end
- this.input.elm.setSelectionRange(position, position)
+ this.input.setSelectionRange(position, position)
this.caret = position
})
},
@@ -285,9 +285,9 @@ const EmojiInput = {
this.$nextTick(function () {
// Re-focus inputbox after clicking suggestion
- this.input.elm.focus()
+ this.input.focus()
// Set selection right after the replacement instead of the very end
- this.input.elm.setSelectionRange(position, position)
+ this.input.setSelectionRange(position, position)
this.caret = position
})
e.preventDefault()
@@ -349,7 +349,7 @@ const EmojiInput = {
}
this.$nextTick(() => {
- const { offsetHeight } = this.input.elm
+ const { offsetHeight } = this.input
const { picker } = this.$refs
const pickerBottom = picker.$el.getBoundingClientRect().bottom
if (pickerBottom > window.innerHeight) {
@@ -414,8 +414,8 @@ const EmojiInput = {
// Scroll the input element to the position of the cursor
this.$nextTick(() => {
- this.input.elm.blur()
- this.input.elm.focus()
+ this.input.blur()
+ this.input.focus()
})
}
// Disable suggestions hotkeys if suggestions are hidden
@@ -444,7 +444,7 @@ const EmojiInput = {
// de-focuses the element (i.e. default browser behavior)
if (key === 'Escape') {
if (!this.temporarilyHideSuggestions) {
- this.input.elm.focus()
+ this.input.focus()
}
}
@@ -480,7 +480,7 @@ const EmojiInput = {
if (!panel) return
const picker = this.$refs.picker.$el
const panelBody = this.$refs['panel-body']
- const { offsetHeight, offsetTop } = this.input.elm
+ const { offsetHeight, offsetTop } = this.input
const offsetBottom = offsetTop + offsetHeight
this.setPlacement(panelBody, panel, offsetBottom)
@@ -494,7 +494,7 @@ const EmojiInput = {
if (this.placement === 'top' || (this.placement === 'auto' && this.overflowsBottom(container))) {
target.style.top = 'auto'
- target.style.bottom = this.input.elm.offsetHeight + 'px'
+ target.style.bottom = this.input.offsetHeight + 'px'
}
},
overflowsBottom (el) {
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index ad62484d..aa2950ce 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -1,5 +1,6 @@
<template>
<div
+ ref="root"
v-click-outside="onClickOutside"
class="emoji-input"
:class="{ 'with-picker': !hideEmojiButton }"
diff --git a/src/components/export_import/export_import.vue b/src/components/export_import/export_import.vue
deleted file mode 100644
index 8ffe34f8..00000000
--- a/src/components/export_import/export_import.vue
+++ /dev/null
@@ -1,102 +0,0 @@
-<template>
- <div class="import-export-container">
- <slot name="before" />
- <button
- class="btn button-default"
- @click="exportData"
- >
- {{ exportLabel }}
- </button>
- <button
- class="btn button-default"
- @click="importData"
- >
- {{ importLabel }}
- </button>
- <slot name="afterButtons" />
- <p
- v-if="importFailed"
- class="alert error"
- >
- {{ importFailedText }}
- </p>
- <slot name="afterError" />
- </div>
-</template>
-
-<script>
-export default {
- props: [
- 'exportObject',
- 'importLabel',
- 'exportLabel',
- 'importFailedText',
- 'validator',
- 'onImport',
- 'onImportFailure'
- ],
- data () {
- return {
- importFailed: false
- }
- },
- methods: {
- exportData () {
- const stringified = JSON.stringify(this.exportObject, null, 2) // Pretty-print and indent with 2 spaces
-
- // Create an invisible link with a data url and simulate a click
- const e = document.createElement('a')
- e.setAttribute('download', 'pleroma_theme.json')
- e.setAttribute('href', 'data:application/json;base64,' + window.btoa(stringified))
- e.style.display = 'none'
-
- document.body.appendChild(e)
- e.click()
- document.body.removeChild(e)
- },
- importData () {
- this.importFailed = false
- const filePicker = document.createElement('input')
- filePicker.setAttribute('type', 'file')
- filePicker.setAttribute('accept', '.json')
-
- filePicker.addEventListener('change', event => {
- if (event.target.files[0]) {
- // eslint-disable-next-line no-undef
- const reader = new FileReader()
- reader.onload = ({ target }) => {
- try {
- const parsed = JSON.parse(target.result)
- const valid = this.validator(parsed)
- if (valid) {
- this.onImport(parsed)
- } else {
- this.importFailed = true
- // this.onImportFailure(valid)
- }
- } catch (e) {
- // This will happen both if there is a JSON syntax error or the theme is missing components
- this.importFailed = true
- // this.onImportFailure(e)
- }
- }
- reader.readAsText(event.target.files[0])
- }
- })
-
- document.body.appendChild(filePicker)
- filePicker.click()
- document.body.removeChild(filePicker)
- }
- }
-}
-</script>
-
-<style lang="scss">
-.import-export-container {
- display: flex;
- flex-wrap: wrap;
- align-items: baseline;
- justify-content: center;
-}
-</style>
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index c6cb9fbe..a3c3c767 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -7,10 +7,7 @@
:bound-to="{ x: 'container' }"
remove-padding
>
- <div
- slot="content"
- slot-scope="{close}"
- >
+ <template v-slot:content="{close}">
<div class="dropdown-menu">
<button
v-if="canMute && !status.thread_muted"
@@ -120,16 +117,15 @@
/><span>{{ $t("user_card.report") }}</span>
</button>
</div>
- </div>
- <span
- slot="trigger"
- class="popover-trigger"
- >
- <FAIcon
- class="fa-scale-110 fa-old-padding"
- icon="ellipsis-h"
- />
- </span>
+ </template>
+ <template v-slot:trigger>
+ <button class="button-unstyled popover-trigger">
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="ellipsis-h"
+ />
+ </button>
+ </template>
</Popover>
</template>
diff --git a/src/components/features_panel/features_panel.js b/src/components/features_panel/features_panel.js
index 8b142d08..d177efeb 100644
--- a/src/components/features_panel/features_panel.js
+++ b/src/components/features_panel/features_panel.js
@@ -2,7 +2,7 @@ import fileSizeFormatService from '../../services/file_size_format/file_size_for
const FeaturesPanel = {
computed: {
- chat: function () { return this.$store.state.instance.chatAvailable },
+ shout: function () { return this.$store.state.instance.shoutAvailable },
pleromaChatMessages: function () { return this.$store.state.instance.pleromaChatMessagesAvailable },
gopher: function () { return this.$store.state.instance.gopherAvailable },
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
diff --git a/src/components/features_panel/features_panel.vue b/src/components/features_panel/features_panel.vue
index 9605d09d..a58a99af 100644
--- a/src/components/features_panel/features_panel.vue
+++ b/src/components/features_panel/features_panel.vue
@@ -8,8 +8,8 @@
</div>
<div class="panel-body features-panel">
<ul>
- <li v-if="chat">
- {{ $t('features_panel.chat') }}
+ <li v-if="shout">
+ {{ $t('features_panel.shout') }}
</li>
<li v-if="pleromaChatMessages">
{{ $t('features_panel.pleroma_chat_messages') }}
diff --git a/src/components/flash/flash.js b/src/components/flash/flash.js
new file mode 100644
index 00000000..d03384c7
--- /dev/null
+++ b/src/components/flash/flash.js
@@ -0,0 +1,52 @@
+import RuffleService from '../../services/ruffle_service/ruffle_service.js'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faStop,
+ faExclamationTriangle
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faStop,
+ faExclamationTriangle
+)
+
+const Flash = {
+ props: [ 'src' ],
+ data () {
+ return {
+ player: false, // can be true, "hidden", false. hidden = element exists
+ loaded: false,
+ ruffleInstance: null
+ }
+ },
+ methods: {
+ openPlayer () {
+ if (this.player) return // prevent double-loading, or re-loading on failure
+ this.player = 'hidden'
+ RuffleService.getRuffle().then((ruffle) => {
+ const player = ruffle.newest().createPlayer()
+ player.config = {
+ letterbox: 'on'
+ }
+ const container = this.$refs.container
+ container.appendChild(player)
+ player.style.width = '100%'
+ player.style.height = '100%'
+ player.load(this.src).then(() => {
+ this.player = true
+ }).catch((e) => {
+ console.error('Error loading ruffle', e)
+ this.player = 'error'
+ })
+ this.ruffleInstance = player
+ })
+ },
+ closePlayer () {
+ console.log(this.ruffleInstance)
+ this.ruffleInstance.remove()
+ this.player = false
+ }
+ }
+}
+
+export default Flash
diff --git a/src/components/flash/flash.vue b/src/components/flash/flash.vue
new file mode 100644
index 00000000..d20d037b
--- /dev/null
+++ b/src/components/flash/flash.vue
@@ -0,0 +1,88 @@
+<template>
+ <div class="Flash">
+ <div
+ v-if="player === true || player === 'hidden'"
+ ref="container"
+ class="player"
+ :class="{ hidden: player === 'hidden' }"
+ />
+ <button
+ v-if="player !== true"
+ class="button-unstyled placeholder"
+ @click="openPlayer"
+ >
+ <span
+ v-if="player === 'hidden'"
+ class="label"
+ >
+ {{ $t('general.loading') }}
+ </span>
+ <span
+ v-if="player === 'error'"
+ class="label"
+ >
+ {{ $t('general.flash_fail') }}
+ </span>
+ <span
+ v-else
+ class="label"
+ >
+ <p>
+ {{ $t('general.flash_content') }}
+ </p>
+ <p>
+ <FAIcon icon="exclamation-triangle" />
+ {{ $t('general.flash_security') }}
+ </p>
+ </span>
+ </button>
+ <button
+ v-if="player"
+ class="button-unstyled hider"
+ @click="closePlayer"
+ >
+ <FAIcon icon="stop" />
+ </button>
+ </div>
+</template>
+
+<script src="./flash.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+.Flash {
+ width: 100%;
+ height: 260px;
+ position: relative;
+
+ .player {
+ height: 100%;
+ width: 100%;
+ }
+
+ .hider {
+ top: 0;
+ }
+
+ .label {
+ text-align: center;
+ flex: 1 1 0;
+ line-height: 1.2;
+ white-space: normal;
+ word-wrap: normal;
+ }
+
+ .hidden {
+ display: none;
+ visibility: 'hidden';
+ }
+
+ .placeholder {
+ height: 100%;
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+}
+</style>
diff --git a/src/components/font_control/font_control.js b/src/components/font_control/font_control.js
index 6274780b..137ef9c0 100644
--- a/src/components/font_control/font_control.js
+++ b/src/components/font_control/font_control.js
@@ -1,14 +1,10 @@
import { set } from 'vue'
-import { library } from '@fortawesome/fontawesome-svg-core'
-import {
- faChevronDown
-} from '@fortawesome/free-solid-svg-icons'
-
-library.add(
- faChevronDown
-)
+import Select from '../select/select.vue'
export default {
+ components: {
+ Select
+ },
props: [
'name', 'label', 'value', 'fallback', 'options', 'no-inherit'
],
diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue
index dd117ec0..29605084 100644
--- a/src/components/font_control/font_control.vue
+++ b/src/components/font_control/font_control.vue
@@ -22,30 +22,20 @@
class="opt-l"
:for="name + '-o'"
/>
- <label
- :for="name + '-font-switcher'"
- class="select"
+ <Select
+ :id="name + '-font-switcher'"
+ v-model="preset"
:disabled="!present"
+ class="font-switcher"
>
- <select
- :id="name + '-font-switcher'"
- v-model="preset"
- :disabled="!present"
- class="font-switcher"
+ <option
+ v-for="option in availableOptions"
+ :key="option"
+ :value="option"
>
- <option
- v-for="option in availableOptions"
- :key="option"
- :value="option"
- >
- {{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
- </option>
- </select>
- <FAIcon
- class="select-down-icon"
- icon="chevron-down"
- />
- </label>
+ {{ option === 'custom' ? $t('settings.style.fonts.custom') : option }}
+ </option>
+ </Select>
<input
v-if="isCustom"
:id="name"
@@ -65,7 +55,8 @@
min-width: 10em;
}
&.custom {
- .select {
+ /* TODO Should make proper joiners... */
+ .font-switcher {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
diff --git a/src/components/global_notice_list/global_notice_list.vue b/src/components/global_notice_list/global_notice_list.vue
index 049e23db..a45f4586 100644
--- a/src/components/global_notice_list/global_notice_list.vue
+++ b/src/components/global_notice_list/global_notice_list.vue
@@ -71,6 +71,14 @@
}
}
+ .global-success {
+ background-color: var(--alertPopupSuccess, $fallback--cGreen);
+ color: var(--alertPopupSuccessText, $fallback--text);
+ .svg-inline--fa {
+ color: var(--alertPopupSuccessText, $fallback--text);
+ }
+ }
+
.global-info {
background-color: var(--alertPopupNeutral, $fallback--fg);
color: var(--alertPopupNeutralText, $fallback--text);
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index dc3bd408..cf307a24 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -3,27 +3,18 @@
<label for="interface-language-switcher">
{{ $t('settings.interfaceLanguage') }}
</label>
- <label
- for="interface-language-switcher"
- class="select"
+ <Select
+ id="interface-language-switcher"
+ v-model="language"
>
- <select
- id="interface-language-switcher"
- v-model="language"
+ <option
+ v-for="lang in languages"
+ :key="lang.code"
+ :value="lang.code"
>
- <option
- v-for="lang in languages"
- :key="lang.code"
- :value="lang.code"
- >
- {{ lang.name }}
- </option>
- </select>
- <FAIcon
- class="select-down-icon"
- icon="chevron-down"
- />
- </label>
+ {{ lang.name }}
+ </option>
+ </Select>
</div>
</template>
@@ -32,16 +23,12 @@ 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
-)
+import Select from '../select/select.vue'
export default {
+ components: {
+ Select
+ },
computed: {
languages () {
return _.map(languagesObject.languages, (code) => ({ code: code, name: this.getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name))
diff --git a/src/components/moderation_tools/moderation_tools.js b/src/components/moderation_tools/moderation_tools.js
index d4fdc53e..2469327a 100644
--- a/src/components/moderation_tools/moderation_tools.js
+++ b/src/components/moderation_tools/moderation_tools.js
@@ -1,6 +1,11 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faChevronDown } from '@fortawesome/free-solid-svg-icons'
+
import DialogModal from '../dialog_modal/dialog_modal.vue'
import Popover from '../popover/popover.vue'
+library.add(faChevronDown)
+
const FORCE_NSFW = 'mrf_tag:media-force-nsfw'
const STRIP_MEDIA = 'mrf_tag:media-strip'
const FORCE_UNLISTED = 'mrf_tag:force-unlisted'
diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue
index 5c7b82ec..96476abe 100644
--- a/src/components/moderation_tools/moderation_tools.vue
+++ b/src/components/moderation_tools/moderation_tools.vue
@@ -8,7 +8,7 @@
@show="setToggled(true)"
@close="setToggled(false)"
>
- <div slot="content">
+ <template v-slot:content>
<div class="dropdown-menu">
<span v-if="user.is_local">
<button
@@ -50,96 +50,98 @@
class="button-default dropdown-item"
@click="toggleTag(tags.FORCE_NSFW)"
>
- {{ $t('user_card.admin_menu.force_nsfw') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }"
/>
+ {{ $t('user_card.admin_menu.force_nsfw') }}
</button>
<button
class="button-default dropdown-item"
@click="toggleTag(tags.STRIP_MEDIA)"
>
- {{ $t('user_card.admin_menu.strip_media') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }"
/>
+ {{ $t('user_card.admin_menu.strip_media') }}
</button>
<button
class="button-default dropdown-item"
@click="toggleTag(tags.FORCE_UNLISTED)"
>
- {{ $t('user_card.admin_menu.force_unlisted') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }"
/>
+ {{ $t('user_card.admin_menu.force_unlisted') }}
</button>
<button
class="button-default dropdown-item"
@click="toggleTag(tags.SANDBOX)"
>
- {{ $t('user_card.admin_menu.sandbox') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }"
/>
+ {{ $t('user_card.admin_menu.sandbox') }}
</button>
<button
v-if="user.is_local"
class="button-default dropdown-item"
@click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)"
>
- {{ $t('user_card.admin_menu.disable_remote_subscription') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }"
/>
+ {{ $t('user_card.admin_menu.disable_remote_subscription') }}
</button>
<button
v-if="user.is_local"
class="button-default dropdown-item"
@click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)"
>
- {{ $t('user_card.admin_menu.disable_any_subscription') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }"
/>
+ {{ $t('user_card.admin_menu.disable_any_subscription') }}
</button>
<button
v-if="user.is_local"
class="button-default dropdown-item"
@click="toggleTag(tags.QUARANTINE)"
>
- {{ $t('user_card.admin_menu.quarantine') }}
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }"
/>
+ {{ $t('user_card.admin_menu.quarantine') }}
</button>
</span>
</div>
- </div>
- <button
- slot="trigger"
- class="btn button-default btn-block"
- :class="{ toggled }"
- >
- {{ $t('user_card.admin_menu.moderation') }}
- </button>
+ </template>
+ <template v-slot:trigger>
+ <button
+ class="btn button-default btn-block moderation-tools-button"
+ :class="{ toggled }"
+ >
+ {{ $t('user_card.admin_menu.moderation') }}
+ <FAIcon icon="chevron-down" />
+ </button>
+ </template>
</Popover>
<portal to="modal">
<DialogModal
v-if="showDeleteUserDialog"
:on-cancel="deleteUserDialog.bind(this, false)"
>
- <template slot="header">
+ <template v-slot:header>
{{ $t('user_card.admin_menu.delete_user') }}
</template>
<p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p>
- <template slot="footer">
+ <template v-slot:footer>
<button
class="btn button-default"
@click="deleteUserDialog(false)"
@@ -163,25 +165,6 @@
<style lang="scss">
@import '../../_variables.scss';
-.menu-checkbox {
- float: right;
- min-width: 22px;
- max-width: 22px;
- min-height: 22px;
- max-height: 22px;
- line-height: 22px;
- text-align: center;
- border-radius: 0px;
- background-color: $fallback--fg;
- background-color: var(--input, $fallback--fg);
- box-shadow: 0px 0px 2px black inset;
- box-shadow: var(--inputShadow);
-
- &.menu-checkbox-checked::after {
- content: '✓';
- }
-}
-
.moderation-tools-popover {
height: 100%;
.trigger {
@@ -189,4 +172,10 @@
height: 100%;
}
}
+
+.moderation-tools-button {
+ svg,i {
+ font-size: 0.8em;
+ }
+}
</style>
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index 81d49cc2..37bcb409 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -1,4 +1,4 @@
-import { timelineNames } from '../timeline_menu/timeline_menu.js'
+import TimelineMenuContent from '../timeline_menu/timeline_menu_content.vue'
import { mapState, mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
@@ -7,10 +7,12 @@ import {
faGlobe,
faBookmark,
faEnvelope,
- faHome,
+ faChevronDown,
+ faChevronUp,
faComments,
faBell,
- faInfoCircle
+ faInfoCircle,
+ faStream
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -18,10 +20,12 @@ library.add(
faGlobe,
faBookmark,
faEnvelope,
- faHome,
+ faChevronDown,
+ faChevronUp,
faComments,
faBell,
- faInfoCircle
+ faInfoCircle,
+ faStream
)
const NavPanel = {
@@ -30,16 +34,20 @@ const NavPanel = {
this.$store.dispatch('startFetchingFollowRequests')
}
},
+ components: {
+ TimelineMenuContent
+ },
+ data () {
+ return {
+ showTimelines: false
+ }
+ },
+ methods: {
+ toggleTimelines () {
+ this.showTimelines = !this.showTimelines
+ }
+ },
computed: {
- onTimelineRoute () {
- return !!timelineNames()[this.$route.name]
- },
- timelinesRoute () {
- if (this.$store.state.interface.lastTimeline) {
- return this.$store.state.interface.lastTimeline
- }
- return this.currentUser ? 'friends' : 'public-timeline'
- },
...mapState({
currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length,
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 0c83d0fe..7ae7b1d6 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -3,19 +3,33 @@
<div class="panel panel-default">
<ul>
<li v-if="currentUser || !privateMode">
- <router-link
- :to="{ name: timelinesRoute }"
- :class="onTimelineRoute && 'router-link-active'"
+ <button
+ class="button-unstyled menu-item"
+ @click="toggleTimelines"
>
<FAIcon
fixed-width
class="fa-scale-110"
- icon="home"
+ icon="stream"
/>{{ $t("nav.timelines") }}
- </router-link>
+ <FAIcon
+ class="timelines-chevron"
+ fixed-width
+ :icon="showTimelines ? 'chevron-up' : 'chevron-down'"
+ />
+ </button>
+ <div
+ v-show="showTimelines"
+ class="timelines-background"
+ >
+ <TimelineMenuContent class="timelines" />
+ </div>
</li>
<li v-if="currentUser">
- <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
+ <router-link
+ class="menu-item"
+ :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
+ >
<FAIcon
fixed-width
class="fa-scale-110"
@@ -24,7 +38,10 @@
</router-link>
</li>
<li v-if="currentUser && pleromaChatMessagesAvailable">
- <router-link :to="{ name: 'chats', params: { username: currentUser.screen_name } }">
+ <router-link
+ class="menu-item"
+ :to="{ name: 'chats', params: { username: currentUser.screen_name } }"
+ >
<div
v-if="unreadChatCount"
class="badge badge-notification"
@@ -39,7 +56,10 @@
</router-link>
</li>
<li v-if="currentUser && currentUser.locked">
- <router-link :to="{ name: 'friend-requests' }">
+ <router-link
+ class="menu-item"
+ :to="{ name: 'friend-requests' }"
+ >
<FAIcon
fixed-width
class="fa-scale-110"
@@ -54,7 +74,10 @@
</router-link>
</li>
<li>
- <router-link :to="{ name: 'about' }">
+ <router-link
+ class="menu-item"
+ :to="{ name: 'about' }"
+ >
<FAIcon
fixed-width
class="fa-scale-110"
@@ -91,14 +114,14 @@
border-color: var(--border, $fallback--border);
padding: 0;
- &:first-child a {
+ &:first-child .menu-item {
border-top-right-radius: $fallback--panelRadius;
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
border-top-left-radius: $fallback--panelRadius;
border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
}
- &:last-child a {
+ &:last-child .menu-item {
border-bottom-right-radius: $fallback--panelRadius;
border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius);
border-bottom-left-radius: $fallback--panelRadius;
@@ -110,13 +133,15 @@
border: none;
}
- a {
+ .menu-item {
display: block;
box-sizing: border-box;
- align-items: stretch;
height: 3.5em;
line-height: 3.5em;
padding: 0 1em;
+ width: 100%;
+ color: $fallback--link;
+ color: var(--link, $fallback--link);
&:hover {
background-color: $fallback--lightBg;
@@ -146,6 +171,25 @@
}
}
+ .timelines-chevron {
+ margin-left: 0.8em;
+ font-size: 1.1em;
+ }
+
+ .timelines-background {
+ padding: 0 0 0 0.6em;
+ background-color: $fallback--lightBg;
+ background-color: var(--selectedMenu, $fallback--lightBg);
+ border-top: 1px solid;
+ border-color: $fallback--border;
+ border-color: var(--border, $fallback--border);
+ }
+
+ .timelines {
+ background-color: $fallback--bg;
+ background-color: var(--bg, $fallback--bg);
+ }
+
.fa-scale-110 {
margin-right: 0.8em;
}
diff --git a/src/components/notifications/notification_filters.vue b/src/components/notifications/notification_filters.vue
new file mode 100644
index 00000000..ba0e90a0
--- /dev/null
+++ b/src/components/notifications/notification_filters.vue
@@ -0,0 +1,122 @@
+<template>
+ <Popover
+ trigger="click"
+ class="NotificationFilters"
+ placement="bottom"
+ :bound-to="{ x: 'container' }"
+ >
+ <template v-slot:content>
+ <div class="dropdown-menu">
+ <button
+ class="button-default dropdown-item"
+ @click="toggleNotificationFilter('likes')"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-checked': filters.likes }"
+ />{{ $t('settings.notification_visibility_likes') }}
+ </button>
+ <button
+ class="button-default dropdown-item"
+ @click="toggleNotificationFilter('repeats')"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-checked': filters.repeats }"
+ />{{ $t('settings.notification_visibility_repeats') }}
+ </button>
+ <button
+ class="button-default dropdown-item"
+ @click="toggleNotificationFilter('follows')"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-checked': filters.follows }"
+ />{{ $t('settings.notification_visibility_follows') }}
+ </button>
+ <button
+ class="button-default dropdown-item"
+ @click="toggleNotificationFilter('mentions')"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-checked': filters.mentions }"
+ />{{ $t('settings.notification_visibility_mentions') }}
+ </button>
+ <button
+ class="button-default dropdown-item"
+ @click="toggleNotificationFilter('emojiReactions')"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-checked': filters.emojiReactions }"
+ />{{ $t('settings.notification_visibility_emoji_reactions') }}
+ </button>
+ <button
+ class="button-default dropdown-item"
+ @click="toggleNotificationFilter('moves')"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-checked': filters.moves }"
+ />{{ $t('settings.notification_visibility_moves') }}
+ </button>
+ </div>
+ </template>
+ <template v-slot:trigger>
+ <button class="button-unstyled">
+ <FAIcon icon="filter" />
+ </button>
+ </template>
+ </Popover>
+</template>
+
+<script>
+import Popover from '../popover/popover.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faFilter } from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faFilter
+)
+
+export default {
+ components: { Popover },
+ computed: {
+ filters () {
+ return this.$store.getters.mergedConfig.notificationVisibility
+ }
+ },
+ methods: {
+ toggleNotificationFilter (type) {
+ this.$store.dispatch('setOption', {
+ name: 'notificationVisibility',
+ value: {
+ ...this.filters,
+ [type]: !this.filters[type]
+ }
+ })
+ }
+ }
+}
+</script>
+
+<style lang="scss">
+
+.NotificationFilters {
+ align-self: stretch;
+
+ > button {
+ font-size: 1.2em;
+ padding-left: 0.7em;
+ padding-right: 0.2em;
+ line-height: 100%;
+ height: 100%;
+ }
+
+ .dropdown-item {
+ margin: 0;
+ }
+}
+
+</style>
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index 49258563..c8f1ebcb 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -1,5 +1,6 @@
import { mapGetters } from 'vuex'
import Notification from '../notification/notification.vue'
+import NotificationFilters from './notification_filters.vue'
import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
import {
notificationsFromStore,
@@ -17,6 +18,10 @@ library.add(
const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
const Notifications = {
+ components: {
+ Notification,
+ NotificationFilters
+ },
props: {
// Disables display of panel header
noHeading: Boolean,
@@ -35,11 +40,6 @@ const Notifications = {
seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
}
},
- created () {
- const store = this.$store
- const credentials = store.state.users.currentUser.credentials
- notificationsFetcher.fetchAndUpdate({ store, credentials })
- },
computed: {
mainClass () {
return this.minimalMode ? '' : 'panel panel-default'
@@ -70,9 +70,6 @@ const Notifications = {
},
...mapGetters(['unreadChatCount'])
},
- components: {
- Notification
- },
watch: {
unseenCountTitle (count) {
if (count > 0) {
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 682ae127..2bb627a8 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -1,6 +1,6 @@
@import '../../_variables.scss';
-.notifications {
+.Notifications {
&:not(.minimal) {
// a bit of a hack to allow scrolling below notifications
padding-bottom: 15em;
@@ -11,6 +11,10 @@
color: var(--text, $fallback--text);
}
+ .notifications-footer {
+ border: none;
+ }
+
.notification {
position: relative;
@@ -82,7 +86,6 @@
}
}
-
.follow-text, .move-text {
padding: 0.5em 0;
overflow-wrap: break-word;
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 725d1ad4..2ce5d56f 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -1,7 +1,7 @@
<template>
<div
:class="{ minimal: minimalMode }"
- class="notifications"
+ class="Notifications"
>
<div :class="mainClass">
<div
@@ -22,6 +22,7 @@
>
{{ $t('notifications.read') }}
</button>
+ <NotificationFilters />
</div>
<div class="panel-body">
<div
@@ -34,10 +35,10 @@
<notification :notification="notification" />
</div>
</div>
- <div class="panel-footer">
+ <div class="panel-footer notifications-footer">
<div
v-if="bottomedOut"
- class="new-status-notification text-center panel-footer faint"
+ class="new-status-notification text-center faint"
>
{{ $t('notifications.no_more_notifications') }}
</div>
@@ -46,13 +47,13 @@
class="button-unstyled -link -fullwidth"
@click.prevent="fetchOlderNotifications()"
>
- <div class="new-status-notification text-center panel-footer">
+ <div class="new-status-notification text-center">
{{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
</div>
</button>
<div
v-else
- class="new-status-notification text-center panel-footer"
+ class="new-status-notification text-center"
>
<FAIcon
icon="circle-notch"
diff --git a/src/components/password_reset/password_reset.vue b/src/components/password_reset/password_reset.vue
index a931cb5a..3ffa5425 100644
--- a/src/components/password_reset/password_reset.vue
+++ b/src/components/password_reset/password_reset.vue
@@ -53,7 +53,7 @@
type="submit"
class="btn button-default btn-block"
>
- {{ $t('general.submit') }}
+ {{ $t('settings.save') }}
</button>
</div>
</div>
diff --git a/src/components/poll/poll_form.js b/src/components/poll/poll_form.js
index 1f8df3f9..e30645c3 100644
--- a/src/components/poll/poll_form.js
+++ b/src/components/poll/poll_form.js
@@ -1,19 +1,21 @@
import * as DateUtils from 'src/services/date_utils/date_utils.js'
import { uniq } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core'
+import Select from '../select/select.vue'
import {
faTimes,
- faChevronDown,
faPlus
} from '@fortawesome/free-solid-svg-icons'
library.add(
faTimes,
- faChevronDown,
faPlus
)
export default {
+ components: {
+ Select
+ },
name: 'PollForm',
props: ['visible'],
data: () => ({
diff --git a/src/components/poll/poll_form.vue b/src/components/poll/poll_form.vue
index c4403210..3620075a 100644
--- a/src/components/poll/poll_form.vue
+++ b/src/components/poll/poll_form.vue
@@ -46,23 +46,19 @@
class="poll-type"
:title="$t('polls.type')"
>
- <label
- for="poll-type-selector"
- class="select"
+ <Select
+ v-model="pollType"
+ class="poll-type-select"
+ unstyled="true"
+ @change="updatePollToParent"
>
- <select
- v-model="pollType"
- class="select"
- @change="updatePollToParent"
- >
- <option value="single">{{ $t('polls.single_choice') }}</option>
- <option value="multiple">{{ $t('polls.multiple_choices') }}</option>
- </select>
- <FAIcon
- class="select-down-icon"
- icon="chevron-down"
- />
- </label>
+ <option value="single">
+ {{ $t('polls.single_choice') }}
+ </option>
+ <option value="multiple">
+ {{ $t('polls.multiple_choices') }}
+ </option>
+ </Select>
</div>
<div
class="poll-expiry"
@@ -76,24 +72,20 @@
:max="maxExpirationInCurrentUnit"
@change="expiryAmountChange"
>
- <label class="expiry-unit select">
- <select
- v-model="expiryUnit"
- @change="expiryAmountChange"
+ <Select
+ v-model="expiryUnit"
+ unstyled="true"
+ class="expiry-unit"
+ @change="expiryAmountChange"
+ >
+ <option
+ v-for="unit in expiryUnits"
+ :key="unit"
+ :value="unit"
>
- <option
- v-for="unit in expiryUnits"
- :key="unit"
- :value="unit"
- >
- {{ $t(`time.${unit}_short`, ['']) }}
- </option>
- </select>
- <FAIcon
- class="select-down-icon"
- icon="chevron-down"
- />
- </label>
+ {{ $t(`time.${unit}_short`, ['']) }}
+ </option>
+ </Select>
</div>
</div>
</div>
@@ -147,10 +139,8 @@
.poll-type {
margin-right: 0.75em;
flex: 1 1 60%;
- .select {
- border: none;
- box-shadow: none;
- background-color: transparent;
+
+ .poll-type-select {
padding-right: 0.75em;
}
}
@@ -162,12 +152,6 @@
width: 3em;
text-align: right;
}
-
- .expiry-unit {
- border: none;
- box-shadow: none;
- background-color: transparent;
- }
}
}
</style>
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
index bfe39212..6ccf32f0 100644
--- a/src/components/popover/popover.js
+++ b/src/components/popover/popover.js
@@ -3,25 +3,32 @@ const Popover = {
props: {
// Action to trigger popover: either 'hover' or 'click'
trigger: String,
+
// Either 'top' or 'bottom'
placement: String,
+
// Takes object with properties 'x' and 'y', values of these can be
// 'container' for using offsetParent as boundaries for either axis
// or 'viewport'
boundTo: Object,
+
// Takes a selector to use as a replacement for the parent container
// for getting boundaries for x an y axis
boundToSelector: String,
+
// Takes a top/bottom/left/right object, how much space to leave
// between boundary and popover element
margin: Object,
+
// Takes a x/y object and tells how many pixels to offset from
// anchor point on either axis
offset: Object,
+
// 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,
+
// 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
@@ -47,8 +54,11 @@ const Popover = {
}
// Popover will be anchored around this element, trigger ref is the container, so
- // its children are what are inside the slot. Expect only one slot="trigger".
+ // its children are what are inside the slot. Expect only one v-slot:trigger.
const anchorEl = (this.$refs.trigger && this.$refs.trigger.children[0]) || this.$el
+ // SVGs don't have offsetWidth/Height, use fallback
+ const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth
+ const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight
const screenBox = anchorEl.getBoundingClientRect()
// Screen position of the origin point for popover
const origin = { x: screenBox.left + screenBox.width * 0.5, y: screenBox.top }
@@ -107,11 +117,11 @@ const Popover = {
const yOffset = (this.offset && this.offset.y) || 0
const translateY = usingTop
- ? -anchorEl.offsetHeight + vPadding - yOffset - content.offsetHeight
+ ? -anchorHeight + vPadding - yOffset - content.offsetHeight
: yOffset
const xOffset = (this.offset && this.offset.x) || 0
- const translateX = (anchorEl.offsetWidth * 0.5) - content.offsetWidth * 0.5 + horizOffset + xOffset
+ const translateX = anchorWidth * 0.5 - content.offsetWidth * 0.5 + horizOffset + xOffset
// Note, separate translateX and translateY avoids blurry text on chromium,
// single translate or translate3d resulted in blurry text.
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
index bacbc590..2e78a09e 100644
--- a/src/components/popover/popover.vue
+++ b/src/components/popover/popover.vue
@@ -82,10 +82,9 @@
.dropdown-item {
line-height: 21px;
- margin-right: 5px;
overflow: auto;
display: block;
- padding: .25rem 1.0rem .25rem 1.5rem;
+ padding: .5em 0.75em;
clear: both;
font-weight: 400;
text-align: inherit;
@@ -101,10 +100,9 @@
--btnText: var(--popoverText, $fallback--text);
&-icon {
- padding-left: 0.5rem;
-
svg {
- margin-right: 0.25rem;
+ width: 22px;
+ margin-right: 0.75rem;
color: var(--menuPopoverIcon, $fallback--icon)
}
}
@@ -123,6 +121,33 @@
}
}
+ .menu-checkbox {
+ display: inline-block;
+ vertical-align: middle;
+ min-width: 22px;
+ max-width: 22px;
+ min-height: 22px;
+ max-height: 22px;
+ line-height: 22px;
+ text-align: center;
+ border-radius: 0px;
+ background-color: $fallback--fg;
+ background-color: var(--input, $fallback--fg);
+ box-shadow: 0px 0px 2px black inset;
+ box-shadow: var(--inputShadow);
+ margin-right: 0.75em;
+
+ &.menu-checkbox-checked::after {
+ font-size: 1.25em;
+ content: '✓';
+ }
+
+ &.menu-checkbox-radio::after {
+ font-size: 2em;
+ content: '•';
+ }
+ }
+
}
}
</style>
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 4148381c..5342894f 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -11,10 +11,10 @@ import { reject, map, uniqBy, debounce } from 'lodash'
import suggestor from '../emoji_input/suggestor.js'
import { mapGetters, mapState } from 'vuex'
import Checkbox from '../checkbox/checkbox.vue'
+import Select from '../select/select.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
- faChevronDown,
faSmileBeam,
faPollH,
faUpload,
@@ -24,7 +24,6 @@ import {
} from '@fortawesome/free-solid-svg-icons'
library.add(
- faChevronDown,
faSmileBeam,
faPollH,
faUpload,
@@ -84,6 +83,7 @@ const PostStatusForm = {
PollForm,
ScopeSelector,
Checkbox,
+ Select,
Attachment,
StatusContent
},
@@ -115,7 +115,7 @@ const PostStatusForm = {
? this.copyMessageScope
: this.$store.state.users.currentUser.default_scope
- const { postContentType: contentType } = this.$store.getters.mergedConfig
+ const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig
return {
dropFiles: [],
@@ -126,7 +126,7 @@ const PostStatusForm = {
newStatus: {
spoilerText: this.subject || '',
status: statusText,
- nsfw: false,
+ nsfw: !!sensitiveByDefault,
files: [],
poll: {},
mediaDescriptions: {},
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 73f6a4f1..fbda41d6 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -189,28 +189,19 @@
v-if="postFormats.length > 1"
class="text-format"
>
- <label
- for="post-content-type"
- class="select"
+ <Select
+ id="post-content-type"
+ v-model="newStatus.contentType"
+ class="form-control"
>
- <select
- id="post-content-type"
- v-model="newStatus.contentType"
- class="form-control"
+ <option
+ v-for="postFormat in postFormats"
+ :key="postFormat"
+ :value="postFormat"
>
- <option
- v-for="postFormat in postFormats"
- :key="postFormat"
- :value="postFormat"
- >
- {{ $t(`post_status.content_type["${postFormat}"]`) }}
- </option>
- </select>
- <FAIcon
- class="select-down-icon"
- icon="chevron-down"
- />
- </label>
+ {{ $t(`post_status.content_type["${postFormat}"]`) }}
+ </option>
+ </Select>
</div>
<div
v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
@@ -272,7 +263,7 @@
disabled
class="btn button-default"
>
- {{ $t('general.submit') }}
+ {{ $t('post_status.post') }}
</button>
<!-- touchstart is used to keep the OSK at the same position after a message send -->
<button
@@ -282,7 +273,7 @@
@touchstart.stop.prevent="postStatus($event, newStatus)"
@click.stop.prevent="postStatus($event, newStatus)"
>
- {{ $t('general.submit') }}
+ {{ $t('post_status.post') }}
</button>
</div>
<div
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index 4839024c..c69c315b 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -8,10 +8,7 @@
remove-padding
@show="focusInput"
>
- <div
- slot="content"
- slot-scope="{close}"
- >
+ <template v-slot:content="{close}">
<div class="reaction-picker-filter">
<input
v-model="filterWord"
@@ -41,17 +38,18 @@
</span>
<div class="reaction-bottom-fader" />
</div>
- </div>
- <span
- slot="trigger"
- class="popover-trigger"
- :title="$t('tool_tip.add_reaction')"
- >
- <FAIcon
- class="fa-scale-110 fa-old-padding"
- :icon="['far', 'smile-beam']"
- />
- </span>
+ </template>
+ <template v-slot:trigger>
+ <button
+ class="button-unstyled popover-trigger"
+ :title="$t('tool_tip.add_reaction')"
+ >
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ :icon="['far', 'smile-beam']"
+ />
+ </button>
+ </template>
</Popover>
</template>
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 062d4121..65b4bb33 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -230,7 +230,7 @@
type="submit"
class="btn button-default"
>
- {{ $t('general.submit') }}
+ {{ $t('registration.register') }}
</button>
</div>
</div>
diff --git a/src/components/select/select.js b/src/components/select/select.js
new file mode 100644
index 00000000..49535d07
--- /dev/null
+++ b/src/components/select/select.js
@@ -0,0 +1,21 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faChevronDown
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faChevronDown
+)
+
+export default {
+ model: {
+ prop: 'value',
+ event: 'change'
+ },
+ props: [
+ 'value',
+ 'disabled',
+ 'unstyled',
+ 'kind'
+ ]
+}
diff --git a/src/components/select/select.vue b/src/components/select/select.vue
new file mode 100644
index 00000000..5ade1fa6
--- /dev/null
+++ b/src/components/select/select.vue
@@ -0,0 +1,62 @@
+
+<template>
+ <label
+ class="Select input"
+ :class="{ disabled, unstyled }"
+ >
+ <select
+ :disabled="disabled"
+ :value="value"
+ @change="$emit('change', $event.target.value)"
+ >
+ <slot />
+ </select>
+ <FAIcon
+ class="select-down-icon"
+ icon="chevron-down"
+ />
+ </label>
+</template>
+
+<script src="./select.js"> </script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.Select {
+ padding: 0;
+
+ select {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ background: transparent;
+ border: none;
+ color: $fallback--text;
+ color: var(--inputText, --text, $fallback--text);
+ margin: 0;
+ padding: 0 2em 0 .2em;
+ font-family: sans-serif;
+ font-family: var(--inputFont, sans-serif);
+ font-size: 14px;
+ width: 100%;
+ z-index: 1;
+ height: 28px;
+ line-height: 16px;
+ }
+
+ .select-down-icon {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: 5px;
+ height: 100%;
+ color: $fallback--text;
+ color: var(--inputText, $fallback--text);
+ line-height: 28px;
+ z-index: 0;
+ pointer-events: none;
+ }
+
+}
+</style>
diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue
index a9bb12a1..3f885881 100644
--- a/src/components/selectable_list/selectable_list.vue
+++ b/src/components/selectable_list/selectable_list.vue
@@ -24,10 +24,7 @@
:items="items"
:get-key="getKey"
>
- <template
- slot="item"
- slot-scope="{item}"
- >
+ <template v-slot:item="{item}">
<div
class="selectable-list-item-inner"
:class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
@@ -44,7 +41,7 @@
/>
</div>
</template>
- <template slot="empty">
+ <template v-slot:empty>
<slot name="empty" />
</template>
</List>
diff --git a/src/components/settings_modal/helpers/boolean_setting.js b/src/components/settings_modal/helpers/boolean_setting.js
new file mode 100644
index 00000000..5c52f697
--- /dev/null
+++ b/src/components/settings_modal/helpers/boolean_setting.js
@@ -0,0 +1,38 @@
+import { get, set } from 'lodash'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
+import ModifiedIndicator from './modified_indicator.vue'
+export default {
+ components: {
+ Checkbox,
+ ModifiedIndicator
+ },
+ props: [
+ 'path',
+ 'disabled'
+ ],
+ computed: {
+ pathDefault () {
+ const [firstSegment, ...rest] = this.path.split('.')
+ return [firstSegment + 'DefaultValue', ...rest].join('.')
+ },
+ state () {
+ const value = get(this.$parent, this.path)
+ if (value === undefined) {
+ return this.defaultState
+ } else {
+ return value
+ }
+ },
+ defaultState () {
+ return get(this.$parent, this.pathDefault)
+ },
+ isChanged () {
+ return this.state !== this.defaultState
+ }
+ },
+ methods: {
+ update (e) {
+ set(this.$parent, this.path, e)
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/boolean_setting.vue b/src/components/settings_modal/helpers/boolean_setting.vue
index 146ad6c1..c3ee6583 100644
--- a/src/components/settings_modal/helpers/boolean_setting.vue
+++ b/src/components/settings_modal/helpers/boolean_setting.vue
@@ -18,40 +18,4 @@
</label>
</template>
-<script>
-import { get, set } from 'lodash'
-import Checkbox from 'src/components/checkbox/checkbox.vue'
-import ModifiedIndicator from './modified_indicator.vue'
-export default {
- components: {
- Checkbox,
- ModifiedIndicator
- },
- props: [
- 'path',
- 'disabled'
- ],
- computed: {
- pathDefault () {
- const [firstSegment, ...rest] = this.path.split('.')
- return [firstSegment + 'DefaultValue', ...rest].join('.')
- },
- state () {
- return get(this.$parent, this.path)
- },
- isChanged () {
- return get(this.$parent, this.path) !== get(this.$parent, this.pathDefault)
- }
- },
- methods: {
- update (e) {
- set(this.$parent, this.path, e)
- }
- }
-}
-</script>
-
-<style lang="scss">
-.BooleanSetting {
-}
-</style>
+<script src="./boolean_setting.js"></script>
diff --git a/src/components/settings_modal/helpers/choice_setting.js b/src/components/settings_modal/helpers/choice_setting.js
new file mode 100644
index 00000000..a15f6bac
--- /dev/null
+++ b/src/components/settings_modal/helpers/choice_setting.js
@@ -0,0 +1,39 @@
+import { get, set } from 'lodash'
+import Select from 'src/components/select/select.vue'
+import ModifiedIndicator from './modified_indicator.vue'
+export default {
+ components: {
+ Select,
+ ModifiedIndicator
+ },
+ props: [
+ 'path',
+ 'disabled',
+ 'options'
+ ],
+ computed: {
+ pathDefault () {
+ const [firstSegment, ...rest] = this.path.split('.')
+ return [firstSegment + 'DefaultValue', ...rest].join('.')
+ },
+ state () {
+ const value = get(this.$parent, this.path)
+ if (value === undefined) {
+ return this.defaultState
+ } else {
+ return value
+ }
+ },
+ defaultState () {
+ return get(this.$parent, this.pathDefault)
+ },
+ isChanged () {
+ return this.state !== this.defaultState
+ }
+ },
+ methods: {
+ update (e) {
+ set(this.$parent, this.path, e)
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/choice_setting.vue b/src/components/settings_modal/helpers/choice_setting.vue
new file mode 100644
index 00000000..fa17661b
--- /dev/null
+++ b/src/components/settings_modal/helpers/choice_setting.vue
@@ -0,0 +1,29 @@
+<template>
+ <label
+ class="ChoiceSetting"
+ >
+ <slot />
+ <Select
+ :value="state"
+ :disabled="disabled"
+ @change="update"
+ >
+ <option
+ v-for="option in options"
+ :key="option.key"
+ :value="option.value"
+ >
+ {{ option.label }}
+ {{ option.value === defaultState ? $t('settings.instance_default_simple') : '' }}
+ </option>
+ </Select>
+ <ModifiedIndicator :changed="isChanged" />
+ </label>
+</template>
+
+<script src="./choice_setting.js"></script>
+
+<style lang="scss">
+.ChoiceSetting {
+}
+</style>
diff --git a/src/components/settings_modal/helpers/modified_indicator.vue b/src/components/settings_modal/helpers/modified_indicator.vue
index 9f4e81fe..ad212db9 100644
--- a/src/components/settings_modal/helpers/modified_indicator.vue
+++ b/src/components/settings_modal/helpers/modified_indicator.vue
@@ -6,18 +6,18 @@
<Popover
trigger="hover"
>
- <span slot="trigger">
+ <template v-slot:trigger>
&nbsp;
<FAIcon
icon="wrench"
+ :aria-label="$t('settings.setting_changed')"
/>
- </span>
- <div
- slot="content"
- class="modified-tooltip"
- >
- {{ $t('settings.setting_changed') }}
- </div>
+ </template>
+ <template v-slot:content>
+ <div class="modified-tooltip">
+ {{ $t('settings.setting_changed') }}
+ </div>
+ </template>
</Popover>
</span>
</template>
diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index f0d49c91..04043483 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -2,10 +2,55 @@ import Modal from 'src/components/modal/modal.vue'
import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue'
import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
+import Popover from '../popover/popover.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { cloneDeep } from 'lodash'
+import {
+ newImporter,
+ newExporter
+} from 'src/services/export_import/export_import.js'
+import {
+ faTimes,
+ faFileUpload,
+ faFileDownload,
+ faChevronDown
+} from '@fortawesome/free-solid-svg-icons'
+import {
+ faWindowMinimize
+} from '@fortawesome/free-regular-svg-icons'
+
+const PLEROMAFE_SETTINGS_MAJOR_VERSION = 1
+const PLEROMAFE_SETTINGS_MINOR_VERSION = 0
+
+library.add(
+ faTimes,
+ faWindowMinimize,
+ faFileUpload,
+ faFileDownload,
+ faChevronDown
+)
const SettingsModal = {
+ data () {
+ return {
+ dataImporter: newImporter({
+ validator: this.importValidator,
+ onImport: this.onImport,
+ onImportFailure: this.onImportFailure
+ }),
+ dataThemeExporter: newExporter({
+ filename: 'pleromafe_settings.full',
+ getExportedObject: () => this.generateExport(true)
+ }),
+ dataExporter: newExporter({
+ filename: 'pleromafe_settings',
+ getExportedObject: () => this.generateExport()
+ })
+ }
+ },
components: {
Modal,
+ Popover,
SettingsModalContent: getResettableAsyncComponent(
() => import('./settings_modal_content.vue'),
{
@@ -21,6 +66,85 @@ const SettingsModal = {
},
peekModal () {
this.$store.dispatch('togglePeekSettingsModal')
+ },
+ importValidator (data) {
+ if (!Array.isArray(data._pleroma_settings_version)) {
+ return {
+ messageKey: 'settings.file_import_export.invalid_file'
+ }
+ }
+
+ const [major, minor] = data._pleroma_settings_version
+
+ if (major > PLEROMAFE_SETTINGS_MAJOR_VERSION) {
+ return {
+ messageKey: 'settings.file_export_import.errors.file_too_new',
+ messageArgs: {
+ fileMajor: major,
+ feMajor: PLEROMAFE_SETTINGS_MAJOR_VERSION
+ }
+ }
+ }
+
+ if (major < PLEROMAFE_SETTINGS_MAJOR_VERSION) {
+ return {
+ messageKey: 'settings.file_export_import.errors.file_too_old',
+ messageArgs: {
+ fileMajor: major,
+ feMajor: PLEROMAFE_SETTINGS_MAJOR_VERSION
+ }
+ }
+ }
+
+ if (minor > PLEROMAFE_SETTINGS_MINOR_VERSION) {
+ this.$store.dispatch('pushGlobalNotice', {
+ level: 'warning',
+ messageKey: 'settings.file_export_import.errors.file_slightly_new'
+ })
+ }
+
+ return true
+ },
+ onImportFailure (result) {
+ if (result.error) {
+ this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_settings_imported', level: 'error' })
+ } else {
+ this.$store.dispatch('pushGlobalNotice', { ...result.validationResult, level: 'error' })
+ }
+ },
+ onImport (data) {
+ if (data) { this.$store.dispatch('loadSettings', data) }
+ },
+ restore () {
+ this.dataImporter.importData()
+ },
+ backup () {
+ this.dataExporter.exportData()
+ },
+ backupWithTheme () {
+ this.dataThemeExporter.exportData()
+ },
+ generateExport (theme = false) {
+ const { config } = this.$store.state
+ let sample = config
+ if (!theme) {
+ const ignoreList = new Set([
+ 'customTheme',
+ 'customThemeSource',
+ 'colors'
+ ])
+ sample = Object.fromEntries(
+ Object
+ .entries(sample)
+ .filter(([key]) => !ignoreList.has(key))
+ )
+ }
+ const clone = cloneDeep(sample)
+ clone._pleroma_settings_version = [
+ PLEROMAFE_SETTINGS_MAJOR_VERSION,
+ PLEROMAFE_SETTINGS_MINOR_VERSION
+ ]
+ return clone
}
},
computed: {
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
index 552ca41f..583c2ecc 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -31,20 +31,84 @@
</transition>
<button
class="btn button-default"
+ :title="$t('general.peek')"
@click="peekModal"
>
- {{ $t('general.peek') }}
+ <FAIcon
+ :icon="['far', 'window-minimize']"
+ fixed-width
+ />
</button>
<button
class="btn button-default"
+ :title="$t('general.close')"
@click="closeModal"
>
- {{ $t('general.close') }}
+ <FAIcon
+ icon="times"
+ fixed-width
+ />
</button>
</div>
<div class="panel-body">
<SettingsModalContent v-if="modalOpenedOnce" />
</div>
+ <div class="panel-footer">
+ <Popover
+ class="export"
+ trigger="click"
+ placement="top"
+ :offset="{ y: 5, x: 5 }"
+ :bound-to="{ x: 'container' }"
+ remove-padding
+ >
+ <template v-slot:trigger>
+ <button
+ class="btn button-default"
+ :title="$t('general.close')"
+ >
+ <span>{{ $t("settings.file_export_import.backup_restore") }}</span>
+ <FAIcon
+ icon="chevron-down"
+ />
+ </button>
+ </template>
+ <template v-slot:content="{close}">
+ <div class="dropdown-menu">
+ <button
+ class="button-default dropdown-item dropdown-item-icon"
+ @click.prevent="backup"
+ @click="close"
+ >
+ <FAIcon
+ icon="file-download"
+ fixed-width
+ /><span>{{ $t("settings.file_export_import.backup_settings") }}</span>
+ </button>
+ <button
+ class="button-default dropdown-item dropdown-item-icon"
+ @click.prevent="backupWithTheme"
+ @click="close"
+ >
+ <FAIcon
+ icon="file-download"
+ fixed-width
+ /><span>{{ $t("settings.file_export_import.backup_settings_theme") }}</span>
+ </button>
+ <button
+ class="button-default dropdown-item dropdown-item-icon"
+ @click.prevent="restore"
+ @click="close"
+ >
+ <FAIcon
+ icon="file-upload"
+ fixed-width
+ /><span>{{ $t("settings.file_export_import.restore_settings") }}</span>
+ </button>
+ </div>
+ </template>
+ </Popover>
+ </div>
</div>
</Modal>
</template>
diff --git a/src/components/settings_modal/settings_modal_content.scss b/src/components/settings_modal/settings_modal_content.scss
index f066234c..81ab434b 100644
--- a/src/components/settings_modal/settings_modal_content.scss
+++ b/src/components/settings_modal/settings_modal_content.scss
@@ -7,13 +7,24 @@
margin: 1em 1em 1.4em;
padding-bottom: 1.4em;
- > div {
+ > div,
+ > label {
+ display: block;
margin-bottom: .5em;
&:last-child {
margin-bottom: 0;
}
}
+ .select-multiple {
+ display: flex;
+
+ .option-list {
+ margin: 0;
+ padding-left: .5em;
+ }
+ }
+
&:last-child {
border-bottom: none;
padding-bottom: 0;
diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js
index 6e95f7af..4eaf4217 100644
--- a/src/components/settings_modal/tabs/filtering_tab.js
+++ b/src/components/settings_modal/tabs/filtering_tab.js
@@ -1,24 +1,23 @@
import { filter, trim } from 'lodash'
import BooleanSetting from '../helpers/boolean_setting.vue'
+import ChoiceSetting from '../helpers/choice_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 () {
return {
- muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n')
+ muteWordsStringLocal: this.$store.getters.mergedConfig.muteWords.join('\n'),
+ replyVisibilityOptions: ['all', 'following', 'self'].map(mode => ({
+ key: mode,
+ value: mode,
+ label: this.$t(`settings.reply_visibility_${mode}`)
+ }))
}
},
components: {
- BooleanSetting
+ BooleanSetting,
+ ChoiceSetting
},
computed: {
...SharedComputedObject(),
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
index 402c2a4a..6fc9ceaa 100644
--- a/src/components/settings_modal/tabs/filtering_tab.vue
+++ b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -36,29 +36,13 @@
</li>
</ul>
</div>
- <div>
+ <ChoiceSetting
+ id="replyVisibility"
+ path="replyVisibility"
+ :options="replyVisibilityOptions"
+ >
{{ $t('settings.replies_in_timeline') }}
- <label
- for="replyVisibility"
- class="select"
- >
- <select
- id="replyVisibility"
- v-model="replyVisibility"
- >
- <option
- value="all"
- selected
- >{{ $t('settings.reply_visibility_all') }}</option>
- <option value="following">{{ $t('settings.reply_visibility_following') }}</option>
- <option value="self">{{ $t('settings.reply_visibility_self') }}</option>
- </select>
- <FAIcon
- class="select-down-icon"
- icon="chevron-down"
- />
- </label>
- </div>
+ </ChoiceSetting>
<div>
<BooleanSetting path="hidePostStats">
{{ $t('settings.hide_post_stats') }}
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index 2db523be..eeda61bf 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -1,21 +1,25 @@
import BooleanSetting from '../helpers/boolean_setting.vue'
+import ChoiceSetting from '../helpers/choice_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 () {
return {
+ subjectLineOptions: ['email', 'noop', 'masto'].map(mode => ({
+ key: mode,
+ value: mode,
+ label: this.$t(`settings.subject_line_${mode === 'masto' ? 'mastodon' : mode}`)
+ })),
loopSilentAvailable:
// Firefox
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
@@ -27,17 +31,26 @@ const GeneralTab = {
},
components: {
BooleanSetting,
+ ChoiceSetting,
InterfaceLanguageSwitcher
},
computed: {
postFormats () {
return this.$store.state.instance.postFormats || []
},
+ postContentOptions () {
+ return this.postFormats.map(format => ({
+ key: format,
+ value: format,
+ label: this.$t(`post_status.content_type["${format}"]`)
+ }))
+ },
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
instanceWallpaperUsed () {
return this.$store.state.instance.background &&
!this.$store.state.users.currentUser.background_image
},
+ instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
...SharedComputedObject()
}
}
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index f93f4ea0..d3e71b31 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -11,11 +11,21 @@
{{ $t('settings.hide_isp') }}
</BooleanSetting>
</li>
+ <li>
+ <BooleanSetting path="sidebarRight">
+ {{ $t('settings.right_sidebar') }}
+ </BooleanSetting>
+ </li>
<li v-if="instanceWallpaperUsed">
<BooleanSetting path="hideInstanceWallpaper">
{{ $t('settings.hide_wallpaper') }}
</BooleanSetting>
</li>
+ <li v-if="instanceShoutboxPresent">
+ <BooleanSetting path="hideShoutbox">
+ {{ $t('settings.hide_shoutbox') }}
+ </BooleanSetting>
+ </li>
</ul>
</div>
<div class="setting-item">
@@ -85,66 +95,31 @@
</BooleanSetting>
</li>
<li>
- <div>
+ <ChoiceSetting
+ id="subjectLineBehavior"
+ path="subjectLineBehavior"
+ :options="subjectLineOptions"
+ >
{{ $t('settings.subject_line_behavior') }}
- <label
- for="subjectLineBehavior"
- class="select"
- >
- <select
- id="subjectLineBehavior"
- v-model="subjectLineBehavior"
- >
- <option value="email">
- {{ $t('settings.subject_line_email') }}
- {{ subjectLineBehaviorDefaultValue == 'email' ? $t('settings.instance_default_simple') : '' }}
- </option>
- <option value="masto">
- {{ $t('settings.subject_line_mastodon') }}
- {{ subjectLineBehaviorDefaultValue == 'mastodon' ? $t('settings.instance_default_simple') : '' }}
- </option>
- <option value="noop">
- {{ $t('settings.subject_line_noop') }}
- {{ subjectLineBehaviorDefaultValue == 'noop' ? $t('settings.instance_default_simple') : '' }}
- </option>
- </select>
- <FAIcon
- class="select-down-icon"
- icon="chevron-down"
- />
- </label>
- </div>
+ </ChoiceSetting>
</li>
<li v-if="postFormats.length > 0">
- <div>
+ <ChoiceSetting
+ id="postContentType"
+ path="postContentType"
+ :options="postContentOptions"
+ >
{{ $t('settings.post_status_content_type') }}
- <label
- for="postContentType"
- class="select"
- >
- <select
- id="postContentType"
- v-model="postContentType"
- >
- <option
- v-for="postFormat in postFormats"
- :key="postFormat"
- :value="postFormat"
- >
- {{ $t(`post_status.content_type["${postFormat}"]`) }}
- {{ postContentTypeDefaultValue === postFormat ? $t('settings.instance_default_simple') : '' }}
- </option>
- </select>
- <FAIcon
- class="select-down-icon"
- icon="chevron-down"
- />
- </label>
- </div>
+ </ChoiceSetting>
</li>
<li>
<BooleanSetting path="minimalScopesMode">
- {{ $t('settings.minimal_scopes_mode') }} {{ minimalScopesModeDefaultValue }}
+ {{ $t('settings.minimal_scopes_mode') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="sensitiveByDefault">
+ {{ $t('settings.sensitive_by_default') }}
</BooleanSetting>
</li>
<li>
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 63d36bf9..32a21415 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
@@ -10,20 +10,18 @@
:query="queryUserIds"
:placeholder="$t('settings.search_user_to_block')"
>
- <BlockCard
- slot-scope="row"
- :user-id="row.item"
- />
+ <template v-slot="row">
+ <BlockCard
+ :user-id="row.item"
+ />
+ </template>
</Autosuggest>
</div>
<BlockList
:refresh="true"
:get-key="i => i"
>
- <template
- slot="header"
- slot-scope="{selected}"
- >
+ <template v-slot:header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -31,7 +29,7 @@
:click="() => blockUsers(selected)"
>
{{ $t('user_card.block') }}
- <template slot="progress">
+ <template v-slot:progress>
{{ $t('user_card.block_progress') }}
</template>
</ProgressButton>
@@ -41,19 +39,16 @@
:click="() => unblockUsers(selected)"
>
{{ $t('user_card.unblock') }}
- <template slot="progress">
+ <template v-slot:progress>
{{ $t('user_card.unblock_progress') }}
</template>
</ProgressButton>
</div>
</template>
- <template
- slot="item"
- slot-scope="{item}"
- >
+ <template v-slot:item="{item}">
<BlockCard :user-id="item" />
</template>
- <template slot="empty">
+ <template v-slot:empty>
{{ $t('settings.no_blocks') }}
</template>
</BlockList>
@@ -68,20 +63,18 @@
:query="queryUserIds"
:placeholder="$t('settings.search_user_to_mute')"
>
- <MuteCard
- slot-scope="row"
- :user-id="row.item"
- />
+ <template v-slot="row">
+ <MuteCard
+ :user-id="row.item"
+ />
+ </template>
</Autosuggest>
</div>
<MuteList
:refresh="true"
:get-key="i => i"
>
- <template
- slot="header"
- slot-scope="{selected}"
- >
+ <template v-slot:header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -89,7 +82,7 @@
:click="() => muteUsers(selected)"
>
{{ $t('user_card.mute') }}
- <template slot="progress">
+ <template v-slot:progress>
{{ $t('user_card.mute_progress') }}
</template>
</ProgressButton>
@@ -99,19 +92,16 @@
:click="() => unmuteUsers(selected)"
>
{{ $t('user_card.unmute') }}
- <template slot="progress">
+ <template v-slot:progress>
{{ $t('user_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
- <template
- slot="item"
- slot-scope="{item}"
- >
+ <template v-slot:item="{item}">
<MuteCard :user-id="item" />
</template>
- <template slot="empty">
+ <template v-slot:empty>
{{ $t('settings.no_mutes') }}
</template>
</MuteList>
@@ -124,20 +114,18 @@
:query="queryKnownDomains"
:placeholder="$t('settings.type_domains_to_mute')"
>
- <DomainMuteCard
- slot-scope="row"
- :domain="row.item"
- />
+ <template v-slot="row">
+ <DomainMuteCard
+ :domain="row.item"
+ />
+ </template>
</Autosuggest>
</div>
<DomainMuteList
:refresh="true"
:get-key="i => i"
>
- <template
- slot="header"
- slot-scope="{selected}"
- >
+ <template v-slot:header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@@ -145,19 +133,16 @@
:click="() => unmuteDomains(selected)"
>
{{ $t('domain_mute_card.unmute') }}
- <template slot="progress">
+ <template v-slot:progress>
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
- <template
- slot="item"
- slot-scope="{item}"
- >
+ <template v-slot:item="{item}">
<DomainMuteCard :domain="item" />
</template>
- <template slot="empty">
+ <template v-slot:empty>
{{ $t('settings.no_mutes') }}
</template>
</DomainMuteList>
diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue
index 8f8fe48e..7e0568ea 100644
--- a/src/components/settings_modal/tabs/notifications_tab.vue
+++ b/src/components/settings_modal/tabs/notifications_tab.vue
@@ -24,7 +24,7 @@
class="btn button-default"
@click="updateNotificationSettings"
>
- {{ $t('general.submit') }}
+ {{ $t('settings.save') }}
</button>
</div>
</div>
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
index 175a0219..bb3c301d 100644
--- a/src/components/settings_modal/tabs/profile_tab.vue
+++ b/src/components/settings_modal/tabs/profile_tab.vue
@@ -153,7 +153,7 @@
class="btn button-default"
@click="updateProfile"
>
- {{ $t('general.submit') }}
+ {{ $t('settings.save') }}
</button>
</div>
<div class="setting-item">
@@ -227,7 +227,7 @@
class="btn button-default"
@click="submitBanner(banner)"
>
- {{ $t('general.submit') }}
+ {{ $t('settings.save') }}
</button>
</div>
<div class="setting-item">
@@ -266,7 +266,7 @@
class="btn button-default"
@click="submitBackground(background)"
>
- {{ $t('general.submit') }}
+ {{ $t('settings.save') }}
</button>
</div>
</div>
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 56bea1f4..275d4616 100644
--- a/src/components/settings_modal/tabs/security_tab/security_tab.vue
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.vue
@@ -22,7 +22,7 @@
class="btn button-default"
@click="changeEmail"
>
- {{ $t('general.submit') }}
+ {{ $t('settings.save') }}
</button>
<p v-if="changedEmail">
{{ $t('settings.changed_email') }}
@@ -60,7 +60,7 @@
class="btn button-default"
@click="changePassword"
>
- {{ $t('general.submit') }}
+ {{ $t('settings.save') }}
</button>
<p v-if="changedPassword">
{{ $t('settings.changed_password') }}
@@ -133,7 +133,7 @@
class="btn button-default"
@click="confirmDelete"
>
- {{ $t('general.submit') }}
+ {{ $t('settings.save') }}
</button>
</div>
</div>
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 6cf75fe7..8b81db5d 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.js
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
@@ -16,6 +16,10 @@ import {
colors2to3
} from 'src/services/style_setter/style_setter.js'
import {
+ newImporter,
+ newExporter
+} from 'src/services/export_import/export_import.js'
+import {
SLOT_INHERITANCE
} from 'src/services/theme_data/pleromafe.js'
import {
@@ -31,18 +35,10 @@ import ShadowControl from 'src/components/shadow_control/shadow_control.vue'
import FontControl from 'src/components/font_control/font_control.vue'
import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.js'
-import ExportImport from 'src/components/export_import/export_import.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
+import Select from 'src/components/select/select.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 = [
@@ -67,8 +63,18 @@ const colorConvert = (color) => {
export default {
data () {
return {
+ themeImporter: newImporter({
+ validator: this.importValidator,
+ onImport: this.onImport,
+ onImportFailure: this.onImportFailure
+ }),
+ themeExporter: newExporter({
+ filename: 'pleroma_theme',
+ getExportedObject: () => this.exportedTheme
+ }),
availableStyles: [],
- selected: this.$store.getters.mergedConfig.theme,
+ selected: '',
+ selectedTheme: this.$store.getters.mergedConfig.theme,
themeWarning: undefined,
tempImportFile: undefined,
engineVersion: 0,
@@ -202,7 +208,7 @@ export default {
}
},
selectedVersion () {
- return Array.isArray(this.selected) ? 1 : 2
+ return Array.isArray(this.selectedTheme) ? 1 : 2
},
currentColors () {
return Object.keys(SLOT_INHERITANCE)
@@ -383,8 +389,8 @@ export default {
FontControl,
TabSwitcher,
Preview,
- ExportImport,
- Checkbox
+ Checkbox,
+ Select
},
methods: {
loadTheme (
@@ -528,10 +534,15 @@ export default {
this.previewColors.mod
)
},
+ importTheme () { this.themeImporter.importData() },
+ exportTheme () { this.themeExporter.exportData() },
onImport (parsed, forceSource = false) {
this.tempImportFile = parsed
this.loadTheme(parsed, 'file', forceSource)
},
+ onImportFailure (result) {
+ this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' })
+ },
importValidator (parsed) {
const version = parsed._pleroma_theme_version
return version >= 1 || version <= 2
@@ -735,6 +746,16 @@ export default {
}
},
selected () {
+ this.selectedTheme = Object.entries(this.availableStyles).find(([k, s]) => {
+ if (Array.isArray(s)) {
+ console.log(s[0] === this.selected, this.selected)
+ return s[0] === this.selected
+ } else {
+ return s.name === this.selected
+ }
+ })[1]
+ },
+ selectedTheme () {
this.dismissWarning()
if (this.selectedVersion === 1) {
if (!this.keepRoundness) {
@@ -752,17 +773,17 @@ export default {
if (!this.keepColor) {
this.clearV1()
- this.bgColorLocal = this.selected[1]
- this.fgColorLocal = this.selected[2]
- this.textColorLocal = this.selected[3]
- this.linkColorLocal = this.selected[4]
- this.cRedColorLocal = this.selected[5]
- this.cGreenColorLocal = this.selected[6]
- this.cBlueColorLocal = this.selected[7]
- this.cOrangeColorLocal = this.selected[8]
+ this.bgColorLocal = this.selectedTheme[1]
+ this.fgColorLocal = this.selectedTheme[2]
+ this.textColorLocal = this.selectedTheme[3]
+ this.linkColorLocal = this.selectedTheme[4]
+ this.cRedColorLocal = this.selectedTheme[5]
+ this.cGreenColorLocal = this.selectedTheme[6]
+ this.cBlueColorLocal = this.selectedTheme[7]
+ this.cOrangeColorLocal = this.selectedTheme[8]
}
} else if (this.selectedVersion >= 2) {
- this.normalizeLocalState(this.selected.theme, 2, this.selected.source)
+ this.normalizeLocalState(this.selectedTheme.theme, 2, this.selectedTheme.source)
}
}
}
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 b8add42f..c02986ed 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
@@ -48,46 +48,47 @@
</template>
</div>
</div>
- <ExportImport
- :export-object="exportedTheme"
- :export-label="$t(&quot;settings.export_theme&quot;)"
- :import-label="$t(&quot;settings.import_theme&quot;)"
- :import-failed-text="$t(&quot;settings.invalid_theme_imported&quot;)"
- :on-import="onImport"
- :validator="importValidator"
- >
- <template slot="before">
- <div class="presets">
- {{ $t('settings.presets') }}
- <label
- for="preset-switcher"
- class="select"
+ <div class="top">
+ <div class="presets">
+ {{ $t('settings.presets') }}
+ <label
+ for="preset-switcher"
+ class="select"
+ >
+ <Select
+ id="preset-switcher"
+ v-model="selected"
+ class="preset-switcher"
>
- <select
- id="preset-switcher"
- v-model="selected"
- class="preset-switcher"
+ <option
+ v-for="style in availableStyles"
+ :key="style.name"
+ :value="style.name || style[0]"
+ :style="{
+ backgroundColor: style[1] || (style.theme || style.source).colors.bg,
+ color: style[3] || (style.theme || style.source).colors.text
+ }"
>
- <option
- v-for="style in availableStyles"
- :key="style.name"
- :value="style"
- :style="{
- backgroundColor: style[1] || (style.theme || style.source).colors.bg,
- color: style[3] || (style.theme || style.source).colors.text
- }"
- >
- {{ style[0] || style.name }}
- </option>
- </select>
- <FAIcon
- class="select-down-icon"
- icon="chevron-down"
- />
- </label>
- </div>
- </template>
- </ExportImport>
+ {{ style[0] || style.name }}
+ </option>
+ </Select>
+ </label>
+ </div>
+ <div class="export-import">
+ <button
+ class="btn button-default"
+ @click="importTheme"
+ >
+ {{ $t(&quot;settings.import_theme&quot;) }}
+ </button>
+ <button
+ class="btn button-default"
+ @click="exportTheme"
+ >
+ {{ $t(&quot;settings.export_theme&quot;) }}
+ </button>
+ </div>
+ </div>
</div>
<div class="save-load-options">
<span class="keep-option">
@@ -902,28 +903,19 @@
<div class="tab-header shadow-selector">
<div class="select-container">
{{ $t('settings.style.shadows.component') }}
- <label
- for="shadow-switcher"
- class="select"
+ <Select
+ id="shadow-switcher"
+ v-model="shadowSelected"
+ class="shadow-switcher"
>
- <select
- id="shadow-switcher"
- v-model="shadowSelected"
- class="shadow-switcher"
+ <option
+ v-for="shadow in shadowsAvailable"
+ :key="shadow"
+ :value="shadow"
>
- <option
- v-for="shadow in shadowsAvailable"
- :key="shadow"
- :value="shadow"
- >
- {{ $t('settings.style.shadows.components.' + shadow) }}
- </option>
- </select>
- <FAIcon
- class="select-down-icon"
- icon="chevron-down"
- />
- </label>
+ {{ $t('settings.style.shadows.components.' + shadow) }}
+ </option>
+ </Select>
</div>
<div class="override">
<label
diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js
index 800c39d5..2d5d6eb1 100644
--- a/src/components/shadow_control/shadow_control.js
+++ b/src/components/shadow_control/shadow_control.js
@@ -1,5 +1,6 @@
import ColorInput from '../color_input/color_input.vue'
import OpacityInput from '../opacity_input/opacity_input.vue'
+import Select from '../select/select.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'
@@ -45,7 +46,8 @@ export default {
},
components: {
ColorInput,
- OpacityInput
+ OpacityInput,
+ Select
},
methods: {
add () {
diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue
index 37d491f0..511e07f3 100644
--- a/src/components/shadow_control/shadow_control.vue
+++ b/src/components/shadow_control/shadow_control.vue
@@ -59,30 +59,20 @@
:disabled="usingFallback"
class="id-control style-control"
>
- <label
- for="shadow-switcher"
- class="select"
+ <Select
+ id="shadow-switcher"
+ v-model="selectedId"
+ class="shadow-switcher"
:disabled="!ready || usingFallback"
>
- <select
- id="shadow-switcher"
- v-model="selectedId"
- class="shadow-switcher"
- :disabled="!ready || usingFallback"
+ <option
+ v-for="(shadow, index) in cValue"
+ :key="index"
+ :value="index"
>
- <option
- v-for="(shadow, index) in cValue"
- :key="index"
- :value="index"
- >
- {{ $t('settings.style.shadows.shadow_id', { value: index }) }}
- </option>
- </select>
- <FAIcon
- icon="chevron-down"
- class="select-down-icon"
- />
- </label>
+ {{ $t('settings.style.shadows.shadow_id', { value: index }) }}
+ </option>
+ </Select>
<button
class="btn button-default"
:disabled="!ready || !present"
@@ -316,20 +306,20 @@
.id-control {
align-items: stretch;
- .select, .btn {
+
+ .shadow-switcher {
+ flex: 1;
+ }
+
+ .shadow-switcher, .btn {
min-width: 1px;
margin-right: 5px;
}
+
.btn {
padding: 0 .4em;
margin: 0 .1em;
}
- .select {
- flex: 1;
- select {
- align-self: initial;
- }
- }
}
}
}
diff --git a/src/components/chat_panel/chat_panel.js b/src/components/shout_panel/shout_panel.js
index c3887098..a6168971 100644
--- a/src/components/chat_panel/chat_panel.js
+++ b/src/components/shout_panel/shout_panel.js
@@ -10,7 +10,7 @@ library.add(
faTimes
)
-const chatPanel = {
+const shoutPanel = {
props: [ 'floating' ],
data () {
return {
@@ -21,12 +21,12 @@ const chatPanel = {
},
computed: {
messages () {
- return this.$store.state.chat.messages
+ return this.$store.state.shout.messages
}
},
methods: {
submit (message) {
- this.$store.state.chat.channel.push('new_msg', { text: message }, 10000)
+ this.$store.state.shout.channel.push('new_msg', { text: message }, 10000)
this.currentMessage = ''
},
togglePanel () {
@@ -35,7 +35,19 @@ const chatPanel = {
userProfileLink (user) {
return generateProfileLink(user.id, user.username, this.$store.state.instance.restrictedNicknames)
}
+ },
+ watch: {
+ messages (newVal) {
+ const scrollEl = this.$el.querySelector('.chat-window')
+ if (!scrollEl) return
+ if (scrollEl.scrollTop + scrollEl.offsetHeight + 20 > scrollEl.scrollHeight) {
+ this.$nextTick(() => {
+ if (!scrollEl) return
+ scrollEl.scrollTop = scrollEl.scrollHeight - scrollEl.offsetHeight
+ })
+ }
+ }
}
}
-export default chatPanel
+export default shoutPanel
diff --git a/src/components/chat_panel/chat_panel.vue b/src/components/shout_panel/shout_panel.vue
index 7993c94d..f90baf80 100644
--- a/src/components/chat_panel/chat_panel.vue
+++ b/src/components/shout_panel/shout_panel.vue
@@ -1,52 +1,50 @@
<template>
<div
v-if="!collapsed || !floating"
- class="chat-panel"
+ class="shout-panel"
>
<div class="panel panel-default">
<div
class="panel-heading timeline-heading"
- :class="{ 'chat-heading': floating }"
+ :class="{ 'shout-heading': floating }"
@click.stop.prevent="togglePanel"
>
<div class="title">
- <span>{{ $t('shoutbox.title') }}</span>
+ {{ $t('shoutbox.title') }}
<FAIcon
v-if="floating"
icon="times"
+ class="close-icon"
/>
</div>
</div>
- <div
- v-chat-scroll
- class="chat-window"
- >
+ <div class="shout-window">
<div
v-for="message in messages"
:key="message.id"
- class="chat-message"
+ class="shout-message"
>
- <span class="chat-avatar">
+ <span class="shout-avatar">
<img :src="message.author.avatar">
</span>
- <div class="chat-content">
+ <div class="shout-content">
<router-link
- class="chat-name"
+ class="shout-name"
:to="userProfileLink(message.author)"
>
{{ message.author.username }}
</router-link>
<br>
- <span class="chat-text">
+ <span class="shout-text">
{{ message.text }}
</span>
</div>
</div>
</div>
- <div class="chat-input">
+ <div class="shout-input">
<textarea
v-model="currentMessage"
- class="chat-input-textarea"
+ class="shout-input-textarea"
rows="1"
@keyup.enter="submit(currentMessage)"
/>
@@ -55,11 +53,11 @@
</div>
<div
v-else
- class="chat-panel"
+ class="shout-panel"
>
<div class="panel panel-default">
<div
- class="panel-heading stub timeline-heading chat-heading"
+ class="panel-heading stub timeline-heading shout-heading"
@click.stop.prevent="togglePanel"
>
<div class="title">
@@ -74,12 +72,12 @@
</div>
</template>
-<script src="./chat_panel.js"></script>
+<script src="./shout_panel.js"></script>
<style lang="scss">
@import '../../_variables.scss';
-.floating-chat {
+.floating-shout {
position: fixed;
right: 0px;
bottom: 0px;
@@ -87,32 +85,39 @@
max-width: 25em;
}
-.chat-panel {
- .chat-heading {
+.shout-panel {
+ .shout-heading {
cursor: pointer;
.icon {
color: $fallback--text;
color: var(--text, $fallback--text);
+ margin-right: 0.5em;
+ }
+
+ .title {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
}
}
- .chat-window {
+ .shout-window {
overflow-y: auto;
overflow-x: hidden;
max-height: 20em;
}
- .chat-window-container {
+ .shout-window-container {
height: 100%;
}
- .chat-message {
+ .shout-message {
display: flex;
padding: 0.2em 0.5em
}
- .chat-avatar {
+ .shout-avatar {
img {
height: 24px;
width: 24px;
@@ -123,7 +128,7 @@
}
}
- .chat-input {
+ .shout-input {
display: flex;
textarea {
flex: 1;
@@ -133,7 +138,7 @@
}
}
- .chat-panel {
+ .shout-panel {
.title {
display: flex;
justify-content: space-between;
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index fe736168..0faf3b9e 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -49,7 +49,6 @@ const SideDrawer = {
currentUser () {
return this.$store.state.users.currentUser
},
- chat () { return this.$store.state.chat.channel.state === 'joined' },
unseenNotifications () {
return unseenNotificationsFromStore(this.$store)
},
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 223b1632..575052be 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -273,9 +273,7 @@
--icon: var(--popoverIcon, $fallback--icon);
.badge {
- position: absolute;
- right: 0.7rem;
- top: 1em;
+ margin-left: 10px;
}
}
diff --git a/src/components/status_popover/status_popover.vue b/src/components/status_popover/status_popover.vue
index 8237ce00..fdca8c9c 100644
--- a/src/components/status_popover/status_popover.vue
+++ b/src/components/status_popover/status_popover.vue
@@ -5,12 +5,10 @@
:bound-to="{ x: 'container' }"
@show="enter"
>
- <template slot="trigger">
+ <template v-slot:trigger>
<slot />
</template>
- <div
- slot="content"
- >
+ <template v-slot:content>
<Status
v-if="status"
:is-preview="true"
@@ -33,7 +31,7 @@
size="2x"
/>
</div>
- </div>
+ </template>
</Popover>
</template>
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 665d195e..44f749c3 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -2,12 +2,14 @@ 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 TimelineQuickSettings from './timeline_quick_settings.vue'
import { debounce, throttle, keyBy } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
+import { faCircleNotch, faCog } from '@fortawesome/free-solid-svg-icons'
library.add(
- faCircleNotch
+ faCircleNotch,
+ faCog
)
export const getExcludedStatusIdsByPinning = (statuses, pinnedStatusIds) => {
@@ -47,7 +49,8 @@ const Timeline = {
components: {
Status,
Conversation,
- TimelineMenu
+ TimelineMenu,
+ TimelineQuickSettings
},
computed: {
newStatusCount () {
diff --git a/src/components/timeline/timeline.scss b/src/components/timeline/timeline.scss
new file mode 100644
index 00000000..2c5a67e2
--- /dev/null
+++ b/src/components/timeline/timeline.scss
@@ -0,0 +1,31 @@
+@import '../../_variables.scss';
+
+.Timeline {
+ .loadmore-text {
+ opacity: 1;
+ }
+
+ &.-blocked {
+ cursor: progress;
+ }
+
+ .timeline-heading {
+ max-width: 100%;
+ flex-wrap: nowrap;
+ align-items: center;
+ position: relative;
+
+ .loadmore-button {
+ flex-shrink: 0;
+ }
+
+ .loadmore-text {
+ flex-shrink: 0;
+ line-height: 1em;
+ }
+ }
+
+ .timeline-footer {
+ border: none;
+ }
+}
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index 4c43fe5c..767428f0 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -16,6 +16,7 @@
>
{{ $t('timeline.up_to_date') }}
</div>
+ <TimelineQuickSettings v-if="!embedded" />
</div>
<div :class="classes.body">
<div
@@ -51,13 +52,13 @@
<div :class="classes.footer">
<div
v-if="count===0"
- class="new-status-notification text-center panel-footer faint"
+ class="new-status-notification text-center faint"
>
{{ $t('timeline.no_statuses') }}
</div>
<div
v-else-if="bottomedOut"
- class="new-status-notification text-center panel-footer faint"
+ class="new-status-notification text-center faint"
>
{{ $t('timeline.no_more_statuses') }}
</div>
@@ -66,13 +67,13 @@
class="button-unstyled -link -fullwidth"
@click.prevent="fetchOlderStatuses()"
>
- <div class="new-status-notification text-center panel-footer">
+ <div class="new-status-notification text-center">
{{ $t('timeline.load_older') }}
</div>
</button>
<div
v-else
- class="new-status-notification text-center panel-footer"
+ class="new-status-notification text-center"
>
<FAIcon
icon="circle-notch"
@@ -86,29 +87,4 @@
<script src="./timeline.js"></script>
-<style lang="scss">
-@import '../../_variables.scss';
-
-.Timeline {
- .loadmore-text {
- opacity: 1;
- }
-
- &.-blocked {
- cursor: progress;
- }
-}
-
-.timeline-heading {
- max-width: 100%;
- flex-wrap: nowrap;
- align-items: center;
- .loadmore-button {
- flex-shrink: 0;
- }
- .loadmore-text {
- flex-shrink: 0;
- line-height: 1em;
- }
-}
-</style>
+<style src="./timeline.scss" lang="scss"> </style>
diff --git a/src/components/timeline/timeline_quick_settings.js b/src/components/timeline/timeline_quick_settings.js
new file mode 100644
index 00000000..eae65a55
--- /dev/null
+++ b/src/components/timeline/timeline_quick_settings.js
@@ -0,0 +1,61 @@
+import Popover from '../popover/popover.vue'
+import { mapGetters } from 'vuex'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faFilter,
+ faFont,
+ faWrench
+)
+
+const TimelineQuickSettings = {
+ components: {
+ Popover
+ },
+ methods: {
+ setReplyVisibility (visibility) {
+ this.$store.dispatch('setOption', { name: 'replyVisibility', value: visibility })
+ this.$store.dispatch('queueFlushAll')
+ },
+ openTab (tab) {
+ this.$store.dispatch('openSettingsModalTab', tab)
+ }
+ },
+ computed: {
+ ...mapGetters(['mergedConfig']),
+ loggedIn () {
+ return !!this.$store.state.users.currentUser
+ },
+ replyVisibilitySelf: {
+ get () { return this.mergedConfig.replyVisibility === 'self' },
+ set () { this.setReplyVisibility('self') }
+ },
+ replyVisibilityFollowing: {
+ get () { return this.mergedConfig.replyVisibility === 'following' },
+ set () { this.setReplyVisibility('following') }
+ },
+ replyVisibilityAll: {
+ get () { return this.mergedConfig.replyVisibility === 'all' },
+ set () { this.setReplyVisibility('all') }
+ },
+ hideMedia: {
+ get () { return this.mergedConfig.hideAttachments || this.mergedConfig.hideAttachmentsInConv },
+ set () {
+ const value = !this.hideMedia
+ this.$store.dispatch('setOption', { name: 'hideAttachments', value })
+ this.$store.dispatch('setOption', { name: 'hideAttachmentsInConv', value })
+ }
+ },
+ hideMutedPosts: {
+ get () { return this.mergedConfig.hideMutedPosts || this.mergedConfig.hideFilteredStatuses },
+ set () {
+ const value = !this.hideMutedPosts
+ this.$store.dispatch('setOption', { name: 'hideMutedPosts', value })
+ this.$store.dispatch('setOption', { name: 'hideFilteredStatuses', value })
+ }
+ }
+ }
+}
+
+export default TimelineQuickSettings
diff --git a/src/components/timeline/timeline_quick_settings.vue b/src/components/timeline/timeline_quick_settings.vue
new file mode 100644
index 00000000..98996ebd
--- /dev/null
+++ b/src/components/timeline/timeline_quick_settings.vue
@@ -0,0 +1,102 @@
+<template>
+ <Popover
+ trigger="click"
+ class="TimelineQuickSettings"
+ :bound-to="{ x: 'container' }"
+ >
+ <template v-slot:content>
+ <div class="dropdown-menu">
+ <div v-if="loggedIn">
+ <button
+ class="button-default dropdown-item"
+ @click="replyVisibilityAll = true"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-radio': replyVisibilityAll }"
+ />{{ $t('settings.reply_visibility_all') }}
+ </button>
+ <button
+ class="button-default dropdown-item"
+ @click="replyVisibilityFollowing = true"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-radio': replyVisibilityFollowing }"
+ />{{ $t('settings.reply_visibility_following_short') }}
+ </button>
+ <button
+ class="button-default dropdown-item"
+ @click="replyVisibilitySelf = true"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-radio': replyVisibilitySelf }"
+ />{{ $t('settings.reply_visibility_self_short') }}
+ </button>
+ <div
+ role="separator"
+ class="dropdown-divider"
+ />
+ </div>
+ <button
+ class="button-default dropdown-item"
+ @click="hideMedia = !hideMedia"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-checked': hideMedia }"
+ />{{ $t('settings.hide_media_previews') }}
+ </button>
+ <button
+ class="button-default dropdown-item"
+ @click="hideMutedPosts = !hideMutedPosts"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-checked': hideMutedPosts }"
+ />{{ $t('settings.hide_all_muted_posts') }}
+ </button>
+ <button
+ class="button-default dropdown-item dropdown-item-icon"
+ @click="openTab('filtering')"
+ >
+ <FAIcon icon="font" />{{ $t('settings.word_filter') }}
+ </button>
+ <button
+ class="button-default dropdown-item dropdown-item-icon"
+ @click="openTab('general')"
+ >
+ <FAIcon icon="wrench" />{{ $t('settings.more_settings') }}
+ </button>
+ </div>
+ </template>
+ <template v-slot:trigger>
+ <button class="button-unstyled">
+ <FAIcon icon="filter" />
+ </button>
+ </template>
+ </Popover>
+</template>
+
+<script src="./timeline_quick_settings.js"></script>
+
+<style lang="scss">
+
+.TimelineQuickSettings {
+ align-self: stretch;
+
+ > button {
+ font-size: 1.2em;
+ padding-left: 0.7em;
+ padding-right: 0.2em;
+ line-height: 100%;
+ height: 100%;
+ }
+
+ .dropdown-item {
+ margin: 0;
+ }
+}
+
+</style>
diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js
index 8d6a58b1..bab51e75 100644
--- a/src/components/timeline_menu/timeline_menu.js
+++ b/src/components/timeline_menu/timeline_menu.js
@@ -1,29 +1,17 @@
import Popover from '../popover/popover.vue'
-import { mapState } from 'vuex'
+import TimelineMenuContent from './timeline_menu_content.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
- faUsers,
- faGlobe,
- faBookmark,
- faEnvelope,
- faHome,
faChevronDown
} from '@fortawesome/free-solid-svg-icons'
-library.add(
- faUsers,
- faGlobe,
- faBookmark,
- faEnvelope,
- faHome,
- faChevronDown
-)
+library.add(faChevronDown)
// Route -> i18n key mapping, exported and not in the computed
// because nav panel benefits from the same information.
export const timelineNames = () => {
return {
- 'friends': 'nav.timeline',
+ 'friends': 'nav.home_timeline',
'bookmarks': 'nav.bookmarks',
'dms': 'nav.dms',
'public-timeline': 'nav.public_tl',
@@ -33,7 +21,8 @@ export const timelineNames = () => {
const TimelineMenu = {
components: {
- Popover
+ Popover,
+ TimelineMenuContent
},
data () {
return {
@@ -41,9 +30,6 @@ const TimelineMenu = {
}
},
created () {
- if (this.currentUser && this.currentUser.locked) {
- this.$store.dispatch('startFetchingFollowRequests')
- }
if (timelineNames()[this.$route.name]) {
this.$store.dispatch('setLastTimeline', this.$route.name)
}
@@ -75,13 +61,6 @@ const TimelineMenu = {
const i18nkey = timelineNames()[this.$route.name]
return i18nkey ? this.$t(i18nkey) : route
}
- },
- computed: {
- ...mapState({
- currentUser: state => state.users.currentUser,
- privateMode: state => state.instance.private,
- federating: state => state.instance.federating
- })
}
}
diff --git a/src/components/timeline_menu/timeline_menu.vue b/src/components/timeline_menu/timeline_menu.vue
index 3c86842b..8f14093f 100644
--- a/src/components/timeline_menu/timeline_menu.vue
+++ b/src/components/timeline_menu/timeline_menu.vue
@@ -9,74 +9,26 @@
@show="openMenu"
@close="() => isOpen = false"
>
- <div
- slot="content"
- class="timeline-menu-popover panel panel-default"
- >
- <ul>
- <li v-if="currentUser">
- <router-link :to="{ name: 'friends' }">
- <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'}">
- <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 } }">
- <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' }">
- <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' }">
- <FAIcon
- fixed-width
- class="fa-scale-110 fa-old-padding "
- icon="globe"
- />{{ $t("nav.twkn") }}
- </router-link>
- </li>
- </ul>
- </div>
- <div
- slot="trigger"
- class="title timeline-menu-title"
- >
- <span class="timeline-title">{{ timelineName() }}</span>
- <span>
- <FAIcon
- size="sm"
- icon="chevron-down"
+ <template v-slot:content>
+ <div class="timeline-menu-popover popover-default">
+ <TimelineMenuContent />
+ </div>
+ </template>
+ <template v-slot:trigger>
+ <button class="button-unstyled title timeline-menu-title">
+ <span class="timeline-title">{{ timelineName() }}</span>
+ <span>
+ <FAIcon
+ size="sm"
+ icon="chevron-down"
+ />
+ </span>
+ <span
+ class="click-blocker"
+ @click="blockOpen"
/>
- </span>
- <span
- class="click-blocker"
- @click="blockOpen"
- />
- </div>
+ </button>
+ </template>
</Popover>
</template>
diff --git a/src/components/timeline_menu/timeline_menu_content.js b/src/components/timeline_menu/timeline_menu_content.js
new file mode 100644
index 00000000..671570dd
--- /dev/null
+++ b/src/components/timeline_menu/timeline_menu_content.js
@@ -0,0 +1,29 @@
+import { mapState } from 'vuex'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faUsers,
+ faGlobe,
+ faBookmark,
+ faEnvelope,
+ faHome
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faUsers,
+ faGlobe,
+ faBookmark,
+ faEnvelope,
+ faHome
+)
+
+const TimelineMenuContent = {
+ computed: {
+ ...mapState({
+ currentUser: state => state.users.currentUser,
+ privateMode: state => state.instance.private,
+ federating: state => state.instance.federating
+ })
+ }
+}
+
+export default TimelineMenuContent
diff --git a/src/components/timeline_menu/timeline_menu_content.vue b/src/components/timeline_menu/timeline_menu_content.vue
new file mode 100644
index 00000000..bed1b679
--- /dev/null
+++ b/src/components/timeline_menu/timeline_menu_content.vue
@@ -0,0 +1,66 @@
+<template>
+ <ul>
+ <li v-if="currentUser">
+ <router-link
+ class="menu-item"
+ :to="{ name: 'friends' }"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding "
+ icon="home"
+ />{{ $t("nav.home_timeline") }}
+ </router-link>
+ </li>
+ <li v-if="currentUser || !privateMode">
+ <router-link
+ class="menu-item"
+ :to="{ name: 'public-timeline' }"
+ >
+ <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
+ class="menu-item"
+ :to="{ name: 'public-external-timeline' }"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding "
+ icon="globe"
+ />{{ $t("nav.twkn") }}
+ </router-link>
+ </li>
+ <li v-if="currentUser">
+ <router-link
+ class="menu-item"
+ :to="{ name: '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
+ class="menu-item"
+ :to="{ name: 'dms', params: { username: currentUser.screen_name } }"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding "
+ icon="envelope"
+ />{{ $t("nav.dms") }}
+ </router-link>
+ </li>
+ </ul>
+</template>
+
+<script src="./timeline_menu_content.js" ></script>
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 3a8efafc..367fbc6c 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -4,23 +4,24 @@ import ProgressButton from '../progress_button/progress_button.vue'
import FollowButton from '../follow_button/follow_button.vue'
import ModerationTools from '../moderation_tools/moderation_tools.vue'
import AccountActions from '../account_actions/account_actions.vue'
+import Select from '../select/select.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
+ faExternalLinkAlt,
+ faEdit
} from '@fortawesome/free-solid-svg-icons'
library.add(
faRss,
faBell,
- faChevronDown,
faSearchPlus,
- faExternalLinkAlt
+ faExternalLinkAlt,
+ faEdit
)
export default {
@@ -118,7 +119,8 @@ export default {
ModerationTools,
AccountActions,
ProgressButton,
- FollowButton
+ FollowButton,
+ Select
},
methods: {
muteUser () {
@@ -153,6 +155,9 @@ export default {
this.$store.state.instance.restrictedNicknames
)
},
+ openProfileTab () {
+ this.$store.dispatch('openSettingsModalTab', 'profile')
+ },
zoomAvatar () {
const attachment = {
url: this.user.profile_image_url_original,
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 60776ebb..528b92fb 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -53,17 +53,29 @@
>
{{ user.name }}
</div>
- <a
+ <button
+ v-if="!isOtherUser && user.is_local"
+ class="button-unstyled edit-profile-button"
+ @click.stop="openProfileTab"
+ >
+ <FAIcon
+ fixed-width
+ class="icon"
+ icon="edit"
+ :title="$t('user_card.edit_profile')"
+ />
+ </button>
+ <button
v-if="isOtherUser && !user.is_local"
:href="user.statusnet_profile_url"
target="_blank"
- class="external-link-button"
+ class="button-unstyled external-link-button"
>
<FAIcon
class="icon"
icon="external-link-alt"
/>
- </a>
+ </button>
<AccountActions
v-if="isOtherUser && loggedIn"
:user="user"
@@ -132,25 +144,24 @@
class="userHighlightCl"
type="color"
>
- <label
- for="theme_tab"
- class="userHighlightSel select"
+ <Select
+ :id="'userHighlightSel'+user.id"
+ v-model="userHighlightType"
+ class="userHighlightSel"
>
- <select
- :id="'userHighlightSel'+user.id"
- v-model="userHighlightType"
- class="userHighlightSel"
- >
- <option value="disabled">No highlight</option>
- <option value="solid">Solid bg</option>
- <option value="striped">Striped bg</option>
- <option value="side">Side stripe</option>
- </select>
- <FAIcon
- class="select-down-icon"
- icon="chevron-down"
- />
- </label>
+ <option value="disabled">
+ {{ $t('user_card.highlight.disabled') }}
+ </option>
+ <option value="solid">
+ {{ $t('user_card.highlight.solid') }}
+ </option>
+ <option value="striped">
+ {{ $t('user_card.highlight.striped') }}
+ </option>
+ <option value="side">
+ {{ $t('user_card.highlight.side') }}
+ </option>
+ </Select>
</div>
</div>
<div
@@ -427,7 +438,7 @@
}
}
- .external-link-button {
+ .external-link-button, .edit-profile-button {
cursor: pointer;
width: 2.5em;
text-align: center;
@@ -541,15 +552,11 @@
flex: 1 0 auto;
}
- .userHighlightSel,
- .userHighlightSel.select {
+ .userHighlightSel {
padding-top: 0;
padding-bottom: 0;
flex: 1 0 auto;
}
- .userHighlightSel.select svg {
- line-height: 22px;
- }
.userHighlightText {
width: 70px;
@@ -558,9 +565,7 @@
.userHighlightCl,
.userHighlightText,
- .userHighlightSel,
- .userHighlightSel.select {
- height: 22px;
+ .userHighlightSel {
vertical-align: top;
margin-right: .5em;
margin-bottom: .25em;
@@ -585,6 +590,10 @@
}
}
+.sidebar .edit-profile-button {
+ display: none;
+}
+
.user-counts {
display: flex;
line-height:16px;
diff --git a/src/components/user_list_popover/user_list_popover.vue b/src/components/user_list_popover/user_list_popover.vue
index d0aa315e..f4b93c9a 100644
--- a/src/components/user_list_popover/user_list_popover.vue
+++ b/src/components/user_list_popover/user_list_popover.vue
@@ -4,40 +4,39 @@
placement="top"
:offset="{ y: 5 }"
>
- <template slot="trigger">
+ <template v-slot:trigger>
<slot />
</template>
- <div
- slot="content"
- class="user-list-popover"
- >
- <div v-if="users.length">
- <div
- v-for="(user) in usersCapped"
- :key="user.id"
- class="user-list-row"
- >
- <UserAvatar
- :user="user"
- class="avatar-small"
- :compact="true"
- />
- <div class="user-list-names">
- <!-- eslint-disable vue/no-v-html -->
- <span v-html="user.name_html" />
- <!-- eslint-enable vue/no-v-html -->
- <span class="user-list-screen-name">{{ user.screen_name_ui }}</span>
+ <template v-slot:content>
+ <div class="user-list-popover">
+ <template v-if="users.length">
+ <div
+ v-for="(user) in usersCapped"
+ :key="user.id"
+ class="user-list-row"
+ >
+ <UserAvatar
+ :user="user"
+ class="avatar-small"
+ :compact="true"
+ />
+ <div class="user-list-names">
+ <!-- eslint-disable vue/no-v-html -->
+ <span v-html="user.name_html" />
+ <!-- eslint-enable vue/no-v-html -->
+ <span class="user-list-screen-name">{{ user.screen_name_ui }}</span>
+ </div>
</div>
- </div>
- </div>
- <div v-else>
- <FAIcon
- icon="circle-notch"
- spin
- size="3x"
- />
+ </template>
+ <template v-else>
+ <FAIcon
+ icon="circle-notch"
+ spin
+ size="3x"
+ />
+ </template>
</div>
- </div>
+ </template>
</Popover>
</template>
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index 745e795d..aef897ae 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -60,10 +60,7 @@
:disabled="!user.friends_count"
>
<FriendList :user-id="userId">
- <template
- slot="item"
- slot-scope="{item}"
- >
+ <template v-slot:item="{item}">
<FollowCard :user="item" />
</template>
</FriendList>
@@ -75,10 +72,7 @@
:disabled="!user.followers_count"
>
<FollowerList :user-id="userId">
- <template
- slot="item"
- slot-scope="{item}"
- >
+ <template v-slot:item="{item}">
<FollowCard
:user="item"
:no-follows-you="isUs"
diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue
index a71c02c1..1f67a5cc 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.vue
+++ b/src/components/user_reporting_modal/user_reporting_modal.vue
@@ -45,10 +45,7 @@
</div>
<div class="user-reporting-panel-right">
<List :items="statuses">
- <template
- slot="item"
- slot-scope="{item}"
- >
+ <template v-slot:item="{item}">
<div class="status-fadein user-reporting-panel-sitem">
<Status
:in-conversation="false"