mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
feat(seo): BlogPosting schema, FAQPage JSON-LD, extensible author system (#2284)
* feat(seo): BlogPosting schema, FAQPage JSON-LD, author system, AI crawler welcome Blog structured data: - Change @type Article to BlogPosting for all blog posts - Author: Organization to Person with extensible default (Elie Habib) - Add per-post author/authorUrl/authorBio/modifiedDate frontmatter fields - Auto-extract FAQPage JSON-LD from FAQ sections in all 17 posts - Show Updated date when modifiedDate differs from pubDate - Add author bio section with GitHub avatar and fallback Main app: - Add commodity variant to middleware VARIANT_HOST_MAP and VARIANT_OG - Add commodity.worldmonitor.app to sitemap.xml - Shorten index.html meta description to 136 chars (was 161) - Remove worksFor block from index.html author JSON-LD - Welcome all bots in robots.txt (removed per-bot blocks, global allows) - Update llms.txt: five variants listed, all 17 blog post URLs added * fix(seo): scope FAQ regex to section boundary, use author-aware avatar - extractFaqLd now slices only to the next ## heading (was: to end of body) preventing bold text in post-FAQ sections from being mistakenly extracted - Avatar src now derived from DEFAULT_AUTHOR_GITHUB constant (koala73) only when using the default author; custom authors fall back to favicon so multi-author posts show a correct image instead of the wrong profile
This commit is contained in:
@@ -10,6 +10,10 @@ const blog = defineCollection({
|
||||
keywords: z.string(),
|
||||
audience: z.string(),
|
||||
pubDate: z.coerce.date(),
|
||||
modifiedDate: z.coerce.date().optional(),
|
||||
author: z.string().optional(),
|
||||
authorUrl: z.string().url().optional(),
|
||||
authorBio: z.string().optional(),
|
||||
heroImage: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -12,9 +12,10 @@ interface Props {
|
||||
section?: string;
|
||||
jsonLd?: Record<string, unknown>;
|
||||
breadcrumbLd?: Record<string, unknown>;
|
||||
faqLd?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const { title, description, metaTitle, keywords, ogType, ogImage, publishedTime, modifiedTime, author, section, jsonLd, breadcrumbLd } = Astro.props;
|
||||
const { title, description, metaTitle, keywords, ogType, ogImage, publishedTime, modifiedTime, author, section, jsonLd, breadcrumbLd, faqLd } = Astro.props;
|
||||
const pageTitle = metaTitle || title;
|
||||
const resolvedOgImage = ogImage || 'https://www.worldmonitor.app/favico/og-image.png';
|
||||
const resolvedOgType = ogType || 'website';
|
||||
@@ -68,6 +69,9 @@ const keywordTags = keywords ? keywords.split(',').map(k => k.trim()).slice(0, 6
|
||||
{breadcrumbLd && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(breadcrumbLd)} />
|
||||
)}
|
||||
{faqLd && (
|
||||
<script type="application/ld+json" set:html={JSON.stringify(faqLd)} />
|
||||
)}
|
||||
|
||||
<style is:global>
|
||||
@import '../styles/global.css';
|
||||
|
||||
@@ -7,12 +7,17 @@ interface Props {
|
||||
metaTitle?: string;
|
||||
keywords?: string;
|
||||
pubDate: Date;
|
||||
modifiedDate?: Date;
|
||||
author?: string;
|
||||
authorUrl?: string;
|
||||
authorBio?: string;
|
||||
audience?: string;
|
||||
heroImage?: string;
|
||||
slug?: string;
|
||||
rawBody?: string;
|
||||
}
|
||||
|
||||
const { title, description, metaTitle, keywords, pubDate, audience, heroImage, slug } = Astro.props;
|
||||
const { title, description, metaTitle, keywords, pubDate, modifiedDate, author, authorUrl, authorBio, audience, heroImage, slug, rawBody } = Astro.props;
|
||||
const ogImage = heroImage || (slug ? `https://www.worldmonitor.app/blog/images/blog/${slug}.jpg` : undefined);
|
||||
const formattedDate = pubDate.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
@@ -20,6 +25,19 @@ const formattedDate = pubDate.toLocaleDateString('en-US', {
|
||||
day: 'numeric',
|
||||
});
|
||||
const isoDate = pubDate.toISOString();
|
||||
const isoModifiedDate = modifiedDate ? modifiedDate.toISOString() : isoDate;
|
||||
const DEFAULT_AUTHOR = 'Elie Habib';
|
||||
const DEFAULT_AUTHOR_URL = 'https://x.com/eliehabib';
|
||||
const DEFAULT_AUTHOR_BIO = 'Founder of World Monitor. Previously co-founder & CEO of Anghami (NASDAQ: ANGH). Building open-source global intelligence infrastructure.';
|
||||
const DEFAULT_AUTHOR_GITHUB = 'koala73';
|
||||
const authorName = author || DEFAULT_AUTHOR;
|
||||
const resolvedAuthorUrl = authorUrl || (authorName === DEFAULT_AUTHOR ? DEFAULT_AUTHOR_URL : undefined);
|
||||
const resolvedAuthorBio = authorBio || (authorName === DEFAULT_AUTHOR ? DEFAULT_AUTHOR_BIO : undefined);
|
||||
const avatarSrc = authorName === DEFAULT_AUTHOR
|
||||
? `https://unavatar.io/github/${DEFAULT_AUTHOR_GITHUB}`
|
||||
: '/favico/apple-touch-icon.png';
|
||||
const showUpdated = modifiedDate && modifiedDate.getTime() !== pubDate.getTime();
|
||||
const formattedModifiedDate = modifiedDate ? modifiedDate.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) : '';
|
||||
|
||||
const breadcrumbLd = {
|
||||
"@context": "https://schema.org",
|
||||
@@ -41,19 +59,15 @@ const breadcrumbLd = {
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"@type": "BlogPosting",
|
||||
"headline": metaTitle || title,
|
||||
"description": description,
|
||||
"datePublished": isoDate,
|
||||
"dateModified": isoDate,
|
||||
"dateModified": isoModifiedDate,
|
||||
"author": {
|
||||
"@type": "Organization",
|
||||
"name": "World Monitor",
|
||||
"url": "https://worldmonitor.app",
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": "https://www.worldmonitor.app/favico/apple-touch-icon.png"
|
||||
}
|
||||
"@type": "Person",
|
||||
"name": authorName,
|
||||
...(resolvedAuthorUrl ? { "url": resolvedAuthorUrl } : {})
|
||||
},
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
@@ -72,6 +86,27 @@ const jsonLd = {
|
||||
"url": Astro.url.href,
|
||||
...(keywords ? { "keywords": keywords } : {})
|
||||
};
|
||||
|
||||
function extractFaqLd(body: string | undefined): object | null {
|
||||
if (!body) return null;
|
||||
const faqIdx = body.indexOf('## Frequently Asked Questions');
|
||||
if (faqIdx === -1) return null;
|
||||
const afterFaq = body.slice(faqIdx + '## Frequently Asked Questions'.length);
|
||||
const nextHeading = afterFaq.search(/\n##\s/);
|
||||
const faqSection = nextHeading === -1 ? afterFaq : afterFaq.slice(0, nextHeading);
|
||||
const qaRegex = /\*\*(.+?)\*\*\n([^\n*]+(?:\n[^\n*#]+)*)/g;
|
||||
const items: { "@type": string; name: string; acceptedAnswer: { "@type": string; text: string } }[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = qaRegex.exec(faqSection)) !== null) {
|
||||
const q = match[1].trim();
|
||||
const a = match[2].replace(/\[([^\]]+)\]\([^)]+\)/g, '$1').trim();
|
||||
if (q && a) items.push({ "@type": "Question", name: q, acceptedAnswer: { "@type": "Answer", text: a } });
|
||||
}
|
||||
if (items.length === 0) return null;
|
||||
return { "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": items };
|
||||
}
|
||||
|
||||
const faqLd = extractFaqLd(rawBody);
|
||||
---
|
||||
|
||||
<Base
|
||||
@@ -82,16 +117,19 @@ const jsonLd = {
|
||||
ogType="article"
|
||||
ogImage={ogImage}
|
||||
publishedTime={isoDate}
|
||||
modifiedTime={isoDate}
|
||||
modifiedTime={isoModifiedDate}
|
||||
author={authorName}
|
||||
section={audience}
|
||||
jsonLd={jsonLd}
|
||||
breadcrumbLd={breadcrumbLd}
|
||||
faqLd={faqLd ?? undefined}
|
||||
>
|
||||
<article>
|
||||
<div class="article-header">
|
||||
<a href="/blog/" class="back">← All articles</a>
|
||||
<div class="meta">
|
||||
<time datetime={isoDate}>{formattedDate}</time>
|
||||
{showUpdated && <span> · Updated <time datetime={isoModifiedDate}>{formattedModifiedDate}</time></span>}
|
||||
{audience && <span> · {audience}</span>}
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,5 +145,17 @@ const jsonLd = {
|
||||
<p>Markets, geopolitics, conflicts, infrastructure. One dashboard. No login required.</p>
|
||||
<a href="https://worldmonitor.app" class="btn" target="_blank" rel="noopener noreferrer">Open Dashboard</a>
|
||||
</div>
|
||||
<div class="author-bio">
|
||||
<img src={avatarSrc} alt={authorName} width="48" height="48" class="author-avatar" loading="lazy" onerror="this.src='/favico/apple-touch-icon.png'" />
|
||||
<div class="author-info">
|
||||
<div class="author-name">
|
||||
{resolvedAuthorUrl
|
||||
? <a href={resolvedAuthorUrl} target="_blank" rel="noopener noreferrer">{authorName}</a>
|
||||
: <span>{authorName}</span>
|
||||
}
|
||||
</div>
|
||||
{resolvedAuthorBio && <div class="author-desc" set:html={resolvedAuthorBio} />}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</Base>
|
||||
|
||||
@@ -20,9 +20,14 @@ const { Content } = await render(post);
|
||||
metaTitle={post.data.metaTitle}
|
||||
keywords={post.data.keywords}
|
||||
pubDate={post.data.pubDate}
|
||||
modifiedDate={post.data.modifiedDate}
|
||||
author={post.data.author}
|
||||
authorUrl={post.data.authorUrl}
|
||||
authorBio={post.data.authorBio}
|
||||
audience={post.data.audience}
|
||||
heroImage={post.data.heroImage}
|
||||
slug={post.id}
|
||||
rawBody={post.body}
|
||||
>
|
||||
<Content />
|
||||
</BlogPost>
|
||||
|
||||
@@ -573,6 +573,49 @@ img { max-width: 100%; height: auto; border-radius: var(--radius); }
|
||||
color: var(--wm-bg);
|
||||
}
|
||||
|
||||
/* ─── Author Bio ─── */
|
||||
.author-bio {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
margin: 2.5rem auto 0;
|
||||
max-width: 720px;
|
||||
padding: 1.25rem 1.5rem;
|
||||
background: rgba(255,255,255,0.04);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.author-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.author-info { flex: 1; }
|
||||
|
||||
.author-name {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.author-name a {
|
||||
color: var(--wm-green);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.author-name a:hover { text-decoration: underline; }
|
||||
|
||||
.author-desc {
|
||||
font-size: 0.82rem;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ─── Responsive ─── */
|
||||
@media (max-width: 768px) {
|
||||
.blog-hero h1 { font-size: 2rem; }
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>World Monitor - Real-Time Global Intelligence Dashboard</title>
|
||||
<meta name="title" content="World Monitor - Real-Time Global Intelligence Dashboard" />
|
||||
<meta name="description" content="AI-powered real-time global intelligence dashboard with live news, markets, military tracking, infrastructure monitoring, and geopolitical data. OSINT in one view." />
|
||||
<meta name="description" content="AI-powered real-time global intelligence dashboard with live news, markets, military tracking, and geopolitical data. OSINT in one view." />
|
||||
<meta name="keywords" content="AI intelligence, AI-powered dashboard, global intelligence, geopolitical dashboard, world news, market data, military bases, nuclear facilities, undersea cables, conflict zones, real-time monitoring, situation awareness, OSINT, flight tracking, AIS ships, earthquake monitor, protest tracker, power outages, oil prices, government spending, polymarket predictions" />
|
||||
<meta name="author" content="Elie Habib" />
|
||||
<meta name="theme-color" content="#0a0f0a" />
|
||||
@@ -75,7 +75,7 @@
|
||||
"name": "World Monitor",
|
||||
"alternateName": "World Monitor",
|
||||
"url": "https://www.worldmonitor.app/",
|
||||
"description": "Open-source real-time OSINT dashboard for geopolitical monitoring, conflict tracking, military flight tracking, maritime AIS, and global threat intelligence. Used by 2M+ people across 190+ countries.",
|
||||
"description": "Open-source real-time global intelligence dashboard aggregating conflicts, military movements, markets, infrastructure, and geopolitical data. Used by 2M+ people across 190+ countries. Featured in WIRED.",
|
||||
"applicationCategory": "SecurityApplication",
|
||||
"operatingSystem": "Web, Windows, macOS, Linux",
|
||||
"offers": [
|
||||
@@ -87,7 +87,6 @@
|
||||
"name": "Elie Habib",
|
||||
"url": "https://x.com/eliehabib",
|
||||
"jobTitle": "CEO",
|
||||
"worksFor": { "@type": "Organization", "name": "Anghami", "url": "https://anghami.com" },
|
||||
"sameAs": ["https://x.com/eliehabib", "https://github.com/koala73"]
|
||||
},
|
||||
"featureList": [
|
||||
|
||||
@@ -14,6 +14,7 @@ const SOCIAL_IMAGE_UA =
|
||||
const VARIANT_HOST_MAP: Record<string, string> = {
|
||||
'tech.worldmonitor.app': 'tech',
|
||||
'finance.worldmonitor.app': 'finance',
|
||||
'commodity.worldmonitor.app': 'commodity',
|
||||
'happy.worldmonitor.app': 'happy',
|
||||
};
|
||||
|
||||
@@ -31,6 +32,12 @@ const VARIANT_OG: Record<string, { title: string; description: string; image: st
|
||||
image: 'https://finance.worldmonitor.app/favico/finance/og-image.png',
|
||||
url: 'https://finance.worldmonitor.app/',
|
||||
},
|
||||
commodity: {
|
||||
title: 'Commodity Monitor - Real-Time Commodity Markets & Supply Chain Dashboard',
|
||||
description: 'Real-time commodity markets dashboard tracking mining sites, processing plants, commodity ports, supply chains, and global commodity trade flows.',
|
||||
image: 'https://commodity.worldmonitor.app/favico/commodity/og-image.png',
|
||||
url: 'https://commodity.worldmonitor.app/',
|
||||
},
|
||||
happy: {
|
||||
title: 'Happy Monitor - Good News & Global Progress',
|
||||
description: 'Curated positive news, progress data, and uplifting stories from around the world.',
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
World Monitor is an open-source (AGPL-3.0) intelligence platform that aggregates 435+ news feeds, 45+ interactive map layers, and multiple AI models into a single dashboard. Used by 2M+ people across 190+ countries, as featured in WIRED. It runs as a web app, installable PWA, and native desktop application (Tauri) for macOS, Windows, and Linux.
|
||||
|
||||
A single codebase produces three specialized variants — geopolitical, technology, and finance — each with distinct feeds, panels, map layers, and branding. The tri-variant architecture uses build-time selection via the VITE_VARIANT environment variable, with runtime switching available via the header bar.
|
||||
A single codebase produces five specialized variants — geopolitical (World Monitor), technology (Tech Monitor), finance (Finance Monitor), commodities (Commodity Monitor), and positive news (Happy Monitor) — each with distinct feeds, panels, map layers, and branding. The multi-variant architecture uses build-time selection via the VITE_VARIANT environment variable, with runtime switching available via the header bar.
|
||||
|
||||
The project is built with TypeScript, Vite, MapLibre GL JS, deck.gl, D3.js, and Tauri. All intelligence analysis (clustering, instability scoring, surge detection) runs client-side in the browser — no backend compute dependency for core intelligence. A browser-side ML pipeline (Transformers.js) provides NER and sentiment analysis without server dependency.
|
||||
|
||||
@@ -13,6 +13,8 @@ The project is built with TypeScript, Vite, MapLibre GL JS, deck.gl, D3.js, and
|
||||
- [World Monitor](https://worldmonitor.app): Geopolitics, military, conflicts, infrastructure
|
||||
- [Tech Monitor](https://tech.worldmonitor.app): Startups, AI/ML, cloud, cybersecurity
|
||||
- [Finance Monitor](https://finance.worldmonitor.app): Global markets, trading, central banks, Gulf FDI
|
||||
- [Commodity Monitor](https://commodity.worldmonitor.app): Mining, metals, energy, supply chains, commodity markets
|
||||
- [Happy Monitor](https://happy.worldmonitor.app): Positive news, breakthroughs, conservation, renewable energy
|
||||
- [World Monitor Pro](https://worldmonitor.app/pro): AI-powered equity research, geopolitical analysis, macro intelligence
|
||||
- [Blog](https://www.worldmonitor.app/blog/): Analysis, OSINT guides, geopolitics, and market intelligence articles
|
||||
|
||||
@@ -57,6 +59,26 @@ The project is built with TypeScript, Vite, MapLibre GL JS, deck.gl, D3.js, and
|
||||
- **AI**: Groq (Llama 3.1), OpenRouter, browser-side T5 fallback
|
||||
- **Monitoring**: Sentry error tracking, data freshness tracker across 22 sources
|
||||
|
||||
## Blog Posts
|
||||
|
||||
- [What Is World Monitor?](https://www.worldmonitor.app/blog/posts/what-is-worldmonitor-real-time-global-intelligence/): Overview of the platform, features, and use cases
|
||||
- [Five Dashboards, One Platform](https://www.worldmonitor.app/blog/posts/five-dashboards-one-platform-worldmonitor-variants/): Guide to all five dashboard variants
|
||||
- [Track Global Conflicts in Real Time](https://www.worldmonitor.app/blog/posts/track-global-conflicts-in-real-time/): Conflict monitoring using ACLED, UCDP, and Telegram OSINT
|
||||
- [OSINT for Everyone](https://www.worldmonitor.app/blog/posts/osint-for-everyone-open-source-intelligence-democratized/): How open-source intelligence is democratized
|
||||
- [Natural Disaster Monitoring](https://www.worldmonitor.app/blog/posts/natural-disaster-monitoring-earthquakes-fires-volcanoes/): USGS earthquakes, NASA FIRMS fires, volcanic activity
|
||||
- [Real-Time Market Intelligence](https://www.worldmonitor.app/blog/posts/real-time-market-intelligence-for-traders-and-analysts/): Financial markets, Fear & Greed, ETF flows
|
||||
- [Cyber Threat Intelligence](https://www.worldmonitor.app/blog/posts/cyber-threat-intelligence-for-security-teams/): Feodo Tracker, URLhaus, botnet C2 monitoring
|
||||
- [Global Supply Chain Monitoring](https://www.worldmonitor.app/blog/posts/monitor-global-supply-chains-and-commodity-disruptions/): Ports, chokepoints, commodity disruptions
|
||||
- [Satellite Imagery and Orbital Surveillance](https://www.worldmonitor.app/blog/posts/satellite-imagery-orbital-surveillance/): SAR, EO, and OSINT satellite data
|
||||
- [Live Webcams from Geopolitical Hotspots](https://www.worldmonitor.app/blog/posts/live-webcams-from-geopolitical-hotspots/): Real-time visual feeds from conflict zones
|
||||
- [Prediction Markets and AI Forecasting](https://www.worldmonitor.app/blog/posts/prediction-markets-ai-forecasting-geopolitics/): Polymarket integration and geopolitical forecasting
|
||||
- [World Monitor in 21 Languages](https://www.worldmonitor.app/blog/posts/worldmonitor-in-21-languages-global-intelligence-for-everyone/): Multilingual global intelligence platform
|
||||
- [Command Palette Search](https://www.worldmonitor.app/blog/posts/command-palette-search-everything-instantly/): Cmd+K fuzzy search across all data layers
|
||||
- [AI-Powered Intelligence Without the Cloud](https://www.worldmonitor.app/blog/posts/ai-powered-intelligence-without-the-cloud/): Local LLMs, Ollama, offline-first AI analysis
|
||||
- [Build on World Monitor](https://www.worldmonitor.app/blog/posts/build-on-worldmonitor-developer-api-open-source/): Developer API, proto definitions, open-source contribution
|
||||
- [World Monitor vs Traditional Intelligence Tools](https://www.worldmonitor.app/blog/posts/worldmonitor-vs-traditional-intelligence-tools/): Comparison with Bloomberg, Palantir, Dataminr, Recorded Future
|
||||
- [Tracking Global Trade Routes](https://www.worldmonitor.app/blog/posts/tracking-global-trade-routes-chokepoints-freight-costs/): Maritime chokepoints, freight costs, trade flow analysis
|
||||
|
||||
## Optional
|
||||
|
||||
- [Source Code](https://github.com/koala73/worldmonitor): GitHub repository (AGPL-3.0)
|
||||
|
||||
@@ -1,29 +1,10 @@
|
||||
# World Monitor - protect API routes from crawlers
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Allow: /api/story
|
||||
Allow: /api/og-story
|
||||
Disallow: /api/
|
||||
Disallow: /tests/
|
||||
|
||||
Sitemap: https://www.worldmonitor.app/sitemap.xml
|
||||
Sitemap: https://www.worldmonitor.app/blog/sitemap-index.xml
|
||||
|
||||
# Allow social media bots for OG previews
|
||||
User-agent: Twitterbot
|
||||
Allow: /api/story
|
||||
Allow: /api/og-story
|
||||
|
||||
User-agent: facebookexternalhit
|
||||
Allow: /api/story
|
||||
Allow: /api/og-story
|
||||
|
||||
User-agent: LinkedInBot
|
||||
Allow: /api/story
|
||||
Allow: /api/og-story
|
||||
|
||||
User-agent: Slackbot
|
||||
Allow: /api/story
|
||||
Allow: /api/og-story
|
||||
|
||||
User-agent: Discordbot
|
||||
Allow: /api/story
|
||||
Allow: /api/og-story
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://commodity.worldmonitor.app/</loc>
|
||||
<lastmod>2026-03-26</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://happy.worldmonitor.app/</loc>
|
||||
<lastmod>2026-03-19</lastmod>
|
||||
|
||||
Reference in New Issue
Block a user