From b7bac69edc96032670217d379b3c353d8993c1c6 Mon Sep 17 00:00:00 2001 From: sebastjanartic <45803536-sebastjanartic@users.noreply.replit.com> Date: Wed, 4 Mar 2026 15:26:35 +0000 Subject: [PATCH] Published your App Replit-Commit-Author: Deployment Replit-Commit-Session-Id: 517dfa7b-26ac-463d-a6e1-a58c6df97188 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: a5b0cf00-faad-490d-a860-0918e26dc01b Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/517dfa7b-26ac-463d-a6e1-a58c6df97188/jdAEdU5 Replit-Commit-Deployment-Build-Id: 3212db73-3802-4ff5-b9d5-49fe3af1b5ad Replit-Helium-Checkpoint-Created: true --- client/src/components/share-buttons.tsx | 102 ++++++++++++++++++++++++ server/vite.ts | 50 +++++++++++- 2 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 client/src/components/share-buttons.tsx diff --git a/client/src/components/share-buttons.tsx b/client/src/components/share-buttons.tsx new file mode 100644 index 0000000..f8c5e11 --- /dev/null +++ b/client/src/components/share-buttons.tsx @@ -0,0 +1,102 @@ +import { SiFacebook, SiLinkedin, SiX, SiWhatsapp, SiTelegram, SiPinterest } from "react-icons/si"; +import { Mail, Link2, Check } from "lucide-react"; +import { useState } from "react"; + +interface ShareButtonsProps { + url: string; + title: string; + image?: string; +} + +const SHARE_TARGETS = [ + { + name: "Facebook", + icon: SiFacebook, + color: "hover:bg-[#1877F2] hover:text-white", + getUrl: (url: string) => `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`, + }, + { + name: "X", + icon: SiX, + color: "hover:bg-[#000] hover:text-white", + getUrl: (url: string, title: string) => `https://x.com/intent/tweet?url=${encodeURIComponent(url)}&text=${encodeURIComponent(title)}`, + }, + { + name: "LinkedIn", + icon: SiLinkedin, + color: "hover:bg-[#0A66C2] hover:text-white", + getUrl: (url: string) => `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}`, + }, + { + name: "WhatsApp", + icon: SiWhatsapp, + color: "hover:bg-[#25D366] hover:text-white", + getUrl: (url: string, title: string) => `https://wa.me/?text=${encodeURIComponent(title + " " + url)}`, + }, + { + name: "Telegram", + icon: SiTelegram, + color: "hover:bg-[#26A5E4] hover:text-white", + getUrl: (url: string, title: string) => `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(title)}`, + }, + { + name: "Pinterest", + icon: SiPinterest, + color: "hover:bg-[#BD081C] hover:text-white", + getUrl: (url: string, title: string, image?: string) => `https://pinterest.com/pin/create/button/?url=${encodeURIComponent(url)}&description=${encodeURIComponent(title)}${image ? "&media=" + encodeURIComponent(image) : ""}`, + }, + { + name: "E-Mail", + icon: Mail, + color: "hover:bg-muted-foreground hover:text-white", + getUrl: (url: string, title: string) => `mailto:?subject=${encodeURIComponent(title)}&body=${encodeURIComponent(title + "\n\n" + url)}`, + }, +]; + +export default function ShareButtons({ url, title, image }: ShareButtonsProps) { + const [copied, setCopied] = useState(false); + + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(url); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch { + const input = document.createElement("input"); + input.value = url; + document.body.appendChild(input); + input.select(); + document.execCommand("copy"); + document.body.removeChild(input); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + }; + + return ( +
+ Teilen: + {SHARE_TARGETS.map((target) => ( + + + + ))} + +
+ ); +} diff --git a/server/vite.ts b/server/vite.ts index b95f5ab..c542785 100644 --- a/server/vite.ts +++ b/server/vite.ts @@ -5,9 +5,14 @@ import viteConfig from "../vite.config"; import fs from "fs"; import path from "path"; import { nanoid } from "nanoid"; +import { storage } from "./storage"; const viteLogger = createLogger(); +function escapeHtml(str: string): string { + return str.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); +} + export async function setupVite(server: Server, app: Express) { const serverOptions = { middlewareMode: true, @@ -42,12 +47,55 @@ export async function setupVite(server: Server, app: Express) { "index.html", ); - // always reload the index.html file from disk incase it changes let template = await fs.promises.readFile(clientTemplate, "utf-8"); template = template.replace( `src="/src/main.tsx"`, `src="/src/main.tsx?v=${nanoid()}"`, ); + + const articleMatch = url.match(/^\/article\/([^?#]+)/); + if (articleMatch) { + try { + const slug = decodeURIComponent(articleMatch[1]); + const article = await storage.getArticleBySlug(slug); + if (article) { + const host = req.get("host") || "folx.tv"; + const protocol = req.get("x-forwarded-proto") || "https"; + const baseUrl = `${protocol}://${host}`; + const articleUrl = `${baseUrl}/article/${article.slug}`; + const imageUrl = article.coverImage ? (article.coverImage.startsWith("http") ? article.coverImage : `${baseUrl}${article.coverImage}`) : ""; + + const ogTags = [ + ``, + ``, + ``, + ``, + imageUrl ? `` : "", + ``, + ``, + ``, + ``, + imageUrl ? `` : "", + `${escapeHtml(article.title)} - Folx Music Television`, + ].filter(Boolean).join("\n "); + + template = template.replace( + /.*?<\/title>/, + ogTags + ); + template = template.replace( + /<meta property="og:title"[^>]*\/>\s*/, + "" + ); + template = template.replace( + /<meta property="og:description"[^>]*\/>\s*/, + "" + ); + } + } catch (e) { + } + } + const page = await vite.transformIndexHtml(url, template); res.status(200).set({ "Content-Type": "text/html" }).end(page); } catch (e) {