2024-08-23 18:43:52 +02:00
|
|
|
import { Marked } from "marked";
|
2024-08-24 19:37:00 +02:00
|
|
|
import type { Renderer, Token } from "marked";
|
2024-08-23 18:43:52 +02:00
|
|
|
import { markedHighlight } from "marked-highlight";
|
2024-08-23 18:43:54 +02:00
|
|
|
import hljs from "highlight.js";
|
2024-08-25 15:06:55 +02:00
|
|
|
import DOMPurify from "isomorphic-dompurify";
|
2024-09-10 17:29:57 +02:00
|
|
|
import type {
|
|
|
|
|
Website,
|
|
|
|
|
Settings,
|
|
|
|
|
Header,
|
|
|
|
|
Home,
|
|
|
|
|
Footer,
|
|
|
|
|
Article,
|
|
|
|
|
DocsCategory,
|
2024-09-20 15:56:07 +02:00
|
|
|
LegalInformation,
|
|
|
|
|
DomainPrefix
|
2024-09-10 17:29:57 +02:00
|
|
|
} from "$lib/db-schema";
|
2024-09-27 16:59:29 +02:00
|
|
|
import type { SubmitFunction } from "@sveltejs/kit";
|
|
|
|
|
import { sending } from "./runes.svelte";
|
2024-08-04 16:15:09 +02:00
|
|
|
|
2024-09-17 22:44:16 +02:00
|
|
|
export const ALLOWED_MIME_TYPES = [
|
|
|
|
|
"image/jpeg",
|
|
|
|
|
"image/png",
|
|
|
|
|
"image/webp",
|
|
|
|
|
"image/avif",
|
|
|
|
|
"image/gif",
|
|
|
|
|
"image/svg+xml"
|
|
|
|
|
];
|
2024-08-04 16:15:09 +02:00
|
|
|
|
2024-09-13 19:30:56 +02:00
|
|
|
export const slugify = (string: string) => {
|
|
|
|
|
return string
|
|
|
|
|
.toString()
|
|
|
|
|
.normalize("NFKD") // Normalize Unicode characters
|
|
|
|
|
.toLowerCase() // Convert to lowercase
|
|
|
|
|
.trim() // Trim leading and trailing whitespace
|
|
|
|
|
.replace(/\s+/g, "-") // Replace spaces with hyphens
|
2024-09-17 22:44:16 +02:00
|
|
|
.replace(/[^\w-]+/g, "") // Remove non-word characters (except hyphens)
|
|
|
|
|
.replace(/-+/g, "-") // Replace multiple hyphens with single hyphen
|
2024-09-13 19:30:56 +02:00
|
|
|
.replace(/^-+/, "") // Remove leading hyphens
|
|
|
|
|
.replace(/-+$/, ""); // Remove trailing hyphens
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-24 20:34:06 +02:00
|
|
|
const createMarkdownParser = (showToc = true) => {
|
2024-08-23 18:43:54 +02:00
|
|
|
const marked = new Marked();
|
|
|
|
|
|
|
|
|
|
marked.use({
|
2024-08-24 20:34:06 +02:00
|
|
|
async: false,
|
2024-08-23 18:43:54 +02:00
|
|
|
pedantic: false,
|
|
|
|
|
gfm: true
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
marked.use(
|
2024-08-23 18:43:52 +02:00
|
|
|
markedHighlight({
|
2024-08-24 20:34:06 +02:00
|
|
|
async: false,
|
2024-08-23 18:43:54 +02:00
|
|
|
langPrefix: "language-",
|
2024-08-23 18:43:52 +02:00
|
|
|
highlight(code, lang) {
|
|
|
|
|
const language = hljs.getLanguage(lang) ? lang : "plaintext";
|
|
|
|
|
return hljs.highlight(code, { language }).value;
|
2024-08-18 18:17:59 +02:00
|
|
|
}
|
2024-08-23 18:43:52 +02:00
|
|
|
})
|
|
|
|
|
);
|
2024-08-18 18:17:59 +02:00
|
|
|
|
2024-08-27 16:39:29 +02:00
|
|
|
const gfmHeadingId = ({ prefix = "", showToc = true } = {}) => {
|
2024-09-17 22:44:16 +02:00
|
|
|
const headings: { text: string; level: number; id: string }[] = [];
|
|
|
|
|
const sectionStack: { level: number; id: string }[] = [];
|
2024-09-13 19:30:56 +02:00
|
|
|
|
2024-08-24 19:37:00 +02:00
|
|
|
return {
|
|
|
|
|
renderer: {
|
|
|
|
|
heading(this: Renderer, { tokens, depth }: { tokens: Token[]; depth: number }) {
|
|
|
|
|
const text = this.parser.parseInline(tokens);
|
|
|
|
|
const level = depth;
|
2024-09-13 19:30:56 +02:00
|
|
|
const id = `${prefix}${slugify(text)}`;
|
|
|
|
|
const heading = { level, text, id };
|
2024-08-24 19:37:00 +02:00
|
|
|
headings.push(heading);
|
|
|
|
|
|
|
|
|
|
let closingSections = "";
|
|
|
|
|
while (sectionStack.length > 0 && sectionStack[sectionStack.length - 1].level >= level) {
|
|
|
|
|
sectionStack.pop();
|
|
|
|
|
closingSections += "</section>";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sectionStack.push({ level, id });
|
|
|
|
|
const openingSection = `<section id="${id}">`;
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
${closingSections}
|
|
|
|
|
${openingSection}
|
|
|
|
|
<h${level}>
|
|
|
|
|
<a href="#${id}">${text}</a>
|
|
|
|
|
</h${level}>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
hooks: {
|
|
|
|
|
postprocess(html: string) {
|
2024-08-25 14:35:57 +02:00
|
|
|
let tableOfContents = "";
|
|
|
|
|
if (showToc && headings.length > 0) {
|
|
|
|
|
const tocItems = [];
|
|
|
|
|
let currentLevel = 0;
|
|
|
|
|
|
|
|
|
|
for (const { id, text, level } of headings) {
|
|
|
|
|
while (currentLevel < level - 1) {
|
|
|
|
|
tocItems.push("<ul>");
|
|
|
|
|
currentLevel++;
|
|
|
|
|
}
|
|
|
|
|
while (currentLevel > level - 1) {
|
|
|
|
|
tocItems.push("</ul>");
|
|
|
|
|
currentLevel--;
|
|
|
|
|
}
|
|
|
|
|
tocItems.push(`<li><a href="#${id}">${text}</a>`);
|
|
|
|
|
if (level > currentLevel) {
|
|
|
|
|
tocItems.push("<ul>");
|
|
|
|
|
currentLevel = level;
|
|
|
|
|
} else {
|
|
|
|
|
tocItems.push("</li>");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (currentLevel > 0) {
|
|
|
|
|
tocItems.push("</ul></li>");
|
|
|
|
|
currentLevel--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tableOfContents = `
|
2024-08-25 19:32:57 +02:00
|
|
|
<section id="table-of-contents">
|
|
|
|
|
<h2>
|
|
|
|
|
<a href="#table-of-contents">Table of contents</a>
|
|
|
|
|
</h2>
|
2024-08-25 14:35:57 +02:00
|
|
|
${tocItems.join("")}
|
2024-08-25 19:32:57 +02:00
|
|
|
</section>
|
2024-08-25 14:35:57 +02:00
|
|
|
`;
|
|
|
|
|
}
|
2024-08-24 19:37:00 +02:00
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
${tableOfContents}
|
|
|
|
|
${html}
|
2024-09-13 19:30:56 +02:00
|
|
|
${"</section>".repeat(sectionStack.length)}
|
2024-08-24 19:37:00 +02:00
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-08-27 16:39:29 +02:00
|
|
|
};
|
2024-08-24 19:37:00 +02:00
|
|
|
|
2024-08-24 20:34:06 +02:00
|
|
|
marked.use(gfmHeadingId({ showToc: showToc }));
|
2024-08-24 19:37:00 +02:00
|
|
|
|
2024-08-23 18:43:52 +02:00
|
|
|
return marked;
|
|
|
|
|
};
|
2024-08-18 18:17:59 +02:00
|
|
|
|
2024-08-24 20:34:06 +02:00
|
|
|
export const md = (markdownContent: string, showToc = true) => {
|
|
|
|
|
const marked = createMarkdownParser(showToc);
|
2024-08-25 15:06:55 +02:00
|
|
|
const html = DOMPurify.sanitize(marked.parse(markdownContent) as string);
|
2024-08-18 18:17:59 +02:00
|
|
|
|
2024-08-25 15:06:55 +02:00
|
|
|
return html;
|
2024-08-23 18:43:52 +02:00
|
|
|
};
|
2024-08-14 19:33:41 +02:00
|
|
|
|
2024-09-27 16:59:29 +02:00
|
|
|
export const LOADING_DELAY = 500;
|
|
|
|
|
let loadingDelay: number;
|
|
|
|
|
|
|
|
|
|
export const enhanceForm = (options?: {
|
|
|
|
|
reset?: boolean;
|
|
|
|
|
closeModal?: boolean;
|
|
|
|
|
}): SubmitFunction => {
|
|
|
|
|
return () => {
|
|
|
|
|
loadingDelay = window.setTimeout(() => (sending.value = true), LOADING_DELAY);
|
|
|
|
|
|
|
|
|
|
return async ({ update }) => {
|
|
|
|
|
await update({ reset: options?.reset ?? true });
|
|
|
|
|
window.clearTimeout(loadingDelay);
|
|
|
|
|
if (options?.closeModal) {
|
|
|
|
|
window.location.hash = "!";
|
|
|
|
|
}
|
|
|
|
|
sending.value = false;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
2024-08-19 19:31:41 +02:00
|
|
|
|
2024-09-27 16:59:29 +02:00
|
|
|
export const hexToHSL = (hex: string) => {
|
|
|
|
|
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
|
|
|
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
|
|
|
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
|
|
|
|
|
|
|
|
const max = Math.max(r, g, b);
|
|
|
|
|
const min = Math.min(r, g, b);
|
|
|
|
|
const d = max - min;
|
|
|
|
|
|
|
|
|
|
let h = 0;
|
|
|
|
|
const l = (max + min) / 2;
|
|
|
|
|
const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
|
|
|
|
|
|
|
|
|
|
if (d !== 0) {
|
|
|
|
|
switch (max) {
|
|
|
|
|
case r:
|
|
|
|
|
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
|
|
|
break;
|
|
|
|
|
case g:
|
|
|
|
|
h = ((b - r) / d + 2) / 6;
|
|
|
|
|
break;
|
|
|
|
|
case b:
|
|
|
|
|
h = ((r - g) / d + 4) / 6;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-09-03 16:06:07 +02:00
|
|
|
}
|
2024-09-27 16:59:29 +02:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
h: Math.round(h * 360),
|
|
|
|
|
s: Math.round(s * 100),
|
|
|
|
|
l: Math.round(l * 100)
|
|
|
|
|
};
|
2024-08-19 19:31:41 +02:00
|
|
|
};
|
2024-09-10 17:29:57 +02:00
|
|
|
|
|
|
|
|
export interface WebsiteOverview extends Website {
|
|
|
|
|
settings: Settings;
|
|
|
|
|
header: Header;
|
|
|
|
|
home: Home;
|
|
|
|
|
footer: Footer;
|
|
|
|
|
article: (Article & { docs_category: DocsCategory | null })[];
|
|
|
|
|
legal_information?: LegalInformation;
|
2024-09-20 15:56:07 +02:00
|
|
|
domain_prefix?: DomainPrefix;
|
2024-09-10 17:29:57 +02:00
|
|
|
}
|