From 72ead31a9d62dc1d279cb68ef5b1fda56d8af0af Mon Sep 17 00:00:00 2001 From: Lucio Rubens Date: Wed, 21 Jan 2026 09:09:37 -0300 Subject: [PATCH 01/11] feat: add repost message handler --- packages/lib-api-types/src/posts/index.ts | 9 +++ packages/reader-main/src/messages/index.ts | 2 + packages/reader-main/src/messages/repost.ts | 76 +++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 packages/reader-main/src/messages/repost.ts diff --git a/packages/lib-api-types/src/posts/index.ts b/packages/lib-api-types/src/posts/index.ts index 446dfca2..eba13d5c 100644 --- a/packages/lib-api-types/src/posts/index.ts +++ b/packages/lib-api-types/src/posts/index.ts @@ -92,6 +92,15 @@ export const ReplyBodySchema = t.Object({ }); export type ReplyBody = Static; +export const RepostBodySchema = t.Object({ + hash: t.String(), + from: t.String(), + post_hash: t.String(), + quantity: t.String(), + timestamp: t.String(), +}); +export type RepostBody = Static; + export const UnfollowBodySchema = t.Object({ hash: t.String(), from: t.String(), diff --git a/packages/reader-main/src/messages/index.ts b/packages/reader-main/src/messages/index.ts index 74d530d4..08bc8c43 100644 --- a/packages/reader-main/src/messages/index.ts +++ b/packages/reader-main/src/messages/index.ts @@ -5,6 +5,7 @@ import { Like } from './like'; import { Post } from './post'; import { Remove } from './remove'; import { Reply } from './reply'; +import { Repost } from './repost'; import { Unfollow } from './unfollow'; export const MessageHandlers = { @@ -15,5 +16,6 @@ export const MessageHandlers = { Post, Remove, Reply, + Repost, Unfollow, }; diff --git a/packages/reader-main/src/messages/repost.ts b/packages/reader-main/src/messages/repost.ts new file mode 100644 index 00000000..66fc5245 --- /dev/null +++ b/packages/reader-main/src/messages/repost.ts @@ -0,0 +1,76 @@ +/* eslint-disable ts/no-namespace */ +import type { Posts } from '@atomone/dither-api-types'; + +import type { ActionWithData, ResponseStatus } from '../types/index'; + +import process from 'node:process'; + +import { extractMemoContent } from '@atomone/chronostate'; + +import { useConfig } from '../config/index'; + +declare module '@atomone/chronostate' { + export namespace MemoExtractor { + export interface TypeMap { + 'dither.Repost': [string]; + } + } +} + +const { AUTH } = useConfig(); +const apiRoot = process.env.API_ROOT ?? 'http://localhost:3000/v1'; + +export async function Repost(action: ActionWithData): Promise { + try { + const [post_hash] = extractMemoContent(action.memo, 'dither.Repost'); + const postBody: Posts.RepostBody = { + hash: action.hash, + from: action.sender, + post_hash, + timestamp: action.timestamp, + quantity: action.quantity, + }; + + const rawResponse = await fetch(`${apiRoot}/repost`, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': AUTH, + }, + body: JSON.stringify(postBody), + }); + + if (rawResponse.status !== 200) { + console.error('Error posting to API:', rawResponse); + return 'RETRY'; + } + + const response = await rawResponse.json() as { status: number; error?: string }; + if (response.status === 200) { + console.log(`dither.Repost message processed successfully: ${action.hash}`); + return 'SUCCESS'; + } + + if (response.status === 500) { + console.log(`dither.Repost could not reach database: ${action.hash}`); + return 'RETRY'; + } + + if (response.status === 400) { + console.log(`dither.Repost message skipped, invalid post hash provided: ${action.hash}`); + return 'SKIP'; + } + + if (response.status === 404) { + console.log(`dither.Repost message skipped, invalid post provided: ${action.hash}`); + return 'SKIP'; + } + + console.warn(`dither.Repost message failed: ${action.hash} (${response.error})`); + return 'RETRY'; + } catch (error) { + console.error('Error processing message:', error); + return 'RETRY'; + }; +} From 08183012e81537c926b08b67a6a1f7c914d5149e Mon Sep 17 00:00:00 2001 From: Lucio Rubens Date: Fri, 23 Jan 2026 21:59:02 -0300 Subject: [PATCH 02/11] refactor: handle repost on api --- bun.lock | 4 +- packages/api-main/drizzle/schema.ts | 19 ++++++- packages/api-main/package.json | 6 +-- packages/api-main/src/posts/index.ts | 1 + packages/api-main/src/posts/repost.ts | 70 ++++++++++++++++++++++++++ packages/api-main/src/routes/reader.ts | 1 + 6 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 packages/api-main/src/posts/repost.ts diff --git a/bun.lock b/bun.lock index 2476ac76..cb9fc79e 100644 --- a/bun.lock +++ b/bun.lock @@ -47,7 +47,7 @@ "@cosmjs/amino": "^0.33.1", "@types/jsonwebtoken": "^9.0.10", "@types/pg": "^8.15.5", - "drizzle-kit": "^0.31.5", + "drizzle-kit": "^0.31.8", "pino-pretty": "^13.1.2", "vitest": "^4.0.10", }, @@ -1016,7 +1016,7 @@ "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], - "drizzle-kit": ["drizzle-kit@0.31.7", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-hOzRGSdyKIU4FcTSFYGKdXEjFsncVwHZ43gY3WU5Bz9j5Iadp6Rh6hxLSQ1IWXpKLBKt/d5y1cpSPcV+FcoQ1A=="], + "drizzle-kit": ["drizzle-kit@0.31.8", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg=="], "drizzle-orm": ["drizzle-orm@0.44.7", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ=="], diff --git a/packages/api-main/drizzle/schema.ts b/packages/api-main/drizzle/schema.ts index 80cde5ba..6760359c 100644 --- a/packages/api-main/drizzle/schema.ts +++ b/packages/api-main/drizzle/schema.ts @@ -16,6 +16,7 @@ export const FeedTable = pgTable( likes: integer().default(0), // The amount of likes this post / reply has dislikes: integer().default(0), // The amount of dislikes this post / reply has flags: integer().default(0), // The amount of flags this post / reply has + reposts: integer().default(0), // The amount of reposts this post / reply has likes_burnt: text().default('0'), // The amount of tokens burnt from each user who liked this post / reply dislikes_burnt: text().default('0'), // The amount of tokens burnt from each user who disliked this post / reply flags_burnt: text().default('0'), // The amount of tokens burnt from each user who wante dto flag this post / reply @@ -75,6 +76,21 @@ export const FlagsTable = pgTable( ], ); +export const RepostsTable = pgTable( + 'reposts', + { + hash: varchar({ length: 64 }).primaryKey(), + post_hash: varchar({ length: 64 }).notNull(), + author: varchar({ length: 44 }).notNull(), + quantity: text().default('0'), + timestamp: timestamp({ withTimezone: true }).notNull(), + }, + t => [ + index('repost_post_hash_idx').on(t.post_hash), + index('repost_author_idx').on(t.author), + ], +); + export const FollowsTable = pgTable( 'follows', { @@ -129,7 +145,7 @@ export const ModeratorTable = pgTable('moderators', { deleted_at: timestamp({ withTimezone: true }), }); -export const notificationTypeEnum = pgEnum('notification_type', ['like', 'dislike', 'flag', 'follow', 'reply']); +export const notificationTypeEnum = pgEnum('notification_type', ['like', 'dislike', 'flag', 'follow', 'reply', 'repost']); export const NotificationTable = pgTable( 'notifications', @@ -159,6 +175,7 @@ export const tables = [ 'likes', 'dislikes', 'flags', + 'reposts', 'follows', 'audits', 'moderators', diff --git a/packages/api-main/package.json b/packages/api-main/package.json index bd5b98c5..3fb6db26 100644 --- a/packages/api-main/package.json +++ b/packages/api-main/package.json @@ -13,8 +13,8 @@ "db:generate": "bunx drizzle-kit generate", "db:migrate": "bunx drizzle-kit migrate", "db:push": "bunx drizzle-kit push", - "db:push:force": "bunx drizzle-kit push --force", - "push-and-start": "bunx drizzle-kit push --force && bun start" + "db:push:force": "drizzle-kit push --force", + "push-and-start": "drizzle-kit push --force && bun start" }, "dependencies": { "@atomone/chronostate": "^2.3.0", @@ -40,7 +40,7 @@ "@cosmjs/amino": "^0.33.1", "@types/jsonwebtoken": "^9.0.10", "@types/pg": "^8.15.5", - "drizzle-kit": "^0.31.5", + "drizzle-kit": "^0.31.8", "pino-pretty": "^13.1.2", "vitest": "^4.0.10" } diff --git a/packages/api-main/src/posts/index.ts b/packages/api-main/src/posts/index.ts index d7030b77..baaf7311 100644 --- a/packages/api-main/src/posts/index.ts +++ b/packages/api-main/src/posts/index.ts @@ -9,5 +9,6 @@ export * from './mod'; export * from './post'; export * from './postRemove'; export * from './reply'; +export * from './repost'; export * from './unfollow'; export * from './updateState'; diff --git a/packages/api-main/src/posts/repost.ts b/packages/api-main/src/posts/repost.ts new file mode 100644 index 00000000..cf9db441 --- /dev/null +++ b/packages/api-main/src/posts/repost.ts @@ -0,0 +1,70 @@ +import type { Posts } from '@atomone/dither-api-types'; + +import { eq, sql } from 'drizzle-orm'; + +import { getDatabase } from '../../drizzle/db'; +import { FeedTable, RepostsTable } from '../../drizzle/schema'; +import { notify } from '../shared/notify'; +import { useSharedQueries } from '../shared/useSharedQueries'; +import { postToDiscord } from '../utility'; + +const sharedQueries = useSharedQueries(); + +const statement = getDatabase() + .insert(RepostsTable) + .values({ + post_hash: sql.placeholder('post_hash'), + hash: sql.placeholder('hash'), + author: sql.placeholder('author'), + quantity: sql.placeholder('quantity'), + timestamp: sql.placeholder('timestamp'), + }) + .onConflictDoNothing() + .prepare('stmnt_add_repost'); + +const statementAddRepostToPost = getDatabase() + .update(FeedTable) + .set({ + reposts: sql`${FeedTable.reposts} + 1`, + }) + .where(eq(FeedTable.hash, sql.placeholder('post_hash'))) + .prepare('stmnt_add_repost_count_to_post'); + +export async function Repost(body: Posts.RepostBody) { + if (body.post_hash.length !== 64) { + return { status: 400, error: 'Provided post_hash is not valid for repost' }; + } + + try { + const result = await sharedQueries.doesPostExist(body.post_hash); + if (result.status !== 200) { + return { status: result.status, error: 'provided post_hash does not exist' }; + } + + const resultChanges = await statement.execute({ + post_hash: body.post_hash.toLowerCase(), + hash: body.hash.toLowerCase(), + author: body.from.toLowerCase(), + quantity: body.quantity, + timestamp: new Date(body.timestamp), + }); + + // Ensure that 'repost' was triggered, and not already existing. + if (typeof resultChanges.rowCount === 'number' && resultChanges.rowCount >= 1) { + await statementAddRepostToPost.execute({ post_hash: body.post_hash }); + await notify({ + post_hash: body.post_hash, + hash: body.hash, + type: 'repost', + timestamp: new Date(body.timestamp), + actor: body.from, + }); + } + + await postToDiscord(`Reposted by ${body.from.toLowerCase()}`, `https://dither.chat/post/${body.hash.toLowerCase()}`); + return { status: 200 }; + } catch (err) { + console.error(err); + return { status: 400, error: 'failed to upsert data for repost, repost already exists' }; + } +} diff --git a/packages/api-main/src/routes/reader.ts b/packages/api-main/src/routes/reader.ts index f8e1d973..4399fbad 100644 --- a/packages/api-main/src/routes/reader.ts +++ b/packages/api-main/src/routes/reader.ts @@ -17,6 +17,7 @@ export const readerRoutes = new Elysia() .post('/like', ({ body }) => PostRequests.Like(body), { body: Posts.LikeBodySchema }) .post('/dislike', ({ body }) => PostRequests.Dislike(body), { body: Posts.DislikeBodySchema }) .post('/flag', ({ body }) => PostRequests.Flag(body), { body: Posts.FlagBodySchema }) + .post('/repost', ({ body }) => PostRequests.Repost(body), { body: Posts.RepostBodySchema }) .post('/post-remove', ({ body }) => PostRequests.PostRemove(body), { body: Posts.PostRemoveBodySchema }) .post('/update-state', ({ body }) => PostRequests.UpdateState(body), { body: t.Object({ last_block: t.String() }), From c143fc853c5e8227dc2cae51a088ee14e8ab4423 Mon Sep 17 00:00:00 2001 From: Lucio Rubens Date: Fri, 23 Jan 2026 22:15:40 -0300 Subject: [PATCH 03/11] refactor: remove postToDiscord --- packages/api-main/.env.example | 2 +- packages/api-main/src/config.ts | 7 ----- packages/api-main/src/posts/dislike.ts | 2 -- packages/api-main/src/posts/like.ts | 2 -- packages/api-main/src/posts/post.ts | 3 -- packages/api-main/src/posts/reply.ts | 2 -- packages/api-main/src/posts/repost.ts | 2 -- packages/api-main/src/utility/index.ts | 42 +------------------------- 8 files changed, 2 insertions(+), 60 deletions(-) diff --git a/packages/api-main/.env.example b/packages/api-main/.env.example index 406b44ca..0f924073 100644 --- a/packages/api-main/.env.example +++ b/packages/api-main/.env.example @@ -1,7 +1,7 @@ PG_URI="postgresql://default:password@localhost:5432/postgres" +AUTH=default_auth_secret JWT=default_jwt_secret JWT_STRICTNESS=lax -DISCORD_WEBHOOK_URL= TELEGRAM_BOT_TOKEN= TELEGRAM_BOT_USERNAME=@bot_username diff --git a/packages/api-main/src/config.ts b/packages/api-main/src/config.ts index 48c34d63..13fa59c2 100644 --- a/packages/api-main/src/config.ts +++ b/packages/api-main/src/config.ts @@ -12,7 +12,6 @@ interface Config { AUTH: string; JWT: string; JWT_STRICTNESS: JWT_STRICTNESS; - DISCORD_WEBHOOK_URL: string; } let config: Config; @@ -41,18 +40,12 @@ export function useConfig(): Config { process.env.JWT_STRICTNESS = 'lax'; } - if (typeof process.env.DISCORD_WEBHOOK_URL === 'undefined') { - console.warn(`DISCORD_WEBHOOK_URL not set, defaulting to empty`); - process.env.DISCORD_WEBHOOK_URL = ''; - } - config = { PORT: process.env.PORT ? Number.parseInt(process.env.PORT) : 3000, PG_URI: process.env.PG_URI, AUTH: process.env.AUTH as string, JWT: process.env.JWT as string, JWT_STRICTNESS: process.env.JWT_STRICTNESS as JWT_STRICTNESS, - DISCORD_WEBHOOK_URL: process.env.DISCORD_WEBHOOK_URL, }; return config; diff --git a/packages/api-main/src/posts/dislike.ts b/packages/api-main/src/posts/dislike.ts index e3398814..90f0659a 100644 --- a/packages/api-main/src/posts/dislike.ts +++ b/packages/api-main/src/posts/dislike.ts @@ -6,7 +6,6 @@ import { getDatabase } from '../../drizzle/db'; import { DislikesTable, FeedTable } from '../../drizzle/schema'; import { notify } from '../shared/notify'; import { useSharedQueries } from '../shared/useSharedQueries'; -import { postToDiscord } from '../utility'; const sharedQueries = useSharedQueries(); @@ -62,7 +61,6 @@ export async function Dislike(body: Posts.DislikeBody) { }); } - await postToDiscord(`Disliked by ${body.from.toLowerCase()}`, `https://dither.chat/post/${body.hash.toLowerCase()}`); return { status: 200 }; } catch (err) { console.error(err); diff --git a/packages/api-main/src/posts/like.ts b/packages/api-main/src/posts/like.ts index 49b39008..3077fc3a 100644 --- a/packages/api-main/src/posts/like.ts +++ b/packages/api-main/src/posts/like.ts @@ -6,7 +6,6 @@ import { getDatabase } from '../../drizzle/db'; import { FeedTable, LikesTable } from '../../drizzle/schema'; import { notify } from '../shared/notify'; import { useSharedQueries } from '../shared/useSharedQueries'; -import { postToDiscord } from '../utility'; const sharedQueries = useSharedQueries(); @@ -62,7 +61,6 @@ export async function Like(body: Posts.LikeBody) { }); } - await postToDiscord(`Liked by ${body.from.toLowerCase()}`, `https://dither.chat/post/${body.hash.toLowerCase()}`); return { status: 200 }; } catch (err) { console.error(err); diff --git a/packages/api-main/src/posts/post.ts b/packages/api-main/src/posts/post.ts index bb80d51a..8744428c 100644 --- a/packages/api-main/src/posts/post.ts +++ b/packages/api-main/src/posts/post.ts @@ -4,7 +4,6 @@ import { desc, eq, sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { AuditTable, FeedTable } from '../../drizzle/schema'; -import { postToDiscord } from '../utility'; const statement = getDatabase() .insert(FeedTable) @@ -34,8 +33,6 @@ export async function Post(body: Posts.PostBody) { await removePostIfBanned(body); - await postToDiscord(body.msg, `https://dither.chat/post/${body.hash.toLowerCase()}`); - return { status: 200 }; } catch (err) { console.error(err); diff --git a/packages/api-main/src/posts/reply.ts b/packages/api-main/src/posts/reply.ts index 511e6561..e7415a69 100644 --- a/packages/api-main/src/posts/reply.ts +++ b/packages/api-main/src/posts/reply.ts @@ -6,7 +6,6 @@ import { getDatabase } from '../../drizzle/db'; import { FeedTable } from '../../drizzle/schema'; import { notify } from '../shared/notify'; import { useSharedQueries } from '../shared/useSharedQueries'; -import { postToDiscord } from '../utility'; const sharedQueries = useSharedQueries(); @@ -64,7 +63,6 @@ export async function Reply(body: Posts.ReplyBody) { }); } - await postToDiscord(`${body.msg}`, `https://dither.chat/post/${body.hash.toLowerCase()}`); return { status: 200 }; } catch (err) { console.error(err); diff --git a/packages/api-main/src/posts/repost.ts b/packages/api-main/src/posts/repost.ts index cf9db441..308fcc61 100644 --- a/packages/api-main/src/posts/repost.ts +++ b/packages/api-main/src/posts/repost.ts @@ -6,7 +6,6 @@ import { getDatabase } from '../../drizzle/db'; import { FeedTable, RepostsTable } from '../../drizzle/schema'; import { notify } from '../shared/notify'; import { useSharedQueries } from '../shared/useSharedQueries'; -import { postToDiscord } from '../utility'; const sharedQueries = useSharedQueries(); @@ -61,7 +60,6 @@ export async function Repost(body: Posts.RepostBody) { }); } - await postToDiscord(`Reposted by ${body.from.toLowerCase()}`, `https://dither.chat/post/${body.hash.toLowerCase()}`); return { status: 200 }; } catch (err) { console.error(err); diff --git a/packages/api-main/src/utility/index.ts b/packages/api-main/src/utility/index.ts index 963b5570..eb06521b 100644 --- a/packages/api-main/src/utility/index.ts +++ b/packages/api-main/src/utility/index.ts @@ -8,7 +8,7 @@ import { sql } from 'drizzle-orm'; import { getDatabase } from '../../drizzle/db'; import { useConfig } from '../config'; -const { AUTH, DISCORD_WEBHOOK_URL } = useConfig(); +const { AUTH } = useConfig(); export function getTransferMessage(messages: Array) { const msgTransfer = messages.find(msg => msg['@type'] === '/cosmos.bank.v1beta1.MsgSend'); @@ -84,43 +84,3 @@ export function getRequestIP(request: Request) { // We'll just default to `host` if not found return request.headers.get('host') ?? 'localhost:3000'; } - -export async function postToDiscord(content: string, url: string) { - if (!DISCORD_WEBHOOK_URL) { - console.log(`DISCORD_WEBHOOK_URL was not provided.`); - return; - } - - const payload = { - content: '', - username: 'dither.chat', - embeds: [ - { - title: 'New Activity', - description: content, - color: 3447003, - url, - }, - ], - }; - - try { - const response = await fetch(DISCORD_WEBHOOK_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - if (response.ok) { - console.log('Discord webhook message sent successfully! 🎉'); - } else { - console.error(`Failed to send Discord message. Status: ${response.status}`); - const errorText = await response.text(); - console.error('Response body:', errorText); - } - } catch (error) { - console.error('An error occurred during the fetch request:', error); - } -} From 63edb9c5e61d4ca041a9f6939708ff5299fe1fe7 Mon Sep 17 00:00:00 2001 From: Lucio Rubens Date: Tue, 27 Jan 2026 11:48:27 -0300 Subject: [PATCH 04/11] feat: repost action button --- packages/frontend-main/src/App.vue | 2 + .../notifications/NotificationType.vue | 5 +- .../src/components/popups/RepostDialog.vue | 85 +++++++++++++++++++ .../src/components/posts/PostActions.vue | 32 ++++++- .../src/composables/usePopups.ts | 2 + .../src/composables/useRepostPost.ts | 83 ++++++++++++++++++ .../frontend-main/src/localization/index.ts | 3 + packages/frontend-main/src/types/index.ts | 2 + .../src/utility/optimisticBuilders.ts | 1 + .../frontend-main/src/utility/sanitize.ts | 21 +++-- 10 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 packages/frontend-main/src/components/popups/RepostDialog.vue create mode 100644 packages/frontend-main/src/composables/useRepostPost.ts diff --git a/packages/frontend-main/src/App.vue b/packages/frontend-main/src/App.vue index 871b2ede..8e81c4a4 100644 --- a/packages/frontend-main/src/App.vue +++ b/packages/frontend-main/src/App.vue @@ -9,6 +9,7 @@ import InvalidDefaultAmountDialog from './components/popups/InvalidDefaultAmount import LikePostDialog from './components/popups/LikePostDialog.vue'; import NewPostDialog from './components/popups/NewPostDialog.vue'; import ReplyDialog from './components/popups/ReplyDialog.vue'; +import RepostDialog from './components/popups/RepostDialog.vue'; import TipUserDialog from './components/popups/TipUserDialog.vue'; import UnfollowDialog from './components/popups/UnfollowUserDialog.vue'; import Sonner from './components/ui/sonner/Sonner.vue'; @@ -38,6 +39,7 @@ onMounted(() => { + diff --git a/packages/frontend-main/src/components/notifications/NotificationType.vue b/packages/frontend-main/src/components/notifications/NotificationType.vue index 04b9de3e..51718d98 100644 --- a/packages/frontend-main/src/components/notifications/NotificationType.vue +++ b/packages/frontend-main/src/components/notifications/NotificationType.vue @@ -1,7 +1,7 @@ + + diff --git a/packages/frontend-main/src/components/posts/PostActions.vue b/packages/frontend-main/src/components/posts/PostActions.vue index f4d8b3f8..04dd2336 100644 --- a/packages/frontend-main/src/components/posts/PostActions.vue +++ b/packages/frontend-main/src/components/posts/PostActions.vue @@ -4,7 +4,7 @@ import type { Post } from 'api-main/types/feed'; import type { PopupState } from '@/composables/usePopups'; import { Decimal } from '@cosmjs/math'; -import { Flag, MessageCircle, ThumbsDown, ThumbsUp } from 'lucide-vue-next'; +import { Flag, MessageCircle, Repeat2, ThumbsDown, ThumbsUp } from 'lucide-vue-next'; import { ref } from 'vue'; import { toast } from 'vue-sonner'; @@ -13,6 +13,7 @@ import { useDislikePost } from '@/composables/useDislikePost'; import { useFlagPost } from '@/composables/useFlagPost'; import { useLikePost } from '@/composables/useLikePost'; import { usePopups } from '@/composables/usePopups'; +import { useRepostPost } from '@/composables/useRepostPost'; import { useWallet } from '@/composables/useWallet'; import { useConfigStore } from '@/stores/useConfigStore'; import { useWalletDialogStore } from '@/stores/useWalletDialogStore'; @@ -41,6 +42,7 @@ const { isDefaultAmountInvalid } = useDefaultAmount(); const { likePost } = useLikePost(); const { dislikePost } = useDislikePost(); const { flagPost } = useFlagPost(); +const { repostPost } = useRepostPost(); function handleAction(type: keyof PopupState, post: Post) { if (wallet.loggedIn.value) { @@ -60,6 +62,20 @@ async function onClickReply() { handleAction('reply', props.post); } } +async function onClickRepost() { + if (isDefaultAmountInvalid.value) { + popups.show('invalidDefaultAmount', 'none'); + } else if (configStore.config.defaultAmountEnabled) { + const toastId = showBroadcastingToast('Repost'); + try { + await repostPost({ post: ref(props.post), amountAtomics: configStore.config.defaultAmountAtomics }); + } finally { + toast.dismiss(toastId); + } + } else { + handleAction('repost', props.post); + } +} async function onClickLike() { if (isDefaultAmountInvalid.value) { popups.show('invalidDefaultAmount', 'none'); @@ -120,6 +136,20 @@ async function onClickFlag() { +
+ + + + {{ formatCompactNumber(post.reposts) }} + + + {{ `${formatCompactNumber(post.reposts)} ${$t('components.PostActions.reposts', post.reposts ?? 0)}` }} + + +
+