2024-08-04 16:15:09 +02:00
|
|
|
import markdownit from "markdown-it";
|
|
|
|
|
import hljs from "highlight.js";
|
2024-08-18 18:17:59 +02:00
|
|
|
import type { StateCore } from "markdown-it/index.js";
|
2024-08-14 19:33:41 +02:00
|
|
|
import { dev } from "$app/environment";
|
2024-08-04 16:15:09 +02:00
|
|
|
|
2024-07-31 07:23:32 +02:00
|
|
|
export const sortOptions = [
|
|
|
|
|
{ value: "creation-time", text: "Creation time" },
|
|
|
|
|
{ value: "last-modified", text: "Last modified" },
|
|
|
|
|
{ value: "title-a-to-z", text: "Title - A to Z" },
|
|
|
|
|
{ value: "title-z-to-a", text: "Title - Z to A" }
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
export const ALLOWED_MIME_TYPES = ["image/jpeg", "image/png", "image/svg+xml", "image/webp"];
|
2024-08-04 16:15:09 +02:00
|
|
|
|
|
|
|
|
export const md = markdownit({
|
|
|
|
|
linkify: true,
|
|
|
|
|
typographer: true,
|
2024-08-18 18:17:59 +02:00
|
|
|
highlight: (str: string, lang: string) => {
|
2024-08-04 16:15:09 +02:00
|
|
|
if (lang && hljs.getLanguage(lang)) {
|
|
|
|
|
try {
|
|
|
|
|
return hljs.highlight(str, { language: lang }).value;
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
}
|
2024-08-18 18:17:59 +02:00
|
|
|
}).use((md) => {
|
|
|
|
|
const addSections = (state: StateCore) => {
|
|
|
|
|
const tokens = [];
|
|
|
|
|
const Token = state.Token;
|
|
|
|
|
const sections: { header: number; nesting: number }[] = [];
|
|
|
|
|
let nestedLevel = 0;
|
|
|
|
|
|
2024-08-18 19:18:32 +02:00
|
|
|
const slugify = (text: string) => {
|
|
|
|
|
return text
|
|
|
|
|
.toLowerCase()
|
|
|
|
|
.replace(/\s+/g, "-")
|
|
|
|
|
.replace(/[^\w-]+/g, "")
|
|
|
|
|
.replace(/--+/g, "-")
|
|
|
|
|
.replace(/^-+/, "")
|
|
|
|
|
.replace(/-+$/, "");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const openSection = (attrs: [string, string][] | null, headingText: string) => {
|
2024-08-18 18:17:59 +02:00
|
|
|
const t = new Token("section_open", "section", 1);
|
|
|
|
|
t.block = true;
|
2024-08-18 19:18:32 +02:00
|
|
|
t.attrs = attrs ? attrs.map((attr) => [attr[0], attr[1]]) : [];
|
|
|
|
|
t.attrs.push(["id", slugify(headingText)]);
|
2024-08-18 18:17:59 +02:00
|
|
|
return t;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const closeSection = () => {
|
|
|
|
|
const t = new Token("section_close", "section", -1);
|
|
|
|
|
t.block = true;
|
|
|
|
|
return t;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const closeSections = (section: { header: number; nesting: number }) => {
|
|
|
|
|
while (sections.length && section.header <= sections[sections.length - 1].header) {
|
|
|
|
|
sections.pop();
|
|
|
|
|
tokens.push(closeSection());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const closeSectionsToCurrentNesting = (nesting: number) => {
|
|
|
|
|
while (sections.length && nesting < sections[sections.length - 1].nesting) {
|
|
|
|
|
sections.pop();
|
|
|
|
|
tokens.push(closeSection());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const closeAllSections = () => {
|
|
|
|
|
while (sections.pop()) {
|
|
|
|
|
tokens.push(closeSection());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-18 19:18:32 +02:00
|
|
|
for (let i = 0; i < state.tokens.length; i++) {
|
|
|
|
|
const token = state.tokens[i];
|
2024-08-18 18:17:59 +02:00
|
|
|
if (token.type.search("heading") !== 0) {
|
|
|
|
|
nestedLevel += token.nesting;
|
|
|
|
|
}
|
|
|
|
|
if (sections.length && nestedLevel < sections[sections.length - 1].nesting) {
|
|
|
|
|
closeSectionsToCurrentNesting(nestedLevel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (token.type === "heading_open") {
|
|
|
|
|
const section: { header: number; nesting: number } = {
|
|
|
|
|
header: parseInt(token.tag.charAt(1)),
|
|
|
|
|
nesting: nestedLevel
|
|
|
|
|
};
|
|
|
|
|
if (sections.length && section.header <= sections[sections.length - 1].header) {
|
|
|
|
|
closeSections(section);
|
|
|
|
|
}
|
2024-08-18 19:18:32 +02:00
|
|
|
|
|
|
|
|
const headingTextToken = state.tokens[i + 1];
|
|
|
|
|
const headingText = headingTextToken.content;
|
|
|
|
|
|
|
|
|
|
tokens.push(openSection(token.attrs, headingText));
|
2024-08-18 18:17:59 +02:00
|
|
|
const idIndex = token.attrIndex("id");
|
|
|
|
|
if (idIndex !== -1) {
|
|
|
|
|
token.attrs?.splice(idIndex, 1);
|
|
|
|
|
}
|
|
|
|
|
sections.push(section);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tokens.push(token);
|
|
|
|
|
}
|
|
|
|
|
closeAllSections();
|
|
|
|
|
|
|
|
|
|
state.tokens = tokens;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
md.core.ruler.push("header_sections", addSections);
|
2024-08-04 16:15:09 +02:00
|
|
|
});
|
2024-08-14 19:33:41 +02:00
|
|
|
|
2024-08-19 19:31:41 +02:00
|
|
|
export const handleImagePaste = async (event: ClipboardEvent) => {
|
|
|
|
|
const clipboardItems = Array.from(event.clipboardData?.items || []);
|
|
|
|
|
const file = clipboardItems.find((item) => item.type.startsWith("image/"));
|
|
|
|
|
|
|
|
|
|
if (!file) return;
|
|
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
|
|
const fileObject = file.getAsFile();
|
|
|
|
|
|
|
|
|
|
if (!fileObject) return;
|
|
|
|
|
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append("file", fileObject);
|
|
|
|
|
|
|
|
|
|
const request = await fetch("?/pasteImage", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
body: formData
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const response = await request.json();
|
|
|
|
|
const fileId = JSON.parse(response.data)[1];
|
|
|
|
|
const fileUrl = `${API_BASE_PREFIX}/rpc/retrieve_file?id=${fileId}`;
|
|
|
|
|
|
|
|
|
|
const target = event.target as HTMLTextAreaElement;
|
|
|
|
|
const newContent =
|
|
|
|
|
target.value.slice(0, target.selectionStart) +
|
|
|
|
|
`` +
|
|
|
|
|
target.value.slice(target.selectionStart);
|
|
|
|
|
|
|
|
|
|
return newContent;
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-19 20:13:49 +02:00
|
|
|
export const API_BASE_PREFIX = dev ? "http://localhost:3000" : `${import.meta.env.PUBLIC_ORIGIN}/api`;
|
2024-08-17 22:07:16 +02:00
|
|
|
export const NGINX_BASE_PREFIX = dev ? "http://localhost:18000" : "";
|