Qwe is born
This commit is contained in:
parent
4879f5a7cf
commit
eb0077c849
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@ node_modules/
|
||||
/bin
|
||||
/dist
|
||||
/build
|
||||
/src/lib
|
||||
|
||||
yarn-error.log
|
||||
neutralinojs.log
|
23
build.js
23
build.js
@ -6,13 +6,20 @@ import { copyFile } from 'fs/promises'
|
||||
let [ platform, environment = 'dev' ] = process.argv.slice(2)
|
||||
let projectRoot = Path.dirname(new URL(import.meta.url).pathname)
|
||||
|
||||
const indexTemplate = scripts => `\
|
||||
const indexTemplate = scriptSources => {
|
||||
let scripts = scriptSources.map(src => "<script async src=" + JSON.stringify(src) + "></script>")
|
||||
|
||||
// if(platform == 'neutralino')
|
||||
// scripts.unshift('<script src="build/neutralino.js"></script>')
|
||||
|
||||
return `\
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
<!-- <link rel="icon" href=".temp/editorapp/resources/icons/appIcon.png"> -->
|
||||
${scripts.map(src => "<script async src=" + JSON.stringify(src) + "></script>").join('\n')}
|
||||
${scripts.join('\n')}
|
||||
<script async type="module" src="./editor.js"></script>
|
||||
|
||||
<style id="stylesheet">
|
||||
body { display: flex; flex-direction: column; margin: 0; height: 100vh; max-height: 100vh; font-size: 14px; }
|
||||
@ -25,16 +32,17 @@ nav input { all: unset; text-decoration: underline; font-family: sans-serif; }
|
||||
|
||||
</html>
|
||||
`
|
||||
}
|
||||
|
||||
let out
|
||||
|
||||
switch(environment) {
|
||||
case 'dev':
|
||||
out = indexTemplate([
|
||||
Path.join('platforms', platform + '.js'),
|
||||
'editor.js',
|
||||
'build/libs.js'
|
||||
])
|
||||
let scripts = [
|
||||
Path.join('platforms', platform + '.js')
|
||||
]
|
||||
|
||||
out = indexTemplate(scripts)
|
||||
break
|
||||
|
||||
case 'prod':
|
||||
@ -45,6 +53,7 @@ switch(environment) {
|
||||
|
||||
out = indexTemplate([
|
||||
'platform.js',
|
||||
'config.js',
|
||||
'editor.js',
|
||||
'libs.js'
|
||||
])
|
||||
|
307
editor.js
307
editor.js
@ -1,307 +0,0 @@
|
||||
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-r',
|
||||
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')
|
||||
console.log(this)
|
||||
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)
|
@ -4,8 +4,7 @@
|
||||
|
||||
<!-- <link rel="icon" href=".temp/editorapp/resources/icons/appIcon.png"> -->
|
||||
<script async src="platforms/neutralino.js"></script>
|
||||
<script async src="editor.js"></script>
|
||||
<script async src="build/libs.js"></script>
|
||||
<script async type="module" src="./editor.js"></script>
|
||||
|
||||
<style id="stylesheet">
|
||||
body { display: flex; flex-direction: column; margin: 0; height: 100vh; max-height: 100vh; font-size: 14px; }
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { EditorState, Compartment, Transaction, Annotation } from '@codemirror/state'
|
||||
import { indentWithTab, undo, redo } from '@codemirror/commands'
|
||||
import { indentWithTab, undo, redo, history, defaultKeymap, historyKeymap } 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.$ = {
|
||||
export default {
|
||||
EditorView,
|
||||
Compartment,
|
||||
EditorState,
|
||||
@ -39,5 +38,6 @@ window.$ = {
|
||||
undo,
|
||||
redo,
|
||||
Transaction,
|
||||
Annotation
|
||||
Annotation,
|
||||
defaultHighlightStyle
|
||||
}
|
2
lib/lang/javascript.js
Normal file
2
lib/lang/javascript.js
Normal file
@ -0,0 +1,2 @@
|
||||
import * as m from '@codemirror/lang-javascript'
|
||||
export default m
|
2
lib/lang/markdown.js
Normal file
2
lib/lang/markdown.js
Normal file
@ -0,0 +1,2 @@
|
||||
import * as m from '@codemirror/lang-markdown'
|
||||
export default m
|
33
lib/platforms/neutralino.js
Normal file
33
lib/platforms/neutralino.js
Normal file
@ -0,0 +1,33 @@
|
||||
export default {
|
||||
async access(path) {
|
||||
try {
|
||||
await Neutralino.filesystem.getStats(path)
|
||||
return
|
||||
} catch(err) {
|
||||
if(err.name = 'NE_FS_NOPATHE') {
|
||||
return false
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
dirname(path) {
|
||||
let index = path.lastIndexOf('/')
|
||||
|
||||
return index === -1 ? '' : path.slice(0, index)
|
||||
},
|
||||
|
||||
filename(path) {
|
||||
let index = path.lastIndexOf('/')
|
||||
|
||||
return index === -1 ? path : path.slice(index + 1)
|
||||
},
|
||||
|
||||
ext(path) {
|
||||
let filename = this.filename(path)
|
||||
let index = filename.lastIndexOf('.')
|
||||
|
||||
return index === -1 ? '' : path.slice(index + 1)
|
||||
}
|
||||
}
|
48
lib/rollup.config.mjs
Normal file
48
lib/rollup.config.mjs
Normal file
@ -0,0 +1,48 @@
|
||||
import nodeResolve from "@rollup/plugin-node-resolve";
|
||||
import Path, { format } from 'path'
|
||||
import FS from 'fs'
|
||||
|
||||
let plugins = [
|
||||
nodeResolve()
|
||||
]
|
||||
|
||||
let targetDir = '../src/lib'
|
||||
|
||||
const dirname = Path.dirname(new URL(import.meta.url).pathname)
|
||||
let langDir = Path.join(dirname, 'lang')
|
||||
|
||||
export default [
|
||||
{
|
||||
input: [
|
||||
'./codemirror.js',
|
||||
...FS.readdirSync(langDir, { withFileTypes: true })
|
||||
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.js'))
|
||||
.map(dirent => Path.join(langDir, dirent.name))
|
||||
],
|
||||
output: {
|
||||
dir: targetDir,
|
||||
format: "es"
|
||||
},
|
||||
plugins
|
||||
},
|
||||
// ...FS.readdirSync(langDir, { withFileTypes: true })
|
||||
// .filter(dirent => dirent.isFile() && dirent.name.endsWith('.js'))
|
||||
// .map(dirent => ({
|
||||
// input: Path.join(langDir, dirent.name),
|
||||
// external: [
|
||||
// '@codemirror/state',
|
||||
// '@codemirror/commands',
|
||||
// '@codemirror/view',
|
||||
// '@codemirror/language',
|
||||
// '@codemirror/search',
|
||||
// '@codemirror/autocomplete',
|
||||
// '@codemirror/lint'
|
||||
// ],
|
||||
// output: {
|
||||
// file: Path.join(targetDir, dirent.name),
|
||||
// format: "es",
|
||||
// globals
|
||||
// },
|
||||
// plugins
|
||||
// }))
|
||||
]
|
@ -1,13 +0,0 @@
|
||||
import nodeResolve from "@rollup/plugin-node-resolve";
|
||||
|
||||
export default {
|
||||
input: "./index.js",
|
||||
output: {
|
||||
file: "../build/libs.js",
|
||||
name: '$',
|
||||
format: "iife"
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve()
|
||||
]
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
"defaultMode": "window",
|
||||
"port": 0,
|
||||
"documentRoot": "/",
|
||||
"url": "/index.html",
|
||||
"url": "/src/index.html",
|
||||
"enableServer": true,
|
||||
"enableNativeAPI": true,
|
||||
"tokenSecurity": "one-time",
|
||||
@ -28,7 +28,7 @@
|
||||
"fullScreen": false,
|
||||
"alwaysOnTop": false,
|
||||
"icon": "/resources/icon.png",
|
||||
"enableInspector": false,
|
||||
"enableInspector": true,
|
||||
"borderless": false,
|
||||
"maximize": false,
|
||||
"hidden": false,
|
||||
@ -37,7 +37,7 @@
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"binaryName": "waw",
|
||||
"binaryName": "qwe",
|
||||
"resourcesPath": "/build/",
|
||||
"clientLibrary": "/build/neutralino.js",
|
||||
"binaryVersion": "4.14.1",
|
||||
|
35
notes.md
Normal file
35
notes.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Deps
|
||||
|
||||
Needed to
|
||||
```sudo apt install libsoup-2.4-dev libwebkit2gtk-4.0-dev libjavascriptcoregtk-4.0-dev```
|
||||
|
||||
---
|
||||
|
||||
# Functionality
|
||||
|
||||
## View settings
|
||||
|
||||
View settings contain
|
||||
- monospace or proportional
|
||||
- line gutters enabled
|
||||
- linting enabled
|
||||
- user-selected language stylization
|
||||
- etc.
|
||||
|
||||
These are associated with the individual file, or the workspace if one is set.
|
||||
|
||||
They are only saved once a file is saved to the filesystem.
|
||||
|
||||
## Workspace
|
||||
|
||||
Your workspace is indicated on the right-side of the status bar, and represents the directory that view settings are being saved to
|
||||
|
||||
It can be edited by clicking on the workspace entry, or pressing Ctrl-W. The prompt will expand to fit the whole status bar, and will default to the workspace of the current file
|
||||
|
||||
Directory view will show the workspace if one is defined
|
||||
|
||||
## Directory view
|
||||
|
||||
Directory view is opened with ctrl-l
|
||||
|
||||
It will show a tree of the current directory, with subdirectories folded. The cursor will by default be at the position of the last file opened.
|
@ -1,4 +0,0 @@
|
||||
# Deps
|
||||
|
||||
Needed to
|
||||
```sudo apt install libsoup-2.4-dev libwebkit2gtk-4.0-dev libjavascriptcoregtk-4.0-dev```
|
@ -4,15 +4,18 @@
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@neutralinojs/lib": "neutralinojs/neutralino.js",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/lang-markdown": "^6.2.3",
|
||||
"@neutralinojs/neu": "^10.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"codemirror": "^6.0.1",
|
||||
"rollup": "^4.9.1"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "node build neutralino > index.html && neu run",
|
||||
"build": "node build neutralino prod > index.html && neu build"
|
||||
"build-deps": "cd lib && rollup -c",
|
||||
"dev": "neu run",
|
||||
"build": "neu build",
|
||||
"update": "neu update"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
function Platform() {
|
||||
|
||||
}
|
34
src/config.js
Normal file
34
src/config.js
Normal file
@ -0,0 +1,34 @@
|
||||
export default {
|
||||
|
||||
statusBarAtTop: false,
|
||||
landingDocument: {
|
||||
name: 'welcome.md',
|
||||
render() {
|
||||
return 'Welcome to qwe!'
|
||||
}
|
||||
},
|
||||
|
||||
languages: [
|
||||
{
|
||||
exts: [ 'js', 'mjs', 'cjs', 'ejs' ],
|
||||
name: 'js',
|
||||
import: './lib/javascript.js',
|
||||
createExtension({ javascript }) {
|
||||
return javascript({
|
||||
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
exts: [ 'md' ],
|
||||
name: 'md',
|
||||
import: './lib/markdown.js',
|
||||
createExtension({ markdown }) {
|
||||
return markdown({
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
}
|
20
src/index.html
Normal file
20
src/index.html
Normal file
@ -0,0 +1,20 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
<script async type="module" src="./qwe.js"></script>
|
||||
|
||||
<style id="stylesheet">
|
||||
body { display: flex; flex-direction: column; margin: 0; height: 100vh; max-height: 100vh; font-size: 14px; }
|
||||
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; }
|
||||
.cm-gutters { pointer-events: none; }
|
||||
.cm-editor::-webkit-scrollbar, .cm-scroller::-webkit-scrollbar { background: #f5f5f5; width: 14px; height: 14px; }
|
||||
.cm-editor::-webkit-scrollbar-thumb, .cm-scroller::-webkit-scrollbar-thumb { background: #ddd }
|
||||
.cm-editor::-webkit-scrollbar-corner, .cm-scroller::-webkit-scrollbar-corner { background: #f5f5f5; }
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
</html>
|
218
src/qwe.js
Normal file
218
src/qwe.js
Normal file
@ -0,0 +1,218 @@
|
||||
import Platform from './lib/platform.js'
|
||||
import config from './config.js'
|
||||
import cm from './lib/codemirror.js'
|
||||
window.cm = cm
|
||||
|
||||
function Editor() {
|
||||
this.open = async () => {
|
||||
if(config.statusBarAtTop)
|
||||
document.body.appendChild(statusbar.element)
|
||||
|
||||
console.log(cm, cm.EditorView)
|
||||
|
||||
this.view = new cm.EditorView({
|
||||
state: await openToDocument()
|
||||
.catch(console.error),
|
||||
parent: document.body
|
||||
})
|
||||
|
||||
if(!config.statusBarAtTop)
|
||||
document.body.appendChild(statusbar.element)
|
||||
|
||||
this.doc.setView(this.view)
|
||||
this.view.focus()
|
||||
}
|
||||
|
||||
// this.updateFilename = () => {
|
||||
// this.doc.path = Platform.dirname(this.doc.path) + '/' + statusbar.filename.value
|
||||
// }
|
||||
|
||||
const openToDocument = async () => {
|
||||
let doc = new Doc(config.landingDocument.render(), config.landingDocument.name)
|
||||
|
||||
this.doc = doc
|
||||
statusbar.filename.value = config.landingDocument.name
|
||||
return await doc.createState()
|
||||
.catch(err => console.error("Could not open landing document", err))
|
||||
}
|
||||
|
||||
this.doc = null
|
||||
}
|
||||
|
||||
function Statusbar() {
|
||||
this.selectFilename = () => {
|
||||
lastFilename = this.filename.value
|
||||
|
||||
this.filename.value = editor.doc.getPath()
|
||||
this.filename.select()
|
||||
this.filename.selectionStart = editor.doc.getPath().lastIndexOf('/') + 1
|
||||
}
|
||||
|
||||
this.element = document.createElement('nav')
|
||||
this.filename = document.createElement('input')
|
||||
let lastFilename = ''
|
||||
|
||||
this.element.appendChild(this.filename)
|
||||
|
||||
this.filename.addEventListener('click', this.selectFilename)
|
||||
|
||||
this.filename.addEventListener('keydown', (event) => {
|
||||
switch(event.key) {
|
||||
case 'Enter':
|
||||
case 'Escape':
|
||||
event.preventDefault()
|
||||
editor.view.focus()
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
this.filename.addEventListener('focusout', event => {
|
||||
if(this.filename.value == '') {
|
||||
this.filename.value = lastFilename
|
||||
} else {
|
||||
let path = this.filename.value
|
||||
|
||||
editor.doc.setPath(path)
|
||||
this.filename.value = Platform.filename(path)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function Doc(content, initialPath = '') {
|
||||
// if(path) {
|
||||
// let workspaces = Platform.storage.get('workspaces')
|
||||
// }
|
||||
|
||||
this.createState = async () => {
|
||||
let extensions = [
|
||||
Doc.zen.of([
|
||||
cm.lineNumbers(),
|
||||
cm.highlightActiveLine(),
|
||||
cm.highlightActiveLineGutter(),
|
||||
cm.highlightSpecialChars(),
|
||||
]),
|
||||
cm.foldGutter(),
|
||||
cm.drawSelection(),
|
||||
cm.dropCursor(),
|
||||
cm.EditorState.allowMultipleSelections.of(true),
|
||||
cm.indentOnInput(),
|
||||
cm.syntaxHighlighting(cm.defaultHighlightStyle, { fallback: true }),
|
||||
cm.bracketMatching(),
|
||||
cm.closeBrackets(),
|
||||
cm.autocompletion(),
|
||||
cm.rectangularSelection(),
|
||||
cm.crosshairCursor(),
|
||||
cm.highlightSelectionMatches(),
|
||||
Keymaps.default
|
||||
]
|
||||
|
||||
if(this.language)
|
||||
extensions.push(
|
||||
await langManager.resolveExtension(this.language)
|
||||
.then(extension => Doc.language.of(extension))
|
||||
)
|
||||
|
||||
return cm.EditorState.create({
|
||||
extensions,
|
||||
doc: content
|
||||
})
|
||||
}
|
||||
|
||||
this.setPath = newPath => {
|
||||
path = newPath
|
||||
this.setLanguage()
|
||||
}
|
||||
|
||||
this.getPath = () =>
|
||||
path
|
||||
|
||||
this.setView = (newView) => {
|
||||
view = newView
|
||||
this.setLanguage()
|
||||
}
|
||||
|
||||
this.setLanguage = (newLanguage = getLanguage()) => {
|
||||
let oldLanguage = this.language
|
||||
this.language = newLanguage
|
||||
|
||||
if(!view)
|
||||
return
|
||||
|
||||
if(this.language == null) {
|
||||
setViewLanguageExtension([])
|
||||
} else if(oldLanguage == null || oldLanguage.name !== this.language.name) {
|
||||
langManager.resolveExtension(this.language)
|
||||
.then(setViewLanguageExtension)
|
||||
.catch(err => console.error('Failed to set language extension', err))
|
||||
}
|
||||
}
|
||||
|
||||
const getLanguage = () => {
|
||||
let ext = Platform.ext(path)
|
||||
|
||||
if(ext) {
|
||||
return langManager.exts[ext]
|
||||
}
|
||||
}
|
||||
|
||||
const setViewLanguageExtension = (extension) => {
|
||||
view.dispatch({
|
||||
effects: Doc.language.reconfigure(extension)
|
||||
})
|
||||
}
|
||||
|
||||
let path = initialPath
|
||||
let view = null
|
||||
this.language = getLanguage()
|
||||
}
|
||||
|
||||
Doc.open = async path => {
|
||||
return new Doc()
|
||||
}
|
||||
|
||||
Doc.tabSize = new cm.Compartment
|
||||
Doc.zen = new cm.Compartment
|
||||
Doc.language = new cm.Compartment
|
||||
|
||||
const Keymaps = {
|
||||
default: cm.keymap.of([
|
||||
...cm.closeBracketsKeymap,
|
||||
...cm.defaultKeymap,
|
||||
...cm.searchKeymap,
|
||||
...cm.foldKeymap,
|
||||
...cm.completionKeymap,
|
||||
...cm.lintKeymap,
|
||||
cm.indentWithTab,
|
||||
{
|
||||
key: 'Ctrl-r',
|
||||
run({ state }, event) {
|
||||
event.preventDefault()
|
||||
let mainSelection = state.selection.main
|
||||
|
||||
if(!mainSelection.empty)
|
||||
statusbar.filename.value = state.sliceDoc(mainSelection.from, mainSelection.to)
|
||||
statusbar.selectFilename()
|
||||
}
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
function LanguageManager() {
|
||||
this.resolveExtension = (language) =>
|
||||
import(language.import)
|
||||
.then(mod => language.createExtension(mod.default))
|
||||
|
||||
this.exts = {}
|
||||
|
||||
for(let language of config.languages) {
|
||||
for(let ext of language.exts)
|
||||
this.exts[ext] = language
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
window.statusbar = new Statusbar()
|
||||
window.editor = new Editor()
|
||||
window.langManager = new LanguageManager()
|
||||
editor.open()
|
||||
})
|
97
yarn.lock
97
yarn.lock
@ -2,7 +2,7 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@codemirror/autocomplete@^6.0.0":
|
||||
"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.7.1":
|
||||
version "6.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.11.1.tgz#c733900eee58ac2de817317b9fd1e91b857c4329"
|
||||
integrity sha512-L5UInv8Ffd6BPw0P3EF7JLYAMeEbclY7+6Q11REt8vhih8RuLreKtPy/xk8wPxs4EQgYqzI7cdgpiYwWlbS/ow==
|
||||
@ -22,7 +22,59 @@
|
||||
"@codemirror/view" "^6.0.0"
|
||||
"@lezer/common" "^1.1.0"
|
||||
|
||||
"@codemirror/language@^6.0.0":
|
||||
"@codemirror/lang-css@^6.0.0":
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/lang-css/-/lang-css-6.2.1.tgz#5dc0a43b8e3c31f6af7aabd55ff07fe9aef2a227"
|
||||
integrity sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==
|
||||
dependencies:
|
||||
"@codemirror/autocomplete" "^6.0.0"
|
||||
"@codemirror/language" "^6.0.0"
|
||||
"@codemirror/state" "^6.0.0"
|
||||
"@lezer/common" "^1.0.2"
|
||||
"@lezer/css" "^1.0.0"
|
||||
|
||||
"@codemirror/lang-html@^6.0.0":
|
||||
version "6.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/lang-html/-/lang-html-6.4.7.tgz#e375e3c9ae898b5aca6e17b5055a3a76c7a8f5ff"
|
||||
integrity sha512-y9hWSSO41XlcL4uYwWyk0lEgTHcelWWfRuqmvcAmxfCs0HNWZdriWo/EU43S63SxEZpc1Hd50Itw7ktfQvfkUg==
|
||||
dependencies:
|
||||
"@codemirror/autocomplete" "^6.0.0"
|
||||
"@codemirror/lang-css" "^6.0.0"
|
||||
"@codemirror/lang-javascript" "^6.0.0"
|
||||
"@codemirror/language" "^6.4.0"
|
||||
"@codemirror/state" "^6.0.0"
|
||||
"@codemirror/view" "^6.17.0"
|
||||
"@lezer/common" "^1.0.0"
|
||||
"@lezer/css" "^1.1.0"
|
||||
"@lezer/html" "^1.3.0"
|
||||
|
||||
"@codemirror/lang-javascript@^6.0.0", "@codemirror/lang-javascript@^6.2.1":
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/lang-javascript/-/lang-javascript-6.2.1.tgz#8068d44365d13cdb044936fb4e3483301c12ef95"
|
||||
integrity sha512-jlFOXTejVyiQCW3EQwvKH0m99bUYIw40oPmFjSX2VS78yzfe0HELZ+NEo9Yfo1MkGRpGlj3Gnu4rdxV1EnAs5A==
|
||||
dependencies:
|
||||
"@codemirror/autocomplete" "^6.0.0"
|
||||
"@codemirror/language" "^6.6.0"
|
||||
"@codemirror/lint" "^6.0.0"
|
||||
"@codemirror/state" "^6.0.0"
|
||||
"@codemirror/view" "^6.17.0"
|
||||
"@lezer/common" "^1.0.0"
|
||||
"@lezer/javascript" "^1.0.0"
|
||||
|
||||
"@codemirror/lang-markdown@^6.2.3":
|
||||
version "6.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/lang-markdown/-/lang-markdown-6.2.3.tgz#ce572230a872e8eef88bce40213f26e66a7e4497"
|
||||
integrity sha512-wCewRLWpdefWi7uVkHIDiE8+45Fe4buvMDZkihqEom5uRUQrl76Zb13emjeK3W+8pcRgRfAmwelURBbxNEKCIg==
|
||||
dependencies:
|
||||
"@codemirror/autocomplete" "^6.7.1"
|
||||
"@codemirror/lang-html" "^6.0.0"
|
||||
"@codemirror/language" "^6.3.0"
|
||||
"@codemirror/state" "^6.0.0"
|
||||
"@codemirror/view" "^6.0.0"
|
||||
"@lezer/common" "^1.0.0"
|
||||
"@lezer/markdown" "^1.0.0"
|
||||
|
||||
"@codemirror/language@^6.0.0", "@codemirror/language@^6.3.0", "@codemirror/language@^6.4.0", "@codemirror/language@^6.6.0":
|
||||
version "6.9.3"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.9.3.tgz#1c127dc43e025d4c9b1ba1b79f4b1ba081d5aeaa"
|
||||
integrity sha512-qq48pYzoi6ldYWV/52+Z9Ou6QouVI+8YwvxFbUypI33NbjG2UeRHKENRyhwljTTiOqjQ33FjyZj6EREQ9apAOQ==
|
||||
@ -66,28 +118,57 @@
|
||||
style-mod "^4.1.0"
|
||||
w3c-keyname "^2.2.4"
|
||||
|
||||
"@lezer/common@^1.0.0", "@lezer/common@^1.1.0":
|
||||
"@lezer/common@^1.0.0", "@lezer/common@^1.0.2", "@lezer/common@^1.1.0":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.1.2.tgz#2fc5cd6788094ffc816b539ab2bc55bafacd2abc"
|
||||
integrity sha512-V+GqBsga5+cQJMfM0GdnHmg4DgWvLzgMWjbldBg0+jC3k9Gu6nJNZDLJxXEBT1Xj8KhRN4jmbC5CY7SIL++sVw==
|
||||
|
||||
"@lezer/highlight@^1.0.0":
|
||||
"@lezer/css@^1.0.0", "@lezer/css@^1.1.0":
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@lezer/css/-/css-1.1.4.tgz#428923881b102ee55e3bdc1e169639c942e71c24"
|
||||
integrity sha512-CuUwjidrU7FOBokqASRJc72SmJ9g1PsHXDOWMoKg4md6+2u/Zxzwx5YsYrAFxRDsLrjLlsIyEF1rZHK3gFEJbw==
|
||||
dependencies:
|
||||
"@lezer/highlight" "^1.0.0"
|
||||
"@lezer/lr" "^1.0.0"
|
||||
|
||||
"@lezer/highlight@^1.0.0", "@lezer/highlight@^1.1.3":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.2.0.tgz#e5898c3644208b4b589084089dceeea2966f7780"
|
||||
integrity sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==
|
||||
dependencies:
|
||||
"@lezer/common" "^1.0.0"
|
||||
|
||||
"@lezer/lr@^1.0.0":
|
||||
"@lezer/html@^1.3.0":
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/@lezer/html/-/html-1.3.7.tgz#d15fcdf441c8516bfe7411e283c1467a117fe9b1"
|
||||
integrity sha512-Wo+rZ5UjLP0VqUTyXjzgmTYRW5bvTJUFn4Uw0K3HCQjX2/+f+zRo9GLN5BCAojwHQISPvaQk8BWSv2SSKx/UcQ==
|
||||
dependencies:
|
||||
"@lezer/common" "^1.0.0"
|
||||
"@lezer/highlight" "^1.0.0"
|
||||
"@lezer/lr" "^1.0.0"
|
||||
|
||||
"@lezer/javascript@^1.0.0":
|
||||
version "1.4.11"
|
||||
resolved "https://registry.yarnpkg.com/@lezer/javascript/-/javascript-1.4.11.tgz#4ca7681e29fda6f960e15d958ee4f4ceaf577223"
|
||||
integrity sha512-B5Y9EJF4BWiMgj4ufxUo2hrORnmMBDrMtR+L7dwIO5pocuSAahG6QBwXR6PbKJOjRywJczU2r2LJPg79ER91TQ==
|
||||
dependencies:
|
||||
"@lezer/highlight" "^1.1.3"
|
||||
"@lezer/lr" "^1.3.0"
|
||||
|
||||
"@lezer/lr@^1.0.0", "@lezer/lr@^1.3.0":
|
||||
version "1.3.14"
|
||||
resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.3.14.tgz#59d4a3b25698bdac0ef182fa6eadab445fc4f29a"
|
||||
integrity sha512-z5mY4LStlA3yL7aHT/rqgG614cfcvklS+8oFRFBYrs4YaWLJyKKM4+nN6KopToX0o9Hj6zmH6M5kinOYuy06ug==
|
||||
dependencies:
|
||||
"@lezer/common" "^1.0.0"
|
||||
|
||||
"@neutralinojs/lib@neutralinojs/neutralino.js":
|
||||
version "3.13.0"
|
||||
resolved "https://codeload.github.com/neutralinojs/neutralino.js/tar.gz/080900b7af3d79c6e387d213b437bdb62870f109"
|
||||
"@lezer/markdown@^1.0.0":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@lezer/markdown/-/markdown-1.1.2.tgz#1570740bc18d75da1b3d9b12f44ebcd481630ae8"
|
||||
integrity sha512-PCla4SVSoxw95QvznrvQxC8c5Gw/sv7sMWLwJMsHsvUl6jsWeU4UneK7gtOq/fY1BaDTQIbzzSlxVuGLTSRScA==
|
||||
dependencies:
|
||||
"@lezer/common" "^1.0.0"
|
||||
"@lezer/highlight" "^1.0.0"
|
||||
|
||||
"@neutralinojs/neu@^10.1.0":
|
||||
version "10.1.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user