Add preview button to post editor

This commit is contained in:
silverpill 2022-12-20 17:52:33 +00:00
parent 5191727639
commit 1a9ae2cb16
7 changed files with 90 additions and 12 deletions

View file

@ -1,6 +1,6 @@
import { BACKEND_URL } from "@/constants" import { BACKEND_URL } from "@/constants"
import { PAGE_SIZE, http } from "./common" import { PAGE_SIZE, http } from "./common"
import { Profile } from "./users" import { defaultProfile, Profile } from "./users"
export interface Attachment { export interface Attachment {
id: string; id: string;
@ -155,6 +155,45 @@ export async function getPostContext(
return data return data
} }
export async function previewPost(
authToken: string,
content: string,
): Promise<Post> {
const url = `${BACKEND_URL}/api/v1/statuses/preview`
const response = await http(url, {
method: "POST",
json: {
status: content,
content_type: "text/markdown",
},
authToken,
})
const data = await response.json()
return {
id: "",
uri: "",
created_at: "",
edited_at: null,
account: defaultProfile(),
content: data.content,
in_reply_to_id: null,
reblog: null,
visibility: Visibility.Public,
replies_count: 0,
favourites_count: 0,
reblogs_count: 0,
media_attachments: [],
mentions: [],
tags: [],
favourited: false,
reblogged: false,
ipfs_cid: null,
token_id: null,
token_tx_id: null,
links: [],
}
}
export interface PostData { export interface PostData {
content: string; content: string;
in_reply_to_id: string | null; in_reply_to_id: string | null;

View file

@ -44,7 +44,7 @@ export interface Profile {
statuses_count: number; statuses_count: number;
} }
export function guest(): Profile { export function defaultProfile(): Profile {
return { return {
id: "", id: "",
username: "", username: "",

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye-off"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>

After

Width:  |  Height:  |  Size: 462 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>

After

Width:  |  Height:  |  Size: 318 B

View file

@ -11,12 +11,18 @@
<textarea <textarea
id="content" id="content"
ref="postFormContentRef" ref="postFormContentRef"
v-show="preview === null"
v-model="content" v-model="content"
@input="saveLocalDraft()" @input="saveLocalDraft()"
rows="1" rows="1"
required required
:placeholder="inReplyTo ? 'Your reply' : 'What\'s on your mind?'" :placeholder="inReplyTo ? 'Your reply' : 'What\'s on your mind?'"
></textarea> ></textarea>
<post-content
v-if="preview"
:post="preview"
@click.prevent=""
></post-content>
<div v-if="attachments.length > 0" class="attachments"> <div v-if="attachments.length > 0" class="attachments">
<div <div
v-for="(attachment, index) in attachments" v-for="(attachment, index) in attachments"
@ -75,6 +81,17 @@
</li> </li>
</menu> </menu>
</div> </div>
<div class="toolbar-space"></div>
<button
v-if="canPreview()"
type="button"
class="icon"
title="Toggle preview"
@click="togglePreview()"
>
<img v-if="preview === null" :src="require('@/assets/feather/eye.svg')">
<img v-else :src="require('@/assets/feather/eye-off.svg')">
</button>
<div class="character-counter" title="Characters left"> <div class="character-counter" title="Characters left">
{{ getCharacterCount() }} {{ getCharacterCount() }}
</div> </div>
@ -106,16 +123,18 @@ import { nextTick, onMounted } from "vue"
import { $, $computed, $ref } from "vue/macros" import { $, $computed, $ref } from "vue/macros"
import { import {
Visibility, createPost,
VISIBILITY_MAP, previewPost,
uploadAttachment,
Attachment,
Mention, Mention,
Post, Post,
createPost, Visibility,
Attachment, VISIBILITY_MAP,
uploadAttachment,
} from "@/api/posts" } from "@/api/posts"
import { User } from "@/api/users" import { User } from "@/api/users"
import Avatar from "@/components/Avatar.vue" import Avatar from "@/components/Avatar.vue"
import PostContent from "@/components/PostContent.vue"
import VisibilityIcon from "@/components/VisibilityIcon.vue" import VisibilityIcon from "@/components/VisibilityIcon.vue"
import { useInstanceInfo } from "@/store/instance" import { useInstanceInfo } from "@/store/instance"
import { useCurrentUser } from "@/store/user" import { useCurrentUser } from "@/store/user"
@ -147,6 +166,7 @@ let attachments = $ref<Attachment[]>([])
let visibility = $ref(Visibility.Public) let visibility = $ref(Visibility.Public)
let visibilityMenuVisible = $ref(false) let visibilityMenuVisible = $ref(false)
let preview = $ref<Post | null>(null)
let isLoading = $ref(false) let isLoading = $ref(false)
let errorMessage = $ref<string | null>(null) let errorMessage = $ref<string | null>(null)
@ -173,6 +193,7 @@ if (props.inReplyTo && props.inReplyTo.visibility !== Visibility.Public) {
onMounted(() => { onMounted(() => {
if (postFormContentRef) { if (postFormContentRef) {
setupAutoResize(postFormContentRef) setupAutoResize(postFormContentRef)
triggerResize(postFormContentRef)
} }
}) })
@ -236,6 +257,18 @@ function getCharacterCount(): number {
return (instance.post_character_limit - content.length) return (instance.post_character_limit - content.length)
} }
function canPreview(): boolean {
return content.length > 0
}
async function togglePreview() {
if (preview === null) {
preview = await previewPost(ensureAuthToken(), content)
} else {
preview = null
}
}
function canPublish(): boolean { function canPublish(): boolean {
return getCharacterCount() >= 0 && !isLoading return getCharacterCount() >= 0 && !isLoading
} }
@ -266,6 +299,7 @@ async function publish() {
removeLocalDraft() removeLocalDraft()
content = "" content = ""
attachments = [] attachments = []
preview = null
if (postFormContentRef) { if (postFormContentRef) {
await nextTick() await nextTick()
triggerResize(postFormContentRef) triggerResize(postFormContentRef)
@ -350,9 +384,12 @@ $line-height: 1.5;
gap: calc($block-inner-padding / 2); gap: calc($block-inner-padding / 2);
padding: calc($block-inner-padding / 1.5) $block-inner-padding; padding: calc($block-inner-padding / 1.5) $block-inner-padding;
.toolbar-space {
flex-grow: 1;
}
.character-counter { .character-counter {
font-weight: bold; font-weight: bold;
margin-left: auto;
} }
.submit-btn-small { .submit-btn-small {

View file

@ -110,7 +110,7 @@ import { onMounted, watch } from "vue"
import { $, $$, $computed, $ref } from "vue/macros" import { $, $$, $computed, $ref } from "vue/macros"
import { searchProfilesByEthereumAddress } from "@/api/search" import { searchProfilesByEthereumAddress } from "@/api/search"
import { guest, Profile, ProfileWrapper } from "@/api/users" import { defaultProfile, Profile, ProfileWrapper } from "@/api/users"
import { import {
cancelSubscription, cancelSubscription,
getSubscriptionConfig, getSubscriptionConfig,
@ -137,7 +137,7 @@ const { instance } = $(useInstanceInfo())
const { connectWallet: connectEthereumWallet } = useWallet() const { connectWallet: connectEthereumWallet } = useWallet()
const recipient = new ProfileWrapper(props.profile) const recipient = new ProfileWrapper(props.profile)
const recipientEthereumAddress = recipient.getVerifiedEthereumAddress() const recipientEthereumAddress = recipient.getVerifiedEthereumAddress()
let sender = $ref<ProfileWrapper>(new ProfileWrapper(currentUser || guest())) let sender = $ref(new ProfileWrapper(currentUser || defaultProfile()))
let { walletAddress, walletError, getSigner } = $(useWallet()) let { walletAddress, walletError, getSigner } = $(useWallet())
let subscriptionsEnabled = $ref<boolean | null>(null) let subscriptionsEnabled = $ref<boolean | null>(null)
let subscriptionConfig = $ref<SubscriptionConfig | null>(null) let subscriptionConfig = $ref<SubscriptionConfig | null>(null)

View file

@ -124,7 +124,7 @@ import {
getPricePerMonth, getPricePerMonth,
Invoice, Invoice,
} from "@/api/subscriptions-monero" } from "@/api/subscriptions-monero"
import { guest, Profile, ProfilePaymentOption, ProfileWrapper } from "@/api/users" import { defaultProfile, Profile, ProfilePaymentOption, ProfileWrapper } from "@/api/users"
import Avatar from "@/components/Avatar.vue" import Avatar from "@/components/Avatar.vue"
import Loader from "@/components/Loader.vue" import Loader from "@/components/Loader.vue"
import QrCode from "@/components/QrCode.vue" import QrCode from "@/components/QrCode.vue"
@ -142,7 +142,7 @@ const { currentUser } = $(useCurrentUser())
const recipient = new ProfileWrapper(props.profile) const recipient = new ProfileWrapper(props.profile)
const senderAcct = $ref("") const senderAcct = $ref("")
let senderError = $ref<string | null>(null) let senderError = $ref<string | null>(null)
let sender = $ref<ProfileWrapper>(new ProfileWrapper(currentUser || guest())) let sender = $ref(new ProfileWrapper(currentUser || defaultProfile()))
let subscriptionOption = $ref<ProfilePaymentOption | null>(null) let subscriptionOption = $ref<ProfilePaymentOption | null>(null)
let subscriptionDetails = $ref<SubscriptionDetails | null>(null) let subscriptionDetails = $ref<SubscriptionDetails | null>(null)
const paymentDuration = $ref<number>(1) const paymentDuration = $ref<number>(1)