mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-31 15:22:39 +00:00
Added indent type, indent size and wrap mode to editors
This commit is contained in:
parent
f9f5b140b8
commit
2aee52e06c
7 changed files with 179 additions and 38 deletions
|
@ -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
|
||||
},
|
||||
}
|
||||
|
|
16
package-lock.json
generated
16
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
145
public/editor.js
vendored
145
public/editor.js
vendored
|
@ -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;
|
||||
}
|
||||
|
|
2
templates/base/gist_header.html
vendored
2
templates/base/gist_header.html
vendored
|
@ -164,7 +164,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/archive/{{ .revision }}" class="whitespace-nowrap text-slate-300 rounded border border-gray-600 bg-gray-800 px-2.5 py-2 text-xs font-medium text-white shadow-sm hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
<a href="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/archive/{{ .revision }}" class="whitespace-nowrap text-slate-300 rounded border border-gray-600 bg-gray-800 px-2.5 py-2 text-xs font-medium shadow-sm hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500 leading-3">
|
||||
Download ZIP</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
23
templates/pages/create.html
vendored
23
templates/pages/create.html
vendored
|
@ -24,10 +24,31 @@
|
|||
</div>
|
||||
<div id="editors" class="space-y-4">
|
||||
<div class="rounded-md border border-1 border-gray-700 editor">
|
||||
<div class="border-b-1 border-gray-700 bg-gray-800 my-auto">
|
||||
<div class="border-b-1 border-gray-700 bg-gray-800 my-auto flex">
|
||||
<p class="mx-2 my-2 inline-flex">
|
||||
<input type="text" name="name" placeholder="Filename with extension" style="line-height: 0.05em" class="form-filename bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-700 rounded-md">
|
||||
</p>
|
||||
<div class="mx-2 my-2 inline-flex ml-auto space-x-2">
|
||||
<select class="editor-indent-type whitespace-nowrap text-slate-300 rounded border border-gray-600 bg-gray-900 pr-8 text-xs font-medium shadow-sm hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500">
|
||||
<optgroup label="Indent mode">
|
||||
<option value="space">Space</option>
|
||||
<option value="tab">Tabs</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<select class="editor-indent-size whitespace-nowrap text-slate-300 rounded border border-gray-600 bg-gray-900 pr-8 text-xs font-medium shadow-sm hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500">
|
||||
<optgroup label="Indent size">
|
||||
<option value="2">2</option>
|
||||
<option value="4">4</option>
|
||||
<option value="8">8</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<select class="editor-wrap-mode whitespace-nowrap text-slate-300 rounded border border-gray-600 bg-gray-900 pr-8 text-xs font-medium shadow-sm hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500">
|
||||
<optgroup label="Wrap mode">
|
||||
<option value="no">No wrap</option>
|
||||
<option value="soft">Soft wrap</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="" name="content" class="form-filecontent">
|
||||
</div>
|
||||
|
|
23
templates/pages/edit.html
vendored
23
templates/pages/edit.html
vendored
|
@ -55,7 +55,7 @@
|
|||
<div id="editors" class="space-y-4">
|
||||
{{ range $file := .files }}
|
||||
<div class="rounded-md border border-1 border-gray-700 editor">
|
||||
<div class="border-b-1 border-gray-700 bg-gray-800 my-auto">
|
||||
<div class="border-b-1 border-gray-700 bg-gray-800 my-auto flex">
|
||||
<p class="mx-2 my-2 inline-flex">
|
||||
<input type="text" value="{{ $file.Filename }}" name="name" placeholder="Filename with extension" style="line-height: 0.05em; z-index: 99999" class="form-filename bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-700 rounded-l-md">
|
||||
<button style="line-height: 0.05em" class="delete-file -ml-px relative inline-flex items-center space-x-2 px-4 py-2 border border-gray-700 text-sm font-medium rounded-r-md text-slate-300 bg-gray-800 hover:bg-gray-900 focus:outline-none" type="button">
|
||||
|
@ -64,6 +64,27 @@
|
|||
</svg>
|
||||
</button>
|
||||
</p>
|
||||
<div class="mx-2 my-2 inline-flex ml-auto space-x-2">
|
||||
<select class="editor-indent-type whitespace-nowrap text-slate-300 rounded border border-gray-600 bg-gray-900 pr-8 text-xs font-medium shadow-sm hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500">
|
||||
<optgroup label="Indent mode">
|
||||
<option value="space">Space</option>
|
||||
<option value="tab">Tabs</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<select class="editor-indent-size whitespace-nowrap text-slate-300 rounded border border-gray-600 bg-gray-900 pr-8 text-xs font-medium shadow-sm hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500">
|
||||
<optgroup label="Indent size">
|
||||
<option value="2">2</option>
|
||||
<option value="4">4</option>
|
||||
<option value="8">8</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<select class="editor-wrap-mode whitespace-nowrap text-slate-300 rounded border border-gray-600 bg-gray-900 pr-8 text-xs font-medium shadow-sm hover:bg-gray-700 hover:border-gray-500 hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500">
|
||||
<optgroup label="Wrap mode">
|
||||
<option value="no">No wrap</option>
|
||||
<option value="soft">Soft wrap</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" value="{{ $file.Content }}" name="content" class="form-filecontent">
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue