When I open a new file with a browser-based editor I’m developing for myself as a learning exercise, I’ve hit the snag that the previous files remain at the bottom (with numbering restarting at 1).
What has me stumped is I’ve got a version which works fine using SWI Prolog communicating to the browser with JSON. Partly to learn Go, I’ve redone it using FormData, and have hit this weird snag of “ghosts of past files” remaining at the bottom of the CodeMirror object.
Firstly my HTML looks like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Editor</title>
<link rel="stylesheet" href="/css/codemirror.css">
<link rel="stylesheet" href="/css/mystyle.css">
<link rel="stylesheet" href="/theme/cobalt.css">
<script src="/lib/codemirror.js"></script>
<script src="/mode/css/css.js"></script>
<script src="/mode/go/go.js"></script>
<script src="/mode/htmlmixed/htmlmixed.js"></script>
<script src="/mode/http/http.js"></script>
<script src="/mode/javascript/javascript.js"></script>
<script src="/mode/markdown/markdown.js"></script>
<script src="/mode/shell/shell.js"></script>
<script src="/mode/sql/sql.js"></script>
<script src="/mode/toml/toml.js"></script>
<script src="/mode/xml/xml.js"></script>
<script src="/mode/yaml/yaml.js"></script>
</head>
<body>
<form action="" method="post">
<input type="text" id="filename" name="fn" size="40">
<button type="button" id="load-button">Open...</button>
<span id="filetime"></span>
<button type="button" id="save-button">Save</button>
<br>
<textarea id="myTextArea"></textarea><br>
</form>
<p id="error"></p>
<script src="/lib/editor.js"></script>
</body>
</html>
Then my Javascript looks like this:
let myCodeMirror = CodeMirror.fromTextArea(document.querySelector("#myTextArea"),
{ lineNumbers: true
, theme: "cobalt"
});
function getmode(filename) {
const modemap = { 'css': 'css'
, 'el': 'erlang'
, 'go': 'go'
, 'html': 'htmlmixed'
, 'js': 'javascript'
, 'json': 'javascript'
, 'md': 'markdown'
, 'sh': 'shell'
, 'sql': 'sql'
, 'toml': 'toml'
, 'yaml': 'yaml'
};
const dot_split = filename.split('.');
const suffix = dot_split[dot_split.length - 1];
return modemap[suffix];
}
function load_file(event) {
event.preventDefault();
const fd = new FormData();
fd.append('cmd', 'load');
fd.append('fn', document.querySelector('#filename').value);
fetch('/form-handler', {
method: 'POST',
body: fd
})
.then(response => response.formData())
.then(function(data) {
myCodeMirror = CodeMirror.fromTextArea(document.querySelector("#myTextArea"),
{ lineNumbers: true
, mode: getmode(fd.get('fn'))
, theme: "cobalt"
});
if (data.has('error')) {
document.querySelector("#error").textContent = data.get('error');
} else {
myCodeMirror.setValue(data.get('content'));
document.querySelector("#filetime").textContent = data.get('modtime');
}
});
}
function save_file(event) {
event.preventDefault();
const fd = new FormData();
fd.append('cmd', 'save');
fd.append('fn', document.querySelector('#filename').value);
fd.append('content', myCodeMirror.getValue())
fetch('/form-handler', {
method: 'POST',
body: fd
})
.then(response => response.formData())
.then(function(data) {
if (data.has('error')) {
document.querySelector("#error").textContent = data.get('error');
} else {
document.querySelector("#filetime").textContent = data.get('modtime');
}
});
}
function keyListener(event) {
if (event.ctrlKey) {
switch(event.key) {
case 's':
event.preventDefault();
save_file(event);
return true;
default:
return false;
}
} else {
return false;
}
}
document.querySelector('#load-button').addEventListener('click', load_file);
document.querySelector('#save-button').addEventListener('click', save_file);
document.addEventListener('keydown', keyListener);
And finally my go server code looks like this:
package main
import (
"log"
"mime/multipart"
"net/http"
"os"
"time"
)
func handler(resp http.ResponseWriter, req *http.Request) {
username, password, ok := req.BasicAuth()
if ok && username == "MyUserName" && password == "MyPassword" {
req.ParseMultipartForm(2097152)
mw := multipart.NewWriter(resp)
resp.Header().Set("Content-Type", mw.FormDataContentType())
resp.WriteHeader(http.StatusOK)
switch req.FormValue("cmd") {
case "load":
content, err := os.ReadFile(req.FormValue("fn"))
if err == nil {
mw.WriteField("content", string(content))
fp, _ := os.Open(req.FormValue("fn"))
info, _ := fp.Stat()
mw.WriteField("modtime", info.ModTime().Format(time.RFC3339))
fp.Close()
} else {
mw.WriteField("error", "Loading problem")
}
case "save":
err := os.WriteFile(req.FormValue("fn"), []byte(req.FormValue("content")), 0644)
if err == nil {
fp, _ := os.Open(req.FormValue("fn"))
info, _ := fp.Stat()
mw.WriteField("modtime", info.ModTime().Format(time.RFC3339))
fp.Close()
} else {
mw.WriteField("error", "Saving problem")
}
default:
mw.WriteField("error", "Unknown command")
}
mw.Close()
} else {
resp.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
http.Error(resp, "Unauthorized", http.StatusUnauthorized)
}
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
The weird thing, I don’t have the same problem doing nearly the identical thing using Prolog with JSON. I’m struggling to understand FormData along with Go, so not sure if the problem is CodeMirror or where?