diff --git a/bin/handlers/PreviewRequestHandler.js b/bin/handlers/PreviewRequestHandler.js
new file mode 100644
index 0000000..3beafd2
--- /dev/null
+++ b/bin/handlers/PreviewRequestHandler.js
@@ -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) => `${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 = [
+ '',
+ '',
+ '
',
+ '',
+ '',
+ '',
+ `
`,
+ '',
+ '',
+ ].join('');
+ } catch(e) {
+ content = [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ `${escapeHTML(String(e))}
`,
+ '',
+ '',
+ ].join('');
+ }
+
+ writeEncoded(encoding, content);
+ }
+}
+
+module.exports = {PreviewRequestHandler};
diff --git a/bin/server.js b/bin/server.js
index dfcd4a7..579438b 100755
--- a/bin/server.js
+++ b/bin/server.js
@@ -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));
diff --git a/docs/ubuntu-nginx-installer.sh b/docs/ubuntu-nginx-installer.sh
index dad2eca..0710bcc 100644
--- a/docs/ubuntu-nginx-installer.sh
+++ b/docs/ubuntu-nginx-installer.sh
@@ -121,6 +121,7 @@ cd - > /dev/null;
EOF
sudo tee /var/www/https/index.htm < /dev/null;
+
$DOMAIN
diff --git a/index.html b/index.html
index 559c106..09e8590 100644
--- a/index.html
+++ b/index.html
@@ -1,3 +1,4 @@
+
@@ -9,7 +10,8 @@
connect-src 'self';
font-src 'self' data:;
img-src 'self' blob:;
- form-action 'none';
+ frame-src 'self';
+ form-action 'self';
">
@@ -78,7 +80,6 @@
Sequence Diagram Online Editor
Loading…
-
+
+
diff --git a/library.htm b/library.htm
index 285ef38..6a6be2b 100644
--- a/library.htm
+++ b/library.htm
@@ -1,3 +1,4 @@
+
diff --git a/web/resources/preview.htm b/web/resources/preview.htm
new file mode 100644
index 0000000..c9d26a5
--- /dev/null
+++ b/web/resources/preview.htm
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+← Press "Update" to see the diagram.
+
+
diff --git a/web/styles/editor.css b/web/styles/editor.css
index 9c1a6fe..9dff9f9 100644
--- a/web/styles/editor.css
+++ b/web/styles/editor.css
@@ -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;
}
diff --git a/web/styles/noscript.css b/web/styles/noscript.css
new file mode 100644
index 0000000..836f326
--- /dev/null
+++ b/web/styles/noscript.css
@@ -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;
+ }
+}
diff --git a/web/styles/preview.css b/web/styles/preview.css
new file mode 100644
index 0000000..a9236ed
--- /dev/null
+++ b/web/styles/preview.css
@@ -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;
+ }
+}