Present FOLX as a music news publisher and unify site SEO
This commit is contained in:
+42
-44
@@ -1,3 +1,4 @@
|
||||
import { publisherSchema, homeSchema, INFO_SEO, articleModifiedTime, articleGenre } from "../shared/seo";
|
||||
import express, { type Express } from "express";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -11,13 +12,7 @@ import sharp from "sharp";
|
||||
// Brez markerja ostane vse kot prej (glasbeno), da starejsi clanki ne nazadujejo.
|
||||
// Besedilne zaznave namenoma NI: preizkus na 30 clankih jo je zgresil pri
|
||||
// Fernsehgartnu in Carpendaleju.
|
||||
function folxGenre(article: any): { words: string[]; section: string } {
|
||||
const m = /<!--\s*folx-badge:\s*([^>]*?)\s*-->/i.exec(String(article?.content || ""));
|
||||
const badge = (m ? m[1] : "MUSIC NEWS").toUpperCase();
|
||||
return badge === "MUSIC NEWS"
|
||||
? { words: ["Volksmusik", "Schlager", "Musik News"], section: "Musik News" }
|
||||
: { words: ["Star News", "Promi News", "Promi"], section: "Star News" };
|
||||
}
|
||||
const folxGenre = (article: any) => articleGenre(String(article?.content || ""));
|
||||
|
||||
|
||||
function escapeHtml(str: string): string {
|
||||
@@ -88,15 +83,7 @@ const ARTICLE_SLUG_REDIRECTS: Record<string, string> = {
|
||||
// v bazi in v /api/articles ostanejo, ker jih bere sidebar na TV kanalih.
|
||||
// Izdajatelj: doslej samo ime in logo. Brez registrirane druzbe, naslova in
|
||||
// profilov Google znamke nima s cim povezati.
|
||||
const PUBLISHER = (base: string) => ({
|
||||
// 4. 9. 2026: izdajatelj spet samo "Folx Music Television" z logom, kot do
|
||||
// 26. 8. - brez registrirane druzbe, naslova in kontaktov.
|
||||
"@type": "Organization",
|
||||
"@id": `${base}/#organization`,
|
||||
"name": "Folx Music Television",
|
||||
"url": base,
|
||||
"logo": { "@type": "ImageObject", "url": `${base}/og-image.jpg` },
|
||||
});
|
||||
const PUBLISHER = publisherSchema;
|
||||
|
||||
const ONE_RUBRIKEN = new Set(["Politik", "Promis", "Sport", "Musik & Film", "Wetter", "Finanzen", "Aktuelles"]);
|
||||
|
||||
@@ -294,28 +281,10 @@ export function serveStatic(app: Express) {
|
||||
if (url === "/" || url.startsWith("/?")) {
|
||||
try {
|
||||
let template = await fs.promises.readFile(indexPath, "utf-8");
|
||||
const homeLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@graph": [
|
||||
PUBLISHER(canonicalBase),
|
||||
{
|
||||
"@type": "WebSite",
|
||||
"@id": `${canonicalBase}/#website`,
|
||||
"url": `${canonicalBase}/`,
|
||||
"name": "FOLX Music Television",
|
||||
"inLanguage": "de-DE",
|
||||
"publisher": { "@id": `${canonicalBase}/#organization` },
|
||||
"potentialAction": {
|
||||
"@type": "SearchAction",
|
||||
"target": { "@type": "EntryPoint", "urlTemplate": `${canonicalBase}/suche?q={search_term_string}` },
|
||||
"query-input": "required name=search_term_string",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const homeLd = homeSchema(canonicalBase);
|
||||
template = template.replace(
|
||||
"</head>",
|
||||
`<script type="application/ld+json">${JSON.stringify(homeLd).replace(/</g, "\\u003c")}</script>\n</head>`);
|
||||
`<script id="home-jsonld" type="application/ld+json">${JSON.stringify(homeLd).replace(/</g, "\\u003c")}</script>\n</head>`);
|
||||
|
||||
// 31. 8. 2026: naslovnica je React aplikacija, zato je robot dobil samo
|
||||
// lupino brez ene same povezave na clanek. Clanki se do Googla pridejo
|
||||
@@ -325,7 +294,8 @@ export function serveStatic(app: Express) {
|
||||
try {
|
||||
const vsi = await storage.getArticles();
|
||||
const zadnji = [...vsi]
|
||||
.sort((a: any, b: any) => String(b.publishedAt).localeCompare(String(a.publishedAt)))
|
||||
.filter((a: any) => !a.targetUrl && !ONE_RUBRIKEN.has(a.category || ""))
|
||||
.sort((a: any, b: any) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime())
|
||||
.slice(0, 40);
|
||||
if (zadnji.length) {
|
||||
const seznam = zadnji
|
||||
@@ -503,11 +473,7 @@ export function serveStatic(app: Express) {
|
||||
: new Date().toISOString();
|
||||
|
||||
// dateModified: ce je clanek dopolnjen (enrich), preberi marker <!--folx-updated:ISO-->
|
||||
const modDate = (() => {
|
||||
const mm = /<!--folx-updated:([^>]+?)-->/.exec(article.content || "");
|
||||
if (mm) { try { return new Date(mm[1]).toISOString(); } catch (e) { /* ignore */ } }
|
||||
return pubDate;
|
||||
})();
|
||||
const modDate = articleModifiedTime(article.content || "", pubDate);
|
||||
|
||||
// VideoObject: ce je v clanku vgrajen YouTube video
|
||||
const videoId = (() => {
|
||||
@@ -518,6 +484,8 @@ export function serveStatic(app: Express) {
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "NewsArticle",
|
||||
"@id": `${articleUrl}#article`,
|
||||
"inLanguage": "de-DE",
|
||||
"headline": article.title,
|
||||
"description": article.excerpt,
|
||||
"image": [finalImage],
|
||||
@@ -536,7 +504,7 @@ export function serveStatic(app: Express) {
|
||||
"@id": articleUrl,
|
||||
},
|
||||
};
|
||||
const jsonLdTag = `<script type="application/ld+json">${JSON.stringify(jsonLd).replace(/</g, "\\u003c")}</script>`;
|
||||
const jsonLdTag = `<script id="article-jsonld" type="application/ld+json">${JSON.stringify(jsonLd).replace(/</g, "\\u003c")}</script>`;
|
||||
|
||||
const videoLd = videoId ? {
|
||||
"@context": "https://schema.org",
|
||||
@@ -557,6 +525,8 @@ export function serveStatic(app: Express) {
|
||||
`<meta property="og:title" content="${escapeHtml(article.title)}" />`,
|
||||
`<meta property="og:description" content="${escapeHtml(article.excerpt)}" />`,
|
||||
`<meta property="og:type" content="article" />`,
|
||||
`<meta property="article:published_time" content="${pubDate}" />`,
|
||||
`<meta property="article:modified_time" content="${modDate}" />`,
|
||||
`<meta property="og:url" content="${escapeHtml(articleUrl)}" />`,
|
||||
`<meta property="og:image" content="${escapeHtml(finalImage)}" />`,
|
||||
`<meta property="og:image:secure_url" content="${escapeHtml(finalImage)}" />`,
|
||||
@@ -575,7 +545,7 @@ export function serveStatic(app: Express) {
|
||||
`<meta name="description" content="${escapeHtml(article.excerpt)}" />`,
|
||||
`<meta name="keywords" content="${escapeHtml(folxGenre(article).words.join(", "))}, ${escapeHtml(article.title)}, ${escapeHtml(article.category || 'News')}, FOLX TV" />`,
|
||||
`<link rel="canonical" href="${escapeHtml(articleUrl)}" />`,
|
||||
`<title>${escapeHtml(article.title)} - Volksmusik & Schlager | Folx Music Television</title>`,
|
||||
`<title>${escapeHtml(article.title)} | FOLX TV</title>`,
|
||||
jsonLdTag,
|
||||
videoLdTag,
|
||||
].join("\n ");
|
||||
@@ -765,6 +735,34 @@ export function serveStatic(app: Express) {
|
||||
return;
|
||||
}
|
||||
|
||||
const infoPath = pathOnly.replace(/\/+$/, "");
|
||||
const info = INFO_SEO[infoPath];
|
||||
if (info) {
|
||||
let template = stripExistingMeta(await fs.promises.readFile(indexPath, "utf-8"));
|
||||
const pageUrl = `${canonicalBase}${infoPath}`;
|
||||
const title = `${info.title} | FOLX TV`;
|
||||
const ld = { "@context": "https://schema.org", "@type": info.type,
|
||||
"@id": `${pageUrl}#webpage`, url: pageUrl, name: title, description: info.description,
|
||||
inLanguage: "de-DE", isPartOf: { "@id": `${canonicalBase}/#website` }, publisher: PUBLISHER(canonicalBase) };
|
||||
template = template.replace(/<title>[^<]*<\/title>/, [
|
||||
`<title>${escapeHtml(title)}</title>`,
|
||||
`<meta name="description" content="${escapeHtml(info.description)}" />`,
|
||||
`<link rel="canonical" href="${pageUrl}" />`,
|
||||
`<meta property="og:title" content="${escapeHtml(title)}" />`,
|
||||
`<meta property="og:description" content="${escapeHtml(info.description)}" />`,
|
||||
`<meta property="og:type" content="website" />`,
|
||||
`<meta property="og:url" content="${pageUrl}" />`,
|
||||
`<meta property="og:image" content="${canonicalBase}/og-image.jpg" />`,
|
||||
`<meta name="twitter:card" content="summary_large_image" />`,
|
||||
`<meta name="twitter:title" content="${escapeHtml(title)}" />`,
|
||||
`<meta name="twitter:description" content="${escapeHtml(info.description)}" />`,
|
||||
`<meta name="twitter:image" content="${canonicalBase}/og-image.jpg" />`,
|
||||
`<script id="info-jsonld" type="application/ld+json">${JSON.stringify(ld).replace(/</g, "\\u003c")}</script>`,
|
||||
].join("\n"));
|
||||
res.status(200).type("html").send(template);
|
||||
return;
|
||||
}
|
||||
|
||||
// Neznana pot mora vrniti PRAVI 404, sicer Google dobi soft 404 (HTTP 200
|
||||
// z naslovnico). Seznam mora ustrezati potem v client/src/App.tsx.
|
||||
const KNOWN = [
|
||||
|
||||
Reference in New Issue
Block a user