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