Compare commits
5 commits
Author | SHA1 | Date | |
---|---|---|---|
715e13d1cc | |||
e449b0c897 | |||
8a8fc11219 | |||
ccac52d046 | |||
8a49d12b8f |
9 changed files with 169 additions and 68 deletions
|
@ -20,7 +20,7 @@ body {
|
||||||
color: var(--text, $fallback--text);
|
color: var(--text, $fallback--text);
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
overscroll-behavior-y: none;
|
overscroll-behavior-y: auto;
|
||||||
overflow-x: clip;
|
overflow-x: clip;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
|
import Popover from '../popover/popover.vue'
|
||||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { faRetweet } from '@fortawesome/free-solid-svg-icons'
|
import { faRetweet, faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
library.add(faRetweet)
|
library.add(faRetweet, faTrash)
|
||||||
|
|
||||||
const RetweetButton = {
|
const RetweetButton = {
|
||||||
props: ['status', 'loggedIn', 'visibility'],
|
props: ['status', 'loggedIn', 'visibility'],
|
||||||
components: {
|
components: {
|
||||||
|
Popover,
|
||||||
ConfirmModal
|
ConfirmModal
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
|
@ -16,16 +18,16 @@ const RetweetButton = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
retweet () {
|
retweet (visibility) {
|
||||||
if (!this.status.repeated && this.shouldConfirmRepeat) {
|
if (!this.status.repeated && this.shouldConfirmRepeat) {
|
||||||
this.showConfirmDialog()
|
this.showConfirmDialog()
|
||||||
} else {
|
} else {
|
||||||
this.doRetweet()
|
this.doRetweet(visibility)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
doRetweet () {
|
doRetweet (visibility) {
|
||||||
if (!this.status.repeated) {
|
if (!this.status.repeated) {
|
||||||
this.$store.dispatch('retweet', { id: this.status.id })
|
this.$store.dispatch('retweet', { id: this.status.id, visibility: visibility })
|
||||||
} else {
|
} else {
|
||||||
this.$store.dispatch('unretweet', { id: this.status.id })
|
this.$store.dispatch('unretweet', { id: this.status.id })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,76 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="RetweetButton">
|
<!-- TODO settings the offset like this feels like a hack -->
|
||||||
|
<Popover
|
||||||
|
class="RetweetButton"
|
||||||
|
trigger="click"
|
||||||
|
placement="bottom"
|
||||||
|
:offset="{ y: 22 }"
|
||||||
|
:bound-to="{ x: 'container' }"
|
||||||
|
remove-padding
|
||||||
|
>
|
||||||
|
<template v-slot:content="{close}">
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<template v-if="status.repeated">
|
||||||
|
<button
|
||||||
|
v-if="status.repeated"
|
||||||
|
class="button-default dropdown-item dropdown-item-icon"
|
||||||
|
@click.prevent="retweet()"
|
||||||
|
>
|
||||||
|
<!-- TODO: i18n -->
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
icon="trash"
|
||||||
|
/><span>Undo repeat</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<!-- TODO: don't show button that would increase post visibility -->
|
||||||
|
<button
|
||||||
|
class="button-default dropdown-item dropdown-item-icon"
|
||||||
|
@click.prevent="retweet('public')"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
icon="globe"
|
||||||
|
/><span>{{ $t("general.scope_in_timeline.public") }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button-default dropdown-item dropdown-item-icon"
|
||||||
|
@click.prevent="retweet('unlisted')"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
icon="lock-open"
|
||||||
|
/><span>{{ $t("general.scope_in_timeline.unlisted") }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button-default dropdown-item dropdown-item-icon"
|
||||||
|
@click.prevent="retweet('private')"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
icon="lock"
|
||||||
|
/><span>{{ $t("general.scope_in_timeline.private") }}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button-default dropdown-item dropdown-item-icon"
|
||||||
|
@click.prevent="retweet('local')"
|
||||||
|
>
|
||||||
|
<!-- TODO: "general.scope_in_timeline.local" feels too long for the popover -->
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
icon="users"
|
||||||
|
/><span>Local</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-slot:trigger>
|
||||||
<button
|
<button
|
||||||
v-if="(visibility !== 'private' || isOwn) && visibility !== 'direct' && loggedIn"
|
v-if="(visibility !== 'private' || isOwn) && visibility !== 'direct' && loggedIn"
|
||||||
class="button-unstyled interactive"
|
class="button-unstyled popover-trigger"
|
||||||
:class="status.repeated && '-repeated'"
|
:class="status.repeated && '-repeated'"
|
||||||
:title="$t('tool_tip.repeat')"
|
:title="$t('tool_tip.repeat')"
|
||||||
@click.prevent="retweet()"
|
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
class="fa-scale-110 fa-old-padding"
|
class="fa-scale-110 fa-old-padding"
|
||||||
|
@ -40,6 +105,7 @@
|
||||||
{{ status.repeat_num }}
|
{{ status.repeat_num }}
|
||||||
</span>
|
</span>
|
||||||
<teleport to="#modal">
|
<teleport to="#modal">
|
||||||
|
<!-- TODO does this work for all? -->
|
||||||
<confirm-modal
|
<confirm-modal
|
||||||
v-if="showingConfirmDialog"
|
v-if="showingConfirmDialog"
|
||||||
:title="$t('status.repeat_confirm_title')"
|
:title="$t('status.repeat_confirm_title')"
|
||||||
|
@ -51,7 +117,8 @@
|
||||||
{{ $t('status.repeat_confirm') }}
|
{{ $t('status.repeat_confirm') }}
|
||||||
</confirm-modal>
|
</confirm-modal>
|
||||||
</teleport>
|
</teleport>
|
||||||
</div>
|
</template>
|
||||||
|
</Popover>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./retweet_button.js"></script>
|
<script src="./retweet_button.js"></script>
|
||||||
|
@ -72,7 +139,7 @@
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.interactive {
|
.popover-trigger {
|
||||||
.svg-inline--fa {
|
.svg-inline--fa {
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
animation: unset;
|
animation: unset;
|
||||||
|
@ -80,6 +147,7 @@
|
||||||
animation-duration: 0.6s;
|
animation-duration: 0.6s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO: don't turn green on hover because it keeps being focused on mobile when the popover is open. Instead make it while(?) like in extra_buttons and only green once it's been repeated. */
|
||||||
&:hover .svg-inline--fa,
|
&:hover .svg-inline--fa,
|
||||||
&.-repeated .svg-inline--fa {
|
&.-repeated .svg-inline--fa {
|
||||||
color: $fallback--cGreen;
|
color: $fallback--cGreen;
|
||||||
|
|
|
@ -215,6 +215,7 @@ const Status = {
|
||||||
retweeter () { return this.statusoid.user.name || this.statusoid.user.screen_name_ui },
|
retweeter () { return this.statusoid.user.name || this.statusoid.user.screen_name_ui },
|
||||||
retweeterHtml () { return this.statusoid.user.name },
|
retweeterHtml () { return this.statusoid.user.name },
|
||||||
retweeterProfileLink () { return this.generateUserProfileLink(this.statusoid.user.id, this.statusoid.user.screen_name) },
|
retweeterProfileLink () { return this.generateUserProfileLink(this.statusoid.user.id, this.statusoid.user.screen_name) },
|
||||||
|
retweeterVisibility () { return this.statusoid.visibility },
|
||||||
status () {
|
status () {
|
||||||
if (this.retweet) {
|
if (this.retweet) {
|
||||||
return this.statusoid.retweeted_status
|
return this.statusoid.retweeted_status
|
||||||
|
@ -440,6 +441,9 @@ const Status = {
|
||||||
visibilityLocalized () {
|
visibilityLocalized () {
|
||||||
return this.$i18n.t('general.scope_in_timeline.' + this.status.visibility)
|
return this.$i18n.t('general.scope_in_timeline.' + this.status.visibility)
|
||||||
},
|
},
|
||||||
|
retweeterVisibilityLocalized () {
|
||||||
|
return this.$i18n.t('general.scope_in_timeline.' + this.statusoid.visibility)
|
||||||
|
},
|
||||||
isEdited () {
|
isEdited () {
|
||||||
return this.status.edited_at !== null
|
return this.status.edited_at !== null
|
||||||
},
|
},
|
||||||
|
|
|
@ -42,10 +42,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: var(--status-margin, $status-margin);
|
padding: var(--status-margin, $status-margin);
|
||||||
|
|
||||||
.content {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,18 @@
|
||||||
/>
|
/>
|
||||||
{{ $t('timeline.repeated') }}
|
{{ $t('timeline.repeated') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="retweeterVisibility"
|
||||||
|
class="visibility-icon"
|
||||||
|
:title="retweeterVisibilityLocalized"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
class="fa-scale-110"
|
||||||
|
:icon="visibilityIcon(retweeterVisibility)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
-webkit-mask-composite: xor;
|
-webkit-mask-composite: xor;
|
||||||
mask-composite: exclude;
|
mask-composite: exclude;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
background-position: top;
|
||||||
mask-size: 100% 60%;
|
mask-size: 100% 60%;
|
||||||
border-top-left-radius: calc(var(--panelRadius) - 1px);
|
border-top-left-radius: calc(var(--panelRadius) - 1px);
|
||||||
border-top-right-radius: calc(var(--panelRadius) - 1px);
|
border-top-right-radius: calc(var(--panelRadius) - 1px);
|
||||||
|
|
|
@ -666,7 +666,7 @@ const statuses = {
|
||||||
retweet ({ rootState, commit }, status) {
|
retweet ({ rootState, commit }, status) {
|
||||||
// Optimistic retweeting...
|
// Optimistic retweeting...
|
||||||
commit('setRetweeted', { status, value: true })
|
commit('setRetweeted', { status, value: true })
|
||||||
rootState.api.backendInteractor.retweet({ id: status.id })
|
rootState.api.backendInteractor.retweet({ id: status.id, visibility: status.visibility })
|
||||||
.then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser }))
|
.then(status => commit('setRetweetedConfirm', { status: status.retweeted_status, user: rootState.users.currentUser }))
|
||||||
},
|
},
|
||||||
unretweet ({ rootState, commit }, status) {
|
unretweet ({ rootState, commit }, status) {
|
||||||
|
|
|
@ -125,22 +125,32 @@ let fetch = (url, options) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const promisedRequest = ({ method, url, params, payload, credentials, headers = {} }) => {
|
const promisedRequest = ({ method, url, params, payload, credentials, headers = {} }) => {
|
||||||
|
const payloadIsFormData = payload instanceof FormData
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...headers
|
...headers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!payloadIsFormData) {
|
||||||
|
// only set content type if payload is not form data
|
||||||
|
// if it is unset the content type will be automatically set to multipart/form-data
|
||||||
|
options.headers['Content-Type'] = 'application/json'
|
||||||
|
}
|
||||||
if (params) {
|
if (params) {
|
||||||
url += '?' + Object.entries(params)
|
url += '?' + Object.entries(params)
|
||||||
.map(([key, value]) => encodeURIComponent(key) + '=' + encodeURIComponent(value))
|
.map(([key, value]) => encodeURIComponent(key) + '=' + encodeURIComponent(value))
|
||||||
.join('&')
|
.join('&')
|
||||||
}
|
}
|
||||||
if (payload) {
|
if (payload) {
|
||||||
|
if (payloadIsFormData) {
|
||||||
|
options.body = payload
|
||||||
|
} else {
|
||||||
options.body = JSON.stringify(payload)
|
options.body = JSON.stringify(payload)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (credentials) {
|
if (credentials) {
|
||||||
options.headers = {
|
options.headers = {
|
||||||
...options.headers,
|
...options.headers,
|
||||||
|
@ -822,8 +832,16 @@ const unfavorite = ({ id, credentials }) => {
|
||||||
.then((data) => parseStatus(data))
|
.then((data) => parseStatus(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
const retweet = ({ id, credentials }) => {
|
const retweet = ({ id, credentials, visibility }) => {
|
||||||
return promisedRequest({ url: MASTODON_RETWEET_URL(id), method: 'POST', credentials })
|
const form = new FormData()
|
||||||
|
if (visibility) form.append('visibility', visibility)
|
||||||
|
|
||||||
|
return promisedRequest({
|
||||||
|
url: MASTODON_RETWEET_URL(id),
|
||||||
|
payload: form,
|
||||||
|
method: 'POST',
|
||||||
|
credentials
|
||||||
|
})
|
||||||
.then((data) => parseStatus(data))
|
.then((data) => parseStatus(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue