257 lines
10 KiB
Plaintext
Executable File
257 lines
10 KiB
Plaintext
Executable File
<!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> |