Add quoting by url / in replies
This commit is contained in:
parent
ac84ff247f
commit
7aefda4211
18 changed files with 501 additions and 110 deletions
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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue