First commit
This commit is contained in:
commit
ebccf275a7
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.temp/
|
||||||
|
node_modules/
|
||||||
|
dist/
|
17
index.html
Normal file
17
index.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<script async src="./dist/libs.js"></script>
|
||||||
|
<script async src="./index.js"></script>
|
||||||
|
|
||||||
|
<style id="stylesheet">
|
||||||
|
body { display: flex; flex-direction: column; margin: 0; height: 100vh; max-height: 100vh; }
|
||||||
|
nav { background: #f5f5f5; color: #6c6c6c; margin: 2px 5px }
|
||||||
|
nav input { all: unset; text-decoration: underline; font-family: sans-serif; }
|
||||||
|
.cm-editor { flex-grow: 1; outline: 1px solid #ddd; overflow-y: auto; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
</html>
|
306
index.js
Normal file
306
index.js
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
const constants = {
|
||||||
|
configLocation: '.config.ini',
|
||||||
|
defaultConfig: `\
|
||||||
|
defaultDocumentPath = "untitled.txt"
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
Util.syncAnnotation = $.Annotation.define()
|
||||||
|
|
||||||
|
app.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const self = this
|
||||||
|
|
||||||
|
this.open = () => {
|
||||||
|
let nav = document.createElement('nav')
|
||||||
|
this.path = new PathPrompt()
|
||||||
|
|
||||||
|
nav.appendChild(this.path.dom)
|
||||||
|
document.body.appendChild(nav)
|
||||||
|
|
||||||
|
this.editor = new $.EditorView({
|
||||||
|
state: this.createState({
|
||||||
|
doc: localStorage[this.path.value]
|
||||||
|
}),
|
||||||
|
parent: document.body,
|
||||||
|
dispatchTransactions: transactions => {
|
||||||
|
if(this.views.length > 1) {
|
||||||
|
for(let i = 1; i < this.views.length; i++) {
|
||||||
|
transactions.forEach(transaction => Util.syncDispatch(transaction, this.editor, this.views[i].editor))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.editor.update(transactions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.views.push(this.editor)
|
||||||
|
|
||||||
|
this.editor.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveDocument = () => {
|
||||||
|
localStorage[this.path.value] = this.editor.state.doc.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.openDocument = () => {
|
||||||
|
this.editor.setState(this.createDocumentState())
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createDocumentState = () => {
|
||||||
|
return this.createState({
|
||||||
|
doc: localStorage[this.path.value]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refreshState = () => {
|
||||||
|
let newState = this.createState({
|
||||||
|
doc: app.editor.state.doc.toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.editor.setState(newState)
|
||||||
|
return newState
|
||||||
|
}
|
||||||
|
|
||||||
|
const openConfig = () => {
|
||||||
|
let config = {}
|
||||||
|
|
||||||
|
if(localStorage[constants.configLocation]) {
|
||||||
|
config = parseConfig(localStorage[constants.configLocation])
|
||||||
|
} else {
|
||||||
|
config = parseConfig(constants.defaultConfig)
|
||||||
|
localStorage[constants.configLocation] = constants.defaultConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseConfig = (text) => {
|
||||||
|
let data = {}
|
||||||
|
|
||||||
|
for(let line of text.split('\n')) {
|
||||||
|
if(line.length == 0 || line[0] == ';')
|
||||||
|
continue
|
||||||
|
|
||||||
|
let delimiterIndex = line.indexOf('=')
|
||||||
|
|
||||||
|
if(delimiterIndex == -1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
let key = line.slice(0, delimiterIndex).trim()
|
||||||
|
let value = line.slice(delimiterIndex + 1).trim()
|
||||||
|
|
||||||
|
if(value[0] == '"') {
|
||||||
|
let quoteEnd = value.lastIndexOf('"')
|
||||||
|
|
||||||
|
value = value.slice(1, quoteEnd == 0 ? undefined : quoteEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
data[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createState = ({
|
||||||
|
doc,
|
||||||
|
extraKeys = [],
|
||||||
|
hasHistory = true
|
||||||
|
}) => {
|
||||||
|
let keymap = [
|
||||||
|
...$.closeBracketsKeymap,
|
||||||
|
...$.defaultKeymap,
|
||||||
|
...$.searchKeymap,
|
||||||
|
...$.foldKeymap,
|
||||||
|
...$.completionKeymap,
|
||||||
|
...$.lintKeymap,
|
||||||
|
$.indentWithTab,
|
||||||
|
{
|
||||||
|
key: 'Ctrl-s',
|
||||||
|
run(state, event) {
|
||||||
|
event.preventDefault()
|
||||||
|
self.saveDocument()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Ctrl-o',
|
||||||
|
run(state, event) {
|
||||||
|
let mainSelection = self.editor.state.selection.main
|
||||||
|
|
||||||
|
if(!mainSelection.empty)
|
||||||
|
self.path.set(self.editor.state.sliceDoc(mainSelection.from, mainSelection.to).trim() )
|
||||||
|
|
||||||
|
self.openDocument()
|
||||||
|
},
|
||||||
|
shift(state, event) {
|
||||||
|
event.preventDefault()
|
||||||
|
let mainSelection = self.editor.state.selection.main
|
||||||
|
|
||||||
|
if(!mainSelection.empty) {
|
||||||
|
let url = new URL(window.location)
|
||||||
|
|
||||||
|
url.hash = self.editor.state.sliceDoc(mainSelection.from, mainSelection.to).trim()
|
||||||
|
window.open(url.href)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Ctrl-n',
|
||||||
|
run(state, event) {
|
||||||
|
event.preventDefault()
|
||||||
|
let mainSelection = self.editor.state.selection.main
|
||||||
|
|
||||||
|
if(!mainSelection.empty)
|
||||||
|
self.path.dom.value = self.editor.state.sliceDoc(mainSelection.from, mainSelection.to)
|
||||||
|
self.path.dom.select()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Ctrl-l',
|
||||||
|
run(state, event) {
|
||||||
|
self.editor.setState(self.createState({
|
||||||
|
doc: Object.keys(localStorage).join('\n')
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Ctrl-b',
|
||||||
|
run(state, event) {
|
||||||
|
app.views.push(new ChildWindow())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...extraKeys
|
||||||
|
]
|
||||||
|
|
||||||
|
if(hasHistory) {
|
||||||
|
keymap = keymap.concat($.historyKeymap)
|
||||||
|
}
|
||||||
|
|
||||||
|
let extensions = [
|
||||||
|
$.lineNumbers(),
|
||||||
|
$.highlightActiveLineGutter(),
|
||||||
|
$.highlightSpecialChars(),
|
||||||
|
$.foldGutter(),
|
||||||
|
$.drawSelection(),
|
||||||
|
$.dropCursor(),
|
||||||
|
$.EditorState.allowMultipleSelections.of(true),
|
||||||
|
$.indentOnInput(),
|
||||||
|
$.syntaxHighlighting($.defaultHighlightStyle, { fallback: true }),
|
||||||
|
$.bracketMatching(),
|
||||||
|
$.closeBrackets(),
|
||||||
|
$.autocompletion(),
|
||||||
|
$.rectangularSelection(),
|
||||||
|
$.crosshairCursor(),
|
||||||
|
$.highlightActiveLine(),
|
||||||
|
$.highlightSelectionMatches(),
|
||||||
|
$.keymap.of(keymap)
|
||||||
|
]
|
||||||
|
|
||||||
|
if(hasHistory) {
|
||||||
|
extensions.push($.history())
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = $.EditorState.create({
|
||||||
|
doc,
|
||||||
|
extensions
|
||||||
|
})
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config = openConfig()
|
||||||
|
this.views = []
|
||||||
|
}
|
||||||
|
|
||||||
|
function PathPrompt() {
|
||||||
|
this.set = value =>
|
||||||
|
document.title = window.location.hash = this.value = this.dom.value = value
|
||||||
|
|
||||||
|
this.dom = document.createElement('input')
|
||||||
|
|
||||||
|
this.dom.addEventListener('focusout', (event) => {
|
||||||
|
this.dom.value = this.value
|
||||||
|
})
|
||||||
|
|
||||||
|
this.dom.addEventListener('keydown', (event) => {
|
||||||
|
switch(event.key) {
|
||||||
|
case 'Enter':
|
||||||
|
event.preventDefault()
|
||||||
|
this.set(this.dom.value)
|
||||||
|
sessionStorage.lastDocument = this.value
|
||||||
|
// app.openDocument()
|
||||||
|
app.editor.focus()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'Escape':
|
||||||
|
event.preventDefault()
|
||||||
|
app.editor.focus()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if(window.location.hash !== '') {
|
||||||
|
this.set(decodeURI(window.location.hash.slice(1) ) )
|
||||||
|
} else {
|
||||||
|
this.set(sessionStorage.lastDocument ?? app.config.defaultDocumentPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChildWindow(index) {
|
||||||
|
let self = this
|
||||||
|
|
||||||
|
let state = app.createState({
|
||||||
|
doc: app.refreshState().doc,
|
||||||
|
hasHistory: false,
|
||||||
|
extraKeys: [
|
||||||
|
{
|
||||||
|
key: 'Mod-z',
|
||||||
|
run: () => $.undo(app.editor)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Mod-y',
|
||||||
|
mac: "Mod-Shift-z",
|
||||||
|
run: () => $.redo(app.editor)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
this.index = app.views.length
|
||||||
|
this.window = window.open('about:blank', '_blank')
|
||||||
|
this.window.document.head.appendChild(document.getElementById('stylesheet').cloneNode(true))
|
||||||
|
this.editor = new $.EditorView({
|
||||||
|
state,
|
||||||
|
parent: this.window.document.body,
|
||||||
|
dispatch: transaction => {
|
||||||
|
for(let i = 0; i < app.views.length; i++) {
|
||||||
|
if(i == this.index)
|
||||||
|
continue
|
||||||
|
|
||||||
|
Util.syncDispatch(transaction, this.editor, app.editor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const Util = {}
|
||||||
|
|
||||||
|
{
|
||||||
|
Util.syncDispatch = (transaction, source, target) => {
|
||||||
|
source.update([ transaction ])
|
||||||
|
|
||||||
|
if(!transaction.changes.empty && !transaction.annotation(Util.syncAnnotation)) {
|
||||||
|
let annotations = [ Util.syncAnnotation.of(true) ]
|
||||||
|
let userEvent = transaction.annotation($.Transaction.userEvent)
|
||||||
|
|
||||||
|
if(userEvent)
|
||||||
|
annotations.push($.Transaction.userEvent.of(userEvent))
|
||||||
|
|
||||||
|
target.dispatch({ changes: transaction.changes, annotations })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.app = new App()
|
||||||
|
window.addEventListener('load', main)
|
43
libs.js
Normal file
43
libs.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { EditorState, Compartment, Transaction, Annotation } from '@codemirror/state'
|
||||||
|
import { indentWithTab, undo, redo } from '@codemirror/commands'
|
||||||
|
import { EditorView, lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine, keymap } from '@codemirror/view'
|
||||||
|
import { foldGutter, indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap } from '@codemirror/language'
|
||||||
|
import { history, defaultKeymap, historyKeymap } from '@codemirror/commands'
|
||||||
|
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
|
||||||
|
import { closeBrackets, autocompletion, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete'
|
||||||
|
import { lintKeymap } from '@codemirror/lint'
|
||||||
|
|
||||||
|
window.$ = {
|
||||||
|
EditorView,
|
||||||
|
Compartment,
|
||||||
|
EditorState,
|
||||||
|
keymap,
|
||||||
|
indentWithTab,
|
||||||
|
lineNumbers,
|
||||||
|
highlightActiveLineGutter,
|
||||||
|
highlightSpecialChars,
|
||||||
|
history,
|
||||||
|
foldGutter,
|
||||||
|
drawSelection,
|
||||||
|
dropCursor,
|
||||||
|
indentOnInput,
|
||||||
|
syntaxHighlighting,
|
||||||
|
bracketMatching,
|
||||||
|
closeBrackets,
|
||||||
|
autocompletion,
|
||||||
|
rectangularSelection,
|
||||||
|
crosshairCursor,
|
||||||
|
highlightActiveLine,
|
||||||
|
highlightSelectionMatches,
|
||||||
|
closeBracketsKeymap,
|
||||||
|
defaultKeymap,
|
||||||
|
searchKeymap,
|
||||||
|
historyKeymap,
|
||||||
|
foldKeymap,
|
||||||
|
completionKeymap,
|
||||||
|
lintKeymap,
|
||||||
|
undo,
|
||||||
|
redo,
|
||||||
|
Transaction,
|
||||||
|
Annotation
|
||||||
|
}
|
12
package.json
Normal file
12
package.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "editor",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@neutralinojs/neu": "^10.1.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
|
"codemirror": "^6.0.1",
|
||||||
|
"rollup": "^4.9.1"
|
||||||
|
}
|
||||||
|
}
|
13
rollup.config.mjs
Normal file
13
rollup.config.mjs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import nodeResolve from "@rollup/plugin-node-resolve";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: "./libs.js",
|
||||||
|
output: {
|
||||||
|
file: "./dist/libs.js",
|
||||||
|
name: '$',
|
||||||
|
format: "iife"
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
nodeResolve()
|
||||||
|
]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user