diff --git a/OliverBooth/Pages/Admin/EditBlogPost.cshtml b/OliverBooth/Pages/Admin/EditBlogPost.cshtml
index b3ddbd4..a559ede 100644
--- a/OliverBooth/Pages/Admin/EditBlogPost.cshtml
+++ b/OliverBooth/Pages/Admin/EditBlogPost.cshtml
@@ -31,7 +31,7 @@
diff --git a/src/ts/admin/AdminUI.ts b/src/ts/admin/AdminUI.ts
new file mode 100644
index 0000000..33f799d
--- /dev/null
+++ b/src/ts/admin/AdminUI.ts
@@ -0,0 +1,62 @@
+import adminUI from "./AdminUI";
+
+declare const Prism: any;
+class AdminUI {
+ static highlightingContent: HTMLElement;
+ static highlighting: HTMLElement;
+ static _content: HTMLTextAreaElement;
+ static _saveButton: HTMLButtonElement;
+
+ static init() {
+ const content = AdminUI.content;
+ AdminUI.highlightingContent = document.getElementById("highlighting-content");
+ AdminUI.highlighting = document.getElementById("highlighting");
+ content.addEventListener("input", () => AdminUI.updateEditView());
+ content.addEventListener("scroll", () => AdminUI.syncEditorScroll());
+ }
+
+ public static get content() {
+ if (!AdminUI._content) {
+ AdminUI._content = document.getElementById("content") as HTMLTextAreaElement;
+ }
+
+ return AdminUI._content;
+ }
+
+ public static get saveButton() {
+ if (!AdminUI._saveButton) {
+ AdminUI._saveButton = document.getElementById("save-button") as HTMLButtonElement;
+ }
+
+ return AdminUI._saveButton;
+ }
+
+ public static updateEditView() {
+ AdminUI.highlightingContent.innerHTML = Prism.highlight(AdminUI.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(languageSpan.parentElement);
+ });
+
+ AdminUI.syncEditorScroll();
+ }
+
+ static syncEditorScroll() {
+ AdminUI.highlighting.scrollTop = AdminUI._content.scrollTop;
+ AdminUI.highlighting.scrollLeft = AdminUI._content.scrollLeft;
+ }
+}
+
+export default AdminUI;
\ No newline at end of file
diff --git a/src/ts/admin/EditBlogPost.ts b/src/ts/admin/EditBlogPost.ts
index 0de9fea..fbcc96e 100644
--- a/src/ts/admin/EditBlogPost.ts
+++ b/src/ts/admin/EditBlogPost.ts
@@ -1,8 +1,8 @@
import BlogPost from "../app/BlogPost";
import API from "../app/API";
import UI from "../app/UI";
-
-declare const Prism: any;
+import AdminUI from "./AdminUI";
+import "./TabSupport"
(() => {
getCurrentBlogPost().then(post => {
@@ -10,14 +10,12 @@ declare const Prism: any;
return;
}
- 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");
+ AdminUI.init();
- saveButton.addEventListener("click", async (e: MouseEvent) => {
+ const preview = document.getElementById("article-preview") as HTMLAnchorElement;
+ const title = document.getElementById("post-title") as HTMLInputElement;
+
+ AdminUI.saveButton.addEventListener("click", async (e: MouseEvent) => {
await savePost();
});
@@ -31,28 +29,15 @@ declare const Prism: any;
}
});
- 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;
- }
- });
-
async function savePost(): Promise {
+ const saveButton = AdminUI.saveButton;
saveButton.classList.add("btn-primary");
saveButton.classList.remove("btn-success");
saveButton.setAttribute("disabled", "disabled");
saveButton.innerHTML = ' Saving ...';
- post = await API.updatePost(post, {content: content.value, title: title.value});
+ post = await API.updatePost(post, {content: AdminUI.content.value, title: title.value});
saveButton.classList.add("btn-success");
saveButton.classList.remove("btn-primary");
@@ -66,33 +51,7 @@ declare const Prism: any;
}, 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;
- }
+ AdminUI.updateEditView();
});
async function getCurrentBlogPost(): Promise {
diff --git a/src/ts/admin/TabSupport.ts b/src/ts/admin/TabSupport.ts
new file mode 100644
index 0000000..250392e
--- /dev/null
+++ b/src/ts/admin/TabSupport.ts
@@ -0,0 +1,112 @@
+import AdminUI from "./AdminUI";
+import adminUI from "./AdminUI";
+
+(() => {
+ const textareas = document.querySelectorAll("textarea.tab-support");
+ textareas.forEach((textarea: HTMLTextAreaElement) => {
+ textarea.addEventListener("keydown", (e: KeyboardEvent) => {
+ let text: string;
+
+ // Enter Key?
+ if (e.key === "Enter") {
+ const selStart = textarea.selectionStart;
+ const selEnd = textarea.selectionEnd;
+ let sel = selStart;
+ // selection?
+ if (sel == selEnd) {
+ // find start of the current line
+ let text = textarea.value;
+ while (sel > 0 && text[sel - 1] !== '\n')
+ sel--;
+
+ console.log(`Line starts at index ${sel}`);
+
+ const lineStart = sel;
+ while (text[sel] === ' ' || text[sel] === '\t')
+ sel++;
+
+ console.log(`Identation ends at ${sel} (sel + ${sel - lineStart})`);
+
+ if (sel > lineStart) {
+ const lineEnd = lineStart + text.indexOf('\n', lineStart);
+ console.log(`Line starts at index ${lineEnd}`);
+ e.preventDefault();
+
+ const indentStr = text.slice(lineStart, sel);
+ console.log(`Indent string is "${indentStr}"`);
+
+ // insert carriage return and indented text
+ textarea.value = `${text.slice(0, selStart)}\n${indentStr}${text.slice(selStart)}`;
+
+ // Scroll caret visible
+ textarea.blur();
+ textarea.focus();
+ textarea.selectionEnd = selEnd + indentStr.length + 1; // +1 for \n
+ AdminUI.updateEditView();
+ return false;
+ }
+ }
+ }
+
+ // Tab key?
+ if (e.key === "Tab") {
+ e.preventDefault();
+ let selStart = textarea.selectionStart;
+
+ // selection?
+ if (selStart == textarea.selectionEnd) {
+ // These single character operations are undoable
+ if (e.shiftKey) {
+ text = textarea.value;
+ if (selStart > 0 && (text[selStart - 1] === '\t' || text.slice(selStart - 4, selStart) === " ")) {
+ document.execCommand("delete");
+ }
+ } else {
+ document.execCommand("insertText", false, " ");
+ }
+ } else {
+ // Block indent/unindent trashes undo stack.
+ // Select whole lines
+ let selEnd = textarea.selectionEnd;
+ text = textarea.value;
+ while (selStart > 0 && text[selStart - 1] !== '\n')
+ selStart--;
+ while (selEnd > 0 && text[selEnd - 1] !== '\n' && selEnd < text.length)
+ selEnd++;
+
+ // Get selected text
+ let lines = text.slice(selStart, selEnd - selStart).split('\n');
+
+ // Insert tabs
+ for (let i = 0; i < lines.length; i++) {
+ // Don't indent last line if cursor at start of line
+ if (i == lines.length - 1 && lines[i].length == 0)
+ continue;
+
+ // Tab or Shift+Tab?
+ if (e.shiftKey) {
+ if (lines[i].startsWith('\t'))
+ lines[i] = lines[i].slice(1);
+ else if (lines[i].startsWith(" "))
+ lines[i] = lines[i].slice(4);
+ } else {
+ lines[i] = ` ${lines[i]}`;
+ }
+ }
+
+ const result = lines.join('\n');
+
+ // Update the text area
+ textarea.value = text.slice(0, selStart) + result + text.slice(selEnd);
+ textarea.selectionStart = selStart;
+ textarea.selectionEnd = selStart + lines.length;
+ }
+
+ AdminUI.updateEditView();
+ return false;
+ }
+
+ return true;
+ });
+ });
+})();
\ No newline at end of file