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 {StaticRequestHandler} = require('./server/StaticRequestHandler');
|
||||
const {RenderRequestHandler} = require('./handlers/RenderRequestHandler');
|
||||
const {PreviewRequestHandler} = require('./handlers/PreviewRequestHandler');
|
||||
const path = require('path');
|
||||
|
||||
const DEV = process.argv.includes('dev');
|
||||
|
@ -45,8 +46,9 @@ const statics = new StaticRequestHandler('')
|
|||
'connect-src \'self\'',
|
||||
'font-src \'self\' data:',
|
||||
'img-src \'self\' blob:',
|
||||
'form-action \'none\'',
|
||||
'frame-ancestors \'none\'',
|
||||
'form-action \'self\'',
|
||||
'frame-ancestors \'self\'',
|
||||
'frame-src \'self\'',
|
||||
].join('; '))
|
||||
.addHeader('X-Content-Type-Options', 'nosniff')
|
||||
.addHeader('X-Frame-Options', 'DENY')
|
||||
|
@ -96,8 +98,23 @@ const render = new RenderRequestHandler('/render')
|
|||
].join('; '))
|
||||
.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()
|
||||
.addHandler(render)
|
||||
.addHandler(preview)
|
||||
.addHandler(statics)
|
||||
.listen(PORT, HOSTNAME)
|
||||
.then((server) => server.printListeningInfo(process.stdout));
|
||||
|
|
|
@ -121,6 +121,7 @@ cd - > /dev/null;
|
|||
EOF
|
||||
|
||||
sudo tee /var/www/https/index.htm <<EOF > /dev/null;
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>$DOMAIN</title>
|
||||
|
|
37
index.html
37
index.html
|
@ -1,3 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
@ -9,7 +10,8 @@
|
|||
connect-src 'self';
|
||||
font-src 'self' data:;
|
||||
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="apple-mobile-web-app-capable" content="yes">
|
||||
|
@ -78,7 +80,6 @@
|
|||
<div id="loader">
|
||||
<h1>Sequence Diagram Online Editor</h1>
|
||||
<p class="loadmsg">Loading…</p>
|
||||
<noscript><p class="noscript">This tool requires Javascript!</p></noscript>
|
||||
<nav>
|
||||
<a href="#" data-touch="Files">My Diagrams</a>
|
||||
<a href="library.htm" target="_blank" data-touch="API">Library</a>
|
||||
|
@ -86,5 +87,37 @@
|
|||
</nav>
|
||||
</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>
|
||||
</html>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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;
|
||||
}
|
||||
|
||||
#loader p.noscript {
|
||||
position: relative;
|
||||
top: -2.3em;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
#loader nav {
|
||||
margin: 80px 0 0;
|
||||
font-size: 0.8em;
|
||||
|
@ -598,7 +592,7 @@ svg a:active, svg a:hover {
|
|||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body, #loader p.noscript {
|
||||
body {
|
||||
background: #222222;
|
||||
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