diff options
| author | kj_sh604 | 2026-02-20 11:56:35 -0500 |
|---|---|---|
| committer | kj_sh604 | 2026-02-20 11:56:35 -0500 |
| commit | 4ea6886a06cef46193f4101d84ebb3dcc7928d84 (patch) | |
| tree | c1289935c50a249893244478dd2f22d7d9f8913e /src | |
| parent | d15e968e3d884e2df5b301cd54654c76c808150b (diff) | |
feat: extra options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.nim | 14 | ||||
| -rw-r--r-- | src/latex/template.tex | 6 | ||||
| -rw-r--r-- | src/static/main.js | 62 | ||||
| -rw-r--r-- | src/templates/index.html | 54 |
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> |
