Replaced markdown preview display iframe with div

No longer need to use the iframe sandboxing techniques, since we now have
CSP rules in-place. Means that embed tags can load without CSP
complications.

Causes slight change to contents of `editor-markdown::setup` editor
event data, since we're changing the `displayEl` property.

Updates markdown editor component to make better use of the component
system.
This commit is contained in:
Dan Brown 2022-05-23 15:16:23 +01:00
parent 641a26cdf7
commit 1d1186c901
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
3 changed files with 27 additions and 65 deletions

View File

@ -18,10 +18,8 @@ class MarkdownEditor {
this.markdown = new MarkdownIt({html: true}); this.markdown = new MarkdownIt({html: true});
this.markdown.use(mdTasksLists, {label: true}); this.markdown.use(mdTasksLists, {label: true});
this.display = this.elem.querySelector('.markdown-display'); this.display = this.$refs.display;
this.input = this.$refs.input;
this.displayStylesLoaded = false;
this.input = this.elem.querySelector('textarea');
this.cm = null; this.cm = null;
this.Code = null; this.Code = null;
@ -32,23 +30,13 @@ class MarkdownEditor {
}); });
this.onMarkdownScroll = this.onMarkdownScroll.bind(this); this.onMarkdownScroll = this.onMarkdownScroll.bind(this);
const displayLoad = () => {
this.displayDoc = this.display.contentDocument;
this.init(cmLoadPromise);
};
if (this.display.contentDocument.readyState === 'complete') {
displayLoad();
} else {
this.display.addEventListener('load', displayLoad.bind(this));
}
window.$events.emitPublic(this.elem, 'editor-markdown::setup', { window.$events.emitPublic(this.elem, 'editor-markdown::setup', {
markdownIt: this.markdown, markdownIt: this.markdown,
displayEl: this.display, displayEl: this.display,
codeMirrorInstance: this.cm, codeMirrorInstance: this.cm,
}); });
this.init(cmLoadPromise);
} }
init(cmLoadPromise) { init(cmLoadPromise) {
@ -56,17 +44,17 @@ class MarkdownEditor {
let lastClick = 0; let lastClick = 0;
// Prevent markdown display link click redirect // Prevent markdown display link click redirect
this.displayDoc.addEventListener('click', event => { this.display.addEventListener('click', event => {
let isDblClick = Date.now() - lastClick < 300; const isDblClick = Date.now() - lastClick < 300;
let link = event.target.closest('a'); const link = event.target.closest('a');
if (link !== null) { if (link !== null) {
event.preventDefault(); event.preventDefault();
window.open(link.getAttribute('href')); window.open(link.getAttribute('href'));
return; return;
} }
let drawing = event.target.closest('[drawio-diagram]'); const drawing = event.target.closest('[drawio-diagram]');
if (drawing !== null && isDblClick) { if (drawing !== null && isDblClick) {
this.actionEditDrawing(drawing); this.actionEditDrawing(drawing);
return; return;
@ -77,10 +65,10 @@ class MarkdownEditor {
// Button actions // Button actions
this.elem.addEventListener('click', event => { this.elem.addEventListener('click', event => {
let button = event.target.closest('button[data-action]'); const button = event.target.closest('button[data-action]');
if (button === null) return; if (button === null) return;
let action = button.getAttribute('data-action'); const action = button.getAttribute('data-action');
if (action === 'insertImage') this.actionInsertImage(); if (action === 'insertImage') this.actionInsertImage();
if (action === 'insertLink') this.actionShowLinkSelector(); if (action === 'insertLink') this.actionShowLinkSelector();
if (action === 'insertDrawing' && (event.ctrlKey || event.metaKey)) { if (action === 'insertDrawing' && (event.ctrlKey || event.metaKey)) {
@ -132,35 +120,11 @@ class MarkdownEditor {
window.$events.emit('editor-markdown-change', content); window.$events.emit('editor-markdown-change', content);
// Set body content // Set body content
this.displayDoc.body.className = 'page-content'; this.display.innerHTML = html;
this.displayDoc.body.innerHTML = html;
// Copy styles from page head and set custom styles for editor
this.loadStylesIntoDisplay();
}
loadStylesIntoDisplay() {
if (this.displayStylesLoaded) return;
this.displayDoc.documentElement.classList.add('markdown-editor-display');
// Set display to be dark mode if parent is
if (document.documentElement.classList.contains('dark-mode')) {
this.displayDoc.documentElement.style.backgroundColor = '#222';
this.displayDoc.documentElement.classList.add('dark-mode');
}
this.displayDoc.head.innerHTML = '';
const styles = document.head.querySelectorAll('style,link[rel=stylesheet]');
for (let style of styles) {
const copy = style.cloneNode(true);
this.displayDoc.head.appendChild(copy);
}
this.displayStylesLoaded = true;
} }
onMarkdownScroll(lineCount) { onMarkdownScroll(lineCount) {
const elems = this.displayDoc.body.children; const elems = this.display.children;
if (elems.length <= lineCount) return; if (elems.length <= lineCount) return;
const topElem = (lineCount === -1) ? elems[elems.length-1] : elems[lineCount]; const topElem = (lineCount === -1) ? elems[elems.length-1] : elems[lineCount];
@ -317,7 +281,7 @@ class MarkdownEditor {
let cursor = cm.getCursor(); let cursor = cm.getCursor();
let lineContent = cm.getLine(cursor.line); let lineContent = cm.getLine(cursor.line);
let lineLen = lineContent.length; let lineLen = lineContent.length;
let newLineContent = lineContent; let newLineContent;
if (lineContent.indexOf(start) === 0 && lineContent.slice(-end.length) === end) { if (lineContent.indexOf(start) === 0 && lineContent.slice(-end.length) === end) {
newLineContent = lineContent.slice(start.length, lineContent.length - end.length); newLineContent = lineContent.slice(start.length, lineContent.length - end.length);
@ -333,9 +297,9 @@ class MarkdownEditor {
let selection = cm.getSelection(); let selection = cm.getSelection();
if (selection === '') return wrapLine(start, end); if (selection === '') return wrapLine(start, end);
let newSelection = selection; let newSelection;
let frontDiff = 0; let frontDiff = 0;
let endDiff = 0; let endDiff;
if (selection.indexOf(start) === 0 && selection.slice(-end.length) === end) { if (selection.indexOf(start) === 0 && selection.slice(-end.length) === end) {
newSelection = selection.slice(start.length, selection.length - end.length); newSelection = selection.slice(start.length, selection.length - end.length);

View File

@ -114,26 +114,20 @@
.markdown-display { .markdown-display {
margin-inline-start: -1px; margin-inline-start: -1px;
}
.markdown-editor-display {
background-color: #fff;
body {
display: block; display: block;
background-color: #fff; background-color: #fff;
padding-inline-start: 16px; padding: $-m;
padding-inline-end: 16px; overflow-y: scroll;
}
[drawio-diagram]:hover { [drawio-diagram]:hover {
outline: 2px solid var(--color-primary); outline: 2px solid var(--color-primary);
} }
[drawio-diagram] embed {
pointer-events: none;
}
} }
html.markdown-editor-display.dark-mode { .dark-mode .markdown-display {
background-color: #222; background-color: #222;
body {
background-color: #222;
}
} }
.editor-toolbar { .editor-toolbar {

View File

@ -23,6 +23,7 @@
<div markdown-input class="flex flex-fill"> <div markdown-input class="flex flex-fill">
<textarea id="markdown-editor-input" <textarea id="markdown-editor-input"
refs="markdown-editor@input"
@if($errors->has('markdown')) class="text-neg" @endif @if($errors->has('markdown')) class="text-neg" @endif
name="markdown" name="markdown"
rows="5">@if(isset($model) || old('markdown')){{ old('markdown') ?? ($model->markdown === '' ? $model->html : $model->markdown) }}@endif</textarea> rows="5">@if(isset($model) || old('markdown')){{ old('markdown') ?? ($model->markdown === '' ? $model->html : $model->markdown) }}@endif</textarea>
@ -34,7 +35,10 @@
<div class="editor-toolbar"> <div class="editor-toolbar">
<div class="editor-toolbar-label">{{ trans('entities.pages_md_preview') }}</div> <div class="editor-toolbar-label">{{ trans('entities.pages_md_preview') }}</div>
</div> </div>
<iframe src="about:blank" class="markdown-display" sandbox="allow-same-origin"></iframe> <div class="markdown-display">
<div refs="markdown-editor@display"
class="page-content"></div>
</div>
</div> </div>
</div> </div>