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

* upstream/develop: (32 commits)
  set flex-shrink and flex-basis explicitly
  set flex amount correctly
  update flex-grow calculation logic
  keep image natural ratio in gallery row
  populate gallery row height without getting width
  refactor using Set
  clean up
  update event name
  update condition
  move modal animation keyframes definition
  migrate viewClass prop to class attribute
  fix eslint errors
  fix message input not auto-focusing bug
  revert changes to render modal into portal
  use higher css specificity
  fix eslint warnings
  remove needless ref definition
  render modals into the “modal” portal
  remove needless console.log
  remove needless importing
  ...
This commit is contained in:
Henry Jameson 2019-10-27 19:28:50 +02:00
commit 6a5d57d2fe
24 changed files with 207 additions and 176 deletions

View file

@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]
### Added
- Ability to hide/show repeats from user
- User profile button clutter organized into a menu
- Emoji picker
- Started changelog anew
### Changed

View file

@ -717,31 +717,6 @@ nav {
}
}
@keyframes modal-background-fadein {
from {
background-color: rgba(0, 0, 0, 0);
}
to {
background-color: rgba(0, 0, 0, 0.5);
}
}
.modal-view {
z-index: 1000;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
overflow: auto;
animation-duration: 0.2s;
background-color: rgba(0, 0, 0, 0.5);
animation-name: modal-background-fadein;
}
.button-icon {
font-size: 1.2em;
}

View file

@ -47,7 +47,7 @@ export default (store) => {
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'settings', path: '/settings', component: Settings },
{ name: 'registration', path: '/registration', component: Registration },
{ name: 'password-reset', path: '/password-reset', component: PasswordReset },
{ name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
{ name: 'registration-token', path: '/registration/:token', component: Registration },
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
{ name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute },

View file

@ -10,7 +10,8 @@ const Attachment = {
'statusId',
'size',
'allowPlay',
'setMedia'
'setMedia',
'naturalSizeLoad'
],
data () {
return {
@ -88,6 +89,11 @@ const Attachment = {
} else {
this.showHidden = !this.showHidden
}
},
onImageLoad (image) {
const width = image.naturalWidth
const height = image.naturalHeight
this.naturalSizeLoad && this.naturalSizeLoad({ width, height })
}
}
}

View file

@ -58,6 +58,7 @@
:referrerpolicy="referrerpolicy"
:mimetype="attachment.mimetype"
:src="attachment.large_thumb_url || attachment.url"
:image-load-handler="onImageLoad"
/>
</a>

View file

@ -1,6 +1,6 @@
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
export default {
props: ['user'],
props: ['user', 'labelFollowing', 'buttonClass'],
data () {
return {
inProgress: false
@ -23,7 +23,7 @@ export default {
if (this.inProgress) {
return this.$t('user_card.follow_progress')
} else if (this.user.following) {
return this.$t('user_card.following')
return this.labelFollowing || this.$t('user_card.following')
} else if (this.user.requested) {
return this.$t('user_card.follow_sent')
} else {

View file

@ -1,20 +1,16 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import RemoteFollow from '../remote_follow/remote_follow.vue'
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
import FollowButton from '../follow_button/follow_button.vue'
const FollowCard = {
props: [
'user',
'noFollowsYou'
],
data () {
return {
inProgress: false
}
},
components: {
BasicUserCard,
RemoteFollow
RemoteFollow,
FollowButton
},
computed: {
isMe () {
@ -23,20 +19,6 @@ const FollowCard = {
loggedIn () {
return this.$store.state.users.currentUser
}
},
methods: {
followUser () {
this.inProgress = true
requestFollow(this.user, this.$store).then(() => {
this.inProgress = false
})
},
unfollowUser () {
this.inProgress = true
requestUnfollow(this.user, this.$store).then(() => {
this.inProgress = false
})
}
}
}

View file

@ -16,36 +16,11 @@
</div>
</template>
<template v-else>
<button
v-if="!user.following"
class="btn btn-default follow-card-follow-button"
:disabled="inProgress"
:title="user.requested ? $t('user_card.follow_again') : ''"
@click="followUser"
>
<template v-if="inProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else-if="user.requested">
{{ $t('user_card.follow_sent') }}
</template>
<template v-else>
{{ $t('user_card.follow') }}
</template>
</button>
<button
v-else
class="btn btn-default follow-card-follow-button pressed"
:disabled="inProgress"
@click="unfollowUser"
>
<template v-if="inProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else>
{{ $t('user_card.follow_unfollow') }}
</template>
</button>
<FollowButton
:user="user"
class="follow-card-follow-button"
:label-following="$t('user_card.follow_unfollow')"
/>
</template>
</div>
</basic-user-card>

View file

@ -1,23 +1,18 @@
import Attachment from '../attachment/attachment.vue'
import { chunk, last, dropRight } from 'lodash'
import { chunk, last, dropRight, sumBy } from 'lodash'
const Gallery = {
data: () => ({
width: 500
}),
props: [
'attachments',
'nsfw',
'setMedia'
],
data () {
return {
sizes: {}
}
},
components: { Attachment },
mounted () {
this.resize()
window.addEventListener('resize', this.resize)
},
destroyed () {
window.removeEventListener('resize', this.resize)
},
computed: {
rows () {
if (!this.attachments) {
@ -33,21 +28,24 @@ const Gallery = {
}
return rows
},
rowHeight () {
return itemsPerRow => ({ 'height': `${(this.width / (itemsPerRow + 0.6))}px` })
},
useContainFit () {
return this.$store.getters.mergedConfig.useContainFit
}
},
methods: {
resize () {
// Quick optimization to make resizing not always trigger state change,
// only update attachment size in 10px steps
const width = Math.floor(this.$el.getBoundingClientRect().width / 10) * 10
if (this.width !== width) {
this.width = width
}
onNaturalSizeLoad (id, size) {
this.$set(this.sizes, id, size)
},
rowStyle (itemsPerRow) {
return { 'padding-bottom': `${(100 / (itemsPerRow + 0.6))}%` }
},
itemStyle (id, row) {
const total = sumBy(row, item => this.getAspectRatio(item.id))
return { flex: `${this.getAspectRatio(id) / total} 1 0%` }
},
getAspectRatio (id) {
const size = this.sizes[id]
return size ? size.width / size.height : 1
}
}
}

View file

@ -7,17 +7,21 @@
v-for="(row, index) in rows"
:key="index"
class="gallery-row"
:style="rowHeight(row.length)"
:style="rowStyle(row.length)"
:class="{ 'contain-fit': useContainFit, 'cover-fit': !useContainFit }"
>
<attachment
v-for="attachment in row"
:key="attachment.id"
:set-media="setMedia"
:nsfw="nsfw"
:attachment="attachment"
:allow-play="false"
/>
<div class="gallery-row-inner">
<attachment
v-for="attachment in row"
:key="attachment.id"
:set-media="setMedia"
:nsfw="nsfw"
:attachment="attachment"
:allow-play="false"
:natural-size-load="onNaturalSizeLoad.bind(null, attachment.id)"
:style="itemStyle(attachment.id, row)"
/>
</div>
</div>
</div>
</template>
@ -28,15 +32,24 @@
@import '../../_variables.scss';
.gallery-row {
height: 200px;
position: relative;
height: 0;
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: stretch;
flex-grow: 1;
margin-top: 0.5em;
.gallery-row-inner {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-content: stretch;
}
// FIXME: specificity problem with this and .attachments.attachment
// we shouldn't have the need for .image here
.attachment.image {

View file

@ -59,6 +59,8 @@ const LoginForm = {
if (result.error) {
if (result.error === 'mfa_required') {
this.requireMFA({ app: app, settings: result })
} else if (result.identifier === 'password_reset_required') {
this.$router.push({ name: 'password-reset', params: { passwordResetRequested: true } })
} else {
this.error = result.error
this.focusOnPasswordInput()

View file

@ -1,11 +1,13 @@
import StillImage from '../still-image/still-image.vue'
import VideoAttachment from '../video_attachment/video_attachment.vue'
import Modal from '../modal/modal.vue'
import fileTypeService from '../../services/file_type/file_type.service.js'
const MediaModal = {
components: {
StillImage,
VideoAttachment
VideoAttachment,
Modal
},
computed: {
showing () {

View file

@ -1,9 +1,8 @@
<template>
<div
<Modal
v-if="showing"
v-body-scroll-lock="showing"
class="modal-view media-modal-view"
@click.prevent="hide"
class="media-modal-view"
@backdropClicked="hide"
>
<img
v-if="type === 'image'"
@ -33,21 +32,15 @@
>
<i class="icon-right-open arrow-icon" />
</button>
</div>
</Modal>
</template>
<script src="./media_modal.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.media-modal-view {
.modal-view.media-modal-view {
z-index: 1001;
body:not(.scroll-locked) & {
display: none;
}
&:hover {
.modal-view-button-arrow {
opacity: 0.75;
@ -114,5 +107,4 @@
}
}
}
</style>

View file

@ -0,0 +1,52 @@
<template>
<div
v-show="isOpen"
v-body-scroll-lock="isOpen"
class="modal-view"
@click.self="$emit('backdropClicked')"
>
<slot />
</div>
</template>
<script>
export default {
props: {
isOpen: {
type: Boolean,
default: true
}
}
}
</script>
<style lang="scss">
.modal-view {
z-index: 1000;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
overflow: auto;
animation-duration: 0.2s;
background-color: rgba(0, 0, 0, 0.5);
animation-name: modal-background-fadein;
body:not(.scroll-locked) & {
opacity: 0;
}
}
@keyframes modal-background-fadein {
from {
background-color: rgba(0, 0, 0, 0);
}
to {
background-color: rgba(0, 0, 0, 0.5);
}
}
</style>

View file

@ -25,6 +25,12 @@ const passwordReset = {
this.$router.push({ name: 'root' })
}
},
props: {
passwordResetRequested: {
default: false,
type: Boolean
}
},
methods: {
dismissError () {
this.error = null

View file

@ -10,7 +10,10 @@
>
<div class="container">
<div v-if="!mailerEnabled">
<p>
<p v-if="passwordResetRequested">
{{ $t('password_reset.password_reset_required_but_mailer_is_disabled') }}
</p>
<p v-else>
{{ $t('password_reset.password_reset_disabled') }}
</p>
</div>
@ -25,6 +28,12 @@
</div>
</div>
<div v-else>
<p
v-if="passwordResetRequested"
class="password-reset-required error"
>
{{ $t('password_reset.password_reset_required') }}
</p>
<p>
{{ $t('password_reset.instruction') }}
</p>
@ -104,6 +113,11 @@
margin: 0.3em 0.0em 1em;
}
.password-reset-required {
background-color: var(--alertError, $fallback--alertError);
padding: 10px 0;
}
.notice-dismissible {
padding-right: 2rem;
}

View file

@ -1,9 +1,11 @@
import PostStatusForm from '../post_status_form/post_status_form.vue'
import Modal from '../modal/modal.vue'
import get from 'lodash/get'
const PostStatusModal = {
components: {
PostStatusForm
PostStatusForm,
Modal
},
data () {
return {

View file

@ -1,14 +1,11 @@
<template>
<div
<Modal
v-if="isLoggedIn && !resettingForm"
v-show="modalActivated"
class="post-form-modal-view modal-view"
@click="closeModal"
:is-open="modalActivated"
class="post-form-modal-view"
@backdropClicked="closeModal"
>
<div
class="post-form-modal-panel panel"
@click.stop=""
>
<div class="post-form-modal-panel panel">
<div class="panel-heading">
{{ $t('post_status.new_status') }}
</div>
@ -18,15 +15,13 @@
@posted="closeModal"
/>
</div>
</div>
</Modal>
</template>
<script src="./post_status_modal.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.post-form-modal-view {
.modal-view.post-form-modal-view {
align-items: flex-start;
}

View file

@ -840,6 +840,11 @@ $status-margin: 0.75em;
&.button-icon-active {
color: $fallback--cBlue;
color: var(--cBlue, $fallback--cBlue);
}
}
.button-icon.icon-reply {
&:not(.button-icon-disabled) {
cursor: pointer;
}
}

View file

@ -3,7 +3,8 @@ const StillImage = {
'src',
'referrerpolicy',
'mimetype',
'imageLoadError'
'imageLoadError',
'imageLoadHandler'
],
data () {
return {
@ -17,6 +18,7 @@ const StillImage = {
},
methods: {
onLoad () {
this.imageLoadHandler && this.imageLoadHandler(this.$refs.src)
const canvas = this.$refs.canvas
if (!canvas) return
const width = this.$refs.src.naturalWidth

View file

@ -2,12 +2,14 @@
import Status from '../status/status.vue'
import List from '../list/list.vue'
import Checkbox from '../checkbox/checkbox.vue'
import Modal from '../modal/modal.vue'
const UserReportingModal = {
components: {
Status,
List,
Checkbox
Checkbox,
Modal
},
data () {
return {

View file

@ -1,13 +1,9 @@
<template>
<div
<Modal
v-if="isOpen"
class="modal-view"
@click="closeModal"
@backdropClicked="closeModal"
>
<div
class="user-reporting-panel panel"
@click.stop=""
>
<div class="user-reporting-panel panel">
<div class="panel-heading">
<div class="title">
{{ $t('user_reporting.title', [user.screen_name]) }}
@ -69,7 +65,7 @@
</div>
</div>
</div>
</div>
</Modal>
</template>
<script src="./user_reporting_modal.js"></script>

View file

@ -2,42 +2,49 @@ import * as bodyScrollLock from 'body-scroll-lock'
let previousNavPaddingRight
let previousAppBgWrapperRight
const lockerEls = new Set([])
const disableBodyScroll = (el) => {
const scrollBarGap = window.innerWidth - document.documentElement.clientWidth
bodyScrollLock.disableBodyScroll(el, {
reserveScrollBarGap: true
})
lockerEls.add(el)
setTimeout(() => {
// If previousNavPaddingRight is already set, don't set it again.
if (previousNavPaddingRight === undefined) {
const navEl = document.getElementById('nav')
previousNavPaddingRight = window.getComputedStyle(navEl).getPropertyValue('padding-right')
navEl.style.paddingRight = previousNavPaddingRight ? `calc(${previousNavPaddingRight} + ${scrollBarGap}px)` : `${scrollBarGap}px`
if (lockerEls.size <= 1) {
// If previousNavPaddingRight is already set, don't set it again.
if (previousNavPaddingRight === undefined) {
const navEl = document.getElementById('nav')
previousNavPaddingRight = window.getComputedStyle(navEl).getPropertyValue('padding-right')
navEl.style.paddingRight = previousNavPaddingRight ? `calc(${previousNavPaddingRight} + ${scrollBarGap}px)` : `${scrollBarGap}px`
}
// If previousAppBgWrapeprRight is already set, don't set it again.
if (previousAppBgWrapperRight === undefined) {
const appBgWrapperEl = document.getElementById('app_bg_wrapper')
previousAppBgWrapperRight = window.getComputedStyle(appBgWrapperEl).getPropertyValue('right')
appBgWrapperEl.style.right = previousAppBgWrapperRight ? `calc(${previousAppBgWrapperRight} + ${scrollBarGap}px)` : `${scrollBarGap}px`
}
document.body.classList.add('scroll-locked')
}
// If previousAppBgWrapeprRight is already set, don't set it again.
if (previousAppBgWrapperRight === undefined) {
const appBgWrapperEl = document.getElementById('app_bg_wrapper')
previousAppBgWrapperRight = window.getComputedStyle(appBgWrapperEl).getPropertyValue('right')
appBgWrapperEl.style.right = previousAppBgWrapperRight ? `calc(${previousAppBgWrapperRight} + ${scrollBarGap}px)` : `${scrollBarGap}px`
}
document.body.classList.add('scroll-locked')
})
}
const enableBodyScroll = (el) => {
lockerEls.delete(el)
setTimeout(() => {
if (previousNavPaddingRight !== undefined) {
document.getElementById('nav').style.paddingRight = previousNavPaddingRight
// Restore previousNavPaddingRight to undefined so disableBodyScroll knows it can be set again.
previousNavPaddingRight = undefined
if (lockerEls.size === 0) {
if (previousNavPaddingRight !== undefined) {
document.getElementById('nav').style.paddingRight = previousNavPaddingRight
// Restore previousNavPaddingRight to undefined so disableBodyScroll knows it can be set again.
previousNavPaddingRight = undefined
}
if (previousAppBgWrapperRight !== undefined) {
document.getElementById('app_bg_wrapper').style.right = previousAppBgWrapperRight
// Restore previousAppBgWrapperRight to undefined so disableBodyScroll knows it can be set again.
previousAppBgWrapperRight = undefined
}
document.body.classList.remove('scroll-locked')
}
if (previousAppBgWrapperRight !== undefined) {
document.getElementById('app_bg_wrapper').style.right = previousAppBgWrapperRight
// Restore previousAppBgWrapperRight to undefined so disableBodyScroll knows it can be set again.
previousAppBgWrapperRight = undefined
}
document.body.classList.remove('scroll-locked')
})
bodyScrollLock.enableBodyScroll(el)
}

View file

@ -635,6 +635,8 @@
"return_home": "Return to the home page",
"not_found": "We couldn't find that email or username.",
"too_many_requests": "You have reached the limit of attempts, try again later.",
"password_reset_disabled": "Password reset is disabled. Please contact your instance administrator."
"password_reset_disabled": "Password reset is disabled. Please contact your instance administrator.",
"password_reset_required": "You must reset your password to log in.",
"password_reset_required_but_mailer_is_disabled": "You must reset your password, but password reset is disabled. Please contact your instance administrator."
}
}