forked from AkkomaGang/akkoma-fe
add rich text preview
This commit is contained in:
parent
391f796cb4
commit
223fabfe90
5 changed files with 180 additions and 23 deletions
|
@ -3,6 +3,7 @@ import MediaUpload from '../media_upload/media_upload.vue'
|
|||
import ScopeSelector from '../scope_selector/scope_selector.vue'
|
||||
import EmojiInput from '../emoji_input/emoji_input.vue'
|
||||
import PollForm from '../poll/poll_form.vue'
|
||||
import StatusContent from '../status_content/status_content.vue'
|
||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
|
||||
import { reject, map, uniqBy } from 'lodash'
|
||||
|
@ -38,7 +39,8 @@ const PostStatusForm = {
|
|||
EmojiInput,
|
||||
PollForm,
|
||||
ScopeSelector,
|
||||
Checkbox
|
||||
Checkbox,
|
||||
StatusContent
|
||||
},
|
||||
mounted () {
|
||||
this.resize(this.$refs.textarea)
|
||||
|
@ -84,7 +86,9 @@ const PostStatusForm = {
|
|||
caret: 0,
|
||||
pollFormVisible: false,
|
||||
showDropIcon: 'hide',
|
||||
dropStopTimeout: null
|
||||
dropStopTimeout: null,
|
||||
preview: null,
|
||||
previewLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -163,8 +167,20 @@ const PostStatusForm = {
|
|||
this.newStatus.poll &&
|
||||
this.newStatus.poll.error
|
||||
},
|
||||
showPreview () {
|
||||
return !!this.preview || this.previewLoading
|
||||
},
|
||||
...mapGetters(['mergedConfig'])
|
||||
},
|
||||
watch: {
|
||||
'newStatus.contentType': function (newType) {
|
||||
if (newType === 'text/plain') {
|
||||
this.closePreview()
|
||||
} else if (this.preview) {
|
||||
this.previewStatus(this.newStatus)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
postStatus (newStatus) {
|
||||
if (this.posting) { return }
|
||||
|
@ -218,6 +234,38 @@ const PostStatusForm = {
|
|||
this.posting = false
|
||||
})
|
||||
},
|
||||
previewStatus (newStatus) {
|
||||
this.previewLoading = true
|
||||
statusPoster.postStatus({
|
||||
status: newStatus.status,
|
||||
spoilerText: newStatus.spoilerText || null,
|
||||
visibility: newStatus.visibility,
|
||||
sensitive: newStatus.nsfw,
|
||||
media: newStatus.files,
|
||||
store: this.$store,
|
||||
inReplyToStatusId: this.replyTo,
|
||||
contentType: newStatus.contentType,
|
||||
poll: {},
|
||||
preview: true
|
||||
}).then((data) => {
|
||||
// Don't apply preview if not loading, because it means
|
||||
// user has closed the preview manually.
|
||||
if (!this.previewLoading) return
|
||||
if (!data.error) {
|
||||
this.preview = data
|
||||
} else {
|
||||
this.preview = { error: data.error }
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.preview = { error }
|
||||
}).finally(() => {
|
||||
this.previewLoading = false
|
||||
})
|
||||
},
|
||||
closePreview () {
|
||||
this.preview = null
|
||||
this.previewLoading = false
|
||||
},
|
||||
addMediaFile (fileInfo) {
|
||||
this.newStatus.files.push(fileInfo)
|
||||
},
|
||||
|
|
|
@ -16,6 +16,58 @@
|
|||
@drop.stop="fileDrop"
|
||||
/>
|
||||
<div class="form-group">
|
||||
<a
|
||||
v-if="newStatus.contentType !== 'text/plain' && !showPreview"
|
||||
class="preview-start faint"
|
||||
type="button"
|
||||
@click.stop.prevent="previewStatus(newStatus)"
|
||||
>
|
||||
{{ $t('status.preview') }}
|
||||
</a>
|
||||
<div
|
||||
v-if="showPreview && newStatus.contentType !== 'text/plain'"
|
||||
class="preview-container"
|
||||
>
|
||||
<span class="preview-heading">
|
||||
<span class="faint preview-title">
|
||||
{{ $t('status.status_preview') }}
|
||||
</span>
|
||||
<i
|
||||
v-if="previewLoading"
|
||||
class="icon-spin3 animate-spin"
|
||||
/>
|
||||
<a
|
||||
v-else
|
||||
class="faint preview-update"
|
||||
@click.stop.prevent="previewStatus(newStatus)"
|
||||
>
|
||||
{{ $t('status.preview_update') }}
|
||||
</a>
|
||||
<a
|
||||
class="preview-close"
|
||||
@click.stop.prevent="closePreview"
|
||||
>
|
||||
<i class="icon-cancel" />
|
||||
</a>
|
||||
</span>
|
||||
<div
|
||||
v-if="!preview"
|
||||
class="preview-status"
|
||||
>
|
||||
{{ $t('general.loading') }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="preview.error"
|
||||
class="preview-status preview-error"
|
||||
>
|
||||
{{ preview.error }}
|
||||
</div>
|
||||
<StatusContent
|
||||
v-else
|
||||
:status="preview"
|
||||
class="preview-status"
|
||||
/>
|
||||
</div>
|
||||
<i18n
|
||||
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'"
|
||||
path="post_status.account_not_locked_warning"
|
||||
|
@ -77,7 +129,6 @@
|
|||
class="form-control"
|
||||
>
|
||||
<input
|
||||
|
||||
v-model="newStatus.spoilerText"
|
||||
type="text"
|
||||
:placeholder="$t('post_status.content_warning')"
|
||||
|
@ -302,14 +353,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.post-status-form {
|
||||
.visibility-tray {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.post-status-form {
|
||||
.form-bottom {
|
||||
display: flex;
|
||||
|
@ -336,6 +379,52 @@
|
|||
max-width: 10em;
|
||||
}
|
||||
|
||||
.preview-start {
|
||||
margin-left: auto;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.preview-heading {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.preview-title {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.preview-close {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.preview-update {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-error {
|
||||
color: $fallback--faint;
|
||||
color: var(--faint, $fallback--faint);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.preview-status {
|
||||
border: 1px solid $fallback--border;
|
||||
border: 1px solid var(--border, $fallback--border);
|
||||
border-radius: $fallback--tooltipRadius;
|
||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.text-format {
|
||||
.only-format {
|
||||
color: $fallback--faint;
|
||||
|
@ -343,6 +432,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.visibility-tray {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.media-upload-icon, .poll-icon, .emoji-icon {
|
||||
font-size: 26px;
|
||||
flex: 1;
|
||||
|
|
|
@ -636,7 +636,10 @@
|
|||
"status_unavailable": "Status unavailable",
|
||||
"copy_link": "Copy link to status",
|
||||
"thread_muted": "Thread muted",
|
||||
"thread_muted_and_words": ", has words:"
|
||||
"thread_muted_and_words": ", has words:",
|
||||
"preview": "Preview",
|
||||
"status_preview": "Status preview",
|
||||
"preview_update": "Update"
|
||||
},
|
||||
"user_card": {
|
||||
"approve": "Approve",
|
||||
|
|
|
@ -617,7 +617,8 @@ const postStatus = ({
|
|||
poll,
|
||||
mediaIds = [],
|
||||
inReplyToStatusId,
|
||||
contentType
|
||||
contentType,
|
||||
preview
|
||||
}) => {
|
||||
const form = new FormData()
|
||||
const pollOptions = poll.options || []
|
||||
|
@ -647,6 +648,9 @@ const postStatus = ({
|
|||
if (inReplyToStatusId) {
|
||||
form.append('in_reply_to_id', inReplyToStatusId)
|
||||
}
|
||||
if (preview) {
|
||||
form.append('preview', 'true')
|
||||
}
|
||||
|
||||
return fetch(MASTODON_POST_STATUS_URL, {
|
||||
body: form,
|
||||
|
@ -654,13 +658,7 @@ const postStatus = ({
|
|||
headers: authHeaders(credentials)
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json()
|
||||
} else {
|
||||
return {
|
||||
error: response
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((data) => data.error ? data : parseStatus(data))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,18 @@
|
|||
import { map } from 'lodash'
|
||||
import apiService from '../api/api.service.js'
|
||||
|
||||
const postStatus = ({ store, status, spoilerText, visibility, sensitive, poll, media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => {
|
||||
const postStatus = ({
|
||||
store,
|
||||
status,
|
||||
spoilerText,
|
||||
visibility,
|
||||
sensitive,
|
||||
poll,
|
||||
media = [],
|
||||
inReplyToStatusId = undefined,
|
||||
contentType = 'text/plain',
|
||||
preview = false
|
||||
}) => {
|
||||
const mediaIds = map(media, 'id')
|
||||
|
||||
return apiService.postStatus({
|
||||
|
@ -13,9 +24,11 @@ const postStatus = ({ store, status, spoilerText, visibility, sensitive, poll, m
|
|||
mediaIds,
|
||||
inReplyToStatusId,
|
||||
contentType,
|
||||
poll })
|
||||
poll,
|
||||
preview
|
||||
})
|
||||
.then((data) => {
|
||||
if (!data.error) {
|
||||
if (!data.error && !preview) {
|
||||
store.dispatch('addNewStatuses', {
|
||||
statuses: [data],
|
||||
timeline: 'friends',
|
||||
|
|
Loading…
Reference in a new issue