Add preview button to post editor
This commit is contained in:
parent
5191727639
commit
1a9ae2cb16
7 changed files with 90 additions and 12 deletions
|
@ -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;
|
||||||
|
|
|
@ -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: "",
|
||||||
|
|
1
src/assets/feather/eye-off.svg
Normal file
1
src/assets/feather/eye-off.svg
Normal 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 |
1
src/assets/feather/eye.svg
Normal file
1
src/assets/feather/eye.svg
Normal 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 |
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue