diff --git a/internal/web/run.go b/internal/web/run.go index f4aa715..9a83386 100644 --- a/internal/web/run.go +++ b/internal/web/run.go @@ -16,6 +16,7 @@ import ( "opengist/internal/config" "opengist/internal/git" "opengist/internal/models" + "os" "path/filepath" "regexp" "strconv" @@ -23,6 +24,7 @@ import ( "time" ) +var devAssets = os.Getenv("DEV_ASSETS") == "1" var store *sessions.CookieStore var re = regexp.MustCompile("[^a-z0-9]+") var fm = template.FuncMap{ @@ -75,6 +77,9 @@ var fm = template.FuncMap{ return fmt.Sprintf("%x", md5.Sum([]byte(strings.ToLower(strings.TrimSpace(email))))) }, "asset": func(jsfile string) string { + if devAssets { + return "http://localhost:16157/" + jsfile + } return "/" + manifestEntries[jsfile].File }, } diff --git a/package-lock.json b/package-lock.json index b596989..659a6e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,9 @@ "devDependencies": { "@codemirror/commands": "^6.2.2", "@codemirror/lang-javascript": "^6.1.4", + "@codemirror/language": "^6.6.0", + "@codemirror/state": "^6.2.0", + "@codemirror/text": "^0.19.6", "@codemirror/view": "^6.9.3", "@tailwindcss/forms": "^0.5.3", "@tailwindcss/typography": "^0.5.9", @@ -150,6 +153,13 @@ "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", "dev": true }, + "node_modules/@codemirror/text": { + "version": "0.19.6", + "resolved": "https://registry.npmjs.org/@codemirror/text/-/text-0.19.6.tgz", + "integrity": "sha512-T9jnREMIygx+TPC1bOuepz18maGq/92q2a+n4qTqObKwvNMg+8cMTslb8yxeEDEq7S3kpgGWxgO1UWbQRij0dA==", + "deprecated": "As of 0.20.0, this package has been merged into @codemirror/state", + "dev": true + }, "node_modules/@codemirror/view": { "version": "6.9.3", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.3.tgz", @@ -4913,6 +4923,12 @@ "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", "dev": true }, + "@codemirror/text": { + "version": "0.19.6", + "resolved": "https://registry.npmjs.org/@codemirror/text/-/text-0.19.6.tgz", + "integrity": "sha512-T9jnREMIygx+TPC1bOuepz18maGq/92q2a+n4qTqObKwvNMg+8cMTslb8yxeEDEq7S3kpgGWxgO1UWbQRij0dA==", + "dev": true + }, "@codemirror/view": { "version": "6.9.3", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.3.tgz", diff --git a/package.json b/package.json index 092a1bd..3212651 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "devDependencies": { "@codemirror/commands": "^6.2.2", "@codemirror/lang-javascript": "^6.1.4", + "@codemirror/language": "^6.6.0", + "@codemirror/state": "^6.2.0", + "@codemirror/text": "^0.19.6", "@codemirror/view": "^6.9.3", "@tailwindcss/forms": "^0.5.3", "@tailwindcss/typography": "^0.5.9", diff --git a/public/editor.js b/public/editor.js index c859ea9..cd57c5a 100644 --- a/public/editor.js +++ b/public/editor.js @@ -1,5 +1,6 @@ -import {EditorView, keymap, gutter, lineNumbers} from "@codemirror/view" -import {indentWithTab} from "@codemirror/commands" +import {EditorView, gutter, keymap, lineNumbers} from "@codemirror/view" +import {Compartment, EditorState, Facet, SelectionRange} from "@codemirror/state" +import {indentLess} from "@codemirror/commands"; EditorView.theme({}, {dark: true}) @@ -8,24 +9,118 @@ let editorsParentdom = document.getElementById('editors') let allEditorsdom = document.querySelectorAll('#editors > .editor') let firstEditordom = allEditorsdom[0] +const txtFacet = Facet.define({ + combine(values) { + return values[0] + } +}) +let indentSize = new Compartment, wrapMode = new Compartment, indentType = new Compartment + const newEditor = (dom, value = '') => { - return new EditorView({ + let editor = new EditorView({ doc: value, + parent: dom, extensions: [ lineNumbers(), gutter({class: "cm-mygutter"}), - keymap.of([indentWithTab]), - ], - parent: dom + 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 +} + +function changeBySelectedLine(state, f) { + 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))} }) } -const eventOnDrop = (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 +function setIndentType(view, type) { + view.dispatch({effects: indentType.reconfigure(txtFacet.of(type))}) } -document.onsubmit = () => { - window.onbeforeunload = null; +function setIndentSize(view, size) { + view.dispatch({effects: indentSize.reconfigure(EditorState.tabSize.of(size))}) +} + +function setLineWrapping(view, enable) { + if (enable) { + view.dispatch({effects: wrapMode.reconfigure(EditorView.lineWrapping)}) + } else { + view.dispatch({effects: wrapMode.reconfigure([])}) + } } let arr = [...allEditorsdom] @@ -33,28 +128,6 @@ arr.forEach(el => { // in case we edit the gist contents let currEditor = newEditor(el, el.querySelector('.form-filecontent').value) editorsjs.push(currEditor) - - currEditor.dom.addEventListener("input", function inputConfirmLeave() { - if (!currEditor.inView) return; // skip events outside the viewport - - currEditor.dom.removeEventListener("input", inputConfirmLeave); - window.onbeforeunload = () => { - return 'Are you sure you want to quit?'; - } - }); - - currEditor.dom.addEventListener("drop", eventOnDrop); - - // remove editor on delete - let deleteBtns = el.querySelector('button.delete-file') - if (deleteBtns !== null) { - - deleteBtns.onclick = () => { - - editorsjs.splice(editorsjs.indexOf(currEditor), 1); - el.remove() - } - } }) document.getElementById('add-file').onclick = () => { @@ -70,8 +143,6 @@ document.getElementById('add-file').onclick = () => { // creating the new codemirror editor and append it in the editor div editorsjs.push(newEditor(newEditorDom)) editorsParentdom.append(newEditorDom) - editorsParentdom.addEventListener("drop", eventOnDrop); - } document.querySelector('form#create').onsubmit = () => { @@ -80,3 +151,7 @@ document.querySelector('form#create').onsubmit = () => { e.value = encodeURIComponent(editorsjs[j++].state.doc.toString()) }) } + +document.onsubmit = () => { + window.onbeforeunload = null; +} diff --git a/templates/base/gist_header.html b/templates/base/gist_header.html index 5e1195e..ea06949 100644 --- a/templates/base/gist_header.html +++ b/templates/base/gist_header.html @@ -164,7 +164,7 @@ - + Download ZIP diff --git a/templates/pages/create.html b/templates/pages/create.html index eabde79..a75a476 100644 --- a/templates/pages/create.html +++ b/templates/pages/create.html @@ -24,10 +24,31 @@
+
+