Commit ea0a12f6 authored by Shpuld Shpludson's avatar Shpuld Shpludson

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

# Conflicts:
#   src/components/settings_modal/tabs/profile_tab.vue
parents bad3dacf bbb91d8a
Pipeline #27753 passed with stages
in 8 minutes and 58 seconds
......@@ -4,28 +4,38 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Changed
- Greentext now has separate color slot for it
- Removed the use of with_move parameters when fetching notifications
- Push notifications now are the same as normal notfication, and are localized.
### Fixed
- Weird bug related to post being sent seemingly after pasting with keyboard (hopefully)
- Multiple issues with muted statuses/notifications
## [Unreleased patch]
### Add
- Added private notifications option for push notifications
- 'Copy link' button for statuses (in the ellipsis menu)
- Autocomplete domains from list of known instances
- 'Bot' settings option and badge
### Changed
- Registration page no longer requires email if the server is configured not to require it
- Change heart to thumbs up in reaction picker
- Close the media modal on navigation events
- Add colons to the emoji alt text, to make them copyable
- Add better visual indication for drag-and-drop for files
### Fixed
- Custom Emoji will display in poll options now.
- Status ellipsis menu closes properly when selecting certain options
- Cropped images look correct in Chrome
- Newlines in the muted words settings work again
- Clicking on non-latin hashtags won't open a new window
- Uploading and drag-dropping multiple files works correctly now.
- Subject field now appears disabled when posting
- Fix status ellipsis menu being cut off in notifications column
- Fixed autocomplete sometimes not returning the right user when there's already some results
## [2.0.3] - 2020-05-02
### Fixed
......
......@@ -3,6 +3,7 @@
<Popover
trigger="click"
placement="bottom"
:bound-to="{ x: 'container' }"
>
<div
slot="content"
......
......@@ -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)
}
}
}
......
......@@ -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>
......@@ -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
......
......@@ -3,6 +3,7 @@
trigger="click"
placement="top"
class="extra-button-popover"
:bound-to="{ x: 'container' }"
>
<div
slot="content"
......
......@@ -78,6 +78,7 @@
video,
canvas {
object-fit: contain;
height: 100%;
}
}
......
......@@ -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(() => {
......
<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')"
......
......@@ -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);
}
}
}
......
......@@ -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"
......
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
......
......@@ -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(() => {
......
......@@ -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>
......@@ -13,6 +13,9 @@ const PostStatusModal = {
}
},
computed: {
isLoggedIn () {
return !!this.$store.state.users.currentUser
},
modalActivated () {
return this.$store.state.postStatus.modalActivated
},
......
<template>
<Modal
v-if="isLoggedIn && !resettingForm"
:is-open="modalActivated"
class="post-form-modal-view"
@backdropClicked="closeModal"
......
......@@ -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 = '' })
}
}
}
......
......@@ -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"
......
......@@ -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,
......
......@@ -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"
......
......@@ -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"
......
......@@ -418,7 +418,7 @@ $status-margin: 0.75em;
max-width: 85%;
font-weight: bold;
img {
img.emoji {
width: 14px;
height: 14px;
vertical-align: middle;
......
......@@ -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) > {
......