diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..558b358 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +node_modules +*.html +*.pdf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..46a5783 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +*.html +*.pdf +!server.js +!convert.js +!md2html.js diff --git a/Dockerfile b/Dockerfile index 291cb46..b62f3b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,5 @@ FROM node:22-slim -# Install Chrome dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ chromium \ fonts-freefont-ttf \ @@ -12,8 +11,14 @@ WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --omit=dev -COPY md2html.js mermaid.min.js ./ +COPY convert.js server.js md2html.js mermaid.min.js ./ ENV CHROME_PATH=/usr/bin/chromium +ENV PORT=3000 -ENTRYPOINT ["node", "md2html.js"] +EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ + CMD node -e "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))" + +CMD ["node", "server.js"] diff --git a/convert.js b/convert.js new file mode 100644 index 0000000..0ceabfd --- /dev/null +++ b/convert.js @@ -0,0 +1,277 @@ +const fs = require("fs"); +const path = require("path"); +const { marked } = require("marked"); + +function escapeHtml(s) { + return s + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} + +function stripHtml(s) { + return s.replace(/<[^>]*>/g, ""); +} + +function buildToc(items) { + if (items.length === 0) return ""; + let html = ""; + let prevDepth = 0; + for (const item of items) { + if (item.depth > prevDepth) { + for (let i = prevDepth; i < item.depth; i++) html += "
${escapeHtml(text)}\n`;
+ }
+ return defaultCode({ text, lang });
+ };
+
+ const tocItems = [];
+ renderer.heading = function ({ text, depth }) {
+ const slug = text
+ .toLowerCase()
+ .replace(/<[^>]*>/g, "")
+ .replace(/[^\w\s-]/g, "")
+ .replace(/\s+/g, "-")
+ .replace(/-+/g, "-")
+ .trim();
+ tocItems.push({ text: stripHtml(text), depth, slug });
+ return `