Basic editing capability when Javascript is disabled
This commit is contained in:
parent
57cfe939f7
commit
4dc77897e9
|
@ -0,0 +1,60 @@
|
||||||
|
const {RequestHandler} = require('../server/RequestHandler');
|
||||||
|
const {VirtualSequenceDiagram} = require('../../lib/sequence-diagram');
|
||||||
|
|
||||||
|
const UNSAFE_HTML = /[^a-zA-Z0-9 :;.,]/g;
|
||||||
|
const escChar = (c) => `&#x${c.charCodeAt(0).toString(16).padStart(4, '0')};`;
|
||||||
|
const escapeHTML = (v) => v.replaceAll(UNSAFE_HTML, escChar);
|
||||||
|
|
||||||
|
class PreviewRequestHandler extends RequestHandler {
|
||||||
|
constructor(baseUrlPattern) {
|
||||||
|
super('GET', new RegExp(`^${baseUrlPattern}/?(.*)$`, 'i'));
|
||||||
|
this.info = `Rendering preview at ${baseUrlPattern}/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle(req, res, {match, pickEncoding, writeEncoded}) {
|
||||||
|
this.applyCommonHeaders(req, res);
|
||||||
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||||
|
|
||||||
|
const encoding = pickEncoding();
|
||||||
|
const params = new URLSearchParams(match[1]);
|
||||||
|
const code = params.get('c');
|
||||||
|
const encoded = code
|
||||||
|
.replaceAll(/[\r\n]+/g, '\n')
|
||||||
|
.split('\n')
|
||||||
|
.filter((ln) => ln !== '')
|
||||||
|
.map(encodeURIComponent)
|
||||||
|
.join('/');
|
||||||
|
|
||||||
|
let content = '';
|
||||||
|
try {
|
||||||
|
new VirtualSequenceDiagram().process(code);
|
||||||
|
content = [
|
||||||
|
'<!DOCTYPE html>',
|
||||||
|
'<html lang="en">',
|
||||||
|
'<head>',
|
||||||
|
'<link rel="stylesheet" href="web/styles/preview.css">',
|
||||||
|
'</head>',
|
||||||
|
'<body>',
|
||||||
|
`<img src="/render/uri/${encoded}.svg" download="diagram.svg">`,
|
||||||
|
'</body>',
|
||||||
|
'</html>',
|
||||||
|
].join('');
|
||||||
|
} catch(e) {
|
||||||
|
content = [
|
||||||
|
'<!DOCTYPE html>',
|
||||||
|
'<html lang="en">',
|
||||||
|
'<head>',
|
||||||
|
'<link rel="stylesheet" href="web/styles/preview.css">',
|
||||||
|
'</head>',
|
||||||
|
'<body>',
|
||||||
|
`<div class="error">${escapeHTML(String(e))}</div>`,
|
||||||
|
'</body>',
|
||||||
|
'</html>',
|
||||||
|
].join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
writeEncoded(encoding, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {PreviewRequestHandler};
|
|
@ -3,6 +3,7 @@
|
||||||
const {Server} = require('./server/Server');
|
const {Server} = require('./server/Server');
|
||||||
const {StaticRequestHandler} = require('./server/StaticRequestHandler');
|
const {StaticRequestHandler} = require('./server/StaticRequestHandler');
|
||||||
const {RenderRequestHandler} = require('./handlers/RenderRequestHandler');
|
const {RenderRequestHandler} = require('./handlers/RenderRequestHandler');
|
||||||
|
const {PreviewRequestHandler} = require('./handlers/PreviewRequestHandler');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const DEV = process.argv.includes('dev');
|
const DEV = process.argv.includes('dev');
|
||||||
|
@ -45,8 +46,9 @@ const statics = new StaticRequestHandler('')
|
||||||
'connect-src \'self\'',
|
'connect-src \'self\'',
|
||||||
'font-src \'self\' data:',
|
'font-src \'self\' data:',
|
||||||
'img-src \'self\' blob:',
|
'img-src \'self\' blob:',
|
||||||
'form-action \'none\'',
|
'form-action \'self\'',
|
||||||
'frame-ancestors \'none\'',
|
'frame-ancestors \'self\'',
|
||||||
|
'frame-src \'self\'',
|
||||||
].join('; '))
|
].join('; '))
|
||||||
.addHeader('X-Content-Type-Options', 'nosniff')
|
.addHeader('X-Content-Type-Options', 'nosniff')
|
||||||
.addHeader('X-Frame-Options', 'DENY')
|
.addHeader('X-Frame-Options', 'DENY')
|
||||||
|
@ -96,8 +98,23 @@ const render = new RenderRequestHandler('/render')
|
||||||
].join('; '))
|
].join('; '))
|
||||||
.addHeader('X-Content-Type-Options', 'nosniff');
|
.addHeader('X-Content-Type-Options', 'nosniff');
|
||||||
|
|
||||||
|
const preview = new PreviewRequestHandler('/preview')
|
||||||
|
.setCacheMaxAge(DEV ? 0 : RENDER_MAX_AGE)
|
||||||
|
.addHeader('Content-Security-Policy', [
|
||||||
|
'base-uri \'self\'',
|
||||||
|
'default-src \'none\'',
|
||||||
|
'style-src \'self\'',
|
||||||
|
'connect-src \'none\'',
|
||||||
|
'img-src \'self\'',
|
||||||
|
'form-action \'none\'',
|
||||||
|
'frame-ancestors \'self\'',
|
||||||
|
'frame-src \'self\'',
|
||||||
|
].join('; '))
|
||||||
|
.addHeader('X-Content-Type-Options', 'nosniff');
|
||||||
|
|
||||||
new Server()
|
new Server()
|
||||||
.addHandler(render)
|
.addHandler(render)
|
||||||
|
.addHandler(preview)
|
||||||
.addHandler(statics)
|
.addHandler(statics)
|
||||||
.listen(PORT, HOSTNAME)
|
.listen(PORT, HOSTNAME)
|
||||||
.then((server) => server.printListeningInfo(process.stdout));
|
.then((server) => server.printListeningInfo(process.stdout));
|
||||||
|
|
|
@ -121,6 +121,7 @@ cd - > /dev/null;
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
sudo tee /var/www/https/index.htm <<EOF > /dev/null;
|
sudo tee /var/www/https/index.htm <<EOF > /dev/null;
|
||||||
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>$DOMAIN</title>
|
<title>$DOMAIN</title>
|
||||||
|
|
37
index.html
37
index.html
|
@ -1,3 +1,4 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
@ -9,7 +10,8 @@
|
||||||
connect-src 'self';
|
connect-src 'self';
|
||||||
font-src 'self' data:;
|
font-src 'self' data:;
|
||||||
img-src 'self' blob:;
|
img-src 'self' blob:;
|
||||||
form-action 'none';
|
frame-src 'self';
|
||||||
|
form-action 'self';
|
||||||
">
|
">
|
||||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, minimal-ui">
|
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, minimal-ui">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
@ -78,7 +80,6 @@
|
||||||
<div id="loader">
|
<div id="loader">
|
||||||
<h1>Sequence Diagram Online Editor</h1>
|
<h1>Sequence Diagram Online Editor</h1>
|
||||||
<p class="loadmsg">Loading…</p>
|
<p class="loadmsg">Loading…</p>
|
||||||
<noscript><p class="noscript">This tool requires Javascript!</p></noscript>
|
|
||||||
<nav>
|
<nav>
|
||||||
<a href="#" data-touch="Files">My Diagrams</a>
|
<a href="#" data-touch="Files">My Diagrams</a>
|
||||||
<a href="library.htm" target="_blank" data-touch="API">Library</a>
|
<a href="library.htm" target="_blank" data-touch="API">Library</a>
|
||||||
|
@ -86,5 +87,37 @@
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<link rel="stylesheet" href="web/styles/noscript.css">
|
||||||
|
<main>
|
||||||
|
<section class="banner">
|
||||||
|
Enable JavaScript for live rendering, templates, PNG export, and local file saving.
|
||||||
|
<nav><a href="library.htm" target="_blank">Library</a><a href="https://github.com/davidje13/SequenceDiagram" target="_blank">GitHub</a></nav>
|
||||||
|
</section>
|
||||||
|
<form target="preview" action="preview" method="GET">
|
||||||
|
<textarea name="c">
|
||||||
|
title Labyrinth
|
||||||
|
|
||||||
|
Bowie -> Goblin: You remind me of the babe
|
||||||
|
Goblin -> Bowie: What babe?
|
||||||
|
Bowie -> Goblin: The babe with the power
|
||||||
|
Goblin -> Bowie: What power?
|
||||||
|
note right of Bowie, Goblin: Most people get muddled here!
|
||||||
|
Bowie -> Goblin: "The power of voodoo"
|
||||||
|
Goblin -> Bowie: "Who-do?"
|
||||||
|
Bowie -> Goblin: You do!
|
||||||
|
Goblin -> Bowie: Do what?
|
||||||
|
Bowie -> Goblin: Remind me of the babe!
|
||||||
|
|
||||||
|
Bowie -> Audience: Sings
|
||||||
|
|
||||||
|
terminators box
|
||||||
|
</textarea>
|
||||||
|
<button type="submit">Update</button>
|
||||||
|
</form>
|
||||||
|
<iframe name="preview" src="web/resources/preview.htm"></iframe>
|
||||||
|
</main>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../styles/preview.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="initial-action">← Press "Update" to see the diagram.</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -29,12 +29,6 @@ html, body {
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
#loader p.noscript {
|
|
||||||
position: relative;
|
|
||||||
top: -2.3em;
|
|
||||||
background: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loader nav {
|
#loader nav {
|
||||||
margin: 80px 0 0;
|
margin: 80px 0 0;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
|
@ -598,7 +592,7 @@ svg a:active, svg a:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body, #loader p.noscript {
|
body {
|
||||||
background: #222222;
|
background: #222222;
|
||||||
color: #EEEEEE;
|
color: #EEEEEE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
#loader {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner {
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
padding: 10px 150px;
|
||||||
|
background: #FFFFCC;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 10px;
|
||||||
|
line-height: 1rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 30vw;
|
||||||
|
height: 100%;
|
||||||
|
resize: horizontal;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 80vw;
|
||||||
|
padding: 10px 50px 100px 10px;
|
||||||
|
white-space: pre;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
border-right: 1px solid #808080;
|
||||||
|
background: #EEEEEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link, a:visited {
|
||||||
|
color: #556688;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover, a:active {
|
||||||
|
color: #5577AA;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.banner {
|
||||||
|
background: #554400;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
color: #FFEEDD;
|
||||||
|
background: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
background: #111111; /* avoid flicker when reloading */
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link, a:visited {
|
||||||
|
color: #99AAEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover, a:active {
|
||||||
|
color: #6699FF;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding: 10px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background: white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #800000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body {
|
||||||
|
background: #111111;
|
||||||
|
color: #DDDDDD;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
filter: invert(1) hue-rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #FF8080;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue