Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3
This commit is contained in:
commit
cc1d7f8fc9
21 changed files with 506 additions and 114 deletions
1
changelog.d/mute-dropdown.fix
Normal file
1
changelog.d/mute-dropdown.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fixed status action mute hiding itself on click
|
||||
1
changelog.d/quote-by-url.add
Normal file
1
changelog.d/quote-by-url.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add quoting by URL and in replies
|
||||
|
|
@ -73,6 +73,7 @@
|
|||
:disable-notice="true"
|
||||
:disable-lock-warning="true"
|
||||
:disable-polls="true"
|
||||
:disable-quotes="true"
|
||||
:disable-sensitivity-checkbox="true"
|
||||
:disable-submit="errorLoadingChat || !currentChat"
|
||||
:disable-preview="true"
|
||||
|
|
|
|||
|
|
@ -45,7 +45,12 @@ const Draft = {
|
|||
}
|
||||
},
|
||||
safeToSave() {
|
||||
return this.draft.status || this.draft.files?.length || this.draft.hasPoll
|
||||
return (
|
||||
this.draft.status ||
|
||||
this.draft.files?.length ||
|
||||
this.draft.hasPoll ||
|
||||
this.draft.hasQuote
|
||||
)
|
||||
},
|
||||
postStatusFormProps() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
v-bind="params"
|
||||
:post-handler="doEditStatus"
|
||||
:disable-polls="true"
|
||||
:disable-quotes="true"
|
||||
:disable-visibility-selector="true"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ const MRFTransparencyPanel = {
|
|||
mrfPolicies: (state) =>
|
||||
get(state, 'federationPolicy.mrf_policies', []),
|
||||
quarantineInstances: (state) =>
|
||||
console.log(state) ||
|
||||
toInstanceReasonObject(
|
||||
get(state, 'federationPolicy.quarantined_instances', []),
|
||||
get(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import EmojiInput from '../emoji_input/emoji_input.vue'
|
|||
import suggestor from '../emoji_input/suggestor.js'
|
||||
import MediaUpload from '../media_upload/media_upload.vue'
|
||||
import PollForm from '../poll/poll_form.vue'
|
||||
import QuoteForm from '../quote/quote_form.vue'
|
||||
import ScopeSelector from '../scope_selector/scope_selector.vue'
|
||||
import Select from '../select/select.vue'
|
||||
import StatusContent from '../status_content/status_content.vue'
|
||||
|
|
@ -37,6 +38,7 @@ import {
|
|||
faChevronRight,
|
||||
faCircleNotch,
|
||||
faPollH,
|
||||
faQuoteRight,
|
||||
faSmileBeam,
|
||||
faTimes,
|
||||
faUpload,
|
||||
|
|
@ -46,6 +48,7 @@ library.add(
|
|||
faSmileBeam,
|
||||
faPollH,
|
||||
faUpload,
|
||||
faQuoteRight,
|
||||
faBan,
|
||||
faTimes,
|
||||
faCircleNotch,
|
||||
|
|
@ -107,6 +110,7 @@ const PostStatusForm = {
|
|||
'disableNotice',
|
||||
'disableLockWarning',
|
||||
'disablePolls',
|
||||
'disableQuotes',
|
||||
'disableSensitivityCheckbox',
|
||||
'disableSubmit',
|
||||
'disablePreview',
|
||||
|
|
@ -138,6 +142,7 @@ const PostStatusForm = {
|
|||
MediaUpload,
|
||||
EmojiInput,
|
||||
PollForm,
|
||||
QuoteForm,
|
||||
ScopeSelector,
|
||||
Checkbox,
|
||||
Select,
|
||||
|
|
@ -147,6 +152,9 @@ const PostStatusForm = {
|
|||
DraftCloser,
|
||||
Popover,
|
||||
},
|
||||
created() {
|
||||
this.initQuote()
|
||||
},
|
||||
mounted() {
|
||||
this.updateIdempotencyKey()
|
||||
this.resize(this.$refs.textarea)
|
||||
|
|
@ -205,6 +213,8 @@ const PostStatusForm = {
|
|||
files: [],
|
||||
poll: {},
|
||||
hasPoll: false,
|
||||
hasQuote: false,
|
||||
quote: {},
|
||||
mediaDescriptions: {},
|
||||
visibility: scope,
|
||||
contentType,
|
||||
|
|
@ -222,6 +232,8 @@ const PostStatusForm = {
|
|||
files: this.statusFiles || [],
|
||||
poll: this.statusPoll || {},
|
||||
hasPoll: false,
|
||||
hasQuote: false,
|
||||
quote: {},
|
||||
mediaDescriptions: this.statusMediaDescriptions || {},
|
||||
visibility: this.statusScope || scope,
|
||||
contentType: statusContentType,
|
||||
|
|
@ -348,12 +360,28 @@ const PostStatusForm = {
|
|||
isEdit() {
|
||||
return typeof this.statusId !== 'undefined' && this.statusId.trim() !== ''
|
||||
},
|
||||
quotable() {
|
||||
quotingAvailable() {
|
||||
if (!useInstanceCapabilitiesStore().quotingAvailable) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!this.replyTo) {
|
||||
return this.disableQuotes !== true
|
||||
},
|
||||
isReply() {
|
||||
return this.newStatus.type === 'reply'
|
||||
},
|
||||
quotable() {
|
||||
return this.quotingAvailable && this.replyTo
|
||||
},
|
||||
quoteThreadToggled() {
|
||||
return this.newStatus.hasQuote && this.newStatus.quote.thread
|
||||
},
|
||||
defaultQuotable() {
|
||||
if (
|
||||
!this.quotingAvailable ||
|
||||
!this.isReply ||
|
||||
!this.$store.getters.mergedConfig.quoteReply
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -375,12 +403,25 @@ const PostStatusForm = {
|
|||
|
||||
return false
|
||||
},
|
||||
inReplyStatusId() {
|
||||
return !this.newStatus.hasQuote ||
|
||||
!this.newStatus.quote.thread ||
|
||||
!this.newStatus.quote.id
|
||||
? this.replyTo
|
||||
: undefined
|
||||
},
|
||||
quoteId() {
|
||||
return this.newStatus.hasQuote ? this.newStatus.quote.id : undefined
|
||||
},
|
||||
debouncedMaybeAutoSaveDraft() {
|
||||
return debounce(this.maybeAutoSaveDraft, 3000)
|
||||
},
|
||||
pollFormVisible() {
|
||||
return this.newStatus.hasPoll
|
||||
},
|
||||
quoteFormVisible() {
|
||||
return this.newStatus.hasQuote && !this.newStatus.quote.thread
|
||||
},
|
||||
shouldAutoSaveDraft() {
|
||||
return useMergedConfigStore().mergedConfig.autoSaveDraft
|
||||
},
|
||||
|
|
@ -398,7 +439,8 @@ const PostStatusForm = {
|
|||
(this.newStatus.status ||
|
||||
this.newStatus.spoilerText ||
|
||||
this.newStatus.files?.length ||
|
||||
this.newStatus.hasPoll) &&
|
||||
this.newStatus.hasPoll ||
|
||||
this.newStatus.hasQuote) &&
|
||||
this.saveable
|
||||
)
|
||||
},
|
||||
|
|
@ -409,7 +451,8 @@ const PostStatusForm = {
|
|||
this.newStatus.status ||
|
||||
this.newStatus.spoilerText ||
|
||||
this.newStatus.files?.length ||
|
||||
this.newStatus.hasPoll
|
||||
this.newStatus.hasPoll ||
|
||||
this.newStatus.hasQuote
|
||||
)
|
||||
)
|
||||
},
|
||||
|
|
@ -459,11 +502,13 @@ const PostStatusForm = {
|
|||
contentType: newStatus.contentType,
|
||||
poll: {},
|
||||
hasPoll: false,
|
||||
hasQuote: false,
|
||||
quote: {},
|
||||
mediaDescriptions: {},
|
||||
quoting: false,
|
||||
}
|
||||
this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
|
||||
this.clearPollForm()
|
||||
this.clearQuoteForm()
|
||||
if (this.preserveFocus) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.textarea.focus()
|
||||
|
|
@ -503,9 +548,7 @@ const PostStatusForm = {
|
|||
return
|
||||
}
|
||||
|
||||
const poll = this.newStatus.hasPoll
|
||||
? pollFormToMasto(this.newStatus.poll)
|
||||
: {}
|
||||
const poll = newStatus.hasPoll ? pollFormToMasto(newStatus.poll) : {}
|
||||
if (this.pollContentError) {
|
||||
this.error = this.pollContentError
|
||||
return
|
||||
|
|
@ -521,10 +564,6 @@ const PostStatusForm = {
|
|||
return
|
||||
}
|
||||
|
||||
const replyOrQuoteAttr = newStatus.quoting
|
||||
? 'quoteId'
|
||||
: 'inReplyToStatusId'
|
||||
|
||||
const postingOptions = {
|
||||
status: newStatus.status,
|
||||
spoilerText: newStatus.spoilerText || null,
|
||||
|
|
@ -532,7 +571,8 @@ const PostStatusForm = {
|
|||
sensitive: newStatus.nsfw,
|
||||
media: newStatus.files,
|
||||
store: this.$store,
|
||||
[replyOrQuoteAttr]: this.replyTo,
|
||||
inReplyToStatusId: this.inReplyStatusId,
|
||||
quoteId: this.quoteId,
|
||||
contentType: newStatus.contentType,
|
||||
poll,
|
||||
idempotencyKey: this.idempotencyKey,
|
||||
|
|
@ -561,9 +601,7 @@ const PostStatusForm = {
|
|||
}
|
||||
const newStatus = this.newStatus
|
||||
this.previewLoading = true
|
||||
const replyOrQuoteAttr = newStatus.quoting
|
||||
? 'quoteId'
|
||||
: 'inReplyToStatusId'
|
||||
|
||||
statusPoster
|
||||
.postStatus({
|
||||
status: newStatus.status,
|
||||
|
|
@ -572,7 +610,8 @@ const PostStatusForm = {
|
|||
sensitive: newStatus.nsfw,
|
||||
media: [],
|
||||
store: this.$store,
|
||||
[replyOrQuoteAttr]: this.replyTo,
|
||||
inReplyToStatusId: this.inReplyStatusId,
|
||||
quoteId: this.quoteId,
|
||||
contentType: newStatus.contentType,
|
||||
poll: {},
|
||||
preview: true,
|
||||
|
|
@ -816,6 +855,32 @@ const PostStatusForm = {
|
|||
this.$refs.pollForm.clear()
|
||||
}
|
||||
},
|
||||
initQuote() {
|
||||
const quote = this.newStatus.quote
|
||||
|
||||
if (Object.keys(quote).length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const quotable = this.defaultQuotable
|
||||
|
||||
quote.id = quotable ? this.replyTo : ''
|
||||
quote.url = ''
|
||||
quote.thread = quotable
|
||||
},
|
||||
setQuoteThread(v) {
|
||||
this.newStatus.hasQuote = v
|
||||
this.newStatus.quote.thread = v
|
||||
this.newStatus.quote.id = v ? this.replyTo : ''
|
||||
},
|
||||
clearQuoteForm() {
|
||||
if (this.$refs.quoteForm) {
|
||||
this.$refs.quoteForm.clear()
|
||||
}
|
||||
},
|
||||
toggleQuoteForm() {
|
||||
this.newStatus.hasQuote = !this.newStatus.hasQuote
|
||||
},
|
||||
dismissScopeNotice() {
|
||||
useSyncConfigStore().setSimplePrefAndSave({
|
||||
path: 'hideScopeNotice',
|
||||
|
|
|
|||
|
|
@ -136,11 +136,17 @@
|
|||
|
||||
.poll-icon {
|
||||
order: 3;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.quote-icon {
|
||||
order: 4;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
.media-upload-icon,
|
||||
.poll-icon,
|
||||
.quote-icon,
|
||||
.emoji-icon {
|
||||
font-size: 1.85em;
|
||||
line-height: 1.1;
|
||||
|
|
|
|||
|
|
@ -115,24 +115,26 @@
|
|||
<button
|
||||
:id="`reply-or-quote-option-${randomSeed}-reply`"
|
||||
class="btn button-default reply-or-quote-option"
|
||||
:class="{ toggled: !newStatus.quoting }"
|
||||
:class="{ toggled: !quoteThreadToggled }"
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
:disabled="quoteFormVisible"
|
||||
:aria-labelledby="`reply-or-quote-option-${randomSeed}-reply`"
|
||||
:aria-checked="!newStatus.quoting"
|
||||
@click="newStatus.quoting = false"
|
||||
:aria-checked="!newStatus.quote.thread"
|
||||
@click="setQuoteThread(false)"
|
||||
>
|
||||
{{ $t('post_status.reply_option') }}
|
||||
</button>
|
||||
<button
|
||||
:id="`reply-or-quote-option-${randomSeed}-quote`"
|
||||
class="btn button-default reply-or-quote-option"
|
||||
:class="{ toggled: newStatus.quoting }"
|
||||
:class="{ toggled: quoteThreadToggled }"
|
||||
tabindex="0"
|
||||
role="radio"
|
||||
:disabled="quoteFormVisible"
|
||||
:aria-labelledby="`reply-or-quote-option-${randomSeed}-quote`"
|
||||
:aria-checked="newStatus.quoting"
|
||||
@click="newStatus.quoting = true"
|
||||
:aria-checked="newStatus.quote.thread"
|
||||
@click="setQuoteThread(true)"
|
||||
>
|
||||
{{ $t('post_status.quote_option') }}
|
||||
</button>
|
||||
|
|
@ -270,6 +272,13 @@
|
|||
:visible="pollFormVisible"
|
||||
:params="newStatus.poll"
|
||||
/>
|
||||
<quote-form
|
||||
v-if="quotingAvailable"
|
||||
ref="quoteForm"
|
||||
:visible="quoteFormVisible"
|
||||
:reply="isReply"
|
||||
:params="newStatus.quote"
|
||||
/>
|
||||
<span
|
||||
v-if="!disableDraft && shouldAutoSaveDraft"
|
||||
class="auto-save-status"
|
||||
|
|
@ -300,6 +309,16 @@
|
|||
>
|
||||
<FAIcon icon="poll-h" />
|
||||
</button>
|
||||
<button
|
||||
v-if="quotingAvailable"
|
||||
class="quote-icon button-unstyled"
|
||||
:disabled="newStatus.quote.thread"
|
||||
:class="{ selected: quoteFormVisible }"
|
||||
:title="$t('tool_tip.add_quote')"
|
||||
@click="toggleQuoteForm"
|
||||
>
|
||||
<FAIcon icon="quote-right" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group post-button-group">
|
||||
<button
|
||||
|
|
|
|||
101
src/components/quote/quote.js
Normal file
101
src/components/quote/quote.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(faCircleNotch)
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Status: defineAsyncComponent(() => import('../status/status.vue')),
|
||||
},
|
||||
name: 'Quote',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
},
|
||||
statusId: {
|
||||
type: String,
|
||||
},
|
||||
statusUrl: {
|
||||
type: String,
|
||||
},
|
||||
statusVisible: {
|
||||
type: Boolean,
|
||||
},
|
||||
initiallyExpanded: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
displayQuote: this.initiallyExpanded,
|
||||
fetchAttempted: false,
|
||||
fetching: false,
|
||||
error: null,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.maybeFetchStatus()
|
||||
},
|
||||
watch: {
|
||||
statusId() {
|
||||
this.maybeFetchStatus()
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
quotedStatus() {
|
||||
return this.statusId
|
||||
? this.$store.state.statuses.allStatusesObject[this.statusId]
|
||||
: undefined
|
||||
},
|
||||
shouldDisplayQuote() {
|
||||
return this.displayQuote && this.quotedStatus
|
||||
},
|
||||
hasVisibleQuote() {
|
||||
return (
|
||||
this.statusUrl &&
|
||||
this.statusVisible &&
|
||||
(this.showSpinner || this.quotedStatus)
|
||||
)
|
||||
},
|
||||
hasInvisibleQuote() {
|
||||
return this.statusUrl && !this.statusVisible
|
||||
},
|
||||
showSpinner() {
|
||||
return this.loading || this.fetching
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleDisplayQuote() {
|
||||
this.displayQuote = !this.displayQuote
|
||||
this.maybeFetchStatus()
|
||||
},
|
||||
maybeFetchStatus() {
|
||||
if (this.statusId && this.displayQuote && !this.quotedStatus) {
|
||||
this.fetchStatus()
|
||||
}
|
||||
},
|
||||
fetchStatus() {
|
||||
this.fetchAttempted = true
|
||||
this.fetching = true
|
||||
this.$emit('loading', true)
|
||||
this.$store
|
||||
.dispatch('fetchStatus', this.statusId)
|
||||
.then(() => {
|
||||
this.displayQuote = true
|
||||
})
|
||||
.catch((error) => {
|
||||
this.error = error
|
||||
this.$emit('error', error)
|
||||
})
|
||||
.finally(() => {
|
||||
this.fetching = false
|
||||
this.$emit('loading', false)
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
82
src/components/quote/quote.vue
Normal file
82
src/components/quote/quote.vue
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<article
|
||||
v-if="hasVisibleQuote"
|
||||
class="quoted-status"
|
||||
>
|
||||
<button
|
||||
class="button-unstyled -link display-quoted-status-button"
|
||||
:aria-expanded="shouldDisplayQuote"
|
||||
@click="toggleDisplayQuote"
|
||||
>
|
||||
{{ shouldDisplayQuote ? $t('status.hide_quote') : $t('status.display_quote') }}
|
||||
<FAIcon
|
||||
class="display-quoted-status-button-icon"
|
||||
:icon="shouldDisplayQuote ? 'chevron-up' : 'chevron-down'"
|
||||
/>
|
||||
</button>
|
||||
<span
|
||||
v-show="showSpinner"
|
||||
class="loading-spinner"
|
||||
>
|
||||
<FAIcon
|
||||
class="fa-old-padding"
|
||||
spin
|
||||
icon="circle-notch"
|
||||
/>
|
||||
</span>
|
||||
<Status
|
||||
v-if="shouldDisplayQuote"
|
||||
:statusoid="quotedStatus"
|
||||
:in-quote="true"
|
||||
/>
|
||||
</article>
|
||||
<p
|
||||
v-else-if="hasInvisibleQuote"
|
||||
class="quoted-status -unavailable-prompt"
|
||||
>
|
||||
<i18n-t
|
||||
scope="global"
|
||||
keypath="status.invisible_quote"
|
||||
>
|
||||
<template #link>
|
||||
<bdi>
|
||||
<a
|
||||
v-if="statusId"
|
||||
:href="statusUrl"
|
||||
target="_blank"
|
||||
>
|
||||
{{ statusUrl }}
|
||||
</a>
|
||||
<router-link
|
||||
v-else
|
||||
:to="{ name: 'search', query: { query: statusUrl } }"
|
||||
>
|
||||
{{ statusUrl }}
|
||||
</router-link>
|
||||
</bdi>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script src="./quote.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.quoted-status {
|
||||
margin-top: 0.5em;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--roundness);
|
||||
|
||||
&.-unavailable-prompt {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.display-quoted-status-button {
|
||||
margin: 0.5em;
|
||||
|
||||
&-icon {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
118
src/components/quote/quote_form.js
Normal file
118
src/components/quote/quote_form.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import { debounce } from 'lodash'
|
||||
|
||||
import Checkbox from '../checkbox/checkbox.vue'
|
||||
import Quote from './quote.vue'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Quote,
|
||||
Checkbox,
|
||||
},
|
||||
name: 'QuoteForm',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
reply: {
|
||||
type: Boolean,
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
text: this.params.url,
|
||||
loading: false,
|
||||
error: false,
|
||||
debounceSetQuote: debounce((value) => {
|
||||
this.fetchStatus(value)
|
||||
}, 1000),
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.params.url && !this.params.id) {
|
||||
this.fetchStatus(this.params.url)
|
||||
} else if (this.params.id) {
|
||||
this.text =
|
||||
window.location.protocol +
|
||||
'//' +
|
||||
this.instanceHost +
|
||||
'/notice/' +
|
||||
this.params.id
|
||||
this.params.url = this.text
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
instanceHost() {
|
||||
return new URL(useInstanceStore().server).host
|
||||
},
|
||||
noticeRegex() {
|
||||
return new RegExp(
|
||||
`^([^/:]*:?//|)(${window.location.host}|${this.instanceHost})/notice/(.*)$`,
|
||||
)
|
||||
},
|
||||
quoteVisible() {
|
||||
return (!!this.params.id || this.loading) && !this.error
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
text(value) {
|
||||
this.debounceSetQuote(value)
|
||||
},
|
||||
visible(value) {
|
||||
if (value && this.params.url) {
|
||||
this.fetchStatus(this.params.url)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clear() {
|
||||
this.text = this.params.url
|
||||
this.loading = false
|
||||
this.error = false
|
||||
},
|
||||
setLoading(value) {
|
||||
this.loading = value
|
||||
},
|
||||
handleError(error) {
|
||||
this.params.id = null
|
||||
this.error = !!error
|
||||
},
|
||||
fetchStatus(value) {
|
||||
this.params.url = value
|
||||
this.error = false
|
||||
|
||||
const notice = this.noticeRegex.exec(value)
|
||||
if (notice && notice.length === 4) {
|
||||
this.params.id = notice[3]
|
||||
} else if (value) {
|
||||
this.loading = true
|
||||
this.$store
|
||||
.dispatch('search', {
|
||||
q: value,
|
||||
resolve: true,
|
||||
offset: 0,
|
||||
limit: 1,
|
||||
type: 'statuses',
|
||||
})
|
||||
.then((data) => {
|
||||
if (data && data.statuses && data.statuses.length === 1) {
|
||||
this.params.id = data.statuses[0].id
|
||||
} else {
|
||||
this.handleError(true)
|
||||
}
|
||||
})
|
||||
.catch(this.handleError)
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
this.params.id = null
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
55
src/components/quote/quote_form.vue
Normal file
55
src/components/quote/quote_form.vue
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="quote-form"
|
||||
>
|
||||
<div class="input-container">
|
||||
<input
|
||||
v-model="text"
|
||||
type="text"
|
||||
size="1"
|
||||
class="input"
|
||||
:placeholder="$t('post_status.quote_url')"
|
||||
>
|
||||
</div>
|
||||
<Quote
|
||||
:status-id="params.id"
|
||||
:status-url="params.url"
|
||||
:status-visible="quoteVisible"
|
||||
:initially-expanded="true"
|
||||
:loading="loading"
|
||||
@loading="setLoading"
|
||||
@error="handleError"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./quote_form.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.quote-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 0.5em 0.5em;
|
||||
|
||||
.input-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
white-space: pre;
|
||||
display: flex;
|
||||
flex-flow: row-reverse;
|
||||
line-height: 2;
|
||||
column-gap: 0.5em;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -12,6 +12,7 @@ import {
|
|||
import AvatarList from '../avatar_list/avatar_list.vue'
|
||||
import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
|
||||
import PostStatusForm from '../post_status_form/post_status_form.vue'
|
||||
import Quote from '../quote/quote.vue'
|
||||
import StatusContent from '../status_content/status_content.vue'
|
||||
import StatusPopover from '../status_popover/status_popover.vue'
|
||||
import Timeago from '../timeago/timeago.vue'
|
||||
|
|
@ -129,6 +130,7 @@ const Status = {
|
|||
MentionsLine,
|
||||
UserPopover,
|
||||
UserLink,
|
||||
Quote,
|
||||
StatusActionButtons,
|
||||
},
|
||||
props: [
|
||||
|
|
@ -174,7 +176,6 @@ const Status = {
|
|||
suspendable: true,
|
||||
error: null,
|
||||
headTailLinks: null,
|
||||
displayQuote: !this.inQuote,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -498,19 +499,17 @@ const Status = {
|
|||
editingAvailable() {
|
||||
return useInstanceCapabilitiesStore().editingAvailable
|
||||
},
|
||||
hasVisibleQuote() {
|
||||
return this.status.quote_url && this.status.quote_visible
|
||||
},
|
||||
hasInvisibleQuote() {
|
||||
return this.status.quote_url && !this.status.quote_visible
|
||||
},
|
||||
quotedStatus() {
|
||||
quoteId() {
|
||||
return this.status.quote_id
|
||||
? this.$store.state.statuses.allStatusesObject[this.status.quote_id]
|
||||
: undefined
|
||||
},
|
||||
shouldDisplayQuote() {
|
||||
return this.quotedStatus && this.displayQuote
|
||||
quoteUrl() {
|
||||
return this.status.quote_url
|
||||
},
|
||||
quoteVisible() {
|
||||
return this.status.quote_visible
|
||||
},
|
||||
quoteExpanded() {
|
||||
return !this.inQuote
|
||||
},
|
||||
scrobblePresent() {
|
||||
if (this.mergedConfig.hideScrobbles) return false
|
||||
|
|
@ -630,17 +629,6 @@ const Status = {
|
|||
}
|
||||
}
|
||||
},
|
||||
toggleDisplayQuote() {
|
||||
if (this.shouldDisplayQuote) {
|
||||
this.displayQuote = false
|
||||
} else if (!this.quotedStatus) {
|
||||
this.$store.dispatch('fetchStatus', this.status.quote_id).then(() => {
|
||||
this.displayQuote = true
|
||||
})
|
||||
} else {
|
||||
this.displayQuote = true
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
highlight: function (id) {
|
||||
|
|
|
|||
|
|
@ -388,22 +388,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.quoted-status {
|
||||
margin-top: 0.5em;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--roundness);
|
||||
|
||||
&.-unavailable-prompt {
|
||||
padding: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.display-quoted-status-button {
|
||||
margin: 0.5em;
|
||||
|
||||
&-icon {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -420,47 +420,12 @@
|
|||
@parse-ready="setHeadTailLinks"
|
||||
/>
|
||||
|
||||
<article
|
||||
v-if="hasVisibleQuote"
|
||||
class="quoted-status"
|
||||
>
|
||||
<button
|
||||
class="button-unstyled -link display-quoted-status-button"
|
||||
:aria-expanded="shouldDisplayQuote"
|
||||
@click="toggleDisplayQuote"
|
||||
>
|
||||
{{ shouldDisplayQuote ? $t('status.hide_quote') : $t('status.display_quote') }}
|
||||
<FAIcon
|
||||
class="display-quoted-status-button-icon"
|
||||
:icon="shouldDisplayQuote ? 'chevron-up' : 'chevron-down'"
|
||||
/>
|
||||
</button>
|
||||
<Status
|
||||
v-if="shouldDisplayQuote"
|
||||
:statusoid="quotedStatus"
|
||||
:in-quote="true"
|
||||
/>
|
||||
</article>
|
||||
<p
|
||||
v-else-if="hasInvisibleQuote"
|
||||
class="quoted-status -unavailable-prompt"
|
||||
>
|
||||
<i18n-t
|
||||
scope="global"
|
||||
keypath="status.invisible_quote"
|
||||
>
|
||||
<template #link>
|
||||
<bdi>
|
||||
<a
|
||||
:href="status.quote_url"
|
||||
target="_blank"
|
||||
>
|
||||
{{ status.quote_url }}
|
||||
</a>
|
||||
</bdi>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</p>
|
||||
<Quote
|
||||
:status-id="quoteId"
|
||||
:status-url="quoteUrl"
|
||||
:status-visible="quoteVisible"
|
||||
:initially-expanded="quoteExpanded"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="inConversation && !isPreview && replies && replies.length"
|
||||
|
|
|
|||
|
|
@ -107,10 +107,11 @@ export const BUTTONS = [
|
|||
icon: 'eye-slash',
|
||||
label: 'status.mute_ellipsis',
|
||||
if: ({ loggedIn }) => loggedIn,
|
||||
toggleable: true,
|
||||
toggleable: false,
|
||||
dropdown: true,
|
||||
// action ({ status, dispatch, emit }) {
|
||||
// }
|
||||
action({ status, dispatch, emit }) {
|
||||
/* prevent hiding */
|
||||
},
|
||||
},
|
||||
{
|
||||
// =========
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
grid-template-columns: repeat(auto-fill, minmax(10%, 3em));
|
||||
grid-auto-flow: row dense;
|
||||
grid-auto-rows: 1fr;
|
||||
grid-gap: 1.25em 1em;
|
||||
grid-gap: 1.25em 0;
|
||||
margin-top: var(--status-margin);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -284,6 +284,7 @@
|
|||
"new_status": "Post new status",
|
||||
"reply_option": "Reply to this status",
|
||||
"quote_option": "Quote this status",
|
||||
"quote_url": "Link to quoted post",
|
||||
"account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
|
||||
"account_not_locked_warning_link": "locked",
|
||||
"attachments_sensitive": "Mark attachments as sensitive",
|
||||
|
|
@ -1765,6 +1766,7 @@
|
|||
"favorite": "Favorite",
|
||||
"unfavorite": "Unfavorite",
|
||||
"add_reaction": "Add Reaction",
|
||||
"add_quote": "Add quote",
|
||||
"user_settings": "User Settings",
|
||||
"accept_follow_request": "Accept follow request",
|
||||
"reject_follow_request": "Reject follow request",
|
||||
|
|
|
|||
|
|
@ -693,7 +693,7 @@ const fetchStatus = ({ id, credentials }) => {
|
|||
if (data.ok) {
|
||||
return data
|
||||
}
|
||||
throw new Error('Error fetching timeline', data)
|
||||
throw new Error('Error fetching timeline', { cause: data })
|
||||
})
|
||||
.then((data) => data.json())
|
||||
.then((data) => parseStatus(data))
|
||||
|
|
@ -706,7 +706,7 @@ const fetchStatusSource = ({ id, credentials }) => {
|
|||
if (data.ok) {
|
||||
return data
|
||||
}
|
||||
throw new Error('Error fetching source', data)
|
||||
throw new Error('Error fetching source', { cause: data })
|
||||
})
|
||||
.then((data) => data.json())
|
||||
.then((data) => parseSource(data))
|
||||
|
|
|
|||
|
|
@ -384,7 +384,7 @@ export const parseStatus = (data) => {
|
|||
}
|
||||
|
||||
output.summary_raw_html = escapeHtml(data.spoiler_text)
|
||||
output.external_url = data.url
|
||||
output.external_url = data.uri || data.url
|
||||
output.poll = data.poll
|
||||
if (output.poll) {
|
||||
output.poll.options = (output.poll.options || []).map((field) => ({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue