aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorkj_sh6042026-02-20 11:56:35 -0500
committerkj_sh6042026-02-20 11:56:35 -0500
commit4ea6886a06cef46193f4101d84ebb3dcc7928d84 (patch)
treec1289935c50a249893244478dd2f22d7d9f8913e /src
parentd15e968e3d884e2df5b301cd54654c76c808150b (diff)
feat: extra options
Diffstat (limited to 'src')
-rw-r--r--src/app.nim14
-rw-r--r--src/latex/template.tex6
-rw-r--r--src/static/main.js62
-rw-r--r--src/templates/index.html54
4 files changed, 101 insertions, 35 deletions
diff --git a/src/app.nim b/src/app.nim
index 80c0502..b321a23 100644
--- a/src/app.nim
+++ b/src/app.nim
@@ -6,6 +6,7 @@ const
AllowedImageExtensions = ["png", "jpg", "jpeg", "gif", "webp", "svg"]
ValidPaperSizes = ["a4paper", "letterpaper", "legalpaper"]
ValidMargins = ["0.75in", "1in", "1.25in", "1.5in"]
+ ValidLineSpacings = ["1", "1.5", "2"]
const
AppName = "likha-pdf"
@@ -229,7 +230,7 @@ proc respondFile(req: Request; filePath: string; asAttachment: bool = false; att
await req.respond(Http200, readFile(filePath), headers)
# pandoc does the heavy lifting
-proc runPandoc(sourceMarkdown: string; outputPath: string; paperSize: string; margin: string; mainFont: string): tuple[ok: bool, output: string, missingPandoc: bool] =
+proc runPandoc(sourceMarkdown: string; outputPath: string; paperSize: string; margin: string; mainFont: string; lineSpacing: string; showPageNumbers: bool): tuple[ok: bool, output: string, missingPandoc: bool] =
let tempDir = getTempDir() / (AppName & "-" & randomHex(10))
createDir(tempDir)
let tempMarkdownPath = tempDir / "source.md"
@@ -247,7 +248,7 @@ proc runPandoc(sourceMarkdown: string; outputPath: string; paperSize: string; ma
# if preprocessing fails, fall back to original content
writeFile(tempMarkdownPath, sourceMarkdown)
- let args = @[
+ var args = @[
tempMarkdownPath,
"--from", "markdown+emoji+hard_line_breaks",
"--pdf-engine=lualatex",
@@ -255,10 +256,15 @@ proc runPandoc(sourceMarkdown: string; outputPath: string; paperSize: string; ma
"-V", "papersize=" & paperSize,
"-V", "margin=" & margin,
"-V", "mainfont=" & mainFont,
+ "-V", "linespacing=" & lineSpacing,
"--resource-path", baseDir() & ":" & uploadsDir() & ":" & tempDir,
"-o", outputPath
]
+ if not showPageNumbers:
+ args.add("-V")
+ args.add("hidepages=true")
+
var process: Process
try:
process = startProcess("pandoc", args = args, options = {poUsePath, poStdErrToStdOut})
@@ -301,11 +307,13 @@ proc handleConvert(req: Request) {.async.} =
mainFontFamily = "serif"
let mainFont = if mainFontFamily == "sans": "TeX Gyre Heros" else: "TeX Gyre Pagella"
+ let lineSpacing = pickOption(formData.getOrDefault("line_spacing", ""), "1", ValidLineSpacings)
+ let showPageNumbers = formData.getOrDefault("page_numbers", "") == "on"
let epoch = int(getTime().toUnix())
let outputName = AppName & "_" & $epoch & "_" & randomHex(32) & ".pdf"
let outputPath = generatedDir() / outputName
- let conversion = runPandoc(markdown, outputPath, paperSize, margin, mainFont)
+ let conversion = runPandoc(markdown, outputPath, paperSize, margin, mainFont, lineSpacing, showPageNumbers)
if not conversion.ok:
let message = if conversion.missingPandoc:
diff --git a/src/latex/template.tex b/src/latex/template.tex
index 9a0410b..ae44742 100644
--- a/src/latex/template.tex
+++ b/src/latex/template.tex
@@ -13,6 +13,8 @@
\usepackage[paper=$papersize$,margin=$margin$]{geometry}
\usepackage{microtype}
\usepackage{parskip}
+\usepackage{setspace}
+\setstretch{$linespacing$}
\usepackage{xcolor}
\usepackage{graphicx}
\usepackage{float}
@@ -66,6 +68,10 @@ $if(highlighting-macros)$
$highlighting-macros$
$endif$
+$if(hidepages)$
+\pagestyle{empty}
+$endif$
+
\begin{document}
$if(title)$
diff --git a/src/static/main.js b/src/static/main.js
index f3f4f71..68d36ad 100644
--- a/src/static/main.js
+++ b/src/static/main.js
@@ -1,41 +1,77 @@
const convertButton = document.getElementById("convert-button");
const uploadButton = document.getElementById("upload-button");
const markdownInput = document.getElementById("markdown");
+const imageInput = document.getElementById("image");
+const mdFileInput = document.getElementById("md-file");
-function sourceForm(event) {
- const sourceElement = event.detail?.elt;
- if (!sourceElement) {
- return null;
+document.body.addEventListener("htmx:beforeRequest", (event) => {
+ const elt = event.detail?.elt;
+ if (!elt) {
+ return;
}
- return sourceElement.closest("form");
-}
-document.body.addEventListener("htmx:beforeRequest", (event) => {
- const form = sourceForm(event);
- if (form?.id === "convert-form" && convertButton) {
+ if (elt.id === "convert-form" && convertButton) {
convertButton.disabled = true;
convertButton.textContent = "generating...";
}
- if (form?.id === "upload-form" && uploadButton) {
+ if (elt.id === "upload-button" && uploadButton) {
uploadButton.disabled = true;
uploadButton.textContent = "uploading...";
}
});
document.body.addEventListener("htmx:afterRequest", (event) => {
- const form = sourceForm(event);
- if (form?.id === "convert-form" && convertButton) {
+ const elt = event.detail?.elt;
+ if (!elt) {
+ return;
+ }
+
+ if (elt.id === "convert-form" && convertButton) {
convertButton.disabled = false;
convertButton.textContent = "generate pdf";
}
- if (form?.id === "upload-form" && uploadButton) {
+ if (elt.id === "upload-button" && uploadButton) {
uploadButton.disabled = false;
uploadButton.textContent = "upload image";
}
});
+if (mdFileInput) {
+ mdFileInput.addEventListener("change", () => {
+ const file = mdFileInput.files?.[0];
+
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ if (markdownInput) {
+ markdownInput.value = /** @type {string} */ (e.target.result);
+ markdownInput.readOnly = true;
+ }
+ if (imageInput) {
+ imageInput.disabled = true;
+ }
+ if (uploadButton) {
+ uploadButton.disabled = true;
+ }
+ };
+ reader.readAsText(file);
+ } else {
+ if (markdownInput) {
+ markdownInput.value = "";
+ markdownInput.readOnly = false;
+ }
+ if (imageInput) {
+ imageInput.disabled = false;
+ }
+ if (uploadButton) {
+ uploadButton.disabled = false;
+ }
+ }
+ });
+}
+
document.body.addEventListener("click", (event) => {
const target = event.target;
if (!(target instanceof HTMLElement)) {
diff --git a/src/templates/index.html b/src/templates/index.html
index e67f582..6cb610c 100644
--- a/src/templates/index.html
+++ b/src/templates/index.html
@@ -22,24 +22,6 @@
<h1>likha-pdf</h1>
<p>simple markdown export with pandoc + lualatex.</p>
- <section>
- <h3>image upload</h3>
- <form
- id="upload-form"
- hx-post="/upload-image"
- hx-target="#upload-result"
- hx-swap="innerHTML"
- hx-encoding="multipart/form-data"
- hx-indicator="#uploading"
- >
- <label for="image">image file</label>
- <input id="image" name="image" type="file" accept="image/*" required />
- <button id="upload-button" type="submit">upload image</button>
- <small id="uploading" class="htmx-indicator">uploading…</small>
- </form>
- <div id="upload-result" aria-live="polite"></div>
- </section>
-
<form
id="convert-form"
hx-post="/convert"
@@ -47,7 +29,7 @@
hx-swap="innerHTML"
hx-indicator="#loading"
>
- <label for="markdown"><strong>textarea</strong></label>
+ <label for="markdown"><h3>textarea</h3></label>
<textarea
id="markdown"
name="markdown"
@@ -56,6 +38,29 @@
required
></textarea>
+ <section id="image-upload-section">
+ <label for="image"><small>image file</small></label>
+ <input id="image" name="image" type="file" accept="image/*" />
+ <button
+ id="upload-button"
+ type="button"
+ hx-post="/upload-image"
+ hx-target="#upload-result"
+ hx-swap="innerHTML"
+ hx-encoding="multipart/form-data"
+ hx-include="#image"
+ hx-indicator="#uploading"
+ >upload image</button>
+ <small id="uploading" class="htmx-indicator">uploading…</small>
+ <div id="upload-result" aria-live="polite"></div>
+ </section>
+
+ <section id="md-file-section">
+ <h3>or upload a markdown file</h3>
+ <label for="md-file"><small><em>(overrides textarea)</em></small></label>
+ <input id="md-file" type="file" accept=".md,.markdown,text/markdown,text/plain" />
+ </section>
+
<section>
<h3>pdf options</h3>
@@ -83,6 +88,17 @@
<input type="radio" name="main_font" value="sans" /> sans-serif
</label>
</fieldset>
+
+ <label for="line_spacing">Line spacing</label>
+ <select id="line_spacing" name="line_spacing">
+ <option value="1" selected>Single (1.0)</option>
+ <option value="1.5">One-and-a-half (1.5)</option>
+ <option value="2">Double (2.0)</option>
+ </select>
+
+ <label>
+ <input type="checkbox" name="page_numbers" value="on" checked /> show page numbers
+ </label>
</section>
<button id="convert-button" type="submit">generate pdf</button>