Convert Post component into script-setup format
This commit is contained in:
parent
2ac54a2ff9
commit
ac4b8e275a
11 changed files with 321 additions and 393 deletions
|
@ -7,19 +7,14 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Vue } from "vue-class-component"
|
/* eslint-disable-next-line no-undef */
|
||||||
import { Prop } from "vue-property-decorator"
|
const props = defineProps<{
|
||||||
|
address: string,
|
||||||
export default class CryptoAddress extends Vue {
|
}>()
|
||||||
|
|
||||||
@Prop()
|
|
||||||
address!: string
|
|
||||||
|
|
||||||
copyAddress() {
|
|
||||||
navigator.clipboard.writeText(this.address)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
function copyAddress() {
|
||||||
|
navigator.clipboard.writeText(props.address)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,6 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Vue } from "vue-class-component"
|
|
||||||
|
|
||||||
export default class Loader extends Vue {
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "../styles/theme";
|
@import "../styles/theme";
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
<router-link
|
<router-link
|
||||||
class="display-name"
|
class="display-name"
|
||||||
:to="{ name: 'profile', params: { profileId: post.account.id }}"
|
:to="{ name: 'profile', params: { profileId: post.account.id }}"
|
||||||
:title="authorName"
|
:title="getAuthorName()"
|
||||||
>
|
>
|
||||||
{{ authorName }}
|
{{ getAuthorName() }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="actor-address" :title="'@' + getActorAddress(post.account)">
|
<div class="actor-address" :title="'@' + getActorAddress(post.account)">
|
||||||
@{{ getActorAddress(post.account) }}
|
@{{ getActorAddress(post.account) }}
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
</a>
|
</a>
|
||||||
<span
|
<span
|
||||||
class="icon icon-small"
|
class="icon icon-small"
|
||||||
:title="visibilityDisplay"
|
:title="getVisibilityDisplay()"
|
||||||
>
|
>
|
||||||
<visibility-icon :visibility="post.visibility"></visibility-icon>
|
<visibility-icon :visibility="post.visibility"></visibility-icon>
|
||||||
</span>
|
</span>
|
||||||
|
@ -39,10 +39,10 @@
|
||||||
{{ formatDate(post.created_at) }}
|
{{ formatDate(post.created_at) }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="post-subheader" v-if="replyingTo.length > 0">
|
<div class="post-subheader" v-if="getReplyMentions().length > 0">
|
||||||
<span>replying to</span>
|
<span>replying to</span>
|
||||||
<router-link
|
<router-link
|
||||||
v-for="mention in replyingTo"
|
v-for="mention in getReplyMentions()"
|
||||||
:to="{ name: 'profile', params: { profileId: mention.id }}"
|
:to="{ name: 'profile', params: { profileId: mention.id }}"
|
||||||
:title="getActorAddress(mention)"
|
:title="getActorAddress(mention)"
|
||||||
:key="mention.id"
|
:key="mention.id"
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
@{{ mention.username }}
|
@{{ mention.username }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="post-content" ref="postContent" v-html="content"></div>
|
<div class="post-content" ref="postContentRef" v-html="getContent()"></div>
|
||||||
<div class="post-attachment" v-if="post.media_attachments.length > 0">
|
<div class="post-attachment" v-if="post.media_attachments.length > 0">
|
||||||
<template v-for="attachment in post.media_attachments" :key="attachment.id">
|
<template v-for="attachment in post.media_attachments" :key="attachment.id">
|
||||||
<img v-if="attachment.type === 'image'" :src="attachment.url">
|
<img v-if="attachment.type === 'image'" :src="attachment.url">
|
||||||
|
@ -110,17 +110,17 @@
|
||||||
<span>{{ post.favourites_count }}</span>
|
<span>{{ post.favourites_count }}</span>
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
v-if="ipfsUrl"
|
v-if="getIpfsUrl()"
|
||||||
class="icon"
|
class="icon"
|
||||||
title="Saved to IPFS"
|
title="Saved to IPFS"
|
||||||
:href="ipfsUrl"
|
:href="getIpfsUrl() || ''"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<img :src="require('@/assets/ipfs.svg')">
|
<img :src="require('@/assets/ipfs.svg')">
|
||||||
</a>
|
</a>
|
||||||
<router-link
|
<router-link
|
||||||
v-if="isTokenized"
|
v-if="isTokenized()"
|
||||||
class="icon tokenized"
|
class="icon tokenized"
|
||||||
title="View token"
|
title="View token"
|
||||||
:to="{ name: 'post-overlay', params: { postId: post.id }}"
|
:to="{ name: 'post-overlay', params: { postId: post.id }}"
|
||||||
|
@ -157,7 +157,7 @@
|
||||||
<button
|
<button
|
||||||
class="icon"
|
class="icon"
|
||||||
title="Mint NFT"
|
title="Mint NFT"
|
||||||
@click="hideMenu(); mintToken()"
|
@click="hideMenu(); onMintToken()"
|
||||||
>
|
>
|
||||||
<img :src="require('@/assets/forkawesome/diamond.svg')">
|
<img :src="require('@/assets/forkawesome/diamond.svg')">
|
||||||
<span>Mint NFT</span>
|
<span>Mint NFT</span>
|
||||||
|
@ -167,7 +167,7 @@
|
||||||
<button
|
<button
|
||||||
class="icon"
|
class="icon"
|
||||||
title="Delete post"
|
title="Delete post"
|
||||||
@click="hideMenu(); deletePost()"
|
@click="hideMenu(); onDeletePost()"
|
||||||
>
|
>
|
||||||
<img :src="require('@/assets/feather/trash.svg')">
|
<img :src="require('@/assets/feather/trash.svg')">
|
||||||
<span>Delete post</span>
|
<span>Delete post</span>
|
||||||
|
@ -200,9 +200,11 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options, Vue, setup } from "vue-class-component"
|
/* eslint-disable vue/no-mutating-props */
|
||||||
import { Prop } from "vue-property-decorator"
|
import { onMounted } from "vue"
|
||||||
|
import { $, $ref } from "vue/macros"
|
||||||
|
import { useRouter } from "vue-router"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
makePermanent,
|
makePermanent,
|
||||||
|
@ -221,7 +223,6 @@ import {
|
||||||
createRepost,
|
createRepost,
|
||||||
deleteRepost,
|
deleteRepost,
|
||||||
} from "@/api/posts"
|
} from "@/api/posts"
|
||||||
import { Profile } from "@/api/users"
|
|
||||||
import Avatar from "@/components/Avatar.vue"
|
import Avatar from "@/components/Avatar.vue"
|
||||||
import CryptoAddress from "@/components/CryptoAddress.vue"
|
import CryptoAddress from "@/components/CryptoAddress.vue"
|
||||||
import PostEditor from "@/components/PostEditor.vue"
|
import PostEditor from "@/components/PostEditor.vue"
|
||||||
|
@ -239,300 +240,283 @@ interface PaymentOption {
|
||||||
address: string;
|
address: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Options({
|
const router = useRouter()
|
||||||
components: {
|
const { currentUser, ensureAuthToken } = $(useCurrentUser())
|
||||||
Avatar,
|
const { instance, getActorAddress } = $(useInstanceInfo())
|
||||||
CryptoAddress,
|
|
||||||
PostEditor,
|
/* eslint-disable-next-line no-undef */
|
||||||
VisibilityIcon,
|
const props = defineProps<{
|
||||||
},
|
post: Post,
|
||||||
|
highlighted: boolean,
|
||||||
|
inThread: boolean,
|
||||||
|
}>()
|
||||||
|
|
||||||
|
/* eslint-disable-next-line no-undef, func-call-spacing */
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: "highlight", postId: string | null): void,
|
||||||
|
(event: "navigate-to", postId: string): void,
|
||||||
|
(event: "comment-created", post: Post): void,
|
||||||
|
(event: "post-deleted"): void,
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const postContentRef = $ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
let commentFormVisible = $ref(false)
|
||||||
|
let menuVisible = $ref(false)
|
||||||
|
let selectedPaymentAddress = $ref<string | null>(null)
|
||||||
|
let isWaitingForToken = $ref(false)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (postContentRef === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const mentions = postContentRef.getElementsByClassName("mention")
|
||||||
|
for (const mentionElement of Array.from(mentions)) {
|
||||||
|
mentionElement.addEventListener("click", (event: Event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
const mention = props.post.mentions
|
||||||
|
.find((mention) => mention.url === mentionElement.getAttribute("href"))
|
||||||
|
if (mention) {
|
||||||
|
router.push({ name: "profile", params: { profileId: mention.id } })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
export default class PostComponent extends Vue {
|
|
||||||
|
|
||||||
@Prop()
|
function getAuthorName(): string {
|
||||||
post!: Post
|
return props.post.account.display_name || props.post.account.username
|
||||||
|
}
|
||||||
|
|
||||||
@Prop()
|
function highlight(postId: string | null) {
|
||||||
highlighted = false
|
emit("highlight", postId)
|
||||||
|
}
|
||||||
|
|
||||||
@Prop()
|
function navigateTo(postId: string) {
|
||||||
inThread = false
|
if (props.inThread) {
|
||||||
|
emit("navigate-to", postId)
|
||||||
|
} else {
|
||||||
|
router.push({ name: "post", params: { postId: postId } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVisibilityDisplay(): string {
|
||||||
|
return VISIBILITY_MAP[props.post.visibility]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContent(): string {
|
||||||
|
const content = addGreentext(props.post.content)
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReplyMentions(): Mention[] {
|
||||||
|
if (props.post.in_reply_to_id === null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return props.post.mentions
|
||||||
|
}
|
||||||
|
|
||||||
|
function canReply(): boolean {
|
||||||
|
return currentUser !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCommentCreated(post: Post) {
|
||||||
commentFormVisible = false
|
commentFormVisible = false
|
||||||
menuVisible = false
|
emit("comment-created", post)
|
||||||
|
}
|
||||||
|
|
||||||
private store = setup(() => {
|
function canRepost(): boolean {
|
||||||
const { currentUser, ensureAuthToken } = useCurrentUser()
|
return currentUser !== null && props.post.visibility === "public"
|
||||||
const { instance, getActorAddress } = useInstanceInfo()
|
}
|
||||||
return { currentUser, ensureAuthToken, instance, getActorAddress }
|
|
||||||
})
|
|
||||||
|
|
||||||
$refs!: { postContent: HTMLElement }
|
async function toggleRepost() {
|
||||||
|
if (!currentUser) {
|
||||||
mounted() {
|
return
|
||||||
const mentions = this.$refs.postContent.getElementsByClassName("mention")
|
|
||||||
for (const mentionElement of Array.from(mentions)) {
|
|
||||||
mentionElement.addEventListener("click", (event: Event) => {
|
|
||||||
event.preventDefault()
|
|
||||||
const mention = this.post.mentions
|
|
||||||
.find((mention) => mention.url === mentionElement.getAttribute("href"))
|
|
||||||
if (mention) {
|
|
||||||
this.$router.push({ name: "profile", params: { profileId: mention.id } })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const authToken = ensureAuthToken()
|
||||||
get authorName(): string {
|
let updatedPost
|
||||||
return this.post.account.display_name || this.post.account.username
|
try {
|
||||||
}
|
if (props.post.reblogged) {
|
||||||
|
updatedPost = await deleteRepost(authToken, props.post.id)
|
||||||
getActorAddress(profile: Profile | Mention): string {
|
|
||||||
return this.store.getActorAddress(profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
highlight(postId: string | null) {
|
|
||||||
this.$emit("highlight", postId)
|
|
||||||
}
|
|
||||||
|
|
||||||
navigateTo(postId: string) {
|
|
||||||
if (this.inThread) {
|
|
||||||
this.$emit("navigate-to", postId)
|
|
||||||
} else {
|
} else {
|
||||||
this.$router.push({ name: "post", params: { postId: postId } })
|
updatedPost = await createRepost(authToken, props.post.id)
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
props.post.reblogs_count = updatedPost.reblogs_count
|
||||||
|
props.post.reblogged = updatedPost.reblogged
|
||||||
|
}
|
||||||
|
|
||||||
get visibilityDisplay(): string {
|
function canLike(): boolean {
|
||||||
return VISIBILITY_MAP[this.post.visibility]
|
return currentUser !== null && props.post.visibility === "public"
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleLike() {
|
||||||
|
if (!currentUser) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
const authToken = ensureAuthToken()
|
||||||
formatDate(isoDate: string): string {
|
let updatedPost
|
||||||
return formatDate(isoDate)
|
try {
|
||||||
}
|
if (props.post.favourited) {
|
||||||
|
updatedPost = await unfavourite(authToken, props.post.id)
|
||||||
get content(): string {
|
} else {
|
||||||
const content = addGreentext(this.post.content)
|
updatedPost = await favourite(authToken, props.post.id)
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
get replyingTo(): Mention[] {
|
|
||||||
if (this.post.in_reply_to_id === null) {
|
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
return this.post.mentions
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
props.post.favourites_count = updatedPost.favourites_count
|
||||||
|
props.post.favourited = updatedPost.favourited
|
||||||
|
}
|
||||||
|
|
||||||
canReply(): boolean {
|
function toggleMenu() {
|
||||||
return this.store.currentUser !== null
|
menuVisible = !menuVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideMenu() {
|
||||||
|
menuVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIpfsUrl(): string | null {
|
||||||
|
const gatewayUrl = instance?.ipfs_gateway_url
|
||||||
|
if (
|
||||||
|
!gatewayUrl ||
|
||||||
|
props.post.ipfs_cid === null ||
|
||||||
|
isTokenized() ||
|
||||||
|
isWaitingForToken
|
||||||
|
) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
return `${gatewayUrl}/ipfs/${props.post.ipfs_cid}`
|
||||||
|
}
|
||||||
|
|
||||||
onCommentCreated(post: Post) {
|
function canSaveToIpfs(): boolean {
|
||||||
this.commentFormVisible = false
|
return (
|
||||||
this.$emit("comment-created", post)
|
Boolean(instance?.ipfs_gateway_url) &&
|
||||||
|
props.post.account.id === currentUser?.id &&
|
||||||
|
props.post.visibility === "public" &&
|
||||||
|
props.post.ipfs_cid === null &&
|
||||||
|
!isWaitingForToken
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveToIpfs() {
|
||||||
|
if (!currentUser || !instance || !instance.ipfs_gateway_url) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
const authToken = ensureAuthToken()
|
||||||
|
const { ipfs_cid } = await makePermanent(authToken, props.post.id)
|
||||||
|
props.post.ipfs_cid = ipfs_cid
|
||||||
|
}
|
||||||
|
|
||||||
canRepost(): boolean {
|
function canDeletePost(): boolean {
|
||||||
return this.store.currentUser !== null && this.post.visibility === "public"
|
return props.post.account.id === currentUser?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDeletePost() {
|
||||||
|
if (confirm("Are you sure you want to delete this post?")) {
|
||||||
|
const authToken = ensureAuthToken()
|
||||||
|
await deletePost(authToken, props.post.id)
|
||||||
|
emit("post-deleted")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async toggleRepost() {
|
function getPaymentOptions(): PaymentOption[] {
|
||||||
if (!this.store.currentUser) {
|
const items = []
|
||||||
return
|
for (const [code, name] of CRYPTOCURRENCIES) {
|
||||||
|
const symbol = `$${code}`
|
||||||
|
const field = props.post.account.fields.find(item => item.name === symbol)
|
||||||
|
if (!field) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
const authToken = this.store.ensureAuthToken()
|
const address = field.value.trim()
|
||||||
let post
|
items.push({ code, name, address })
|
||||||
try {
|
|
||||||
if (this.post.reblogged) {
|
|
||||||
post = await deleteRepost(authToken, this.post.id)
|
|
||||||
} else {
|
|
||||||
post = await createRepost(authToken, this.post.id)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.post.reblogs_count = post.reblogs_count
|
|
||||||
this.post.reblogged = post.reblogged
|
|
||||||
}
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
canLike(): boolean {
|
function togglePaymentAddress(payment: PaymentOption) {
|
||||||
return this.store.currentUser !== null && this.post.visibility === "public"
|
selectedPaymentAddress = selectedPaymentAddress === payment.address ? null : payment.address
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTokenized(): boolean {
|
||||||
|
return props.post.token_id !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
function canMintToken(): boolean {
|
||||||
|
return (
|
||||||
|
Boolean(instance?.ipfs_gateway_url) &&
|
||||||
|
Boolean(instance?.blockchain_contract_address) &&
|
||||||
|
Boolean(instance?.blockchain_features?.minter) &&
|
||||||
|
props.post.account.id === currentUser?.id &&
|
||||||
|
props.post.visibility === "public" &&
|
||||||
|
Boolean(currentUser?.wallet_address) &&
|
||||||
|
!isTokenized() &&
|
||||||
|
!isWaitingForToken
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onMintToken() {
|
||||||
|
if (
|
||||||
|
!currentUser ||
|
||||||
|
!currentUser.wallet_address ||
|
||||||
|
!instance ||
|
||||||
|
!instance.blockchain_contract_address
|
||||||
|
) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
if (isTokenized() || isWaitingForToken) {
|
||||||
async toggleLike() {
|
return
|
||||||
if (!this.store.currentUser) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const authToken = this.store.ensureAuthToken()
|
|
||||||
let post
|
|
||||||
try {
|
|
||||||
if (this.post.favourited) {
|
|
||||||
post = await unfavourite(authToken, this.post.id)
|
|
||||||
} else {
|
|
||||||
post = await favourite(authToken, this.post.id)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.post.favourites_count = post.favourites_count
|
|
||||||
this.post.favourited = post.favourited
|
|
||||||
}
|
}
|
||||||
|
const authToken = ensureAuthToken()
|
||||||
toggleMenu() {
|
isWaitingForToken = true
|
||||||
this.menuVisible = !this.menuVisible
|
if (props.post.ipfs_cid === null) {
|
||||||
|
const { ipfs_cid } = await makePermanent(authToken, props.post.id)
|
||||||
|
props.post.ipfs_cid = ipfs_cid
|
||||||
}
|
}
|
||||||
|
const tokenUri = `ipfs://${props.post.ipfs_cid}`
|
||||||
hideMenu() {
|
console.info("token URI:", tokenUri)
|
||||||
this.menuVisible = false
|
let signature
|
||||||
|
try {
|
||||||
|
signature = await getMintingAuthorization(authToken, props.post.id)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
isWaitingForToken = false
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
const signer = await getWallet()
|
||||||
get ipfsUrl(): string | null {
|
if (!signer) {
|
||||||
const gatewayUrl = this.store.instance?.ipfs_gateway_url
|
isWaitingForToken = false
|
||||||
if (
|
return
|
||||||
!gatewayUrl ||
|
|
||||||
this.post.ipfs_cid === null ||
|
|
||||||
this.isTokenized ||
|
|
||||||
this.isWaitingForToken
|
|
||||||
) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return `${gatewayUrl}/ipfs/${this.post.ipfs_cid}`
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
canSaveToIpfs(): boolean {
|
const transaction = await mintToken(
|
||||||
return (
|
instance.blockchain_contract_address,
|
||||||
Boolean(this.store.instance?.ipfs_gateway_url) &&
|
signer,
|
||||||
this.post.account.id === this.store.currentUser?.id &&
|
currentUser.wallet_address,
|
||||||
this.post.visibility === "public" &&
|
tokenUri,
|
||||||
this.post.ipfs_cid === null &&
|
signature,
|
||||||
!this.isWaitingForToken
|
|
||||||
)
|
)
|
||||||
|
await onTokenMinted(authToken, props.post.id, transaction.hash)
|
||||||
|
} catch (error) {
|
||||||
|
// User has rejected tx
|
||||||
|
isWaitingForToken = false
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
// Wait until the server sees the tx
|
||||||
async saveToIpfs() {
|
const intervalId = setInterval(async () => {
|
||||||
const { currentUser, instance } = this.store
|
const updatedPost = await getPost(authToken, props.post.id)
|
||||||
if (!currentUser || !instance || !instance.ipfs_gateway_url) {
|
if (updatedPost.token_id) {
|
||||||
return
|
clearInterval(intervalId)
|
||||||
|
isWaitingForToken = false
|
||||||
|
// Update post
|
||||||
|
props.post.token_id = updatedPost.token_id
|
||||||
|
props.post.token_tx_id = updatedPost.token_tx_id
|
||||||
}
|
}
|
||||||
const authToken = this.store.ensureAuthToken()
|
}, 5000)
|
||||||
const { ipfs_cid } = await makePermanent(authToken, this.post.id)
|
|
||||||
this.post.ipfs_cid = ipfs_cid
|
|
||||||
}
|
|
||||||
|
|
||||||
canDeletePost(): boolean {
|
|
||||||
return this.post.account.id === this.store.currentUser?.id
|
|
||||||
}
|
|
||||||
|
|
||||||
async deletePost() {
|
|
||||||
if (confirm("Are you sure you want to delete this post?")) {
|
|
||||||
const authToken = this.store.ensureAuthToken()
|
|
||||||
await deletePost(authToken, this.post.id)
|
|
||||||
this.$emit("post-deleted")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPaymentOptions(): PaymentOption[] {
|
|
||||||
const items = []
|
|
||||||
for (const [code, name] of CRYPTOCURRENCIES) {
|
|
||||||
const symbol = `$${code}`
|
|
||||||
const field = this.post.account.fields.find(item => item.name === symbol)
|
|
||||||
if (!field) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const address = field.value.trim()
|
|
||||||
items.push({ code, name, address })
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedPaymentAddress: string | null = null
|
|
||||||
|
|
||||||
togglePaymentAddress(payment: PaymentOption) {
|
|
||||||
this.selectedPaymentAddress = this.selectedPaymentAddress === payment.address ? null : payment.address
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTokenized(): boolean {
|
|
||||||
return this.post.token_id !== null
|
|
||||||
}
|
|
||||||
|
|
||||||
isWaitingForToken = false
|
|
||||||
|
|
||||||
canMintToken(): boolean {
|
|
||||||
return (
|
|
||||||
Boolean(this.store.instance?.ipfs_gateway_url) &&
|
|
||||||
Boolean(this.store.instance?.blockchain_contract_address) &&
|
|
||||||
Boolean(this.store.instance?.blockchain_features?.minter) &&
|
|
||||||
this.post.account.id === this.store.currentUser?.id &&
|
|
||||||
this.post.visibility === "public" &&
|
|
||||||
Boolean(this.store.currentUser?.wallet_address) &&
|
|
||||||
!this.isTokenized &&
|
|
||||||
!this.isWaitingForToken
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async mintToken() {
|
|
||||||
const { currentUser, instance } = this.store
|
|
||||||
if (
|
|
||||||
!currentUser ||
|
|
||||||
!currentUser.wallet_address ||
|
|
||||||
!instance ||
|
|
||||||
!instance.blockchain_contract_address
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.isTokenized || this.isWaitingForToken) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const authToken = this.store.ensureAuthToken()
|
|
||||||
this.isWaitingForToken = true
|
|
||||||
if (this.post.ipfs_cid === null) {
|
|
||||||
const { ipfs_cid } = await makePermanent(authToken, this.post.id)
|
|
||||||
this.post.ipfs_cid = ipfs_cid
|
|
||||||
}
|
|
||||||
const tokenUri = `ipfs://${this.post.ipfs_cid}`
|
|
||||||
console.info("token URI:", tokenUri)
|
|
||||||
let signature
|
|
||||||
try {
|
|
||||||
signature = await getMintingAuthorization(authToken, this.post.id)
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
this.isWaitingForToken = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const signer = await getWallet()
|
|
||||||
if (!signer) {
|
|
||||||
this.isWaitingForToken = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const transaction = await mintToken(
|
|
||||||
instance.blockchain_contract_address,
|
|
||||||
signer,
|
|
||||||
currentUser.wallet_address,
|
|
||||||
tokenUri,
|
|
||||||
signature,
|
|
||||||
)
|
|
||||||
await onTokenMinted(authToken, this.post.id, transaction.hash)
|
|
||||||
} catch (error) {
|
|
||||||
// User has rejected tx
|
|
||||||
this.isWaitingForToken = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Wait until the server sees the tx
|
|
||||||
const intervalId = setInterval(async () => {
|
|
||||||
const post = await getPost(authToken, this.post.id)
|
|
||||||
if (post.token_id) {
|
|
||||||
clearInterval(intervalId)
|
|
||||||
this.isWaitingForToken = false
|
|
||||||
// Update post
|
|
||||||
this.post.token_id = post.token_id
|
|
||||||
this.post.token_tx_id = post.token_tx_id
|
|
||||||
}
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
</menu>
|
</menu>
|
||||||
</div>
|
</div>
|
||||||
<div class="character-counter" title="Characters left">
|
<div class="character-counter" title="Characters left">
|
||||||
{{ characterCounter }}
|
{{ getCharacterCount() }}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@ -78,7 +78,7 @@
|
||||||
<button
|
<button
|
||||||
class="btn"
|
class="btn"
|
||||||
type="submit"
|
type="submit"
|
||||||
:disabled="characterCounter < 0"
|
:disabled="getCharacterCount() < 0"
|
||||||
@click.prevent="publish()"
|
@click.prevent="publish()"
|
||||||
>Publish</button>
|
>Publish</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -188,7 +188,7 @@ export default class PostEditor extends Vue {
|
||||||
this.visibilityMenuVisible = false
|
this.visibilityMenuVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
get characterCounter(): number {
|
getCharacterCount(): number {
|
||||||
if (!this.store.instance) {
|
if (!this.store.instance) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,10 @@ import PostOrRepost from "@/components/PostOrRepost.vue"
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
posts: PostObject[],
|
posts: PostObject[],
|
||||||
}>()
|
}>()
|
||||||
/* eslint-disable-next-line no-undef */
|
/* eslint-disable-next-line no-undef, func-call-spacing */
|
||||||
const emit = defineEmits<{(event: "load-next-page", maxId: string): void}>()
|
const emit = defineEmits<{
|
||||||
|
(event: "load-next-page", maxId: string): void,
|
||||||
|
}>()
|
||||||
|
|
||||||
let initialPostCount: number | null = null
|
let initialPostCount: number | null = null
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,18 @@
|
||||||
</div>
|
</div>
|
||||||
<post
|
<post
|
||||||
:post="post.reblog"
|
:post="post.reblog"
|
||||||
|
:highlighted="false"
|
||||||
|
:in-thread="false"
|
||||||
@post-deleted="onPostDeleted(post.reblog.id); onPostDeleted(post.id)"
|
@post-deleted="onPostDeleted(post.reblog.id); onPostDeleted(post.id)"
|
||||||
></post>
|
></post>
|
||||||
</template>
|
</template>
|
||||||
<post v-else :post="post" @post-deleted="onPostDeleted(post.id)"></post>
|
<post
|
||||||
|
v-else
|
||||||
|
:post="post"
|
||||||
|
:highlighted="false"
|
||||||
|
:in-thread="false"
|
||||||
|
@post-deleted="onPostDeleted(post.id)"
|
||||||
|
></post>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<avatar :profile="profile"></avatar>
|
<avatar :profile="profile"></avatar>
|
||||||
<div class="name-group">
|
<div class="name-group">
|
||||||
<div class="display-name">{{ profile.display_name || profile.username }}</div>
|
<div class="display-name">{{ profile.display_name || profile.username }}</div>
|
||||||
<div class="actor-address">@{{ actorAddress }}</div>
|
<div class="actor-address">@{{ getActorAddress(profile) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bio" v-html="profile.note"></div>
|
<div class="bio" v-html="profile.note"></div>
|
||||||
|
@ -22,37 +22,18 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options, Vue, setup } from "vue-class-component"
|
|
||||||
import { Prop } from "vue-property-decorator"
|
|
||||||
|
|
||||||
import { Profile } from "@/api/users"
|
import { Profile } from "@/api/users"
|
||||||
import Avatar from "@/components/Avatar.vue"
|
import Avatar from "@/components/Avatar.vue"
|
||||||
import { useInstanceInfo } from "@/store/instance"
|
import { useInstanceInfo } from "@/store/instance"
|
||||||
|
|
||||||
@Options({
|
/* eslint-disable-next-line no-undef */
|
||||||
components: {
|
defineProps<{
|
||||||
Avatar,
|
profile: Profile,
|
||||||
},
|
compact: boolean,
|
||||||
})
|
}>()
|
||||||
export default class ProfileCard extends Vue {
|
|
||||||
|
|
||||||
@Prop()
|
const { getActorAddress } = useInstanceInfo()
|
||||||
profile!: Profile
|
|
||||||
|
|
||||||
@Prop()
|
|
||||||
compact = false
|
|
||||||
|
|
||||||
private store = setup(() => {
|
|
||||||
const { instance, getActorAddress } = useInstanceInfo()
|
|
||||||
return { instance, getActorAddress }
|
|
||||||
})
|
|
||||||
|
|
||||||
get actorAddress(): string {
|
|
||||||
return this.store.getActorAddress(this.profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
|
@ -10,21 +10,19 @@
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Vue } from "vue-class-component"
|
import { $ref } from "vue/macros"
|
||||||
|
import { useRouter } from "vue-router"
|
||||||
|
|
||||||
export default class Search extends Vue {
|
const router = useRouter()
|
||||||
|
let q = $ref("")
|
||||||
|
|
||||||
|
function clear() {
|
||||||
q = ""
|
q = ""
|
||||||
|
}
|
||||||
|
|
||||||
clear() {
|
function search() {
|
||||||
this.q = ""
|
router.push({ name: "search", query: { q: q } })
|
||||||
}
|
|
||||||
|
|
||||||
search() {
|
|
||||||
this.$router.push({ name: "search", query: { q: this.q } })
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -8,35 +8,14 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options, Vue, setup } from "vue-class-component"
|
import { $ } from "vue/macros"
|
||||||
|
|
||||||
import { InstanceInfo } from "@/api/instance"
|
|
||||||
import Sidebar from "@/components/Sidebar.vue"
|
import Sidebar from "@/components/Sidebar.vue"
|
||||||
import { useInstanceInfo } from "@/store/instance"
|
import { useInstanceInfo } from "@/store/instance"
|
||||||
import { renderMarkdown } from "@/utils/markdown"
|
import { renderMarkdown } from "@/utils/markdown"
|
||||||
|
|
||||||
@Options({
|
const { instance } = $(useInstanceInfo())
|
||||||
components: {
|
|
||||||
Sidebar,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
export default class AboutPage extends Vue {
|
|
||||||
|
|
||||||
private store = setup(() => {
|
|
||||||
const { instance } = useInstanceInfo()
|
|
||||||
return { instance }
|
|
||||||
})
|
|
||||||
|
|
||||||
get instance(): InstanceInfo | null {
|
|
||||||
return this.store.instance
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMarkdown(description: string): string {
|
|
||||||
return renderMarkdown(description)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
|
@ -8,8 +8,9 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options, Vue, setup } from "vue-class-component"
|
import { onMounted } from "vue"
|
||||||
|
import { $ref } from "vue/macros"
|
||||||
|
|
||||||
import { Post, getHomeTimeline } from "@/api/posts"
|
import { Post, getHomeTimeline } from "@/api/posts"
|
||||||
import PostEditor from "@/components/PostEditor.vue"
|
import PostEditor from "@/components/PostEditor.vue"
|
||||||
|
@ -17,37 +18,23 @@ import PostList from "@/components/PostList.vue"
|
||||||
import Sidebar from "@/components/Sidebar.vue"
|
import Sidebar from "@/components/Sidebar.vue"
|
||||||
import { useCurrentUser } from "@/store/user"
|
import { useCurrentUser } from "@/store/user"
|
||||||
|
|
||||||
@Options({
|
const { ensureAuthToken } = useCurrentUser()
|
||||||
components: {
|
|
||||||
PostEditor,
|
let posts = $ref<Post[]>([])
|
||||||
PostList,
|
|
||||||
Sidebar,
|
onMounted(async () => {
|
||||||
},
|
const authToken = ensureAuthToken()
|
||||||
|
posts = await getHomeTimeline(authToken)
|
||||||
})
|
})
|
||||||
export default class HomeTimeline extends Vue {
|
|
||||||
|
|
||||||
private store = setup(() => {
|
function insertPost(post: Post) {
|
||||||
const { ensureAuthToken } = useCurrentUser()
|
posts = [post, ...posts]
|
||||||
return { ensureAuthToken }
|
}
|
||||||
})
|
|
||||||
|
|
||||||
posts: Post[] = []
|
|
||||||
|
|
||||||
async created() {
|
|
||||||
const authToken = this.store.ensureAuthToken()
|
|
||||||
this.posts = await getHomeTimeline(authToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
insertPost(post: Post) {
|
|
||||||
this.posts = [post, ...this.posts]
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadNextPage(maxId: string) {
|
|
||||||
const authToken = this.store.ensureAuthToken()
|
|
||||||
const posts = await getHomeTimeline(authToken, maxId)
|
|
||||||
this.posts.push(...posts)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
async function loadNextPage(maxId: string) {
|
||||||
|
const authToken = ensureAuthToken()
|
||||||
|
const nextPage = await getHomeTimeline(authToken, maxId)
|
||||||
|
posts.push(...nextPage)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,8 @@
|
||||||
<post
|
<post
|
||||||
v-if="notification.status"
|
v-if="notification.status"
|
||||||
:post="notification.status"
|
:post="notification.status"
|
||||||
|
:highlighted="false"
|
||||||
|
:in-thread="false"
|
||||||
@post-deleted="onPostDeleted(index)"
|
@post-deleted="onPostDeleted(index)"
|
||||||
></post>
|
></post>
|
||||||
<router-link
|
<router-link
|
||||||
|
|
Loading…
Reference in a new issue