diff --git a/src/JobsJobsJobs/Server/Handlers.fs b/src/JobsJobsJobs/Server/Handlers.fs index cd12ace..762c5f1 100644 --- a/src/JobsJobsJobs/Server/Handlers.fs +++ b/src/JobsJobsJobs/Server/Handlers.fs @@ -218,6 +218,20 @@ open JobsJobsJobs.Data open JobsJobsJobs.ViewModels +/// Handlers for /api routes +[] +module Api = + + open System.IO + + // POST: /api/markdown-preview + let markdownPreview : HttpHandler = requireUser >=> fun next ctx -> task { + use reader = new StreamReader (ctx.Request.Body) + let! preview = reader.ReadToEndAsync () + return! htmlString (MarkdownString.toHtml (Text (defaultArg (Option.ofObj preview) "--"))) next ctx + } + + /// Handlers for /citizen routes [] module Citizen = @@ -768,6 +782,7 @@ let allEndpoints = [ POST [ route "s" Listing.add ] PUT [ routef "/%O" Listing.update ] ] + POST [ route "/markdown-preview" Api.markdownPreview ] subRoute "/profile" [ GET_HEAD [ route "" ProfileApi.current diff --git a/src/JobsJobsJobs/Server/Views/Common.fs b/src/JobsJobsJobs/Server/Views/Common.fs index 755879c..0c32d13 100644 --- a/src/JobsJobsJobs/Server/Views/Common.fs +++ b/src/JobsJobsJobs/Server/Views/Common.fs @@ -33,14 +33,19 @@ let continentList attrs name (continents : Continent list) emptyLabel selectedVa let markdownEditor attrs name value editorLabel = div [ _class "col-12" ] [ nav [ _class "nav nav-pills pb-1" ] [ - button [ _type "button"; _class "jjj-md-source"; _onclick "jjj.showMarkdown('TODO')" ] [ + button [ _type "button"; _id $"{name}EditButton"; _class "btn btn-primary btn-sm rounded-pill" ] [ rawText "Markdown" - ]; rawText "   " - button [ _type "button"; _class "jjj-md-preview"; _onclick "jjj.showPreview('TODO')" ] [ rawText "Preview" ] + ] + rawText "   " + button [ _type "button"; _id $"{name}PreviewButton"; _class "btn btn-outline-secondary btn-sm rounded-pill" + _onclick $"jjj.showPreview('{name}')" ] [ + rawText "Preview" + ] ] - section [ _id $"{name}Preview"; _class "jjj-preview"; _ariaLabel "Rendered Markdown preview" ] [] - div [ _class "form-floating" ] [ - textarea (List.append attrs [ _id name; _name name; _class "form-control md-edit"; _rows "10" ]) [ + section [ _id $"{name}Preview"; _class "jjj-not-shown"; _ariaLabel "Rendered Markdown preview" ] [] + div [ _id $"{name}Edit"; _class "form-floating jjj-shown" ] [ + textarea (List.append attrs + [ _id name; _name name; _class "form-control jjj-markdown-editor"; _rows "10" ]) [ rawText value ] label [ _for name ] [ rawText editorLabel ] diff --git a/src/JobsJobsJobs/Server/wwwroot/script.js b/src/JobsJobsJobs/Server/wwwroot/script.js index 7b50c10..d31d6b9 100644 --- a/src/JobsJobsJobs/Server/wwwroot/script.js +++ b/src/JobsJobsJobs/Server/wwwroot/script.js @@ -53,6 +53,73 @@ this.jjj = { } catch (_) { } }, + /** + * Show a preview of the Markdown in the given editor + * @param {string} editorId The ID of the Markdown editor whose preview should be shown + */ + async showPreview(editorId) { + /** @type {HTMLButtonElement} */ + const editBtn = document.getElementById(`${editorId}EditButton`) + /** @type {HTMLDivElement} */ + const editDiv = document.getElementById(`${editorId}Edit`) + /** @type {HTMLButtonElement} */ + const previewBtn = document.getElementById(`${editorId}PreviewButton`) + /** @type {HTMLDivElement} */ + const previewDiv = document.getElementById(`${editorId}Preview`) + + editBtn.classList.remove("btn-primary") + editBtn.classList.add("btn-outline-secondary") + editBtn.addAttribute("onclick", `jjj.showEditor('{editorId}')`) + previewBtn.classList.remove("btn-outline-secondary") + previewBtn.classList.add("btn-primary") + previewBtn.removeAttribute("onclick") + + editDiv.classList.remove("jjj-shown") + editDiv.classList.add("jjj-not-shown") + previewDiv.innerHTML = "

Loading preview...

" + previewtDiv.classList.remove("jjj-not-shown") + previewDiv.classList.add("jjj-shown") + + const preview = await fetch("/api/markdown-preview", + { method: "POST", body: document.getElementById(editorId).textContent }) + + let text + if (preview.ok) { + text = await preview.text() + } else { + text = `

ERROR ${preview.status} – ${preview.statusText}` + } + previewDiv.innerHTML = text + }, + + /** + * Show the Markdown editor (hides preview) + * @param {string} editorId The ID of the Markdown editor to show + */ + showEditor(editorId) { + /** @type {HTMLButtonElement} */ + const editBtn = document.getElementById(`${editorId}EditButton`) + /** @type {HTMLDivElement} */ + const editDiv = document.getElementById(`${editorId}Edit`) + /** @type {HTMLButtonElement} */ + const previewBtn = document.getElementById(`${editorId}PreviewButton`) + /** @type {HTMLDivElement} */ + const previewDiv = document.getElementById(`${editorId}Preview`) + + previewtBtn.classList.remove("btn-primary") + previewBtn.classList.add("btn-outline-secondary") + previewtBtn.addAttribute("onclick", `jjj.showPreview('{editorId}')`) + editBtn.classList.remove("btn-outline-secondary") + editBtn.classList.add("btn-primary") + editBtn.removeAttribute("onclick") + + previewDiv.classList.remove("jjj-shown") + previewDiv.classList.add("jjj-not-shown") + previewDiv.innerHTML = "" + editDiv.classList.remove("jjj-not-shown") + editDiv.classList.add("jjj-shown") + }, + /** * Script for profile pages */ diff --git a/src/JobsJobsJobs/Server/wwwroot/style.css b/src/JobsJobsJobs/Server/wwwroot/style.css index a2cf30d..24822a7 100644 --- a/src/JobsJobsJobs/Server/wwwroot/style.css +++ b/src/JobsJobsJobs/Server/wwwroot/style.css @@ -199,6 +199,19 @@ span.jjj-audio-clip { span.jjj-audio-clip:hover { cursor: pointer; } +/* Markdown Editor styling */ +.jjj-not-shown { + display: none; +} +.jjj-shown { + display: inherit; +} +.jjj-markdown-editor { + width: 100%; + /* When wrapping this with Bootstrap's floating label, it shrinks the input down to what a normal one-line input + would be; this overrides that for this textarea specifically */ + height: inherit !important; +} /* Footer styling */ footer { display: flex;