Merge branch 'develop' into 'simplePolicy_reasons_for_instance_specific_policies'

# Conflicts:
#   src/i18n/nl.json
This commit is contained in:
Haelwenn 2021-07-23 03:47:39 +00:00
commit ff37dbbd23
99 changed files with 3292 additions and 1711 deletions

View file

@ -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;

View file

@ -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
},

View file

@ -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%;
}

View file

@ -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"

View file

@ -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>

View file

@ -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>

View file

@ -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) {

View file

@ -1,5 +1,6 @@
<template>
<div
ref="root"
v-click-outside="onClickOutside"
class="emoji-input"
:class="{ 'with-picker': !hideEmojiButton }"

View file

@ -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>

View file

@ -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>

View file

@ -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 },

View file

@ -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') }}

View file

@ -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

View file

@ -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>

View file

@ -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'
],

View file

@ -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;
}

View file

@ -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))

View file

@ -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
@ -121,26 +121,27 @@
</button>
</span>
</div>
</div>
<button
slot="trigger"
class="btn button-default btn-block moderation-tools-button"
:class="{ toggled }"
>
{{ $t('user_card.admin_menu.moderation') }}
<FAIcon icon="chevron-down" />
</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)"

View file

@ -5,9 +5,7 @@
placement="bottom"
:bound-to="{ x: 'container' }"
>
<template
v-slot:content
>
<template v-slot:content>
<div class="dropdown-menu">
<button
class="button-default dropdown-item"
@ -66,7 +64,9 @@
</div>
</template>
<template v-slot:trigger>
<FAIcon icon="filter" />
<button class="button-unstyled">
<FAIcon icon="filter" />
</button>
</template>
</Popover>
</template>

View file

@ -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;

View file

@ -1,7 +1,7 @@
<template>
<div
:class="{ minimal: minimalMode }"
class="notifications"
class="Notifications"
>
<div :class="mainClass">
<div
@ -35,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>
@ -47,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"

View file

@ -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: () => ({

View file

@ -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>

View file

@ -54,7 +54,7 @@ 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

View file

@ -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
},

View file

@ -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'"

View file

@ -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>

View file

@ -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'
]
}

View file

@ -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>

View file

@ -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>

View file

@ -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)
}
}
}

View file

@ -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>

View file

@ -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)
}
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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: {

View file

@ -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>

View file

@ -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;

View file

@ -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(),

View file

@ -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') }}

View file

@ -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()
}
}

View file

@ -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,62 +95,22 @@
</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">

View file

@ -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>

View file

@ -15,6 +15,10 @@ import {
shadows2to3,
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'
@ -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)
}
}
}

View file

@ -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

View file

@ -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 () {

View file

@ -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;
}
}
}
}
}

View file

@ -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 () {
@ -50,4 +50,4 @@ const chatPanel = {
}
}
export default chatPanel
export default shoutPanel

View file

@ -1,12 +1,12 @@
<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">
@ -18,33 +18,33 @@
/>
</div>
</div>
<div 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)"
/>
@ -53,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">
@ -72,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;
@ -85,8 +85,8 @@
max-width: 25em;
}
.chat-panel {
.chat-heading {
.shout-panel {
.shout-heading {
cursor: pointer;
.icon {
@ -102,22 +102,22 @@
}
}
.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;
@ -128,7 +128,7 @@
}
}
.chat-input {
.shout-input {
display: flex;
textarea {
flex: 1;
@ -138,7 +138,7 @@
}
}
.chat-panel {
.shout-panel {
.title {
display: flex;
justify-content: space-between;

View file

@ -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)
},

View file

@ -273,9 +273,7 @@
--icon: var(--popoverIcon, $fallback--icon);
.badge {
position: absolute;
right: 0.7rem;
top: 1em;
margin-left: 10px;
}
}

View file

@ -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>

View file

@ -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;
}
}

View file

@ -52,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>
@ -67,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"
@ -87,32 +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;
position: relative;
.loadmore-button {
flex-shrink: 0;
}
.loadmore-text {
flex-shrink: 0;
line-height: 1em;
}
}
</style>
<style src="./timeline.scss" lang="scss"> </style>

View file

@ -4,77 +4,78 @@
class="TimelineQuickSettings"
:bound-to="{ x: 'container' }"
>
<div
slot="content"
class="dropdown-menu"
>
<div v-if="loggedIn">
<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="replyVisibilityAll = true"
@click="hideMedia = !hideMedia"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-radio': replyVisibilityAll }"
/>{{ $t('settings.reply_visibility_all') }}
:class="{ 'menu-checkbox-checked': hideMedia }"
/>{{ $t('settings.hide_media_previews') }}
</button>
<button
class="button-default dropdown-item"
@click="replyVisibilityFollowing = true"
@click="hideMutedPosts = !hideMutedPosts"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-radio': replyVisibilityFollowing }"
/>{{ $t('settings.reply_visibility_following_short') }}
:class="{ 'menu-checkbox-checked': hideMutedPosts }"
/>{{ $t('settings.hide_all_muted_posts') }}
</button>
<button
class="button-default dropdown-item"
@click="replyVisibilitySelf = true"
class="button-default dropdown-item dropdown-item-icon"
@click="openTab('filtering')"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-radio': replyVisibilitySelf }"
/>{{ $t('settings.reply_visibility_self_short') }}
<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
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') }}
</template>
<template v-slot:trigger>
<button class="button-unstyled">
<FAIcon icon="filter" />
</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>
<div slot="trigger">
<FAIcon icon="filter" />
</div>
</template>
</Popover>
</template>

View file

@ -9,28 +9,26 @@
@show="openMenu"
@close="() => isOpen = false"
>
<div
slot="content"
class="timeline-menu-popover panel panel-default"
>
<TimelineMenuContent />
</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>

View file

@ -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,

View file

@ -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">{{ $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>
<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;

View file

@ -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>
</template>
<template v-else>
<FAIcon
icon="circle-notch"
spin
size="3x"
/>
</template>
</div>
<div v-else>
<FAIcon
icon="circle-notch"
spin
size="3x"
/>
</div>
</div>
</template>
</Popover>
</template>

View file

@ -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"

View file

@ -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"