aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
authorHenry Jameson <me@hjkos.com>2019-10-29 09:36:16 +0200
committerHenry Jameson <me@hjkos.com>2019-10-29 09:36:16 +0200
commitb66564a30dfadfdd2b216072931576c6c4dcbd61 (patch)
treede26f9a87bcaa6d8e576cf33165247f5c66b61af /src/components
parentc79b2501d275d8d022c6a9a2810211f606f14cde (diff)
parent42f428d90e87315107dd20975548a5bd63dc2d53 (diff)
Merge remote-tracking branch 'upstream/develop' into settings-refactor
* upstream/develop: (89 commits) remove needless ref show preview popover when hover numbered replies refactor conditions do not make too many nested div add fetchStatus action refactor status loading logic split status preview popover into a separate component uninstall mobile-detect library listen both events minor css fix restrict distance at top side only set different trigger event in desktop and mobile by default fix eslint warnings fix popper go behind the top bar migrate Popper to v-popover fix popper go behind the top bar fix eslint warnings reset font-size to normal text size using rem use top placement by default hide status preview popper when hover popper content ...
Diffstat (limited to 'src/components')
-rw-r--r--src/components/account_actions/account_actions.js35
-rw-r--r--src/components/account_actions/account_actions.vue93
-rw-r--r--src/components/attachment/attachment.js8
-rw-r--r--src/components/attachment/attachment.vue1
-rw-r--r--src/components/extra_buttons/extra_buttons.vue2
-rw-r--r--src/components/follow_button/follow_button.js53
-rw-r--r--src/components/follow_button/follow_button.vue13
-rw-r--r--src/components/follow_card/follow_card.js26
-rw-r--r--src/components/follow_card/follow_card.vue35
-rw-r--r--src/components/gallery/gallery.js40
-rw-r--r--src/components/gallery/gallery.vue41
-rw-r--r--src/components/login_form/login_form.js2
-rw-r--r--src/components/media_modal/media_modal.js4
-rw-r--r--src/components/media_modal/media_modal.vue18
-rw-r--r--src/components/modal/modal.vue52
-rw-r--r--src/components/moderation_tools/moderation_tools.vue2
-rw-r--r--src/components/password_reset/password_reset.js6
-rw-r--r--src/components/password_reset/password_reset.vue16
-rw-r--r--src/components/popper/popper.scss9
-rw-r--r--src/components/post_status_modal/post_status_modal.js29
-rw-r--r--src/components/post_status_modal/post_status_modal.vue20
-rw-r--r--src/components/side_drawer/side_drawer.vue11
-rw-r--r--src/components/status/status.js29
-rw-r--r--src/components/status/status.vue153
-rw-r--r--src/components/status_popover/status_popover.js34
-rw-r--r--src/components/status_popover/status_popover.vue85
-rw-r--r--src/components/still-image/still-image.js4
-rw-r--r--src/components/user_card/user_card.js43
-rw-r--r--src/components/user_card/user_card.vue120
-rw-r--r--src/components/user_reporting_modal/user_reporting_modal.js4
-rw-r--r--src/components/user_reporting_modal/user_reporting_modal.vue12
31 files changed, 603 insertions, 397 deletions
diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js
new file mode 100644
index 00000000..204d506a
--- /dev/null
+++ b/src/components/account_actions/account_actions.js
@@ -0,0 +1,35 @@
+import ProgressButton from '../progress_button/progress_button.vue'
+
+const AccountActions = {
+ props: [
+ 'user'
+ ],
+ data () {
+ return { }
+ },
+ components: {
+ ProgressButton
+ },
+ methods: {
+ showRepeats () {
+ this.$store.dispatch('showReblogs', this.user.id)
+ },
+ hideRepeats () {
+ this.$store.dispatch('hideReblogs', this.user.id)
+ },
+ blockUser () {
+ this.$store.dispatch('blockUser', this.user.id)
+ },
+ unblockUser () {
+ this.$store.dispatch('unblockUser', this.user.id)
+ },
+ reportUser () {
+ this.$store.dispatch('openUserReportingModal', this.user.id)
+ },
+ mentionUser () {
+ this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
+ }
+ }
+}
+
+export default AccountActions
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
new file mode 100644
index 00000000..046cba93
--- /dev/null
+++ b/src/components/account_actions/account_actions.vue
@@ -0,0 +1,93 @@
+<template>
+ <div class="account-actions">
+ <v-popover
+ trigger="click"
+ class="account-tools-popover"
+ :container="false"
+ placement="bottom-end"
+ :offset="5"
+ >
+ <div slot="popover">
+ <div class="dropdown-menu">
+ <button
+ class="btn btn-default btn-block dropdown-item"
+ @click="mentionUser"
+ >
+ {{ $t('user_card.mention') }}
+ </button>
+ <template v-if="user.following">
+ <div
+ role="separator"
+ class="dropdown-divider"
+ />
+ <button
+ v-if="user.showing_reblogs"
+ class="btn btn-default dropdown-item"
+ @click="hideRepeats"
+ >
+ {{ $t('user_card.hide_repeats') }}
+ </button>
+ <button
+ v-if="!user.showing_reblogs"
+ class="btn btn-default dropdown-item"
+ @click="showRepeats"
+ >
+ {{ $t('user_card.show_repeats') }}
+ </button>
+ </template>
+ <div
+ role="separator"
+ class="dropdown-divider"
+ />
+ <button
+ v-if="user.statusnet_blocking"
+ class="btn btn-default btn-block dropdown-item"
+ @click="unblockUser"
+ >
+ {{ $t('user_card.unblock') }}
+ </button>
+ <button
+ v-else
+ class="btn btn-default btn-block dropdown-item"
+ @click="blockUser"
+ >
+ {{ $t('user_card.block') }}
+ </button>
+ <button
+ class="btn btn-default btn-block dropdown-item"
+ @click="reportUser"
+ >
+ {{ $t('user_card.report') }}
+ </button>
+ </div>
+ </div>
+ <div class="btn btn-default ellipsis-button">
+ <i class="icon-ellipsis trigger-button" />
+ </div>
+ </v-popover>
+ </div>
+</template>
+
+<script src="./account_actions.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+@import '../popper/popper.scss';
+.account-actions {
+ margin: 0 .8em;
+}
+
+.account-actions button.dropdown-item {
+ margin-left: 0;
+}
+.account-actions .trigger-button {
+ color: $fallback--lightText;
+ color: var(--lightText, $fallback--lightText);
+ opacity: .8;
+ cursor: pointer;
+ &:hover {
+ color: $fallback--text;
+ color: var(--text, $fallback--text);
+ }
+}
+</style>
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
index 21cd0351..06b496b0 100644
--- a/src/components/attachment/attachment.js
+++ b/src/components/attachment/attachment.js
@@ -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 })
}
}
}
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
index af16e302..0748b2f0 100644
--- a/src/components/attachment/attachment.vue
+++ b/src/components/attachment/attachment.vue
@@ -58,6 +58,7 @@
:referrerpolicy="referrerpolicy"
:mimetype="attachment.mimetype"
:src="attachment.large_thumb_url || attachment.url"
+ :image-load-handler="onImageLoad"
/>
</a>
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index 6781a4f8..746f1c91 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -4,8 +4,6 @@
trigger="click"
placement="top"
class="extra-button-popover"
- :offset="5"
- :container="false"
>
<div slot="popover">
<div class="dropdown-menu">
diff --git a/src/components/follow_button/follow_button.js b/src/components/follow_button/follow_button.js
new file mode 100644
index 00000000..12da2645
--- /dev/null
+++ b/src/components/follow_button/follow_button.js
@@ -0,0 +1,53 @@
+import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
+export default {
+ props: ['user', 'labelFollowing', 'buttonClass'],
+ data () {
+ return {
+ inProgress: false
+ }
+ },
+ computed: {
+ isPressed () {
+ return this.inProgress || this.user.following
+ },
+ title () {
+ if (this.inProgress || this.user.following) {
+ return this.$t('user_card.follow_unfollow')
+ } else if (this.user.requested) {
+ return this.$t('user_card.follow_again')
+ } else {
+ return this.$t('user_card.follow')
+ }
+ },
+ label () {
+ if (this.inProgress) {
+ return this.$t('user_card.follow_progress')
+ } else if (this.user.following) {
+ return this.labelFollowing || this.$t('user_card.following')
+ } else if (this.user.requested) {
+ return this.$t('user_card.follow_sent')
+ } else {
+ return this.$t('user_card.follow')
+ }
+ }
+ },
+ methods: {
+ onClick () {
+ this.user.following ? this.unfollow() : this.follow()
+ },
+ follow () {
+ this.inProgress = true
+ requestFollow(this.user, this.$store).then(() => {
+ this.inProgress = false
+ })
+ },
+ unfollow () {
+ const store = this.$store
+ this.inProgress = true
+ requestUnfollow(this.user, store).then(() => {
+ this.inProgress = false
+ store.commit('removeStatus', { timeline: 'friends', userId: this.user.id })
+ })
+ }
+ }
+}
diff --git a/src/components/follow_button/follow_button.vue b/src/components/follow_button/follow_button.vue
new file mode 100644
index 00000000..f0cbb94b
--- /dev/null
+++ b/src/components/follow_button/follow_button.vue
@@ -0,0 +1,13 @@
+<template>
+ <button
+ class="btn btn-default follow-button"
+ :class="{ pressed: isPressed }"
+ :disabled="inProgress"
+ :title="title"
+ @click="onClick"
+ >
+ {{ label }}
+ </button>
+</template>
+
+<script src="./follow_button.js"></script>
diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js
index dc4a0d41..aefd609e 100644
--- a/src/components/follow_card/follow_card.js
+++ b/src/components/follow_card/follow_card.js
@@ -1,21 +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,
- requestSent: false
- }
- },
components: {
BasicUserCard,
- RemoteFollow
+ RemoteFollow,
+ FollowButton
},
computed: {
isMe () {
@@ -24,21 +19,6 @@ const FollowCard = {
loggedIn () {
return this.$store.state.users.currentUser
}
- },
- methods: {
- followUser () {
- this.inProgress = true
- requestFollow(this.user, this.$store).then(({ sent }) => {
- this.inProgress = false
- this.requestSent = sent
- })
- },
- unfollowUser () {
- this.inProgress = true
- requestUnfollow(this.user, this.$store).then(() => {
- this.inProgress = false
- })
- }
}
}
diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue
index 310fe843..81e6e6dc 100644
--- a/src/components/follow_card/follow_card.vue
+++ b/src/components/follow_card/follow_card.vue
@@ -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="requestSent ? $t('user_card.follow_again') : ''"
- @click="followUser"
- >
- <template v-if="inProgress">
- {{ $t('user_card.follow_progress') }}
- </template>
- <template v-else-if="requestSent">
- {{ $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>
diff --git a/src/components/gallery/gallery.js b/src/components/gallery/gallery.js
index 96ac1b93..f856fd0a 100644
--- a/src/components/gallery/gallery.js
+++ b/src/components/gallery/gallery.js
@@ -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'
],
- components: { Attachment },
- mounted () {
- this.resize()
- window.addEventListener('resize', this.resize)
- },
- destroyed () {
- window.removeEventListener('resize', this.resize)
+ data () {
+ return {
+ sizes: {}
+ }
},
+ components: { Attachment },
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
}
}
}
diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue
index 6169d294..7abc2161 100644
--- a/src/components/gallery/gallery.vue
+++ b/src/components/gallery/gallery.vue
@@ -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 {
diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js
index 10f52fe2..0b574a04 100644
--- a/src/components/login_form/login_form.js
+++ b/src/components/login_form/login_form.js
@@ -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()
diff --git a/src/components/media_modal/media_modal.js b/src/components/media_modal/media_modal.js
index 992d7129..4832abda 100644
--- a/src/components/media_modal/media_modal.js
+++ b/src/components/media_modal/media_modal.js
@@ -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 () {
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 06ced5a1..2597f4e3 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -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>
diff --git a/src/components/modal/modal.vue b/src/components/modal/modal.vue
new file mode 100644
index 00000000..cee24241
--- /dev/null
+++ b/src/components/modal/modal.vue
@@ -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>
diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue
index d97ca3aa..006d6373 100644
--- a/src/components/moderation_tools/moderation_tools.vue
+++ b/src/components/moderation_tools/moderation_tools.vue
@@ -3,9 +3,7 @@
<v-popover
trigger="click"
class="moderation-tools-popover"
- :container="false"
placement="bottom-end"
- :offset="5"
@show="showDropDown = true"
@hide="showDropDown = false"
>
diff --git a/src/components/password_reset/password_reset.js b/src/components/password_reset/password_reset.js
index fa71e07a..62e74e30 100644
--- a/src/components/password_reset/password_reset.js
+++ b/src/components/password_reset/password_reset.js
@@ -25,6 +25,12 @@ const passwordReset = {
this.$router.push({ name: 'root' })
}
},
+ props: {
+ passwordResetRequested: {
+ default: false,
+ type: Boolean
+ }
+ },
methods: {
dismissError () {
this.error = null
diff --git a/src/components/password_reset/password_reset.vue b/src/components/password_reset/password_reset.vue
index 00474e95..713c9dce 100644
--- a/src/components/password_reset/password_reset.vue
+++ b/src/components/password_reset/password_reset.vue
@@ -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;
}
diff --git a/src/components/popper/popper.scss b/src/components/popper/popper.scss
index 279b01be..06daa871 100644
--- a/src/components/popper/popper.scss
+++ b/src/components/popper/popper.scss
@@ -20,7 +20,6 @@
margin: 5px;
border-color: $fallback--bg;
border-color: var(--bg, $fallback--bg);
- z-index: 1;
}
&[x-placement^="top"] {
@@ -31,7 +30,7 @@
border-left-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
- bottom: -5px;
+ bottom: -4px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
@@ -46,7 +45,7 @@
border-left-color: transparent !important;
border-right-color: transparent !important;
border-top-color: transparent !important;
- top: -5px;
+ top: -4px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
@@ -61,7 +60,7 @@
border-left-color: transparent !important;
border-top-color: transparent !important;
border-bottom-color: transparent !important;
- left: -5px;
+ left: -4px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
@@ -76,7 +75,7 @@
border-top-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
- right: -5px;
+ right: -4px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
diff --git a/src/components/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js
index 1033ba11..b44354db 100644
--- a/src/components/post_status_modal/post_status_modal.js
+++ b/src/components/post_status_modal/post_status_modal.js
@@ -1,24 +1,43 @@
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 {
+ resettingForm: false
+ }
},
computed: {
isLoggedIn () {
return !!this.$store.state.users.currentUser
},
- isOpen () {
- return this.isLoggedIn && this.$store.state.postStatus.modalActivated
+ modalActivated () {
+ return this.$store.state.postStatus.modalActivated
+ },
+ isFormVisible () {
+ return this.isLoggedIn && !this.resettingForm && this.modalActivated
},
params () {
return this.$store.state.postStatus.params || {}
}
},
watch: {
- isOpen (val) {
+ params (newVal, oldVal) {
+ if (get(newVal, 'repliedUser.id') !== get(oldVal, 'repliedUser.id')) {
+ this.resettingForm = true
+ this.$nextTick(() => {
+ this.resettingForm = false
+ })
+ }
+ },
+ isFormVisible (val) {
if (val) {
- this.$nextTick(() => this.$el.querySelector('textarea').focus())
+ this.$nextTick(() => this.$el && this.$el.querySelector('textarea').focus())
}
}
},
diff --git a/src/components/post_status_modal/post_status_modal.vue b/src/components/post_status_modal/post_status_modal.vue
index 3f8eec69..dbcd321e 100644
--- a/src/components/post_status_modal/post_status_modal.vue
+++ b/src/components/post_status_modal/post_status_modal.vue
@@ -1,13 +1,11 @@
<template>
- <div
- v-if="isOpen"
- class="post-form-modal-view modal-view"
- @click="closeModal"
+ <Modal
+ v-if="isLoggedIn && !resettingForm"
+ :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>
@@ -17,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;
}
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 5b2d4473..214b8e0c 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -123,6 +123,17 @@
</router-link>
</li>
<li
+ v-if="currentUser && currentUser.role === 'admin'"
+ @click="toggleDrawer"
+ >
+ <a
+ href="/pleroma/admin/#/login-pleroma"
+ target="_blank"
+ >
+ {{ $t("nav.administration") }}
+ </a>
+ </li>
+ <li
v-if="currentUser"
@click="toggleDrawer"
>
diff --git a/src/components/status/status.js b/src/components/status/status.js
index fa46debf..4fbd5ac3 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -10,11 +10,12 @@ import Gallery from '../gallery/gallery.vue'
import LinkPreview from '../link-preview/link-preview.vue'
import AvatarList from '../avatar_list/avatar_list.vue'
import Timeago from '../timeago/timeago.vue'
+import StatusPopover from '../status_popover/status_popover.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import fileType from 'src/services/file_type/file_type.service'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
-import { filter, find, unescape, uniqBy } from 'lodash'
+import { filter, unescape, uniqBy } from 'lodash'
import { mapGetters } from 'vuex'
const Status = {
@@ -38,8 +39,6 @@ const Status = {
replying: false,
unmuted: false,
userExpanded: false,
- preview: null,
- showPreview: false,
showingTall: this.inConversation && this.focused,
showingLongSubject: false,
error: null,
@@ -293,7 +292,8 @@ const Status = {
Gallery,
LinkPreview,
AvatarList,
- Timeago
+ Timeago,
+ StatusPopover
},
methods: {
visibilityIcon (visibility) {
@@ -368,27 +368,6 @@ const Status = {
this.expandingSubject = true
}
},
- replyEnter (id, event) {
- this.showPreview = true
- const targetId = id
- const statuses = this.$store.state.statuses.allStatuses
-
- if (!this.preview) {
- // if we have the status somewhere already
- this.preview = find(statuses, { 'id': targetId })
- // or if we have to fetch it
- if (!this.preview) {
- this.$store.state.api.backendInteractor.fetchStatus({ id }).then((status) => {
- this.preview = status
- })
- }
- } else if (this.preview.id !== targetId) {
- this.preview = find(statuses, { 'id': targetId })
- }
- },
- replyLeave () {
- this.showPreview = false
- },
generateUserProfileLink (id, name) {
return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames)
},
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 93f37a49..65778b2e 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -174,20 +174,26 @@
v-if="isReply"
class="reply-to-and-accountname"
>
- <a
+ <StatusPopover
+ v-if="!isPreview"
+ :status-id="status.in_reply_to_status_id"
+ >
+ <a
+ class="reply-to"
+ href="#"
+ :aria-label="$t('tool_tip.reply')"
+ @click.prevent="gotoOriginal(status.in_reply_to_status_id)"
+ >
+ <i class="button-icon icon-reply" />
+ <span class="faint-link reply-to-text">{{ $t('status.reply_to') }}</span>
+ </a>
+ </StatusPopover>
+ <span
+ v-else
class="reply-to"
- href="#"
- :aria-label="$t('tool_tip.reply')"
- @click.prevent="gotoOriginal(status.in_reply_to_status_id)"
- @mouseenter.prevent.stop="replyEnter(status.in_reply_to_status_id, $event)"
- @mouseleave.prevent.stop="replyLeave()"
>
- <i
- v-if="!isPreview"
- class="button-icon icon-reply"
- />
- <span class="faint-link reply-to-text">{{ $t('status.reply_to') }}</span>
- </a>
+ <span class="reply-to-text">{{ $t('status.reply_to') }}</span>
+ </span>
<router-link :to="replyProfileLink">
{{ replyToName }}
</router-link>
@@ -199,51 +205,26 @@
</span>
</div>
<div
- v-if="inConversation && !isPreview"
+ v-if="inConversation && !isPreview && replies && replies.length"
class="replies"
>
- <span
- v-if="replies && replies.length"
- class="faint"
- >{{ $t('status.replies_list') }}</span>
- <template v-if="replies">
- <span
- v-for="reply in replies"
- :key="reply.id"
- class="reply-link faint"
- >
- <a
- href="#"
- @click.prevent="gotoOriginal(reply.id)"
- @mouseenter="replyEnter(reply.id, $event)"
- @mouseout="replyLeave()"
- >{{ reply.name }}</a>
- </span>
- </template>
+ <span class="faint">{{ $t('status.replies_list') }}</span>
+ <StatusPopover
+ v-for="reply in replies"
+ :key="reply.id"
+ :status-id="reply.id"
+ >
+ <a
+ href="#"
+ class="reply-link"
+ @click.prevent="gotoOriginal(reply.id)"
+ >{{ reply.name }}</a>
+ </StatusPopover>
</div>
</div>
</div>
<div
- v-if="showPreview"
- class="status-preview-container"
- >
- <status
- v-if="preview"
- class="status-preview"
- :is-preview="true"
- :statusoid="preview"
- :compact="true"
- />
- <div
- v-else
- class="status-preview status-preview-loading"
- >
- <i class="icon-spin4 animate-spin" />
- </div>
- </div>
-
- <div
v-if="longSubject"
class="status-content-wrapper"
:class="{ 'tall-status': !showingLongSubject }"
@@ -439,18 +420,6 @@ $status-margin: 0.75em;
min-width: 0;
}
-.status-preview.status-el {
- border-style: solid;
- border-width: 1px;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
-}
-
-.status-preview-container {
- position: relative;
- max-width: 100%;
-}
-
.status-pin {
padding: $status-margin $status-margin 0;
display: flex;
@@ -458,44 +427,6 @@ $status-margin: 0.75em;
justify-content: flex-end;
}
-.status-preview {
- position: absolute;
- max-width: 95%;
- display: flex;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- border-style: solid;
- border-width: 1px;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
- box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
- box-shadow: var(--popupShadow);
- margin-top: 0.25em;
- margin-left: 0.5em;
- z-index: 50;
-
- .status {
- flex: 1;
- border: 0;
- min-width: 15em;
- }
-}
-
-.status-preview-loading {
- display: block;
- min-width: 15em;
- padding: 1em;
- text-align: center;
- border-width: 1px;
- border-style: solid;
-
- i {
- font-size: 2em;
- }
-}
-
.media-left {
margin-right: $status-margin;
}
@@ -553,11 +484,6 @@ $status-margin: 0.75em;
flex-basis: 100%;
margin-bottom: 0.5em;
- a {
- display: inline-block;
- word-break: break-all;
- }
-
small {
font-weight: lighter;
}
@@ -568,6 +494,11 @@ $status-margin: 0.75em;
justify-content: space-between;
line-height: 18px;
+ a {
+ display: inline-block;
+ word-break: break-all;
+ }
+
.name-and-account-name {
display: flex;
min-width: 0;
@@ -600,6 +531,7 @@ $status-margin: 0.75em;
}
.heading-reply-row {
+ position: relative;
align-content: baseline;
font-size: 12px;
line-height: 18px;
@@ -608,11 +540,13 @@ $status-margin: 0.75em;
flex-wrap: wrap;
align-items: stretch;
- a {
+ > .reply-to-and-accountname > a {
max-width: 100%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
+ display: inline-block;
+ word-break: break-all;
}
}
@@ -639,6 +573,8 @@ $status-margin: 0.75em;
overflow: hidden;
text-overflow: ellipsis;
margin: 0 0.4em 0 0.2em;
+ color: $fallback--faint;
+ color: var(--faint, $fallback--faint);
}
.replies-separator {
@@ -840,6 +776,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;
}
}
diff --git a/src/components/status_popover/status_popover.js b/src/components/status_popover/status_popover.js
new file mode 100644
index 00000000..19f16bd9
--- /dev/null
+++ b/src/components/status_popover/status_popover.js
@@ -0,0 +1,34 @@
+import { find } from 'lodash'
+
+const StatusPopover = {
+ name: 'StatusPopover',
+ props: [
+ 'statusId'
+ ],
+ data () {
+ return {
+ popperOptions: {
+ modifiers: {
+ preventOverflow: { padding: { top: 50 }, boundariesElement: 'viewport' }
+ }
+ }
+ }
+ },
+ computed: {
+ status () {
+ return find(this.$store.state.statuses.allStatuses, { id: this.statusId })
+ }
+ },
+ components: {
+ Status: () => import('../status/status.vue')
+ },
+ methods: {
+ enter () {
+ if (!this.status) {
+ this.$store.dispatch('fetchStatus', this.statusId)
+ }
+ }
+ }
+}
+
+export default StatusPopover
diff --git a/src/components/status_popover/status_popover.vue b/src/components/status_popover/status_popover.vue
new file mode 100644
index 00000000..eacf4c06
--- /dev/null
+++ b/src/components/status_popover/status_popover.vue
@@ -0,0 +1,85 @@
+<template>
+ <v-popover
+ popover-class="status-popover"
+ placement="top-start"
+ :popper-options="popperOptions"
+ @show="enter()"
+ >
+ <template slot="popover">
+ <Status
+ v-if="status"
+ :is-preview="true"
+ :statusoid="status"
+ :compact="true"
+ />
+ <div
+ v-else
+ class="status-preview-loading"
+ >
+ <i class="icon-spin4 animate-spin" />
+ </div>
+ </template>
+
+ <slot />
+ </v-popover>
+</template>
+
+<script src="./status_popover.js" ></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.tooltip.popover.status-popover {
+ font-size: 1rem;
+ min-width: 15em;
+ max-width: 95%;
+ margin-left: 0.5em;
+
+ .popover-inner {
+ border-color: $fallback--border;
+ border-color: var(--border, $fallback--border);
+ border-style: solid;
+ border-width: 1px;
+ border-radius: $fallback--tooltipRadius;
+ border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+ box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
+ box-shadow: var(--popupShadow);
+ }
+
+ .popover-arrow::before {
+ position: absolute;
+ content: '';
+ left: -7px;
+ border: solid 7px transparent;
+ z-index: -1;
+ }
+
+ &[x-placement^="bottom-start"] .popover-arrow::before {
+ top: -2px;
+ border-top-width: 0;
+ border-bottom-color: $fallback--border;
+ border-bottom-color: var(--border, $fallback--border);
+ }
+
+ &[x-placement^="top-start"] .popover-arrow::before {
+ bottom: -2px;
+ border-bottom-width: 0;
+ border-top-color: $fallback--border;
+ border-top-color: var(--border, $fallback--border);
+ }
+
+ .status-el.status-el {
+ border: none;
+ }
+
+ .status-preview-loading {
+ padding: 1em;
+ text-align: center;
+
+ i {
+ font-size: 2em;
+ }
+ }
+}
+
+</style>
diff --git a/src/components/still-image/still-image.js b/src/components/still-image/still-image.js
index a5559d5d..e48fef47 100644
--- a/src/components/still-image/still-image.js
+++ b/src/components/still-image/still-image.js
@@ -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
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index c84afe77..cc8a1ed6 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -1,14 +1,17 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import RemoteFollow from '../remote_follow/remote_follow.vue'
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 { hex2rgb } from '../../services/color_convert/color_convert.js'
-import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { mapGetters } from 'vuex'
export default {
- props: [ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar' ],
+ props: [
+ 'user', 'switcher', 'selected', 'hideBio', 'rounded', 'bordered', 'allowZoomingAvatar'
+ ],
data () {
return {
followRequestInProgress: false,
@@ -96,30 +99,11 @@ export default {
UserAvatar,
RemoteFollow,
ModerationTools,
- ProgressButton
+ AccountActions,
+ ProgressButton,
+ FollowButton
},
methods: {
- followUser () {
- const store = this.$store
- this.followRequestInProgress = true
- requestFollow(this.user, store).then(() => {
- this.followRequestInProgress = false
- })
- },
- unfollowUser () {
- const store = this.$store
- this.followRequestInProgress = true
- requestUnfollow(this.user, store).then(() => {
- this.followRequestInProgress = false
- store.commit('removeStatus', { timeline: 'friends', userId: this.user.id })
- })
- },
- blockUser () {
- this.$store.dispatch('blockUser', this.user.id)
- },
- unblockUser () {
- this.$store.dispatch('unblockUser', this.user.id)
- },
muteUser () {
this.$store.dispatch('muteUser', this.user.id)
},
@@ -147,10 +131,10 @@ export default {
}
},
userProfileLink (user) {
- return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
- },
- reportUser () {
- this.$store.dispatch('openUserReportingModal', this.user.id)
+ return generateProfileLink(
+ user.id, user.screen_name,
+ this.$store.state.instance.restrictedNicknames
+ )
},
zoomAvatar () {
const attachment = {
@@ -159,9 +143,6 @@ export default {
}
this.$store.dispatch('setMedia', [attachment])
this.$store.dispatch('setCurrent', attachment)
- },
- mentionUser () {
- this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
}
}
}
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 6bcc3aac..2755d89b 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -66,8 +66,11 @@
>
<i class="icon-link-ext usersettings" />
</a>
+ <AccountActions
+ v-if="isOtherUser && loggedIn"
+ :user="user"
+ />
</div>
-
<div class="bottom-line">
<router-link
class="user-screen-name"
@@ -135,72 +138,27 @@
v-if="loggedIn && isOtherUser"
class="user-interactions"
>
- <div v-if="!user.following">
- <button
- class="btn btn-default btn-block"
- :disabled="followRequestInProgress"
- :title="user.requested ? $t('user_card.follow_again') : ''"
- @click="followUser"
- >
- <template v-if="followRequestInProgress">
- {{ $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>
- </div>
- <div v-else-if="followRequestInProgress">
- <button
- class="btn btn-default btn-block pressed"
- disabled
- :title="$t('user_card.follow_unfollow')"
- @click="unfollowUser"
- >
- {{ $t('user_card.follow_progress') }}
- </button>
- </div>
- <div
- v-else
- class="btn-group"
- >
- <button
- class="btn btn-default pressed"
- :title="$t('user_card.follow_unfollow')"
- @click="unfollowUser"
- >
- {{ $t('user_card.following') }}
- </button>
- <ProgressButton
- v-if="!user.subscribed"
- class="btn btn-default"
- :click="subscribeUser"
- :title="$t('user_card.subscribe')"
- >
- <i class="icon-bell-alt" />
- </ProgressButton>
- <ProgressButton
- v-else
- class="btn btn-default pressed"
- :click="unsubscribeUser"
- :title="$t('user_card.unsubscribe')"
- >
- <i class="icon-bell-ringing-o" />
- </ProgressButton>
- </div>
-
- <div>
- <button
- class="btn btn-default btn-block"
- @click="mentionUser"
- >
- {{ $t('user_card.mention') }}
- </button>
+ <div class="btn-group">
+ <FollowButton :user="user" />
+ <template v-if="user.following">
+ <ProgressButton
+ v-if="!user.subscribed"
+ class="btn btn-default"
+ :click="subscribeUser"
+ :title="$t('user_card.subscribe')"
+ >
+ <i class="icon-bell-alt" />
+ </ProgressButton>
+ <ProgressButton
+ v-else
+ class="btn btn-default pressed"
+ :click="unsubscribeUser"
+ :title="$t('user_card.unsubscribe')"
+ >
+ <i class="icon-bell-ringing-o" />
+ </ProgressButton>
+ </template>
</div>
-
<div>
<button
v-if="user.muted"
@@ -217,33 +175,6 @@
{{ $t('user_card.mute') }}
</button>
</div>
-
- <div>
- <button
- v-if="user.statusnet_blocking"
- class="btn btn-default btn-block pressed"
- @click="unblockUser"
- >
- {{ $t('user_card.blocked') }}
- </button>
- <button
- v-else
- class="btn btn-default btn-block"
- @click="blockUser"
- >
- {{ $t('user_card.block') }}
- </button>
- </div>
-
- <div>
- <button
- class="btn btn-default btn-block"
- @click="reportUser"
- >
- {{ $t('user_card.report') }}
- </button>
- </div>
-
<ModerationTools
v-if="loggedIn.role === &quot;admin&quot;"
:user="user"
@@ -587,13 +518,12 @@
position: relative;
display: flex;
flex-flow: row wrap;
- justify-content: space-between;
margin-right: -.75em;
> * {
- flex: 1 0 0;
margin: 0 .75em .6em 0;
white-space: nowrap;
+ min-width: 95px;
}
button {
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
index 7c6ea409..833fa98a 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.js
+++ b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -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 {
diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue
index c79a3707..6ee53461 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.vue
+++ b/src/components/user_reporting_modal/user_reporting_modal.vue
@@ -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>