Merge remote-tracking branch 'origin/develop' into shigusegubu

* origin/develop:
  fix remote follow button style
  Disable horizontal textarea resize
  Add uk.json in messages.js
  Display upload limit on the Features panel
  use title html for poll options before vote
  fix recent mistakes in react button
  update changelog
  move external source button to extra buttons, make expand button easier to click and highlight on hover
  make click blocking actually work
  block clicks in blank area of timeline menu, fix 'up-to-date' align
  remove vertical align, update changelog
  fix the close button on global notices
  refactor error handling in profile tab
This commit is contained in:
Henry Jameson 2020-12-17 11:35:56 +02:00
commit 0710b6cb11
26 changed files with 155 additions and 121 deletions

View file

@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mouseover titles for emojis in reaction picker
- Support to input emoji into the search box in reaction picker
- Added some missing unicode emoji
- Added the upload limit to the Features panel in the About page
### Fixed
- Fixed the occasional bug where screen would scroll 1px when typing into a reply form
@ -17,11 +18,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fixed custom emoji not working in profile field names
- Fixed pinned statuses not appearing in user profiles
- Fixed some elements not being keyboard navigation friendly
- Fixed error handling when updating various profile images
- Fixed your latest chat messages disappearing when closing chat view and opening it again during the same session
- Fixed custom emoji not showing in poll options before voting
### Changed
- Errors when fetching are now shown with popup errors instead of "Error fetching updates" in panel headers
- Made reply/fav/repeat etc buttons easier to hit
- Adjusted timeline menu clickable area to match the visible button
- Moved external source link from status heading to the ellipsis menu
- Disabled horizontal textarea resize
## [2.2.1] - 2020-11-11

View file

@ -332,6 +332,10 @@ input, textarea, .select, .input {
box-sizing: border-box;
}
}
&.resize-height {
resize: vertical;
}
}
option {

View file

@ -5,7 +5,8 @@ import {
faBookmark,
faEyeSlash,
faThumbtack,
faShareAlt
faShareAlt,
faExternalLinkAlt
} from '@fortawesome/free-solid-svg-icons'
import {
faBookmark as faBookmarkReg
@ -17,7 +18,8 @@ library.add(
faBookmarkReg,
faEyeSlash,
faThumbtack,
faShareAlt
faShareAlt,
faExternalLinkAlt
)
const ExtraButtons = {

View file

@ -1,5 +1,6 @@
<template>
<Popover
class="ExtraButtons"
trigger="click"
placement="top"
:offset="{ y: 5 }"
@ -96,11 +97,23 @@
icon="share-alt"
/><span>{{ $t("status.copy_link") }}</span>
</button>
<a
v-if="!status.is_local"
class="button-default dropdown-item dropdown-item-icon"
title="Source"
:href="status.external_url"
target="_blank"
>
<FAIcon
fixed-width
icon="external-link-alt"
/><span>{{ $t("status.external_source") }}</span>
</a>
</div>
</div>
<span
slot="trigger"
class="ExtraButtons"
class="popover-trigger"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
@ -116,13 +129,15 @@
@import '../../_variables.scss';
.ExtraButtons {
position: static;
padding: 10px;
margin: -10px;
.popover-trigger {
position: static;
padding: 10px;
margin: -10px;
&:hover .svg-inline--fa {
color: $fallback--text;
color: var(--text, $fallback--text);
&:hover .svg-inline--fa {
color: $fallback--text;
color: var(--text, $fallback--text);
}
}
}
</style>

View file

@ -1,3 +1,5 @@
import fileSizeFormatService from '../../services/file_size_format/file_size_format.js'
const FeaturesPanel = {
computed: {
chat: function () { return this.$store.state.instance.chatAvailable },
@ -6,7 +8,8 @@ const FeaturesPanel = {
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable },
minimalScopesMode: function () { return this.$store.state.instance.minimalScopesMode },
textlimit: function () { return this.$store.state.instance.textlimit }
textlimit: function () { return this.$store.state.instance.textlimit },
uploadlimit: function () { return fileSizeFormatService.fileSizeFormat(this.$store.state.instance.uploadlimit) }
}
}

View file

@ -25,6 +25,7 @@
</li>
<li>{{ $t('features_panel.scope_options') }}</li>
<li>{{ $t('features_panel.text_limit') }} = {{ textlimit }}</li>
<li>{{ $t('features_panel.upload_limit') }} = {{ uploadlimit.num }} {{ $t('upload.file_size_units.' + uploadlimit.unit) }}</li>
</ul>
</div>
</div>

View file

@ -9,11 +9,15 @@
<div class="notice-message">
{{ $t(notice.messageKey, notice.messageArgs) }}
</div>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
<button
class="button-unstyled close-notice"
@click="closeNotice(notice)"
/>
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
/>
</button>
</div>
</div>
</template>
@ -54,7 +58,7 @@
.global-error {
background-color: var(--alertPopupError, $fallback--cRed);
color: var(--alertPopupErrorText, $fallback--text);
i {
.svg-inline--fa {
color: var(--alertPopupErrorText, $fallback--text);
}
}
@ -62,7 +66,7 @@
.global-warning {
background-color: var(--alertPopupWarning, $fallback--cOrange);
color: var(--alertPopupWarningText, $fallback--text);
i {
.svg-inline--fa {
color: var(--alertPopupWarningText, $fallback--text);
}
}
@ -70,9 +74,16 @@
.global-info {
background-color: var(--alertPopupNeutral, $fallback--fg);
color: var(--alertPopupNeutralText, $fallback--text);
i {
.svg-inline--fa {
color: var(--alertPopupNeutralText, $fallback--text);
}
}
.close-notice {
padding-right: 0.2em;
.svg-inline--fa:hover {
opacity: 0.6;
}
}
}
</style>

View file

@ -2,12 +2,10 @@ import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.css'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
faCircleNotch
} from '@fortawesome/free-solid-svg-icons'
library.add(
faTimes,
faCircleNotch
)
@ -53,8 +51,7 @@ const ImageCropper = {
cropper: undefined,
dataUrl: undefined,
filename: undefined,
submitting: false,
submitError: null
submitting: false
}
},
computed: {
@ -66,9 +63,6 @@ const ImageCropper = {
},
cancelText () {
return this.cancelButtonLabel || this.$t('image_cropper.cancel')
},
submitErrorMsg () {
return this.submitError && this.submitError instanceof Error ? this.submitError.toString() : this.submitError
}
},
methods: {
@ -82,12 +76,8 @@ const ImageCropper = {
},
submit (cropping = true) {
this.submitting = true
this.avatarUploadError = null
this.submitHandler(cropping && this.cropper, this.file)
.then(() => this.destroy())
.catch((err) => {
this.submitError = err
})
.finally(() => {
this.submitting = false
})
@ -113,9 +103,6 @@ const ImageCropper = {
reader.readAsDataURL(this.file)
this.$emit('changed', this.file, reader)
}
},
clearError () {
this.submitError = null
}
},
mounted () {

View file

@ -37,17 +37,6 @@
icon="circle-notch"
/>
</div>
<div
v-if="submitError"
class="alert error"
>
{{ submitErrorMsg }}
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
@click="clearError"
/>
</div>
</div>
<input
ref="input"

View file

@ -42,7 +42,8 @@
:value="index"
>
<label class="option-vote">
<div>{{ option.title }}</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="option.title_html" />
</label>
</div>
</div>

View file

@ -95,6 +95,7 @@
box-shadow: none;
width: 100%;
height: 100%;
box-sizing: border-box;
--btnText: var(--popoverText, $fallback--text);

View file

@ -20,10 +20,10 @@
<div class="reaction-picker">
<span
v-for="emoji in commonEmojis"
:key="emoji"
:key="emoji.replacement"
class="emoji-button"
:title="emoji.displayText"
@click="addReaction($event, emoji, close)"
@click="addReaction($event, emoji.replacement, close)"
>
{{ emoji.replacement }}
</span>

View file

@ -16,7 +16,7 @@
>
<button
click="submit"
class="remote-button"
class="button-default remote-button"
>
{{ $t('user_card.remote_follow') }}
</button>

View file

@ -75,6 +75,7 @@
<p>{{ $t('settings.filtering_explanation') }}</p>
<textarea
id="muteWords"
class="resize-height"
v-model="muteWordsString"
/>
</div>

View file

@ -45,9 +45,7 @@ const ProfileTab = {
banner: null,
bannerPreview: null,
background: null,
backgroundPreview: null,
bannerUploadError: null,
backgroundUploadError: null
backgroundPreview: null
}
},
components: {
@ -162,18 +160,18 @@ const ProfileTab = {
if (file.size > this.$store.state.instance[slot + 'limit']) {
const filesize = fileSizeFormatService.fileSizeFormat(file.size)
const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit'])
this[slot + 'UploadError'] = [
this.$t('upload.error.base'),
this.$t(
'upload.error.file_too_big',
{
this.$store.dispatch('pushGlobalNotice', {
messageKey: 'upload.error.message',
messageArgs: [
this.$t('upload.error.file_too_big', {
filesize: filesize.num,
filesizeunit: filesize.unit,
allowedsize: allowedsize.num,
allowedsizeunit: allowedsize.unit
}
)
].join(' ')
})
],
level: 'error'
})
return
}
// eslint-disable-next-line no-undef
@ -213,8 +211,9 @@ const ProfileTab = {
that.$store.commit('setCurrentUser', user)
resolve()
})
.catch((err) => {
reject(new Error(that.$t('upload.error.base') + ' ' + err.message))
.catch((error) => {
that.displayUploadError(error)
reject(error)
})
}
@ -235,24 +234,27 @@ const ProfileTab = {
this.$store.commit('setCurrentUser', user)
this.bannerPreview = null
})
.catch((err) => {
this.bannerUploadError = this.$t('upload.error.base') + ' ' + err.message
})
.then(() => { this.bannerUploading = false })
.catch(this.displayUploadError)
.finally(() => { this.bannerUploading = false })
},
submitBackground (background) {
if (!this.backgroundPreview && background !== '') { return }
this.backgroundUploading = true
this.$store.state.api.backendInteractor.updateProfileImages({ background }).then((data) => {
if (!data.error) {
this.$store.state.api.backendInteractor.updateProfileImages({ background })
.then((data) => {
this.$store.commit('addNewUsers', [data])
this.$store.commit('setCurrentUser', data)
this.backgroundPreview = null
} else {
this.backgroundUploadError = this.$t('upload.error.base') + data.error
}
this.backgroundUploading = false
})
.catch(this.displayUploadError)
.finally(() => { this.backgroundUploading = false })
},
displayUploadError (error) {
this.$store.dispatch('pushGlobalNotice', {
messageKey: 'upload.error.message',
messageArgs: [error.message],
level: 'error'
})
}
}

View file

@ -11,7 +11,7 @@
<input
id="username"
v-model="newName"
classname="name-changer"
class="name-changer"
>
</EmojiInput>
<p>{{ $t('settings.bio') }}</p>
@ -22,7 +22,7 @@
>
<textarea
v-model="newBio"
classname="bio"
class="bio resize-height"
/>
</EmojiInput>
<p>
@ -229,17 +229,6 @@
>
{{ $t('general.submit') }}
</button>
<div
v-if="bannerUploadError"
class="alert error"
>
Error: {{ bannerUploadError }}
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="times"
@click="clearUploadError('banner')"
/>
</div>
</div>
<div class="setting-item">
<h2>{{ $t('settings.profile_background') }}</h2>
@ -279,18 +268,6 @@
>
{{ $t('general.submit') }}
</button>
<div
v-if="backgroundUploadError"
class="alert error"
>
Error: {{ backgroundUploadError }}
<FAIcon
size="lg"
class="fa-scale-110 fa-old-padding"
icon="times"
@click="clearUploadError('background')"
/>
</div>
</div>
</div>
</template>

View file

@ -26,7 +26,6 @@ import {
faTimes,
faRetweet,
faReply,
faExternalLinkSquareAlt,
faPlusSquare,
faSmileBeam,
faEllipsisH,
@ -44,7 +43,6 @@ library.add(
faTimes,
faRetweet,
faReply,
faExternalLinkSquareAlt,
faPlusSquare,
faStar,
faSmileBeam,

View file

@ -139,6 +139,20 @@ $status-margin: 0.75em;
.heading-right {
display: flex;
flex-shrink: 0;
.button-unstyled {
padding: 5px;
margin: -5px;
&:hover svg {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
}
.svg-inline--fa {
margin-left: 0.25em;
}
}
.timeago {

View file

@ -184,30 +184,20 @@
:title="status.visibility | capitalize"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
fixed-width
class="fa-scale-110"
:icon="visibilityIcon(status.visibility)"
/>
</span>
<a
v-if="!status.is_local && !isPreview"
:href="status.external_url"
target="_blank"
class="source_url"
title="Source"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="external-link-square-alt"
/>
</a>
<button
v-if="expandable && !isPreview"
class="button-unstyled"
title="Expand"
:title="$t('status.expand')"
@click.prevent="toggleExpanded"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
fixed-width
class="fa-scale-110"
icon="plus-square"
/>
</button>
@ -217,8 +207,9 @@
@click.prevent="toggleMute"
>
<FAIcon
fixed-width
icon="eye-slash"
class="fa-scale-110 fa-old-padding"
class="fa-scale-110"
/>
</button>
</span>

View file

@ -102,6 +102,7 @@
.timeline-heading {
max-width: 100%;
flex-wrap: nowrap;
align-items: center;
.loadmore-button {
flex-shrink: 0;
}

View file

@ -59,6 +59,14 @@ const TimelineMenu = {
this.isOpen = true
}, 25)
},
blockOpen (event) {
// For the blank area inside the button element.
// Just setting @click.stop="" makes unintuitive behavior when
// menu is open and clicking on the blank area doesn't close it.
if (!this.isOpen) {
event.stopPropagation()
}
},
timelineName () {
const route = this.$route.name
if (route === 'tag-timeline') {

View file

@ -65,10 +65,16 @@
slot="trigger"
class="title timeline-menu-title"
>
<span>{{ timelineName() }}</span>
<FAIcon
size="sm"
icon="chevron-down"
<span class="timeline-title">{{ timelineName() }}</span>
<span>
<FAIcon
size="sm"
icon="chevron-down"
/>
</span>
<span
class="click-blocker"
@click="blockOpen"
/>
</div>
</Popover>
@ -117,8 +123,9 @@
cursor: pointer;
user-select: none;
width: 100%;
display: flex;
span {
.timeline-menu-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@ -128,6 +135,11 @@
margin-left: 0.6em;
transition: transform 100ms;
}
.click-blocker {
cursor: default;
flex-grow: 1;
}
}
&.open .timeline-menu-title svg {

View file

@ -50,7 +50,8 @@
"scope_options": "Scope options",
"text_limit": "Text limit",
"title": "Features",
"who_to_follow": "Who to follow"
"who_to_follow": "Who to follow",
"upload_limit": "Upload limit"
},
"finder": {
"error_fetching_user": "Error fetching user",
@ -661,6 +662,7 @@
"unmute_conversation": "Unmute conversation",
"status_unavailable": "Status unavailable",
"copy_link": "Copy link to status",
"external_source": "External source",
"thread_muted": "Thread muted",
"thread_muted_and_words": ", has words:",
"show_full_subject": "Show full subject",
@ -668,7 +670,8 @@
"show_content": "Show content",
"hide_content": "Hide content",
"status_deleted": "This post was deleted",
"nsfw": "NSFW"
"nsfw": "NSFW",
"expand": "Expand"
},
"user_card": {
"approve": "Approve",
@ -758,6 +761,7 @@
"upload": {
"error": {
"base": "Upload failed.",
"message": "Upload failed: {0}",
"file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
"default": "Try again later"
},

View file

@ -33,6 +33,7 @@ const loaders = {
ro: () => import('./ro.json'),
ru: () => import('./ru.json'),
te: () => import('./te.json'),
uk: () => import('./uk.json'),
zh: () => import('./zh.json')
}

View file

@ -162,7 +162,12 @@ const updateProfileImages = ({ credentials, avatar = null, banner = null, backgr
body: form
})
.then((data) => data.json())
.then((data) => parseUser(data))
.then((data) => {
if (data.error) {
throw new Error(data.error)
}
return parseUser(data)
})
}
const updateProfile = ({ credentials, params }) => {

View file

@ -280,7 +280,7 @@ export const parseStatus = (data) => {
if (output.poll) {
output.poll.options = (output.poll.options || []).map(field => ({
...field,
title_html: addEmojis(field.title, data.emojis)
title_html: addEmojis(escape(field.title), data.emojis)
}))
}
output.pinned = data.pinned