First commit

This commit is contained in:
dakedres 2023-12-22 14:52:30 -05:00
commit ebccf275a7
7 changed files with 1768 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.temp/
node_modules/
dist/

17
index.html Normal file
View 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
View 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
View 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
View 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
View 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()
]
}

1374
yarn.lock Normal file

File diff suppressed because it is too large Load Diff