Version 3 #40
|
@ -218,6 +218,20 @@ open JobsJobsJobs.Data
|
||||||
open JobsJobsJobs.ViewModels
|
open JobsJobsJobs.ViewModels
|
||||||
|
|
||||||
|
|
||||||
|
/// Handlers for /api routes
|
||||||
|
[<RequireQualifiedAccess>]
|
||||||
|
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
|
/// Handlers for /citizen routes
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Citizen =
|
module Citizen =
|
||||||
|
@ -768,6 +782,7 @@ let allEndpoints = [
|
||||||
POST [ route "s" Listing.add ]
|
POST [ route "s" Listing.add ]
|
||||||
PUT [ routef "/%O" Listing.update ]
|
PUT [ routef "/%O" Listing.update ]
|
||||||
]
|
]
|
||||||
|
POST [ route "/markdown-preview" Api.markdownPreview ]
|
||||||
subRoute "/profile" [
|
subRoute "/profile" [
|
||||||
GET_HEAD [
|
GET_HEAD [
|
||||||
route "" ProfileApi.current
|
route "" ProfileApi.current
|
||||||
|
|
|
@ -33,14 +33,19 @@ let continentList attrs name (continents : Continent list) emptyLabel selectedVa
|
||||||
let markdownEditor attrs name value editorLabel =
|
let markdownEditor attrs name value editorLabel =
|
||||||
div [ _class "col-12" ] [
|
div [ _class "col-12" ] [
|
||||||
nav [ _class "nav nav-pills pb-1" ] [
|
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 "Markdown"
|
||||||
]; rawText " "
|
|
||||||
button [ _type "button"; _class "jjj-md-preview"; _onclick "jjj.showPreview('TODO')" ] [ rawText "Preview" ]
|
|
||||||
]
|
]
|
||||||
section [ _id $"{name}Preview"; _class "jjj-preview"; _ariaLabel "Rendered Markdown preview" ] []
|
rawText " "
|
||||||
div [ _class "form-floating" ] [
|
button [ _type "button"; _id $"{name}PreviewButton"; _class "btn btn-outline-secondary btn-sm rounded-pill"
|
||||||
textarea (List.append attrs [ _id name; _name name; _class "form-control md-edit"; _rows "10" ]) [
|
_onclick $"jjj.showPreview('{name}')" ] [
|
||||||
|
rawText "Preview"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
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
|
rawText value
|
||||||
]
|
]
|
||||||
label [ _for name ] [ rawText editorLabel ]
|
label [ _for name ] [ rawText editorLabel ]
|
||||||
|
|
|
@ -53,6 +53,73 @@ this.jjj = {
|
||||||
} catch (_) { }
|
} 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 = "<p><strong><em>Loading preview...</em></strong></p>"
|
||||||
|
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 = `<p class="text-danger"><strong> ERROR ${preview.status}</strong> – ${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
|
* Script for profile pages
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -199,6 +199,19 @@ span.jjj-audio-clip {
|
||||||
span.jjj-audio-clip:hover {
|
span.jjj-audio-clip:hover {
|
||||||
cursor: pointer;
|
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 styling */
|
||||||
footer {
|
footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user