Initial commit
This commit is contained in:
257
views/editor.ejs
Normal file
257
views/editor.ejs
Normal file
@@ -0,0 +1,257 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Oddbyte</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link id="favicon" rel="shortcut icon" type="image/png" href="data:image/png;base64,<%- faviconb64 %>">
|
||||
<link rel="canonical" href="https://oddbyte.dev/"/>
|
||||
<meta name="fediverse:creator" content="@oddbyte@mastodon.social">
|
||||
<style>
|
||||
<%- include('partials/style') %>
|
||||
/* Begin per-page stylesheet */
|
||||
#markdown-editor {
|
||||
width: 95%;
|
||||
height: 500px;
|
||||
border: 1px solid;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
font-family: 'Times New Roman', Times, serif;
|
||||
resize: none;
|
||||
background-color: #2d3339;
|
||||
color: #f8f9fa;
|
||||
border-color: #495057;
|
||||
white-space: pre-line;
|
||||
}
|
||||
#editor-container {
|
||||
width: 100%;
|
||||
}
|
||||
#html-preview {
|
||||
width: 95%;
|
||||
height: 500px;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%- include('partials/navbar') %>
|
||||
<div id="container_top">
|
||||
<div id="container_main">
|
||||
<div id="container_thing">
|
||||
<main>
|
||||
<a href="#" id="button" class="switchbutton selectDisable" style="margin-top: 30px">Switch to preview</a>
|
||||
<div id="editor-container">
|
||||
<!-- Editor panel -->
|
||||
<h1 class="selectDisable">Markdown Editor</h1>
|
||||
<textarea id="markdown-editor" class="form-control" placeholder="Write stuff here ..." aria-label="Markdown input"></textarea>
|
||||
</div>
|
||||
<div id="preview-container" hidden>
|
||||
<h1>Preview</h1>
|
||||
<iframe id="html-preview" src="/blog?sample=1"></iframe>
|
||||
</div>
|
||||
<div id="rawtext-container" hidden>
|
||||
<h1>Raw HTML:</h1>
|
||||
<pre><code id="rawtext"></code></pre>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<div id="v-break"></div>
|
||||
<footer class="selectDisable"><aside><div class="selectDisable">Made by Oddbyte.</div><br /><%- include('partials/funfact') %></aside></footer>
|
||||
</div>
|
||||
<script>
|
||||
function parsemd(inputMarkdown) {
|
||||
if (!inputMarkdown) return "";
|
||||
|
||||
let html = inputMarkdown;
|
||||
|
||||
// Headings (h2, h3, h4)
|
||||
html = html.replace(/^# (.*$)/gm, '<h2>$1</h2>');
|
||||
html = html.replace(/^## (.*$)/gm, '<h3>$1</h3>');
|
||||
html = html.replace(/^### (.*$)/gm, '<h4>$1</h4>');
|
||||
|
||||
// Bold text
|
||||
html = html.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>');
|
||||
|
||||
// Italic text
|
||||
html = html.replace(/\*(.*?)\*/g, '<i>$1</i>');
|
||||
|
||||
// Links
|
||||
html = html.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>');
|
||||
|
||||
// Code blocks with language detection for syntax highlighting
|
||||
// TODO: actually implement the lang stuffs in the blog page's CSS
|
||||
html = html.replace(/```([a-z]*)\n([\s\S]*?)```/g, (match, lang, code) => {
|
||||
if (lang) {
|
||||
return `<pre><code class="language-${lang}">${code}</code></pre>`;
|
||||
}
|
||||
return `<pre><code>${code}</code></pre>`;
|
||||
});
|
||||
|
||||
// Fallback for code blocks without language specification
|
||||
html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
|
||||
|
||||
// Inline code
|
||||
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
|
||||
// Unordered list
|
||||
html = html.replace(/^- (.*)$/gm, '<li>$1</li>');
|
||||
html = html.replace(/(<li>.*<\/li>\n)+/g, '<ul>$&</ul>');
|
||||
|
||||
// Ordered list
|
||||
html = html.replace(/^\d+\. (.*)$/gm, '<li>$1</li>');
|
||||
html = html.replace(/(<li>.*<\/li>\n)+/g, function (match) {
|
||||
// convert to <ol> if its not in <ul>
|
||||
if (!match.startsWith('<ul>')) {
|
||||
return '<ol>' + match + '</ol>';
|
||||
}
|
||||
return match;
|
||||
});
|
||||
|
||||
// Handle newlines
|
||||
html = html.replaceAll("\n", '<br />\n');
|
||||
|
||||
return html;
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
let editor = document.getElementById("markdown-editor");
|
||||
let iframe = document.getElementById("html-preview");
|
||||
let iframewindow = iframe.contentWindow;
|
||||
let switchbutton = document.getElementsByClassName("switchbutton")[0];
|
||||
let editorcontainer = document.getElementById("editor-container");
|
||||
let previewcontainer = document.getElementById("preview-container");
|
||||
let rawtextcontainer = document.getElementById("rawtext-container");
|
||||
let rawtextbox = document.getElementById("rawtext");
|
||||
let loaded = false;
|
||||
let locked = false;
|
||||
function getSelectionText() {
|
||||
let text = "";
|
||||
|
||||
if (window.getSelection) {
|
||||
text = window.getSelection().toString();
|
||||
} else if (document.selection) {
|
||||
text = document.selection.createRange().text;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.origin !== "https://oddbyte.dev" || event.data != "Loaded" || loaded) return;
|
||||
else {
|
||||
// Send off current value
|
||||
iframewindow.postMessage(parsemd(editor.value));
|
||||
|
||||
// Send off future values
|
||||
editor.addEventListener("input", (event => {
|
||||
iframewindow.postMessage(parsemd(editor.value));
|
||||
rawtextbox.innerText = parsemd(editor.value);
|
||||
}));
|
||||
|
||||
loaded = true;
|
||||
}
|
||||
});
|
||||
switchbutton.addEventListener("click", (event) => {
|
||||
editorcontainer.hidden = !editorcontainer.hidden;
|
||||
previewcontainer.hidden = !previewcontainer.hidden;
|
||||
rawtextcontainer.hidden = !rawtextcontainer.hidden;
|
||||
if (!previewcontainer.hidden) switchbutton.textContent = "Switch to editor"; else switchbutton.textContent = "Switch to preview"
|
||||
});
|
||||
const keyDownHandler = (event) => {
|
||||
if (!locked) { try { navigator.keyboard.lock(); } catch (_) {} locked = true; }
|
||||
if (event.ctrlKey && (event.key === "k" || event.key === "b" || event.key === "i")) {
|
||||
if (editorcontainer.hidden) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.key === "k") linkText(editor);
|
||||
if (event.key === "b") boldText(editor);
|
||||
if (event.key === "i") italicText(editor);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", keyDownHandler);
|
||||
</script>
|
||||
<script>
|
||||
function getInputSelection(el) {
|
||||
var start = 0, end = 0, normalizedValue, range,
|
||||
textInputRange, len, endRange;
|
||||
|
||||
if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
|
||||
start = el.selectionStart;
|
||||
end = el.selectionEnd;
|
||||
} else {
|
||||
range = document.selection.createRange();
|
||||
|
||||
if (range && range.parentElement() == el) {
|
||||
len = el.value.length;
|
||||
normalizedValue = el.value.replace(/\r\n/g, "\n");
|
||||
|
||||
// Create a working TextRange that lives only in the input
|
||||
textInputRange = el.createTextRange();
|
||||
textInputRange.moveToBookmark(range.getBookmark());
|
||||
|
||||
// Check if the start and end of the selection are at the very end
|
||||
// of the input, since moveStart/moveEnd doesn't return what i want
|
||||
// in those cases
|
||||
endRange = el.createTextRange();
|
||||
endRange.collapse(false);
|
||||
|
||||
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
|
||||
start = end = len;
|
||||
} else {
|
||||
start = -textInputRange.moveStart("character", -len);
|
||||
start += normalizedValue.slice(0, start).split("\n").length - 1;
|
||||
|
||||
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
|
||||
end = len;
|
||||
} else {
|
||||
end = -textInputRange.moveEnd("character", -len);
|
||||
end += normalizedValue.slice(0, end).split("\n").length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
}
|
||||
|
||||
function replaceSelectedText(el, text) {
|
||||
var sel = getInputSelection(el), val = el.value;
|
||||
el.value = val.slice(0, sel.start) + text + val.slice(sel.end);
|
||||
}
|
||||
|
||||
function linkText(el) {
|
||||
var sel = getInputSelection(el), val = el.value;
|
||||
|
||||
// Check if the selected text starts with http:// or https://
|
||||
var selectedText = val.slice(sel.start, sel.end);
|
||||
var isUrl = selectedText.match(/^https?:\/\//);
|
||||
|
||||
if (isUrl) {
|
||||
// If selected text is already a url, use it as the link target
|
||||
// and use the provided text as the link text
|
||||
el.value = val.slice(0, sel.start) + '[](' + selectedText + ')' + val.slice(sel.end);
|
||||
} else {
|
||||
// If not a url, use the provided text as the link text
|
||||
// and the selected text as the link target
|
||||
el.value = val.slice(0, sel.start) + '[' + selectedText + ']()' + val.slice(sel.end);
|
||||
}
|
||||
}
|
||||
|
||||
function boldText(el) {
|
||||
var sel = getInputSelection(el), val = el.value;
|
||||
var selectedText = val.slice(sel.start, sel.end);
|
||||
el.value = val.slice(0, sel.start) + '**' + selectedText + '**' + val.slice(sel.end);
|
||||
}
|
||||
|
||||
function italicText(el) {
|
||||
var sel = getInputSelection(el), val = el.value;
|
||||
var selectedText = val.slice(sel.start, sel.end);
|
||||
el.value = val.slice(0, sel.start) + '*' + selectedText + '*' + val.slice(sel.end);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user