From 593036a712d830abea96d617fa64398ba607b843 Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Mon, 26 Feb 2024 17:43:58 +0000 Subject: [PATCH] feat: add syntax highlighting to post editor --- OliverBooth/Pages/Admin/EditBlogPost.cshtml | 22 +++++-- src/scss/admin.scss | 65 ++++++++++++++++++++- src/ts/admin/EditBlogPost.ts | 54 ++++++++++++++++- 3 files changed, 134 insertions(+), 7 deletions(-) diff --git a/OliverBooth/Pages/Admin/EditBlogPost.cshtml b/OliverBooth/Pages/Admin/EditBlogPost.cshtml index 6f23017..68cb70c 100644 --- a/OliverBooth/Pages/Admin/EditBlogPost.cshtml +++ b/OliverBooth/Pages/Admin/EditBlogPost.cshtml @@ -12,12 +12,26 @@ - - Preview +
+ + Preview +
+ + + + + + + + + + +
Post ID
Title
-
- +
+ +
diff --git a/src/scss/admin.scss b/src/scss/admin.scss index 03a0494..87c700a 100644 --- a/src/scss/admin.scss +++ b/src/scss/admin.scss @@ -40,4 +40,67 @@ pre { code[class*="language-"] { background: none !important; -} \ No newline at end of file +} + +article { + *:last-child { + margin-bottom: 0; + } +} + +div.alert { + *:last-child { + margin-bottom: 0; + } +} + +#save-button { + transition: 0.4s; + width: 10em; +} + +textarea { + font-family: monospace; +} + +#editing-area { + .toolbar { + display: none; + } +} + +#highlighting, #content { + margin: 10px; + padding: 10px; + border: 0; + width: calc(100% - 32px); + height: 500px; + position: relative; +} + +#highlighting, #content, #highlighting * { + font-size: 12pt; + font-family: monospace; + line-height: 15pt; +} + +#highlighting { + margin-top: -511px; + z-index: 0; + background: #1E1E1E; +} + +#content { + z-index: 10; + color: transparent; + background: transparent; + caret-color: #FFFFFF; + resize: none; + overflow-wrap: normal; + overflow-x: scroll; + white-space: pre; +} + +#highlighting-content, #highlighting-content code { + white-space: pre !important; +} diff --git a/src/ts/admin/EditBlogPost.ts b/src/ts/admin/EditBlogPost.ts index ee5ec2e..e58d90c 100644 --- a/src/ts/admin/EditBlogPost.ts +++ b/src/ts/admin/EditBlogPost.ts @@ -1,5 +1,8 @@ import BlogPost from "../app/BlogPost"; import API from "../app/API"; +import UI from "../app/UI"; + +declare const Prism: any; (() => { getCurrentBlogPost().then(post => { @@ -7,9 +10,12 @@ import API from "../app/API"; return; } - const saveButton = document.getElementById("save-button"); - const preview = document.getElementById("article-preview"); + const saveButton = document.getElementById("save-button") as HTMLButtonElement; + const preview = document.getElementById("article-preview") as HTMLAnchorElement; const content = document.getElementById("content") as HTMLTextAreaElement; + const title = document.getElementById("post-title") as HTMLInputElement; + const highlighting = document.getElementById("highlighting"); + const highlightingContent = document.getElementById("highlighting-content"); saveButton.addEventListener("click", async (e: MouseEvent) => { await savePost(); @@ -20,6 +26,22 @@ import API from "../app/API"; e.preventDefault(); await savePost(); preview.innerHTML = post.content; + UI.updateUI(preview); + // Prism.highlightAllUnder(preview); + } + }); + + content.addEventListener("keydown", async (e: KeyboardEvent) => { + if (e.key === "Tab") { + e.preventDefault(); + + const start = content.selectionStart; + const end = content.selectionEnd; + const text = content.value; + content.value = `${text.slice(0, start)} ${text.slice(start, end)}`; + updateEditView(); + content.selectionStart = start + 4; + content.selectionEnd = end ? end + 4 : start + 4; } }); @@ -43,6 +65,34 @@ import API from "../app/API"; saveButton.innerHTML = ' Save (Ctrl+S)'; }, 2000); } + + updateEditView(); + content.addEventListener("input", () => updateEditView()); + content.addEventListener("scroll", () => syncEditorScroll()); + function updateEditView() { + highlightingContent.innerHTML = Prism.highlight(content.value, Prism.languages.markdown); + document.querySelectorAll("#highlighting-content span.token.code").forEach(el => { + const languageSpan = el.querySelector(".code-language") as HTMLSpanElement; + if (!languageSpan) { + return; + } + + const language = languageSpan.innerText; + const span = el.querySelector(".code-block"); + if (!span) { + return; + } + + span.outerHTML = `${span.innerHTML}`; + Prism.highlightAllUnder(highlightingContent); + }); + syncEditorScroll(); + } + + function syncEditorScroll() { + highlighting.scrollTop = content.scrollTop; + highlighting.scrollLeft = content.scrollLeft; + } }); async function getCurrentBlogPost(): Promise {