Merge branch 'develop' into 'iss-149/profile-fields-setting'

# Conflicts:
#   src/components/settings_modal/tabs/profile_tab.vue
This commit is contained in:
Shpuld Shpludson 2020-06-27 07:19:49 +00:00
commit ea0a12f604
50 changed files with 570 additions and 286 deletions

View file

@ -3,6 +3,7 @@
<Popover
trigger="click"
placement="bottom"
:bound-to="{ x: 'container' }"
>
<div
slot="content"

View file

@ -5,9 +5,20 @@ const DomainMuteCard = {
components: {
ProgressButton
},
computed: {
user () {
return this.$store.state.users.currentUser
},
muted () {
return this.user.domainMutes.includes(this.domain)
}
},
methods: {
unmuteDomain () {
return this.$store.dispatch('unmuteDomain', this.domain)
},
muteDomain () {
return this.$store.dispatch('muteDomain', this.domain)
}
}
}

View file

@ -4,6 +4,7 @@
{{ domain }}
</div>
<ProgressButton
v-if="muted"
:click="unmuteDomain"
class="btn btn-default"
>
@ -12,6 +13,16 @@
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
<ProgressButton
v-else
:click="muteDomain"
class="btn btn-default"
>
{{ $t('domain_mute_card.mute') }}
<template slot="progress">
{{ $t('domain_mute_card.mute_progress') }}
</template>
</ProgressButton>
</div>
</template>
@ -34,5 +45,9 @@
button {
width: 10em;
}
.autosuggest-results & {
padding-left: 1em;
}
}
</style>

View file

@ -13,7 +13,7 @@ import { debounce } from 'lodash'
const debounceUserSearch = debounce((data, input) => {
data.updateUsersList(input)
}, 500, { leading: true, trailing: false })
}, 500)
export default data => input => {
const firstChar = input[0]
@ -97,8 +97,8 @@ export const suggestUsers = data => input => {
replacement: '@' + screen_name + ' '
}))
// BE search users if there are no matches
if (newUsers.length === 0 && data.updateUsersList) {
// BE search users to get more comprehensive results
if (data.updateUsersList) {
debounceUserSearch(data, noPrefix)
}
return newUsers

View file

@ -3,6 +3,7 @@
trigger="click"
placement="top"
class="extra-button-popover"
:bound-to="{ x: 'container' }"
>
<div
slot="content"

View file

@ -78,6 +78,7 @@
video,
canvas {
object-fit: contain;
height: 100%;
}
}

View file

@ -45,20 +45,6 @@ const mediaUpload = {
this.$emit('all-uploaded')
}
},
fileDrop (e) {
if (e.dataTransfer.files.length > 0) {
e.preventDefault() // allow dropping text like before
this.multiUpload(e.dataTransfer.files)
}
},
fileDrag (e) {
let types = e.dataTransfer.types
if (types.contains('Files')) {
e.dataTransfer.dropEffect = 'copy'
} else {
e.dataTransfer.dropEffect = 'none'
}
},
clearFile () {
this.uploadReady = false
this.$nextTick(() => {

View file

@ -1,10 +1,5 @@
<template>
<div
class="media-upload"
@drop.prevent
@dragover.prevent="fileDrag"
@drop="fileDrop"
>
<div class="media-upload">
<label
class="label"
:title="$t('tool_tip.media_upload')"

View file

@ -54,25 +54,20 @@
flex-wrap: nowrap;
padding: 0.6em;
min-width: 0;
.avatar-container {
width: 32px;
height: 32px;
}
.status-el {
.status {
padding: 0.25em 0;
color: $fallback--faint;
color: var(--faint, $fallback--faint);
a {
color: var(--faintLink);
}
.status-content a {
color: var(--postFaintLink);
}
.status-body {
color: $fallback--faint;
color: var(--faint, $fallback--faint);
a {
color: var(--faintLink);
}
padding: 0;
.media-body {
margin: 0;
.status-content a {
color: var(--postFaintLink);
}
}
}

View file

@ -17,7 +17,7 @@
<span class="result-percentage">
{{ percentageForOption(option.votes_count) }}%
</span>
<span>{{ option.title }}</span>
<span v-html="option.title_html"></span>
</div>
<div
class="result-fill"

View file

@ -1,4 +1,3 @@
const Popover = {
name: 'Popover',
props: {
@ -10,6 +9,9 @@ const Popover = {
// '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,
@ -27,6 +29,10 @@ const Popover = {
}
},
methods: {
containerBoundingClientRect () {
const container = this.boundToSelector ? this.$el.closest(this.boundToSelector) : this.$el.offsetParent
return container.getBoundingClientRect()
},
updateStyles () {
if (this.hidden) {
this.styles = {
@ -45,7 +51,8 @@ const Popover = {
// Minor optimization, don't call a slow reflow call if we don't have to
const parentBounds = this.boundTo &&
(this.boundTo.x === 'container' || this.boundTo.y === 'container') &&
this.$el.offsetParent.getBoundingClientRect()
this.containerBoundingClientRect()
const margin = this.margin || {}
// What are the screen bounds for the popover? Viewport vs container

View file

@ -82,7 +82,9 @@ const PostStatusForm = {
contentType
},
caret: 0,
pollFormVisible: false
pollFormVisible: false,
showDropIcon: 'hide',
dropStopTimeout: null
}
},
computed: {
@ -248,13 +250,27 @@ const PostStatusForm = {
}
},
fileDrop (e) {
if (e.dataTransfer.files.length > 0) {
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
e.preventDefault() // allow dropping text like before
this.dropFiles = e.dataTransfer.files
clearTimeout(this.dropStopTimeout)
this.showDropIcon = 'hide'
}
},
fileDragStop (e) {
// The false-setting is done with delay because just using leave-events
// directly caused unwanted flickering, this is not perfect either but
// much less noticable.
clearTimeout(this.dropStopTimeout)
this.showDropIcon = 'fade'
this.dropStopTimeout = setTimeout(() => (this.showDropIcon = 'hide'), 500)
},
fileDrag (e) {
e.dataTransfer.dropEffect = 'copy'
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
clearTimeout(this.dropStopTimeout)
this.showDropIcon = 'show'
}
},
onEmojiInputInput (e) {
this.$nextTick(() => {

View file

@ -6,7 +6,15 @@
<form
autocomplete="off"
@submit.prevent="postStatus(newStatus)"
@dragover.prevent="fileDrag"
>
<div
v-show="showDropIcon !== 'hide'"
:style="{ animation: showDropIcon === 'show' ? 'fade-in 0.25s' : 'fade-out 0.5s' }"
class="drop-indicator icon-upload"
@dragleave="fileDragStop"
@drop.stop="fileDrop"
/>
<div class="form-group">
<i18n
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'"
@ -73,6 +81,7 @@
v-model="newStatus.spoilerText"
type="text"
:placeholder="$t('post_status.content_warning')"
:disabled="posting"
class="form-post-subject"
>
</EmojiInput>
@ -96,9 +105,7 @@
:disabled="posting"
class="form-post-body"
@keydown.meta.enter="postStatus(newStatus)"
@keyup.ctrl.enter="postStatus(newStatus)"
@drop="fileDrop"
@dragover.prevent="fileDrag"
@keydown.ctrl.enter="postStatus(newStatus)"
@input="resize"
@compositionupdate="resize"
@paste="paste"
@ -447,7 +454,8 @@
form {
display: flex;
flex-direction: column;
padding: 0.6em;
margin: 0.6em;
position: relative;
}
.form-group {
@ -505,5 +513,35 @@
cursor: pointer;
z-index: 4;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 0.6; }
}
@keyframes fade-out {
from { opacity: 0.6; }
to { opacity: 0; }
}
.drop-indicator {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
font-size: 5em;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
color: $fallback--text;
color: var(--text, $fallback--text);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
border: 2px dashed $fallback--text;
border: 2px dashed var(--text, $fallback--text);
}
}
</style>

View file

@ -13,6 +13,9 @@ const PostStatusModal = {
}
},
computed: {
isLoggedIn () {
return !!this.$store.state.users.currentUser
},
modalActivated () {
return this.$store.state.postStatus.modalActivated
},

View file

@ -1,5 +1,6 @@
<template>
<Modal
v-if="isLoggedIn && !resettingForm"
:is-open="modalActivated"
class="post-form-modal-view"
@backdropClicked="closeModal"

View file

@ -32,12 +32,12 @@ const DomainMuteList = withSubscription({
const MutesAndBlocks = {
data () {
return {
activeTab: 'profile',
newDomainToMute: ''
activeTab: 'profile'
}
},
created () {
this.$store.dispatch('fetchTokens')
this.$store.dispatch('getKnownDomains')
},
components: {
TabSwitcher,
@ -51,6 +51,14 @@ const MutesAndBlocks = {
Autosuggest,
Checkbox
},
computed: {
knownDomains () {
return this.$store.state.instance.knownDomains
},
user () {
return this.$store.state.users.currentUser
}
},
methods: {
importFollows (file) {
return this.$store.state.api.backendInteractor.importFollows({ file })
@ -86,13 +94,13 @@ const MutesAndBlocks = {
filterUnblockedUsers (userIds) {
return reject(userIds, (userId) => {
const relationship = this.$store.getters.relationship(this.userId)
return relationship.blocking || userId === this.$store.state.users.currentUser.id
return relationship.blocking || userId === this.user.id
})
},
filterUnMutedUsers (userIds) {
return reject(userIds, (userId) => {
const relationship = this.$store.getters.relationship(this.userId)
return relationship.muting || userId === this.$store.state.users.currentUser.id
return relationship.muting || userId === this.user.id
})
},
queryUserIds (query) {
@ -111,12 +119,16 @@ const MutesAndBlocks = {
unmuteUsers (ids) {
return this.$store.dispatch('unmuteUsers', ids)
},
filterUnMutedDomains (urls) {
return urls.filter(url => !this.user.domainMutes.includes(url))
},
queryKnownDomains (query) {
return new Promise((resolve, reject) => {
resolve(this.knownDomains.filter(url => url.toLowerCase().includes(query)))
})
},
unmuteDomains (domains) {
return this.$store.dispatch('unmuteDomains', domains)
},
muteDomain () {
return this.$store.dispatch('muteDomain', this.newDomainToMute)
.then(() => { this.newDomainToMute = '' })
}
}
}

View file

@ -119,21 +119,16 @@
<div :label="$t('settings.domain_mutes')">
<div class="domain-mute-form">
<input
v-model="newDomainToMute"
<Autosuggest
:filter="filterUnMutedDomains"
:query="queryKnownDomains"
:placeholder="$t('settings.type_domains_to_mute')"
type="text"
@keyup.enter="muteDomain"
>
<ProgressButton
class="btn btn-default domain-mute-button"
:click="muteDomain"
>
{{ $t('domain_mute_card.mute') }}
<template slot="progress">
{{ $t('domain_mute_card.mute_progress') }}
</template>
</ProgressButton>
<DomainMuteCard
slot-scope="row"
:domain="row.item"
/>
</Autosuggest>
</div>
<DomainMuteList
:refresh="true"

View file

@ -25,6 +25,7 @@ const ProfileTab = {
showRole: this.$store.state.users.currentUser.show_role,
role: this.$store.state.users.currentUser.role,
discoverable: this.$store.state.users.currentUser.discoverable,
bot: this.$store.state.users.currentUser.bot,
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
pickAvatarBtnVisible: true,
bannerUploading: false,
@ -94,6 +95,7 @@ const ProfileTab = {
hide_follows: this.hideFollows,
hide_followers: this.hideFollowers,
discoverable: this.discoverable,
bot: this.bot,
allow_following_move: this.allowFollowingMove,
hide_follows_count: this.hideFollowsCount,
hide_followers_count: this.hideFollowersCount,

View file

@ -143,6 +143,11 @@
{{ $t("settings.profile_fields.add_field") }}
</a>
</div>
<p>
<Checkbox v-model="bot">
{{ $t('settings.bot') }}
</Checkbox>
</p>
<button
:disabled="newName && newName.length === 0"
class="btn btn-default"

View file

@ -256,6 +256,13 @@
:label="$t('settings.links')"
/>
<ContrastRatio :contrast="previewContrast.postLink" />
<ColorInput
v-model="postGreentextColorLocal"
name="postGreentextColor"
:fallback="previewTheme.colors.cGreen"
:label="$t('settings.greentext')"
/>
<ContrastRatio :contrast="previewContrast.postGreentext" />
<h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
<ColorInput
v-model="alertErrorColorLocal"

View file

@ -418,7 +418,7 @@ $status-margin: 0.75em;
max-width: 85%;
font-weight: bold;
img {
img.emoji {
width: 14px;
height: 14px;
vertical-align: middle;

View file

@ -164,23 +164,23 @@ $status-margin: 0.75em;
word-break: break-all;
}
img, video {
max-width: 100%;
max-height: 400px;
vertical-align: middle;
object-fit: contain;
&.emoji {
width: 32px;
height: 32px;
}
}
.status-content {
font-family: var(--postFont, sans-serif);
line-height: 1.4em;
white-space: pre-wrap;
img, video {
max-width: 100%;
max-height: 400px;
vertical-align: middle;
object-fit: contain;
&.emoji {
width: 32px;
height: 32px;
}
}
blockquote {
margin: 0.2em 0 0.2em 2em;
font-style: italic;
@ -226,7 +226,7 @@ $status-margin: 0.75em;
.greentext {
color: $fallback--cGreen;
color: var(--cGreen, $fallback--cGreen);
color: var(--postGreentext, $fallback--cGreen);
}
.timeline :not(.panel-disabled) > {

View file

@ -23,13 +23,6 @@
<style lang="scss">
@import '../../_variables.scss';
.contain-fit {
.still-image {
img {
height: 100%;
}
}
}
.still-image {
position: relative;
@ -38,6 +31,7 @@
width: 100%;
height: 100%;
display: flex;
align-items: center;
&:hover canvas {
display: none;
@ -45,8 +39,8 @@
img {
width: 100%;
min-height: 100%;
object-fit: contain;
align-self: center;
}
&.animated {

View file

@ -70,10 +70,20 @@
>
@{{ user.screen_name }}
</router-link>
<span
v-if="!hideBio && !!visibleRole"
class="alert staff"
>{{ visibleRole }}</span>
<template v-if="!hideBio">
<span
v-if="!!visibleRole"
class="alert user-role"
>
{{ visibleRole }}
</span>
<span
v-if="user.bot"
class="alert user-role"
>
bot
</span>
</template>
<span v-if="user.locked"><i class="icon icon-lock" /></span>
<span
v-if="!mergedConfig.hideUserStats && !hideBio"
@ -458,7 +468,7 @@
color: var(--text, $fallback--text);
}
.staff {
.user-role {
flex: none;
text-transform: capitalize;
color: $fallback--text;

View file

@ -124,6 +124,14 @@ const UserProfile = {
onTabSwitch (tab) {
this.tab = tab
this.$router.replace({ query: { tab } })
},
linkClicked ({ target }) {
if (target.tagName === 'SPAN') {
target = target.parentNode
}
if (target.tagName === 'A') {
window.open(target.href, '_blank')
}
}
},
watch: {

View file

@ -11,6 +11,31 @@
:allow-zooming-avatar="true"
rounded="top"
/>
<div
v-if="user.fields_html && user.fields_html.length > 0"
class="user-profile-fields"
>
<dl
v-for="(field, index) in user.fields_html"
:key="index"
class="user-profile-field"
>
<!-- eslint-disable vue/no-v-html -->
<dt
:title="user.fields_text[index].name"
class="user-profile-field-name"
@click.prevent="linkClicked"
v-html="field.name"
/>
<dd
:title="user.fields_text[index].value"
class="user-profile-field-value"
@click.prevent="linkClicked"
v-html="field.value"
/>
<!-- eslint-enable vue/no-v-html -->
</dl>
</div>
<tab-switcher
:active-tab="tab"
:render-only-focused="true"
@ -108,11 +133,60 @@
<script src="./user_profile.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.user-profile {
flex: 2;
flex-basis: 500px;
.user-profile-fields {
margin: 0 0.5em;
img {
object-fit: contain;
vertical-align: middle;
max-width: 100%;
max-height: 400px;
&.emoji {
width: 18px;
height: 18px;
}
}
.user-profile-field {
display: flex;
margin: 0.25em auto;
max-width: 32em;
border: 1px solid var(--border, $fallback--border);
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
.user-profile-field-name {
flex: 0 1 30%;
font-weight: 500;
text-align: right;
color: var(--lightText);
min-width: 120px;
border-right: 1px solid var(--border, $fallback--border);
}
.user-profile-field-value {
flex: 1 1 70%;
color: var(--text);
margin: 0 0 0 0.25em;
}
.user-profile-field-name, .user-profile-field-value {
line-height: 18px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding: 0.5em 1.5em;
box-sizing: border-box;
}
}
}
.userlist-placeholder {
display: flex;
justify-content: center;