<h1>A Markdown Editor in a Textarea</h1>

<main>
<div class="plain">
	<textarea class="plain__editor" data-el="input"># test
This is a *test*
With a [Link](https://iamschulz.com)!</textarea>
	<pre aria-hidden="true" class="plain__highlights"><code data-el="highlight" class="markdown"></code></pre>
</div>
<div class="text">
	<div data-el="output"></div>
</div>
</main>
main {
	display: flex;
}

.plain,
.text {
	flex: 1 0 20rem;
	position: relative;
}

[data-el="input"] {
	border-width: 1px;
	color: black;
	caret-color: black;
	white-space: break-spaces;
	word-break: break-word;
	resize: vertical;
}

[data-el="input"][data-initialized="true"] {
	color: transparent;
	resize: none;
	overflow: hidden;
}

[data-el="highlight"] {
	font-family: inherit;
	line-height: inherit;
	font-size: inherit;
	margin: 0;
	padding: 0;
	white-space: break-spaces;
	word-break: break-word;
}

.plain__highlights {
	position: absolute;
	top: 0;
	left: 0;
	width: 90%;
	border: 1px solid transparent;
	pointer-events: none;
}

.plain__editor, .plain__highlights {
	width: 90%;
	font-size: 1.1rem;
	font-family: monospace;
	margin: 0;
	padding: 0.7rem 1.4ch;
	line-height: 1.313;
}
const inputEl = document.querySelector('[data-el="input"]');
		const highlightEl = document.querySelector('[data-el="highlight"]');
		const outputEl = document.querySelector('[data-el="output"]');

const converter = new showdown.Converter({
	metadata: true,
	parseImgDimensions: true,
	strikethrough: true,
	tables: true,
	ghCodeBlocks: true,
	smoothLivePreview: true,
	simpleLineBreaks: true,
	emoji: true,
});

const resizeTextarea = (textArea) => {
	if (!textArea) {
		return;
	}


	window.requestAnimationFrame(() => {
		textArea.style.height = 0;
		if (textArea.scrollHeight > 0) {
			textArea.style.height = `${textArea.scrollHeight + 2}px`;
		}
	});
};

const highlight = () => {
	window.requestAnimationFrame(() => {
		const highlighted = hljs.highlight(
			"markdown",
			inputEl.value
		).value;
		highlightEl.innerHTML = highlighted;
	});
};

const updateReadonly = () => {
	window.requestAnimationFrame(() => {
		const htmlContent = converter.makeHtml(inputEl.value);
		outputEl.innerHTML = htmlContent;
	});
};

const init = () => {
	inputEl.addEventListener("input", () => {
		resizeTextarea(inputEl);
		highlight();
		updateReadonly();
	});
	inputEl.setAttribute('data-initialized', true);
}

document.addEventListener("DOMContentLoaded", () => {
	init();
	resizeTextarea(inputEl);
	highlight();
	updateReadonly();
})

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/styles/default.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.1/showdown.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/highlight.min.js