forked from AkkomaGang/akkoma-fe
parent
04bb4112c0
commit
a2541bb4e0
17 changed files with 274 additions and 11 deletions
|
@ -56,6 +56,7 @@ const pxStringToNumber = (str) => {
|
||||||
const PostStatusForm = {
|
const PostStatusForm = {
|
||||||
props: [
|
props: [
|
||||||
'replyTo',
|
'replyTo',
|
||||||
|
'quoteId',
|
||||||
'repliedUser',
|
'repliedUser',
|
||||||
'attentions',
|
'attentions',
|
||||||
'copyMessageScope',
|
'copyMessageScope',
|
||||||
|
@ -99,12 +100,12 @@ const PostStatusForm = {
|
||||||
this.updateIdempotencyKey()
|
this.updateIdempotencyKey()
|
||||||
this.resize(this.$refs.textarea)
|
this.resize(this.$refs.textarea)
|
||||||
|
|
||||||
if (this.replyTo) {
|
if (this.replyTo || this.quoteId) {
|
||||||
const textLength = this.$refs.textarea.value.length
|
const textLength = this.$refs.textarea.value.length
|
||||||
this.$refs.textarea.setSelectionRange(textLength, textLength)
|
this.$refs.textarea.setSelectionRange(textLength, textLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.replyTo || this.autoFocus) {
|
if (this.replyTo || this.quoteId || this.autoFocus) {
|
||||||
this.$refs.textarea.focus()
|
this.$refs.textarea.focus()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -112,7 +113,7 @@ const PostStatusForm = {
|
||||||
const preset = this.$route.query.message
|
const preset = this.$route.query.message
|
||||||
let statusText = preset || ''
|
let statusText = preset || ''
|
||||||
|
|
||||||
if (this.replyTo) {
|
if (this.replyTo || this.quoteId) {
|
||||||
const currentUser = this.$store.state.users.currentUser
|
const currentUser = this.$store.state.users.currentUser
|
||||||
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
|
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
|
||||||
}
|
}
|
||||||
|
@ -314,6 +315,7 @@ const PostStatusForm = {
|
||||||
media: newStatus.files,
|
media: newStatus.files,
|
||||||
store: this.$store,
|
store: this.$store,
|
||||||
inReplyToStatusId: this.replyTo,
|
inReplyToStatusId: this.replyTo,
|
||||||
|
quoteId: this.quoteId,
|
||||||
contentType: newStatus.contentType,
|
contentType: newStatus.contentType,
|
||||||
poll,
|
poll,
|
||||||
idempotencyKey: this.idempotencyKey
|
idempotencyKey: this.idempotencyKey
|
||||||
|
@ -347,6 +349,7 @@ const PostStatusForm = {
|
||||||
media: [],
|
media: [],
|
||||||
store: this.$store,
|
store: this.$store,
|
||||||
inReplyToStatusId: this.replyTo,
|
inReplyToStatusId: this.replyTo,
|
||||||
|
quoteId: this.quoteId,
|
||||||
contentType: newStatus.contentType,
|
contentType: newStatus.contentType,
|
||||||
poll: {},
|
poll: {},
|
||||||
preview: true
|
preview: true
|
||||||
|
|
16
src/components/quote_button/quote_button.js
Normal file
16
src/components/quote_button/quote_button.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import { faQuoteLeft } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(faQuoteLeft)
|
||||||
|
|
||||||
|
const QuoteButton = {
|
||||||
|
name: 'QuoteButton',
|
||||||
|
props: ['status', 'quoting', 'visibility'],
|
||||||
|
computed: {
|
||||||
|
loggedIn () {
|
||||||
|
return !!this.$store.state.users.currentUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QuoteButton
|
55
src/components/quote_button/quote_button.vue
Normal file
55
src/components/quote_button/quote_button.vue
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="loggedIn"
|
||||||
|
class="QuoteButton"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
v-if="visibility === 'public' || visibility === 'unlisted'"
|
||||||
|
class="button-unstyled interactive"
|
||||||
|
:class="{'-active': quoting}"
|
||||||
|
:title="$t('tool_tip.quote')"
|
||||||
|
@click.prevent="$emit('toggle')"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
class="fa-scale-110 fa-old-padding"
|
||||||
|
icon="quote-left"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<span v-else-if="loggedIn">
|
||||||
|
<FAIcon
|
||||||
|
class="fa-scale-110 fa-old-padding"
|
||||||
|
icon="lock"
|
||||||
|
:title="$t('timeline.no_quote_hint')"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./quote_button.js"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.QuoteButton {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> :first-child {
|
||||||
|
padding: 10px;
|
||||||
|
margin: -10px -8px -10px -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-counter {
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interactive {
|
||||||
|
&:hover .svg-inline--fa,
|
||||||
|
&.-active .svg-inline--fa {
|
||||||
|
color: $fallback--cBlue;
|
||||||
|
color: var(--cBlue, $fallback--cBlue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
32
src/components/quote_card/quote_card.js
Normal file
32
src/components/quote_card/quote_card.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import QuoteCardContent from '../quote_card_content/quote_card_content.vue'
|
||||||
|
|
||||||
|
const QuoteCard = {
|
||||||
|
name: 'QuoteCard',
|
||||||
|
props: [
|
||||||
|
'status'
|
||||||
|
],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
imageLoaded: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters([
|
||||||
|
'mergedConfig'
|
||||||
|
]),
|
||||||
|
statusLink () {
|
||||||
|
return {
|
||||||
|
name: 'conversation',
|
||||||
|
params: {
|
||||||
|
id: this.status.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
QuoteCardContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QuoteCard
|
76
src/components/quote_card/quote_card.vue
Normal file
76
src/components/quote_card/quote_card.vue
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
class="quote-card"
|
||||||
|
:href="$router.resolve(statusLink).href"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
<QuoteCardContent
|
||||||
|
:status="status"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./quote_card"></script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
|
.quote-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
|
||||||
|
.card-image {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 120px;
|
||||||
|
max-width: 25%;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: $fallback--attachmentRadius;
|
||||||
|
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
max-height: 100%;
|
||||||
|
margin: 0.5em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-host {
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-description {
|
||||||
|
margin: 0.5em 0 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: 1.2em;
|
||||||
|
// cap description at 3 lines, the 1px is to clean up some stray pixels
|
||||||
|
// TODO: fancier fade-out at the bottom to show off that it's too long?
|
||||||
|
max-height: calc(1.2em * 3 - 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nsfw-alert {
|
||||||
|
margin: 2em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
color: $fallback--text;
|
||||||
|
color: var(--text, $fallback--text);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-radius: $fallback--attachmentRadius;
|
||||||
|
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
|
||||||
|
border-color: $fallback--border;
|
||||||
|
border-color: var(--border, $fallback--border);
|
||||||
|
}
|
||||||
|
</style>
|
22
src/components/quote_card_content/quote_card_content.vue
Normal file
22
src/components/quote_card_content/quote_card_content.vue
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<Status
|
||||||
|
v-if="status"
|
||||||
|
:is-preview="true"
|
||||||
|
:statusoid="status"
|
||||||
|
:compact="true"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'QuoteCardContent',
|
||||||
|
components: {
|
||||||
|
Status: defineAsyncComponent(() => import('../status/status.vue'))
|
||||||
|
},
|
||||||
|
props: [
|
||||||
|
'status'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,7 +1,7 @@
|
||||||
import { extractCommit } from 'src/services/version/version.service'
|
import { extractCommit } from 'src/services/version/version.service'
|
||||||
|
|
||||||
const pleromaFeCommitUrl = 'https://akkoma.dev/AkkomaGang/pleroma-fe/commit/'
|
const pleromaFeCommitUrl = 'https://akkoma.dev/AkkomaGang/pleroma-fe/commit/'
|
||||||
const pleromaBeCommitUrl = 'https://akkoma.dev/AkkomaGang/akkoma/commits/'
|
const pleromaBeCommitUrl = 'https://akkoma.dev/AkkomaGang/akkoma/commit/'
|
||||||
|
|
||||||
const VersionTab = {
|
const VersionTab = {
|
||||||
data () {
|
data () {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import ReplyButton from '../reply_button/reply_button.vue'
|
import ReplyButton from '../reply_button/reply_button.vue'
|
||||||
|
import QuoteButton from '../quote_button/quote_button.vue'
|
||||||
import FavoriteButton from '../favorite_button/favorite_button.vue'
|
import FavoriteButton from '../favorite_button/favorite_button.vue'
|
||||||
import ReactButton from '../react_button/react_button.vue'
|
import ReactButton from '../react_button/react_button.vue'
|
||||||
import RetweetButton from '../retweet_button/retweet_button.vue'
|
import RetweetButton from '../retweet_button/retweet_button.vue'
|
||||||
|
@ -115,7 +116,8 @@ const Status = {
|
||||||
StatusContent,
|
StatusContent,
|
||||||
RichContent,
|
RichContent,
|
||||||
MentionLink,
|
MentionLink,
|
||||||
MentionsLine
|
MentionsLine,
|
||||||
|
QuoteButton
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'statusoid',
|
'statusoid',
|
||||||
|
@ -145,6 +147,8 @@ const Status = {
|
||||||
'controlledToggleShowingLongSubject',
|
'controlledToggleShowingLongSubject',
|
||||||
'controlledReplying',
|
'controlledReplying',
|
||||||
'controlledToggleReplying',
|
'controlledToggleReplying',
|
||||||
|
'controlledQuoting',
|
||||||
|
'controlledToggleQuoting',
|
||||||
'controlledMediaPlaying',
|
'controlledMediaPlaying',
|
||||||
'controlledSetMediaPlaying',
|
'controlledSetMediaPlaying',
|
||||||
'dive'
|
'dive'
|
||||||
|
@ -152,6 +156,7 @@ const Status = {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
uncontrolledReplying: false,
|
uncontrolledReplying: false,
|
||||||
|
uncontrolledQuoting: false,
|
||||||
unmuted: false,
|
unmuted: false,
|
||||||
userExpanded: false,
|
userExpanded: false,
|
||||||
uncontrolledMediaPlaying: [],
|
uncontrolledMediaPlaying: [],
|
||||||
|
@ -161,7 +166,7 @@ const Status = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...controlledOrUncontrolledGetters(['replying', 'mediaPlaying']),
|
...controlledOrUncontrolledGetters(['replying', 'quoting', 'mediaPlaying']),
|
||||||
muteWords () {
|
muteWords () {
|
||||||
return this.mergedConfig.muteWords
|
return this.mergedConfig.muteWords
|
||||||
},
|
},
|
||||||
|
@ -418,6 +423,9 @@ const Status = {
|
||||||
toggleReplying () {
|
toggleReplying () {
|
||||||
controlledOrUncontrolledToggle(this, 'replying')
|
controlledOrUncontrolledToggle(this, 'replying')
|
||||||
},
|
},
|
||||||
|
toggleQuoting () {
|
||||||
|
controlledOrUncontrolledToggle(this, 'quoting')
|
||||||
|
},
|
||||||
gotoOriginal (id) {
|
gotoOriginal (id) {
|
||||||
if (this.inConversation) {
|
if (this.inConversation) {
|
||||||
this.$emit('goto', id)
|
this.$emit('goto', id)
|
||||||
|
|
|
@ -101,6 +101,10 @@
|
||||||
|
|
||||||
.status-heading {
|
.status-heading {
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
--emoji-size: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading-name-row {
|
.heading-name-row {
|
||||||
|
@ -355,6 +359,15 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quote-form {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.favs-repeated-users {
|
.favs-repeated-users {
|
||||||
margin-top: var(--status-margin, $status-margin);
|
margin-top: var(--status-margin, $status-margin);
|
||||||
}
|
}
|
||||||
|
|
|
@ -430,6 +430,12 @@
|
||||||
:status="status"
|
:status="status"
|
||||||
@toggle="toggleReplying"
|
@toggle="toggleReplying"
|
||||||
/>
|
/>
|
||||||
|
<quote-button
|
||||||
|
:visibility="status.visibility"
|
||||||
|
:quoting="quoting"
|
||||||
|
:status="status"
|
||||||
|
@toggle="toggleQuoting"
|
||||||
|
/>
|
||||||
<retweet-button
|
<retweet-button
|
||||||
:visibility="status.visibility"
|
:visibility="status.visibility"
|
||||||
:logged-in="loggedIn"
|
:logged-in="loggedIn"
|
||||||
|
@ -488,6 +494,20 @@
|
||||||
@posted="toggleReplying"
|
@posted="toggleReplying"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="quoting"
|
||||||
|
class="status-container quote-form"
|
||||||
|
>
|
||||||
|
<PostStatusForm
|
||||||
|
class="quote-body"
|
||||||
|
:quote-id="status.id"
|
||||||
|
:attentions="[status.user]"
|
||||||
|
:replied-user="status.user"
|
||||||
|
:copy-message-scope="status.visibility"
|
||||||
|
:subject="replySubject"
|
||||||
|
@posted="toggleQuoting"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -6,9 +6,8 @@
|
||||||
|
|
||||||
.emoji {
|
.emoji {
|
||||||
--_still_image-label-scale: 0.5;
|
--_still_image-label-scale: 0.5;
|
||||||
|
--emoji-size: 50px;
|
||||||
width: 50px;
|
--emoji-size: 50px;
|
||||||
height: 50px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
._mfm_x2_ {
|
._mfm_x2_ {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import Poll from '../poll/poll.vue'
|
||||||
import Gallery from '../gallery/gallery.vue'
|
import Gallery from '../gallery/gallery.vue'
|
||||||
import StatusBody from 'src/components/status_body/status_body.vue'
|
import StatusBody from 'src/components/status_body/status_body.vue'
|
||||||
import LinkPreview from '../link-preview/link-preview.vue'
|
import LinkPreview from '../link-preview/link-preview.vue'
|
||||||
|
import QuoteCard from '../quote_card/quote_card.vue'
|
||||||
import { mapGetters, mapState } from 'vuex'
|
import { mapGetters, mapState } from 'vuex'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
|
@ -109,7 +110,8 @@ const StatusContent = {
|
||||||
Poll,
|
Poll,
|
||||||
Gallery,
|
Gallery,
|
||||||
LinkPreview,
|
LinkPreview,
|
||||||
StatusBody
|
StatusBody,
|
||||||
|
QuoteCard
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleShowingTall () {
|
toggleShowingTall () {
|
||||||
|
|
|
@ -40,7 +40,14 @@
|
||||||
@play="$emit('mediaplay', attachment.id)"
|
@play="$emit('mediaplay', attachment.id)"
|
||||||
@pause="$emit('mediapause', attachment.id)"
|
@pause="$emit('mediapause', attachment.id)"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
v-if="status.quote && !compact"
|
||||||
|
class="quote"
|
||||||
|
>
|
||||||
|
<QuoteCard
|
||||||
|
:status="status.quote"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="status.card && !noHeading && !compact"
|
v-if="status.card && !noHeading && !compact"
|
||||||
class="link-preview media-body"
|
class="link-preview media-body"
|
||||||
|
|
|
@ -782,6 +782,7 @@
|
||||||
"error": "Error fetching timeline: {0}",
|
"error": "Error fetching timeline: {0}",
|
||||||
"load_older": "Load older statuses",
|
"load_older": "Load older statuses",
|
||||||
"no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
|
"no_retweet_hint": "Post is marked as followers-only or direct and cannot be repeated",
|
||||||
|
"no_quote_hint": "Post is marked as followers-only or direct and cannot be quoted",
|
||||||
"repeated": "repeated",
|
"repeated": "repeated",
|
||||||
"show_new": "Show new",
|
"show_new": "Show new",
|
||||||
"reload": "Reload",
|
"reload": "Reload",
|
||||||
|
|
|
@ -763,6 +763,7 @@ const postStatus = ({
|
||||||
poll,
|
poll,
|
||||||
mediaIds = [],
|
mediaIds = [],
|
||||||
inReplyToStatusId,
|
inReplyToStatusId,
|
||||||
|
quoteId,
|
||||||
contentType,
|
contentType,
|
||||||
preview,
|
preview,
|
||||||
idempotencyKey
|
idempotencyKey
|
||||||
|
@ -795,6 +796,9 @@ const postStatus = ({
|
||||||
if (inReplyToStatusId) {
|
if (inReplyToStatusId) {
|
||||||
form.append('in_reply_to_id', inReplyToStatusId)
|
form.append('in_reply_to_id', inReplyToStatusId)
|
||||||
}
|
}
|
||||||
|
if (quoteId) {
|
||||||
|
form.append('quote_id', quoteId)
|
||||||
|
}
|
||||||
if (preview) {
|
if (preview) {
|
||||||
form.append('preview', 'true')
|
form.append('preview', 'true')
|
||||||
}
|
}
|
||||||
|
|
|
@ -347,6 +347,9 @@ export const parseStatus = (data) => {
|
||||||
output.visibility = data.visibility
|
output.visibility = data.visibility
|
||||||
output.card = data.card
|
output.card = data.card
|
||||||
output.created_at = new Date(data.created_at)
|
output.created_at = new Date(data.created_at)
|
||||||
|
if (data.quote) {
|
||||||
|
output.quote = parseStatus(data.quote)
|
||||||
|
}
|
||||||
|
|
||||||
// Converting to string, the right way.
|
// Converting to string, the right way.
|
||||||
output.in_reply_to_status_id = output.in_reply_to_status_id
|
output.in_reply_to_status_id = output.in_reply_to_status_id
|
||||||
|
|
|
@ -10,6 +10,7 @@ const postStatus = ({
|
||||||
poll,
|
poll,
|
||||||
media = [],
|
media = [],
|
||||||
inReplyToStatusId = undefined,
|
inReplyToStatusId = undefined,
|
||||||
|
quoteId = undefined,
|
||||||
contentType = 'text/plain',
|
contentType = 'text/plain',
|
||||||
preview = false,
|
preview = false,
|
||||||
idempotencyKey = ''
|
idempotencyKey = ''
|
||||||
|
@ -24,6 +25,7 @@ const postStatus = ({
|
||||||
sensitive,
|
sensitive,
|
||||||
mediaIds,
|
mediaIds,
|
||||||
inReplyToStatusId,
|
inReplyToStatusId,
|
||||||
|
quoteId,
|
||||||
contentType,
|
contentType,
|
||||||
poll,
|
poll,
|
||||||
preview,
|
preview,
|
||||||
|
|
Loading…
Reference in a new issue