Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
1 parent b3d5ea6764
commit e341c5938a
5784 files changed
+1104929 -3

No files matched your search

@@ -0,0 +1,386 @@
import type { KnowledgeBasesTemplateDto } from "../dtos/KnowledgeBasesTemplateDto";
import deTranslations from "../../../../public/locales/de/translations.json";
import enTranslations from "../../../../public/locales/en/translations.json";
type SupportedTemplateLanguage = "en" | "de";
type TemplateCatalog = typeof enTranslations;
type TemplateKnowledgeBase = Omit<KnowledgeBasesTemplateDto["knowledgeBases"][number], "description" | "links" | "title"> & {
description: string;
links: (Omit<KnowledgeBasesTemplateDto["knowledgeBases"][number]["links"][number], "name"> & { name: string })[];
title: string;
};
type TemplateCategory = Omit<KnowledgeBasesTemplateDto["categories"][number], "description" | "sections" | "title"> & {
description: string;
sections: (Omit<KnowledgeBasesTemplateDto["categories"][number]["sections"][number], "description" | "title"> & {
description: string;
title: string;
})[];
title: string;
};
type TemplateArticle = Omit<
KnowledgeBasesTemplateDto["articles"][number],
"contentDraft" | "contentPublished" | "contentPublishedAsText" | "description" | "title"
> & {
contentDraft: string;
contentPublished: string;
contentPublishedAsText: string;
description: string;
title: string;
};
type TemplateSource = {
knowledgeBases: TemplateKnowledgeBase[];
categories: TemplateCategory[];
articles: TemplateArticle[];
};
const TEMPLATE_CATALOGS: Record<SupportedTemplateLanguage, TemplateCatalog> = {
de: deTranslations,
en: enTranslations,
};
function templateKey(path: string) {
return `knowledgeBase.defaultTemplates.sample.${path}`;
}
function getCatalogValue(catalog: TemplateCatalog, key: string) {
return key.split(".").reduce<unknown>((current, segment) => {
if (!current || typeof current !== "object" || !(segment in current)) {
return undefined;
}
return (current as Record<string, unknown>)[segment];
}, catalog);
}
function translateTemplateValue(language: SupportedTemplateLanguage, key: string) {
const value = getCatalogValue(TEMPLATE_CATALOGS[language], key);
if (typeof value === "string") {
return value;
}
const fallback = getCatalogValue(TEMPLATE_CATALOGS.en, key);
return typeof fallback === "string" ? fallback : key;
}
const SAMPLE_SOURCE: TemplateSource = {
knowledgeBases: [
{
basePath: "/",
slug: "docs",
title: templateKey("knowledgeBases.docs.title"),
description: templateKey("knowledgeBases.docs.description"),
defaultLanguage: "en",
layout: "list",
color: 15,
enabled: true,
languages: ["en"],
links: [],
logo: "dark",
seoImage: "",
},
{
basePath: "/docs",
slug: "center",
title: templateKey("knowledgeBases.center.title"),
description: templateKey("knowledgeBases.center.description"),
defaultLanguage: "en",
layout: "articles",
color: 16,
enabled: true,
languages: ["de", "en"],
links: [
{
name: templateKey("knowledgeBases.center.links.backToSite"),
href: "/",
order: 1,
},
],
logo: "dark",
seoImage: "",
},
],
categories: [
{
knowledgeBaseSlug: "docs",
slug: "introduction",
order: 1,
title: templateKey("categories.introduction.title"),
description: templateKey("categories.introduction.description"),
icon: "",
language: "en",
seoImage: "",
sections: [],
},
{
knowledgeBaseSlug: "center",
slug: "getting-started",
order: 1,
title: templateKey("categories.gettingStarted.title"),
description: templateKey("categories.gettingStarted.description"),
icon: '<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="50" height="50" viewBox="0 0 50 50" className={className} fill="currentColor"> <path d="M47.16,21.221l-5.91-0.966c-0.346-1.186-0.819-2.326-1.411-3.405l3.45-4.917c0.279-0.397,0.231-0.938-0.112-1.282 l-3.889-3.887c-0.347-0.346-0.893-0.391-1.291-0.104l-4.843,3.481c-1.089-0.602-2.239-1.08-3.432-1.427l-1.031-5.886 C28.607,2.35,28.192,2,27.706,2h-5.5c-0.49,0-0.908,0.355-0.987,0.839l-0.956,5.854c-1.2,0.345-2.352,0.818-3.437,1.412l-4.83-3.45 c-0.399-0.285-0.942-0.239-1.289,0.106L6.82,10.648c-0.343,0.343-0.391,0.883-0.112,1.28l3.399,4.863 c-0.605,1.095-1.087,2.254-1.438,3.46l-5.831,0.971c-0.482,0.08-0.836,0.498-0.836,0.986v5.5c0,0.485,0.348,0.9,0.825,0.985 l5.831,1.034c0.349,1.203,0.831,2.362,1.438,3.46l-3.441,4.813c-0.284,0.397-0.239,0.942,0.106,1.289l3.888,3.891 c0.343,0.343,0.884,0.391,1.281,0.112l4.87-3.411c1.093,0.601,2.248,1.078,3.445,1.424l0.976,5.861C21.3,47.647,21.717,48,22.206,48 h5.5c0.485,0,0.9-0.348,0.984-0.825l1.045-5.89c1.199-0.353,2.348-0.833,3.43-1.435l4.905,3.441 c0.398,0.281,0.938,0.232,1.282-0.111l3.888-3.891c0.346-0.347,0.391-0.894,0.104-1.292l-3.498-4.857 c0.593-1.08,1.064-2.222,1.407-3.408l5.918-1.039c0.479-0.084,0.827-0.5,0.827-0.985v-5.5C47.999,21.718,47.644,21.3,47.16,21.221z M25,32c-3.866,0-7-3.134-7-7c0-3.866,3.134-7,7-7s7,3.134,7,7C32,28.866,28.866,32,25,32z"></path> </svg>',
language: "en",
seoImage: "",
sections: [],
},
{
knowledgeBaseSlug: "center",
slug: "kategorie-1",
order: 1,
title: templateKey("categories.gettingStartedDe.title"),
description: templateKey("categories.gettingStartedDe.description"),
icon: '<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="50" height="50" viewBox="0 0 50 50" className={className} fill="currentColor"> <path d="M47.16,21.221l-5.91-0.966c-0.346-1.186-0.819-2.326-1.411-3.405l3.45-4.917c0.279-0.397,0.231-0.938-0.112-1.282 l-3.889-3.887c-0.347-0.346-0.893-0.391-1.291-0.104l-4.843,3.481c-1.089-0.602-2.239-1.08-3.432-1.427l-1.031-5.886 C28.607,2.35,28.192,2,27.706,2h-5.5c-0.49,0-0.908,0.355-0.987,0.839l-0.956,5.854c-1.2,0.345-2.352,0.818-3.437,1.412l-4.83-3.45 c-0.399-0.285-0.942-0.239-1.289,0.106L6.82,10.648c-0.343,0.343-0.391,0.883-0.112,1.28l3.399,4.863 c-0.605,1.095-1.087,2.254-1.438,3.46l-5.831,0.971c-0.482,0.08-0.836,0.498-0.836,0.986v5.5c0,0.485,0.348,0.9,0.825,0.985 l5.831,1.034c0.349,1.203,0.831,2.362,1.438,3.46l-3.441,4.813c-0.284,0.397-0.239,0.942,0.106,1.289l3.888,3.891 c0.343,0.343,0.884,0.391,1.281,0.112l4.87-3.411c1.093,0.601,2.248,1.078,3.445,1.424l0.976,5.861C21.3,47.647,21.717,48,22.206,48 h5.5c0.485,0,0.9-0.348,0.984-0.825l1.045-5.89c1.199-0.353,2.348-0.833,3.43-1.435l4.905,3.441 c0.398,0.281,0.938,0.232,1.282-0.111l3.888-3.891c0.346-0.347,0.391-0.894,0.104-1.292l-3.498-4.857 c0.593-1.08,1.064-2.222,1.407-3.408l5.918-1.039c0.479-0.084,0.827-0.5,0.827-0.985v-5.5C47.999,21.718,47.644,21.3,47.16,21.221z M25,32c-3.866,0-7-3.134-7-7c0-3.866,3.134-7,7-7s7,3.134,7,7C32,28.866,28.866,32,25,32z"></path> </svg>',
language: "de",
seoImage: "",
sections: [
{
order: 1,
title: templateKey("categories.gettingStartedDe.sections.basics.title"),
description: templateKey("categories.gettingStartedDe.sections.basics.description"),
},
],
},
{
knowledgeBaseSlug: "center",
slug: "kategorie-2",
order: 2,
title: templateKey("categories.accountManagementDe.title"),
description: templateKey("categories.accountManagementDe.description"),
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> <path fillRule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clipRule="evenodd" /> </svg>',
language: "de",
seoImage: "",
sections: [],
},
{
knowledgeBaseSlug: "center",
slug: "account-management",
order: 2,
title: templateKey("categories.accountManagement.title"),
description: templateKey("categories.accountManagement.description"),
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> <path fillRule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clipRule="evenodd" /> </svg>',
language: "en",
seoImage: "",
sections: [],
},
],
articles: [
{
knowledgeBaseSlug: "docs",
categorySlug: "introduction",
categorySectionOrder: null,
slug: "example-site-knowledge-base",
title: templateKey("articles.exampleSiteKnowledgeBase.title"),
description: templateKey("articles.exampleSiteKnowledgeBase.description"),
order: 0,
contentDraft: templateKey("articles.exampleSiteKnowledgeBase.content.markup"),
contentPublished: templateKey("articles.exampleSiteKnowledgeBase.content.markup"),
contentPublishedAsText: templateKey("articles.exampleSiteKnowledgeBase.content.text"),
contentType: "wysiwyg",
language: "en",
featuredOrder: 2,
seoImage: "",
publishedAt: "2023-07-01T23:42:31.371Z",
createdByUserEmail: null,
relatedArticles: [],
},
{
knowledgeBaseSlug: "docs",
categorySlug: "introduction",
categorySectionOrder: null,
slug: "publish-product-documentation",
title: templateKey("articles.publishProductDocumentation.title"),
description: templateKey("articles.publishProductDocumentation.description"),
order: 0,
contentDraft: templateKey("articles.publishProductDocumentation.content.markup"),
contentPublished: templateKey("articles.publishProductDocumentation.content.markup"),
contentPublishedAsText: templateKey("articles.publishProductDocumentation.content.text"),
contentType: "wysiwyg",
language: "en",
featuredOrder: 1,
seoImage: "",
publishedAt: "2023-07-01T21:49:40.834Z",
createdByUserEmail: null,
relatedArticles: [],
},
{
knowledgeBaseSlug: "center",
categorySlug: "kategorie-1",
categorySectionOrder: 1,
slug: "beispiel-2",
title: templateKey("articles.structureKnowledgeBaseDe.title"),
description: templateKey("articles.structureKnowledgeBaseDe.description"),
order: 0,
contentDraft: templateKey("articles.structureKnowledgeBaseDe.content.markup"),
contentPublished: templateKey("articles.structureKnowledgeBaseDe.content.markup"),
contentPublishedAsText: templateKey("articles.structureKnowledgeBaseDe.content.text"),
contentType: "wysiwyg",
language: "de",
featuredOrder: 3,
seoImage: "",
publishedAt: "2023-07-01T21:39:51.857Z",
createdByUserEmail: null,
relatedArticles: [],
},
{
knowledgeBaseSlug: "center",
categorySlug: "getting-started",
categorySectionOrder: null,
slug: "structure-your-help-center",
title: templateKey("articles.structureYourHelpCenter.title"),
description: templateKey("articles.structureYourHelpCenter.description"),
order: 0,
contentDraft: templateKey("articles.structureYourHelpCenter.content.markup"),
contentPublished: templateKey("articles.structureYourHelpCenter.content.markup"),
contentPublishedAsText: templateKey("articles.structureYourHelpCenter.content.text"),
contentType: "wysiwyg",
language: "en",
featuredOrder: 2,
seoImage: "",
publishedAt: "2023-07-01T23:35:32.524Z",
createdByUserEmail: null,
relatedArticles: [
{
slug: "welcome-to-the-help-center",
},
{
slug: "manage-team-access",
},
],
},
{
knowledgeBaseSlug: "center",
categorySlug: "getting-started",
categorySectionOrder: null,
slug: "welcome-to-the-help-center",
title: templateKey("articles.welcomeToTheHelpCenter.title"),
description: templateKey("articles.welcomeToTheHelpCenter.description"),
order: 0,
contentDraft: templateKey("articles.welcomeToTheHelpCenter.content.markup"),
contentPublished: templateKey("articles.welcomeToTheHelpCenter.content.markup"),
contentPublishedAsText: templateKey("articles.welcomeToTheHelpCenter.content.text"),
contentType: "wysiwyg",
language: "en",
featuredOrder: 1,
seoImage: "",
publishedAt: "2023-07-01T21:39:38.696Z",
createdByUserEmail: null,
relatedArticles: [],
},
{
knowledgeBaseSlug: "center",
categorySlug: "kategorie-1",
categorySectionOrder: null,
slug: "beispiel-1",
title: templateKey("articles.welcomeToKnowledgeBaseDe.title"),
description: templateKey("articles.welcomeToKnowledgeBaseDe.description"),
order: 0,
contentDraft: templateKey("articles.welcomeToKnowledgeBaseDe.content.markup"),
contentPublished: templateKey("articles.welcomeToKnowledgeBaseDe.content.markup"),
contentPublishedAsText: templateKey("articles.welcomeToKnowledgeBaseDe.content.text"),
contentType: "wysiwyg",
language: "de",
featuredOrder: 3,
seoImage: "",
publishedAt: "2023-07-01T21:39:49.570Z",
createdByUserEmail: null,
relatedArticles: [],
},
{
knowledgeBaseSlug: "center",
categorySlug: "kategorie-2",
categorySectionOrder: null,
slug: "beispiel-3",
title: templateKey("articles.manageTeamAccessDe.title"),
description: templateKey("articles.manageTeamAccessDe.description"),
order: 0,
contentDraft: templateKey("articles.manageTeamAccessDe.content.markup"),
contentPublished: templateKey("articles.manageTeamAccessDe.content.markup"),
contentPublishedAsText: templateKey("articles.manageTeamAccessDe.content.text"),
contentType: "wysiwyg",
language: "de",
featuredOrder: null,
seoImage: "",
publishedAt: "2023-07-01T21:39:47.550Z",
createdByUserEmail: null,
relatedArticles: [],
},
{
knowledgeBaseSlug: "center",
categorySlug: "account-management",
categorySectionOrder: null,
slug: "manage-team-access",
title: templateKey("articles.manageTeamAccess.title"),
description: templateKey("articles.manageTeamAccess.description"),
order: 0,
contentDraft: templateKey("articles.manageTeamAccess.content.markup"),
contentPublished: templateKey("articles.manageTeamAccess.content.markup"),
contentPublishedAsText: templateKey("articles.manageTeamAccess.content.text"),
contentType: "wysiwyg",
language: "en",
featuredOrder: null,
seoImage: "",
publishedAt: "2023-07-01T21:39:36.515Z",
createdByUserEmail: null,
relatedArticles: [],
},
],
};
function buildSample(language: SupportedTemplateLanguage): KnowledgeBasesTemplateDto {
const knowledgeBases = SAMPLE_SOURCE.knowledgeBases
.filter((kb) => kb.languages.includes(language))
.map((kb) => ({
...kb,
title: translateTemplateValue(language, kb.title),
description: translateTemplateValue(language, kb.description),
defaultLanguage: language,
languages: [language],
links: kb.links.map((link) => ({
...link,
name: translateTemplateValue(language, link.name),
})),
}));
const knowledgeBaseSlugs = new Set(knowledgeBases.map((kb) => kb.slug));
const categories = SAMPLE_SOURCE.categories
.filter((category) => category.language === language && knowledgeBaseSlugs.has(category.knowledgeBaseSlug))
.map((category) => ({
...category,
title: translateTemplateValue(language, category.title),
description: translateTemplateValue(language, category.description),
sections: category.sections.map((section) => ({
...section,
title: translateTemplateValue(language, section.title),
description: translateTemplateValue(language, section.description),
})),
}));
const categorySlugs = new Set(categories.map((category) => `${category.knowledgeBaseSlug}:${category.slug}`));
const articles = SAMPLE_SOURCE.articles
.filter(
(article) =>
article.language === language &&
knowledgeBaseSlugs.has(article.knowledgeBaseSlug) &&
(!article.categorySlug || categorySlugs.has(`${article.knowledgeBaseSlug}:${article.categorySlug}`))
)
.map((article) => ({
...article,
title: translateTemplateValue(language, article.title),
description: translateTemplateValue(language, article.description),
contentDraft: translateTemplateValue(language, article.contentDraft),
contentPublished: translateTemplateValue(language, article.contentPublished),
contentPublishedAsText: translateTemplateValue(language, article.contentPublishedAsText),
}));
return {
knowledgeBases,
categories,
articles,
};
}
const SAMPLE_EN = buildSample("en");
const SAMPLE_DE = buildSample("de");
const SAMPLE = SAMPLE_EN;
export default {
SAMPLE,
SAMPLE_DE,
SAMPLE_EN,
};
@@ -0,0 +1,162 @@
import type { TFunction } from "i18next";
import UrlUtils from "~/utils/app/UrlUtils";
import { KnowledgeBaseArticleWithDetails } from "../db/kbArticles.db.server";
import { KbCategorySectionDto } from "../dtos/KbCategorySectionDto";
import { KnowledgeBaseDto } from "../dtos/KnowledgeBaseDto";
import { KbCategoryDto } from "../dtos/KbCategoryDto";
import { KbArticleDto } from "../dtos/KbArticleDto";
const defaultLanguage = "de";
const supportedLanguages: { name: string; value: string; labelKey: string }[] = [
{ name: "Deutsch", value: "de", labelKey: "shared.locales.de" },
{ name: "English", value: "en", labelKey: "shared.locales.en" },
];
function getLanguageName(language: string, t?: TFunction) {
const supportedLanguage = supportedLanguages.find((l) => l.value === language);
if (!supportedLanguage) {
return "";
}
return t ? String(t(supportedLanguage.labelKey, { defaultValue: supportedLanguage.name })) : supportedLanguage.name;
}
function getSupportedLanguageOptions(t: TFunction): { name: string; value: string }[] {
return supportedLanguages.map((language) => ({
name: getLanguageName(language.value, t),
value: language.value,
}));
}
function getAvailableArticleSlug({ allArticles, initialSlug }: { allArticles: KnowledgeBaseArticleWithDetails[]; initialSlug: string }) {
let number = 1;
let slug = "";
const findExistingSlug = (slug: string) => {
return allArticles.find((p) => p.slug === slug);
};
do {
slug = `${initialSlug}-${number}`;
const existingWithSlug = findExistingSlug(slug);
if (!existingWithSlug) {
break;
}
// if (number > 10) {
// throw Error("Too many duplicates");
// }
number++;
} while (true);
let maxOrder = 0;
let firstOrder = 0;
if (allArticles.length > 0) {
maxOrder = Math.max(...allArticles.map((p) => p.order));
firstOrder = Math.min(...allArticles.map((p) => p.order));
}
return {
slug,
maxOrder,
firstOrder,
number,
};
}
function getKbUrl({ kb, params }: { kb: { basePath: string; slug: string }; params: { lang?: string } }) {
let url = UrlUtils.join(kb.basePath, kb.slug);
const foundLang = supportedLanguages.find((l) => l.value === params.lang);
if (params.lang && foundLang) {
return `${url}/${params.lang}`;
}
return `${url}`;
}
function getArticleUrl({ kb, article, params }: { kb: { slug: string; basePath: string }; article: { slug: string }; params: { lang?: string } }) {
let kbUrl = getKbUrl({ kb, params });
return `${kbUrl}/articles/${article.slug}`;
}
function getCategoryUrl({ kb, category, params }: { kb: { slug: string; basePath: string }; category: { slug: string }; params: { lang?: string } }) {
let kbUrl = getKbUrl({ kb, params });
return `${kbUrl}/categories/${category.slug}`;
}
function getCategoryArticlesBySections({ kb, category }: { kb: KnowledgeBaseDto; category: KbCategoryDto }) {
let sections: {
section: KbCategorySectionDto | null;
articles: {
order: number;
title: string;
description: string;
href: string;
section: string | null;
sectionId: string | null;
}[];
}[] = [];
category.articles.forEach((article) => {
const section = category.sections.find((item) => item.id === article.sectionId) ?? null;
const sectionIndex = sections.findIndex((item) => item.section?.id === section?.id);
if (sectionIndex === -1) {
sections.push({
section,
articles: [
{
...article,
section: section?.title ?? null,
},
],
});
} else {
sections[sectionIndex].articles.push({
...article,
section: section?.title ?? null,
});
}
});
sections.forEach((item) => {
item.articles = item.articles.sort((a, b) => {
if (a.order && b.order) {
return a.order - b.order;
}
return 0;
});
});
sections = sections.sort((a, b) => {
if (a.section?.order && b.section?.order) {
return a.section.order - b.section.order;
}
return -1;
});
return sections;
}
function fixDarkMode({ article }: { article: KbArticleDto }) {
const replaces = {
"rounded-sm bg-stone-100 p-5 font-mono font-medium text-stone-800": "rounded-sm bg-secondary text-secondary-foreground p-5 font-mono font-medium",
"rounded-sm bg-secondary p-5 font-mono font-medium": "rounded-sm bg-secondary text-secondary-foreground p-5 font-mono font-medium",
"rounded-md bg-secondary px-1.5 py-1 font-mono font-medium": "rounded-md bg-secondary text-secondary-foreground px-1.5 py-1 font-mono font-medium",
"rounded-md bg-stone-200 px-1.5 py-1 font-mono font-medium text-black":
"rounded-md bg-secondary px-1.5 py-1 font-mono font-medium text-secondary-foreground",
"text-stone-600 underline underline-offset-[3px] hover:text-stone-700 transition-colors cursor-pointer text-stone-600 underline underline-offset-[3px] hover:text-stone-700 transition-colors cursor-pointer":
"text-muted-foreground underline underline-offset-[3px] hover:text-foreground transition-colors cursor-pointer",
"underline underline-offset-[3px] hover:text-foreground transition-colors cursor-pointer":
"text-muted-foreground underline underline-offset-[3px] hover:text-foreground transition-colors cursor-pointer",
};
if (article.contentPublished) {
for (const [key, value] of Object.entries(replaces)) {
article.contentPublished = article.contentPublished.replaceAll(key, value);
}
}
}
export default {
defaultLanguage,
supportedLanguages,
getSupportedLanguageOptions,
getLanguageName,
getAvailableArticleSlug,
getKbUrl,
getArticleUrl,
getCategoryUrl,
getCategoryArticlesBySections,
fixDarkMode,
};
@@ -0,0 +1,151 @@
import fs from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import DefaultKbsTemplate from "./DefaultKbsTemplate";
const sourcePath = path.resolve(process.cwd(), "apps/web/app/modules/knowledgeBase/utils/DefaultKbsTemplate.ts");
const enTranslationsPath = path.resolve(process.cwd(), "apps/web/public/locales/en/translations.json");
const deTranslationsPath = path.resolve(process.cwd(), "apps/web/public/locales/de/translations.json");
function readTranslations(localePath: string) {
return JSON.parse(fs.readFileSync(localePath, "utf8")) as {
knowledgeBase: {
defaultTemplates: {
sample: {
knowledgeBases: {
docs: { description: string; title: string };
center: { description: string; links: { backToSite: string }; title: string };
};
articles: {
exampleSiteKnowledgeBase: { description: string; content: { text: string } };
publishProductDocumentation: { description: string; content: { text: string } };
};
categories: {
introduction: { description: string };
kategorie1: { sections: { basics: { description: string } } };
};
};
};
};
};
}
describe("DefaultKbsTemplate content", () => {
it("keeps seeded knowledge-base copy free from placeholder junk and misspelled API names", () => {
const source = fs.readFileSync(sourcePath, "utf8");
expect(source).toContain("knowledgeBase.defaultTemplates.sample");
expect(source).not.toContain("knowlegeBase");
expect(source).not.toContain("asdf");
expect(source).not.toContain("Sample description");
expect(source).not.toContain("Category 1");
expect(source).not.toContain("Introduction category description sample");
expect(source).not.toContain("Example Site - Knowledge Base");
expect(source).not.toContain("loom.com");
expect(source).not.toContain("demo@toolctrl.local");
expect(source).not.toContain("Dynamic vs Static Knowledge Bases");
expect(source).not.toContain("apps/web/app/routes/docs/($lang).tsx");
expect(source).not.toContain("related articles to guide readers");
});
it("stores semantic locale keys for default knowledge-base seed copy", () => {
const source = fs.readFileSync(sourcePath, "utf8");
const en = readTranslations(enTranslationsPath);
const de = readTranslations(deTranslationsPath);
expect(source).toContain('templateKey("knowledgeBases.docs.title")');
expect(source).toContain('templateKey("articles.publishProductDocumentation.content.text")');
expect(source).not.toContain("ui.autogenerated");
expect(en.knowledgeBase.defaultTemplates.sample.knowledgeBases.docs.title).toBe("Docs");
expect(en.knowledgeBase.defaultTemplates.sample.articles.publishProductDocumentation.content.text).toContain("related articles");
expect(de.knowledgeBase.defaultTemplates.sample.knowledgeBases.center.links.backToSite).toBe("Zur Website");
expect(de.knowledgeBase.defaultTemplates.sample.articles.publishProductDocumentation.content.text).toContain("verwandte Artikel");
});
it("exposes locale-scoped admin import samples instead of mixed public chrome", () => {
for (const [template, language] of [
[DefaultKbsTemplate.SAMPLE_EN, "en"],
[DefaultKbsTemplate.SAMPLE_DE, "de"],
] as const) {
expect(Object.keys(template).sort()).toEqual(["articles", "categories", "knowledgeBases"]);
expect(template.knowledgeBases.every((kb) => kb.defaultLanguage === language)).toBe(true);
expect(template.knowledgeBases.every((kb) => kb.languages.length === 1 && kb.languages[0] === language)).toBe(true);
expect(template.categories.every((category) => category.language === language)).toBe(true);
expect(template.articles.every((article) => article.language === language)).toBe(true);
}
expect(DefaultKbsTemplate.SAMPLE_DE.knowledgeBases.map((kb) => kb.title)).toContain("Hilfe-Center");
expect(JSON.stringify(DefaultKbsTemplate.SAMPLE_DE)).not.toContain("Help Center");
expect(JSON.stringify(DefaultKbsTemplate.SAMPLE_DE)).not.toContain("Back to site");
});
it("keeps German knowledge-base copy generic while preserving the Hilfe-Center product title", () => {
const deSample = DefaultKbsTemplate.SAMPLE_DE;
const deCopy = JSON.stringify(deSample);
expect(deSample.knowledgeBases.find((kb) => kb.slug === "center")?.title).toBe("Hilfe-Center");
expect(deSample.categories.find((category) => category.slug === "kategorie-1")?.description).toBe(
"Grundlagen für die Einrichtung und Nutzung der Wissensdatenbank."
);
expect(deSample.articles.find((article) => article.slug === "beispiel-2")?.title).toBe("Wissensdatenbank strukturieren");
expect(deSample.articles.find((article) => article.slug === "beispiel-1")?.contentPublishedAsText).toContain("Diese Wissensdatenbank bündelt");
expect(deCopy).not.toContain("Nutzung des Hilfe-Centers");
expect(deCopy).not.toContain("Dieses Hilfe-Center bündelt");
});
it("keeps default category and article metadata useful for imported starter content", () => {
for (const template of [DefaultKbsTemplate.SAMPLE_EN, DefaultKbsTemplate.SAMPLE_DE]) {
for (const knowledgeBase of template.knowledgeBases) {
expect(knowledgeBase.title.trim()).not.toHaveLength(0);
expect(knowledgeBase.description.trim()).not.toHaveLength(0);
}
for (const category of template.categories) {
expect(category.title.trim()).not.toHaveLength(0);
expect(category.description.trim()).not.toHaveLength(0);
for (const section of category.sections) {
expect(section.title.trim()).not.toHaveLength(0);
expect(section.description.trim()).not.toHaveLength(0);
}
}
for (const article of template.articles) {
expect(article.title.trim()).not.toHaveLength(0);
expect(article.description.trim()).not.toHaveLength(0);
}
}
expect(DefaultKbsTemplate.SAMPLE_EN.knowledgeBases.find((kb) => kb.slug === "docs")?.description).toBe(
"Public product documentation and starter guides for your team."
);
expect(DefaultKbsTemplate.SAMPLE_EN.knowledgeBases.find((kb) => kb.slug === "center")?.description).toBe(
"Customer-facing help articles, setup guidance, and account support."
);
expect(
DefaultKbsTemplate.SAMPLE_DE.categories.find((category) => category.slug === "kategorie-1")?.sections.find((section) => section.order === 1)
?.description
).toBe("Wichtige Einrichtungshinweise, um hilfreiche Startinhalte zu strukturieren.");
expect(DefaultKbsTemplate.SAMPLE_EN.categories.find((category) => category.slug === "introduction")?.description).toBe(
"Start here for an overview of the documentation structure."
);
const starterArticle = DefaultKbsTemplate.SAMPLE_EN.articles.find((article) => article.slug === "example-site-knowledge-base");
expect(starterArticle?.description).toBe("A short starter article for the public docs knowledge base.");
expect(starterArticle?.contentPublishedAsText).toContain("neutral starting point");
expect(starterArticle?.createdByUserEmail).toBeNull();
const publishingArticle = DefaultKbsTemplate.SAMPLE_EN.articles.find((article) => article.slug === "publish-product-documentation");
expect(publishingArticle?.description).toBe("A practical checklist for turning draft articles into useful public documentation.");
expect(publishingArticle?.contentPublishedAsText).toContain("answers a real user question");
expect(publishingArticle?.contentPublishedAsText).not.toContain("route files");
});
it("keeps imported sample articles self-contained and attributed to the importing admin", () => {
for (const template of [DefaultKbsTemplate.SAMPLE_EN, DefaultKbsTemplate.SAMPLE_DE]) {
const serialized = JSON.stringify(template);
expect(serialized).not.toContain("loom.com");
expect(serialized).not.toContain("demo@toolctrl.local");
expect(template.articles.every((article) => article.createdByUserEmail === null)).toBe(true);
}
});
});