Wrap editor code in DOMContentLoaded

This commit is contained in:
Thomas Miceli 2023-04-03 00:43:23 +02:00
parent 2aee52e06c
commit 0eb1b103d0
No known key found for this signature in database
GPG key ID: D86C6F6390AF050F

273
public/editor.js vendored
View file

@ -2,156 +2,161 @@ import {EditorView, gutter, keymap, lineNumbers} from "@codemirror/view"
import {Compartment, EditorState, Facet, SelectionRange} from "@codemirror/state" import {Compartment, EditorState, Facet, SelectionRange} from "@codemirror/state"
import {indentLess} from "@codemirror/commands"; import {indentLess} from "@codemirror/commands";
EditorView.theme({}, {dark: true}) document.addEventListener('DOMContentLoaded', () => {
EditorView.theme({}, {dark: true})
let editorsjs = [] let editorsjs = []
let editorsParentdom = document.getElementById('editors') let editorsParentdom = document.getElementById('editors')
let allEditorsdom = document.querySelectorAll('#editors > .editor') let allEditorsdom = document.querySelectorAll('#editors > .editor')
let firstEditordom = allEditorsdom[0] let firstEditordom = allEditorsdom[0]
const txtFacet = Facet.define({ const txtFacet = Facet.define({
combine(values) { combine(values) {
return values[0] return values[0]
}
})
let indentSize = new Compartment, wrapMode = new Compartment, indentType = new Compartment
const newEditor = (dom, value = '') => {
let editor = new EditorView({
doc: value,
parent: dom,
extensions: [
lineNumbers(), gutter({class: "cm-mygutter"}),
keymap.of([{key: "Tab", run: customIndentMore, shift: indentLess}]),
indentSize.of(EditorState.tabSize.of(2)),
wrapMode.of([]),
indentType.of(txtFacet.of("space")),
]
})
dom.querySelector('.editor-indent-type').onchange = (e) => {
let newTabType = e.target.value
setIndentType(editor, !['tab', 'space'].includes(newTabType) ? 'space' : newTabType)
}
dom.querySelector('.editor-indent-size').onchange = (e) => {
let newTabSize = parseInt(e.target.value)
setIndentSize(editor, ![2, 4, 8].includes(newTabSize) ? 2 : newTabSize)
}
dom.querySelector('.editor-wrap-mode').onchange = (e) => {
let newWrapMode = e.target.value
setLineWrapping(editor, newWrapMode === 'soft')
}
dom.addEventListener("drop", (e) => {
e.preventDefault(); // prevent the browser from opening the dropped file
e.target.closest('.editor').querySelector('input.form-filename').value = e.dataTransfer.files[0].name
});
// remove editor on delete
let deleteBtns = dom.querySelector('button.delete-file')
if (deleteBtns !== null) {
deleteBtns.onclick = () => {
editorsjs.splice(editorsjs.indexOf(editor), 1);
dom.remove()
}
}
editor.dom.addEventListener("input", function inputConfirmLeave() {
if (!editor.inView) return; // skip events outside the viewport
editor.dom.removeEventListener("input", inputConfirmLeave);
window.onbeforeunload = () => {
return 'Are you sure you want to quit?';
}
});
return editor;
}
function getIndentation(state) {
if (indentType.get(state).value === 'tab') {
return '\t';
}
return ' '.repeat(indentSize.get(state).value);
}
function customIndentMore({state, dispatch}) {
let indentation = getIndentation(state)
dispatch({
...state.update(changeBySelectedLine(state, (line, changes) => {
changes.push({from: state.selection.ranges[0].from, insert: indentation})
})), selection: {
anchor: state.selection.ranges[0].from + indentation.length,
head: state.selection.ranges[0].from + indentation.length,
} }
}) })
return true let indentSize = new Compartment, wrapMode = new Compartment, indentType = new Compartment
}
function changeBySelectedLine(state, f) { const newEditor = (dom, value = '') => {
let atLine = -1 let editor = new EditorView({
return state.changeByRange(range => { doc: value,
let changes = [] parent: dom,
for (let line = state.doc.lineAt(range.from); ;) { extensions: [
if (line.number > atLine) { lineNumbers(), gutter({class: "cm-mygutter"}),
f(line, changes) keymap.of([{key: "Tab", run: customIndentMore, shift: indentLess}]),
atLine = line.number indentSize.of(EditorState.tabSize.of(2)),
wrapMode.of([]),
indentType.of(txtFacet.of("space")),
]
})
dom.querySelector('.editor-indent-type').onchange = (e) => {
let newTabType = e.target.value
setIndentType(editor, !['tab', 'space'].includes(newTabType) ? 'space' : newTabType)
}
dom.querySelector('.editor-indent-size').onchange = (e) => {
let newTabSize = parseInt(e.target.value)
setIndentSize(editor, ![2, 4, 8].includes(newTabSize) ? 2 : newTabSize)
}
dom.querySelector('.editor-wrap-mode').onchange = (e) => {
let newWrapMode = e.target.value
setLineWrapping(editor, newWrapMode === 'soft')
}
dom.addEventListener("drop", (e) => {
e.preventDefault(); // prevent the browser from opening the dropped file
e.target.closest('.editor').querySelector('input.form-filename').value = e.dataTransfer.files[0].name
});
// remove editor on delete
let deleteBtns = dom.querySelector('button.delete-file')
if (deleteBtns !== null) {
deleteBtns.onclick = () => {
editorsjs.splice(editorsjs.indexOf(editor), 1);
dom.remove()
} }
if (range.to <= line.to) break
line = state.doc.lineAt(line.number + 1)
} }
let changeSet = state.changes(changes)
return {changes, range: new SelectionRange(changeSet.mapPos(range.anchor, 1), changeSet.mapPos(range.head, 1))}
})
}
function setIndentType(view, type) { editor.dom.addEventListener("input", function inputConfirmLeave() {
view.dispatch({effects: indentType.reconfigure(txtFacet.of(type))}) if (!editor.inView) return; // skip events outside the viewport
}
function setIndentSize(view, size) { editor.dom.removeEventListener("input", inputConfirmLeave);
view.dispatch({effects: indentSize.reconfigure(EditorState.tabSize.of(size))}) window.onbeforeunload = () => {
} return 'Are you sure you want to quit?';
}
});
function setLineWrapping(view, enable) { return editor;
if (enable) {
view.dispatch({effects: wrapMode.reconfigure(EditorView.lineWrapping)})
} else {
view.dispatch({effects: wrapMode.reconfigure([])})
} }
}
let arr = [...allEditorsdom] function getIndentation(state) {
arr.forEach(el => { if (indentType.get(state).value === 'tab') {
// in case we edit the gist contents return '\t';
let currEditor = newEditor(el, el.querySelector('.form-filecontent').value) }
editorsjs.push(currEditor) return ' '.repeat(indentSize.get(state).value);
}) }
document.getElementById('add-file').onclick = () => { function customIndentMore({state, dispatch}) {
let newEditorDom = firstEditordom.cloneNode(true) let indentation = getIndentation(state)
dispatch({
...state.update(changeBySelectedLine(state, (line, changes) => {
changes.push({from: state.selection.ranges[0].from, insert: indentation})
})), selection: {
anchor: state.selection.ranges[0].from + indentation.length,
head: state.selection.ranges[0].from + indentation.length,
}
})
return true
}
// reset the filename of the new cloned element function changeBySelectedLine(state, f) {
newEditorDom.querySelector('input[name="name"]').value = "" let atLine = -1
return state.changeByRange(range => {
let changes = []
for (let line = state.doc.lineAt(range.from); ;) {
if (line.number > atLine) {
f(line, changes)
atLine = line.number
}
if (range.to <= line.to) break
line = state.doc.lineAt(line.number + 1)
}
let changeSet = state.changes(changes)
return {
changes,
range: new SelectionRange(changeSet.mapPos(range.anchor, 1), changeSet.mapPos(range.head, 1))
}
})
}
// removing the previous codemirror editor function setIndentType(view, type) {
let newEditorDomCM = newEditorDom.querySelector('.cm-editor') view.dispatch({effects: indentType.reconfigure(txtFacet.of(type))})
newEditorDomCM.remove() }
// creating the new codemirror editor and append it in the editor div function setIndentSize(view, size) {
editorsjs.push(newEditor(newEditorDom)) view.dispatch({effects: indentSize.reconfigure(EditorState.tabSize.of(size))})
editorsParentdom.append(newEditorDom) }
}
document.querySelector('form#create').onsubmit = () => { function setLineWrapping(view, enable) {
let j = 0 if (enable) {
document.querySelectorAll('.form-filecontent').forEach((e) => { view.dispatch({effects: wrapMode.reconfigure(EditorView.lineWrapping)})
e.value = encodeURIComponent(editorsjs[j++].state.doc.toString()) } else {
view.dispatch({effects: wrapMode.reconfigure([])})
}
}
let arr = [...allEditorsdom]
arr.forEach(el => {
// in case we edit the gist contents
let currEditor = newEditor(el, el.querySelector('.form-filecontent').value)
editorsjs.push(currEditor)
}) })
}
document.onsubmit = () => { document.getElementById('add-file').onclick = () => {
window.onbeforeunload = null; let newEditorDom = firstEditordom.cloneNode(true)
}
// reset the filename of the new cloned element
newEditorDom.querySelector('input[name="name"]').value = ""
// removing the previous codemirror editor
let newEditorDomCM = newEditorDom.querySelector('.cm-editor')
newEditorDomCM.remove()
// creating the new codemirror editor and append it in the editor div
editorsjs.push(newEditor(newEditorDom))
editorsParentdom.append(newEditorDom)
}
document.querySelector('form#create').onsubmit = () => {
let j = 0
document.querySelectorAll('.form-filecontent').forEach((e) => {
e.value = encodeURIComponent(editorsjs[j++].state.doc.toString())
})
}
document.onsubmit = () => {
window.onbeforeunload = null;
}
})