feat: add syntax highlighting to post editor
This commit is contained in:
parent
6efbd749be
commit
593036a712
@ -12,12 +12,26 @@
|
|||||||
|
|
||||||
<input type="hidden" data-blog-pid="@post.Id">
|
<input type="hidden" data-blog-pid="@post.Id">
|
||||||
|
|
||||||
<button id="save-button" class="btn btn-primary"><i class="fa-solid fa-floppy-disk fa-fw"></i> Save <span class="text-muted">(Ctrl+S)</span></button>
|
<div style="margin-bottom: 20px;">
|
||||||
<a href="/blog/@post.Published.ToString(@"yyyy\/MM\/dd")/@post.Slug" target="_blank" class="btn btn-info"><i class="fa-solid fa-magnifying-glass"></i> Preview</a>
|
<button id="save-button" class="btn btn-primary"><i class="fa-solid fa-floppy-disk fa-fw"></i> Save <span class="text-muted">(Ctrl+S)</span></button>
|
||||||
|
<a href="/blog/@post.Published.ToString(@"yyyy\/MM\/dd")/@post.Slug" target="_blank" class="btn btn-info"><i class="fa-solid fa-magnifying-glass"></i> Preview</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th>Post ID</th>
|
||||||
|
<td><input class="form-control" type="text" value="@post.Id" disabled="disabled"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<td><input class="form-control" id="post-title" type="text" value="@post.Title"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<div class="row" style="margin-top: 20px;">
|
<div class="row" style="margin-top: 20px;">
|
||||||
<div class="col-md-6 col-sm-12">
|
<div id="editing-area" class="col-md-6 col-sm-12">
|
||||||
<textarea id="content" style="width: 100%; font-family: monospace; min-height: calc(100vh - 80px); max-height: 100%">@post.Body</textarea>
|
<textarea id="content" spellcheck="false">@post.Body</textarea>
|
||||||
|
<pre id="highlighting" aria-hidden="true"><code id="highlighting-content" class="language-markdown">@post.Body</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-sm-12" style="overflow-y: scroll; background: #1E1E1E">
|
<div class="col-md-6 col-sm-12" style="overflow-y: scroll; background: #1E1E1E">
|
||||||
<article id="article-preview" style="background: #333; max-width: 700px; margin: 20px auto; padding: 20px;">
|
<article id="article-preview" style="background: #333; max-width: 700px; margin: 20px auto; padding: 20px;">
|
||||||
|
@ -41,3 +41,66 @@ pre {
|
|||||||
code[class*="language-"] {
|
code[class*="language-"] {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import BlogPost from "../app/BlogPost";
|
import BlogPost from "../app/BlogPost";
|
||||||
import API from "../app/API";
|
import API from "../app/API";
|
||||||
|
import UI from "../app/UI";
|
||||||
|
|
||||||
|
declare const Prism: any;
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
getCurrentBlogPost().then(post => {
|
getCurrentBlogPost().then(post => {
|
||||||
@ -7,9 +10,12 @@ import API from "../app/API";
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveButton = document.getElementById("save-button");
|
const saveButton = document.getElementById("save-button") as HTMLButtonElement;
|
||||||
const preview = document.getElementById("article-preview");
|
const preview = document.getElementById("article-preview") as HTMLAnchorElement;
|
||||||
const content = document.getElementById("content") as HTMLTextAreaElement;
|
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) => {
|
saveButton.addEventListener("click", async (e: MouseEvent) => {
|
||||||
await savePost();
|
await savePost();
|
||||||
@ -20,6 +26,22 @@ import API from "../app/API";
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await savePost();
|
await savePost();
|
||||||
preview.innerHTML = post.content;
|
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 = '<i class="fa-solid fa-floppy-disk fa-fw"></i> Save <span class="text-muted">(Ctrl+S)</span>';
|
saveButton.innerHTML = '<i class="fa-solid fa-floppy-disk fa-fw"></i> Save <span class="text-muted">(Ctrl+S)</span>';
|
||||||
}, 2000);
|
}, 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 = `<code class="${span.className} language-${language}" style="padding:0;">${span.innerHTML}</code>`;
|
||||||
|
Prism.highlightAllUnder(highlightingContent);
|
||||||
|
});
|
||||||
|
syncEditorScroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncEditorScroll() {
|
||||||
|
highlighting.scrollTop = content.scrollTop;
|
||||||
|
highlighting.scrollLeft = content.scrollLeft;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getCurrentBlogPost(): Promise<BlogPost> {
|
async function getCurrentBlogPost(): Promise<BlogPost> {
|
||||||
|
Loading…
Reference in New Issue
Block a user