akceo 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- akceo/__init__.py +1 -0
- akceo/__main__.py +3 -0
- akceo/assets/base.css +64 -0
- akceo/assets/deck.js +33 -0
- akceo/assets/diagrams.js +60 -0
- akceo/assets/md-viewer.html +257 -0
- akceo/assets/mermaid-shims.js +76 -0
- akceo/assets/page.html +27 -0
- akceo/assets/vendor/mermaid/LICENSE +21 -0
- akceo/assets/vendor/mermaid/README.md +56 -0
- akceo/assets/vendor/mermaid/THIRD_PARTY_NOTICES +4872 -0
- akceo/assets/vendor/mermaid/manifest.json +8 -0
- akceo/assets/vendor/mermaid/mermaid.min.js +4376 -0
- akceo/cli.py +92 -0
- akceo/errors.py +2 -0
- akceo/files.py +17 -0
- akceo/images.py +42 -0
- akceo/mermaid.py +388 -0
- akceo/parse.py +348 -0
- akceo/render.py +247 -0
- akceo/themes/midnight.css +14 -0
- akceo/themes/paper.css +14 -0
- akceo/themes.py +70 -0
- akceo-0.1.0.dist-info/METADATA +184 -0
- akceo-0.1.0.dist-info/RECORD +30 -0
- akceo-0.1.0.dist-info/WHEEL +4 -0
- akceo-0.1.0.dist-info/entry_points.txt +2 -0
- akceo-0.1.0.dist-info/licenses/LICENSE +21 -0
- akceo-0.1.0.dist-info/licenses/src/akceo/assets/vendor/mermaid/LICENSE +21 -0
- akceo-0.1.0.dist-info/licenses/src/akceo/assets/vendor/mermaid/THIRD_PARTY_NOTICES +4872 -0
akceo/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Build self-contained HTML slide decks from Markdown."""
|
akceo/__main__.py
ADDED
akceo/assets/base.css
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* Layout rules. Colors and fonts come from the theme's tokens (see docs/syntax.md#themes). */
|
|
2
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
3
|
+
html, body { height: 100%; background: var(--bg); color: var(--text); font-family: var(--font); overflow: hidden; }
|
|
4
|
+
#deck { height: 100%; }
|
|
5
|
+
.slide {
|
|
6
|
+
position: absolute; inset: 0; display: none; flex-direction: column;
|
|
7
|
+
justify-content: center; padding: 5.5vh 7vw 8vh; animation: fade .28s ease;
|
|
8
|
+
}
|
|
9
|
+
.slide.active { display: flex; }
|
|
10
|
+
@keyframes fade { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
|
|
11
|
+
.kicker { color: var(--accent); font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
|
|
12
|
+
font-size: clamp(11px, 1.3vw, 15px); margin-bottom: 1.4vh; }
|
|
13
|
+
h1 { font-size: clamp(30px, 5.4vw, 62px); line-height: 1.05; letter-spacing: -.02em; }
|
|
14
|
+
h2 { font-size: clamp(26px, 4vw, 46px); line-height: 1.08; letter-spacing: -.02em; margin-bottom: 2.4vh; }
|
|
15
|
+
.lead { font-size: clamp(20px, 2.7vw, 34px); line-height: 1.32; color: var(--text); max-width: 42ch; }
|
|
16
|
+
.hl { color: var(--accent); }
|
|
17
|
+
.dim { color: var(--muted); }
|
|
18
|
+
.strong { color: var(--strong); }
|
|
19
|
+
p.sub { color: var(--muted); font-size: clamp(15px, 1.7vw, 21px); line-height: 1.5; margin-top: 2.2vh; max-width: 62ch; }
|
|
20
|
+
ul { list-style: none; display: flex; flex-direction: column; gap: 1.5vh; margin-top: 1vh; }
|
|
21
|
+
li { font-size: clamp(15px, 1.9vw, 24px); line-height: 1.34; padding-left: 1.4em; position: relative; max-width: 60ch; }
|
|
22
|
+
li::before { content: "▸"; color: var(--accent); position: absolute; left: 0; top: 0; }
|
|
23
|
+
li b { color: var(--strong); }
|
|
24
|
+
code { font-family: var(--mono); color: var(--accent2); font-size: .92em; }
|
|
25
|
+
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 4vw; align-items: center; height: 100%; }
|
|
26
|
+
.split.img-wide { grid-template-columns: 1.35fr 1fr; }
|
|
27
|
+
.figwrap { height: 82vh; display: flex; align-items: center; justify-content: center; }
|
|
28
|
+
.figwrap img, .figure-full img,
|
|
29
|
+
.figwrap svg.diagram, .figure-full svg.diagram { max-width: 100%; max-height: 100%; object-fit: contain;
|
|
30
|
+
border-radius: 10px; background: var(--figure-bg); padding: 10px; box-shadow: var(--figure-shadow); }
|
|
31
|
+
.placeholder { width: 100%; max-height: 100%; aspect-ratio: 4 / 3; border: 2px dashed var(--line);
|
|
32
|
+
border-radius: 10px; }
|
|
33
|
+
.figure-full { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; }
|
|
34
|
+
.figure-full .placeholder { width: auto; height: 100%; max-width: 100%; }
|
|
35
|
+
.kicker + .figure-full { margin-top: 2vh; }
|
|
36
|
+
.figwrap img.bare, .figure-full img.bare,
|
|
37
|
+
.figwrap svg.bare, .figure-full svg.bare { background: none; box-shadow: none; padding: 0; border-radius: 0; }
|
|
38
|
+
/* A diagram's source waits hidden until the page script draws it as an <svg class="diagram">. */
|
|
39
|
+
svg.diagram { width: auto; height: auto; }
|
|
40
|
+
.diagram-src { display: none; }
|
|
41
|
+
.diagram.failed { max-width: 100%; padding: 1em; border: 2px dashed var(--line); border-radius: 10px;
|
|
42
|
+
color: var(--muted); white-space: pre-wrap; }
|
|
43
|
+
.phase { border-left: 3px solid var(--accent); padding: .3em 0 .3em 1em; margin-bottom: 2.2vh; }
|
|
44
|
+
.phase h3 { font-size: clamp(16px, 2vw, 25px); color: var(--strong); }
|
|
45
|
+
.phase p { color: var(--muted); font-size: clamp(13px, 1.5vw, 19px); line-height: 1.4; margin-top: .3em; }
|
|
46
|
+
.note-inline { color: var(--muted); font-size: clamp(13px, 1.5vw, 18px); margin-top: 2vh; font-style: italic; }
|
|
47
|
+
ol.steps { counter-reset: s; display: flex; flex-direction: column; gap: 1.6vh; margin-top: 1vh; }
|
|
48
|
+
ol.steps li { list-style: none; padding-left: 2.4em; }
|
|
49
|
+
ol.steps li::before { counter-increment: s; content: counter(s); color: var(--bg); background: var(--accent);
|
|
50
|
+
width: 1.6em; height: 1.6em; border-radius: 50%; display: grid; place-items: center; font-weight: 700;
|
|
51
|
+
font-size: .8em; left: 0; top: .05em; }
|
|
52
|
+
table { border-collapse: collapse; width: 100%; margin-top: 1vh; font-size: clamp(12px, 1.5vw, 19px); }
|
|
53
|
+
th, td { text-align: left; padding: 1.2vh 1.1vw; border-bottom: 1px solid var(--line); vertical-align: top; }
|
|
54
|
+
th { color: var(--accent); font-size: .8em; letter-spacing: .08em; text-transform: uppercase; }
|
|
55
|
+
td:first-child b { color: var(--strong); }
|
|
56
|
+
.title-wrap { text-align: center; align-items: center; justify-content: center; }
|
|
57
|
+
.title-wrap .meta { color: var(--muted); font-size: clamp(14px, 1.7vw, 20px); margin-top: 3.2vh; }
|
|
58
|
+
.title-wrap .rule { width: 64px; height: 4px; background: var(--accent); border-radius: 2px; margin: 3.4vh auto 0; }
|
|
59
|
+
#hud { position: fixed; bottom: 0; left: 0; right: 0; height: 4px; background: transparent; z-index: 20; }
|
|
60
|
+
#bar { height: 100%; background: var(--accent); width: 0; transition: width .28s ease; }
|
|
61
|
+
#count { position: fixed; bottom: 14px; right: 20px; color: var(--muted); font-size: 13px;
|
|
62
|
+
font-family: var(--mono); z-index: 20; }
|
|
63
|
+
#hint { position: fixed; bottom: 14px; left: 20px; color: var(--line); font-size: 12px;
|
|
64
|
+
font-family: var(--mono); z-index: 20; }
|
akceo/assets/deck.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
var slides = Array.prototype.slice.call(document.querySelectorAll('.slide'));
|
|
2
|
+
var i = 0;
|
|
3
|
+
var bar = document.getElementById('bar');
|
|
4
|
+
var count = document.getElementById('count');
|
|
5
|
+
function render() {
|
|
6
|
+
slides.forEach(function(s, n){ s.classList.toggle('active', n === i); });
|
|
7
|
+
bar.style.width = (slides.length > 1 ? i / (slides.length - 1) * 100 : 100) + '%';
|
|
8
|
+
count.textContent = (i + 1) + ' / ' + slides.length;
|
|
9
|
+
// replaceState keeps slide steps out of the browser history and doesn't fire hashchange.
|
|
10
|
+
history.replaceState(null, '', '#' + (i + 1));
|
|
11
|
+
}
|
|
12
|
+
function go(n){ i = Math.max(0, Math.min(slides.length - 1, n)); render(); }
|
|
13
|
+
// The hash holds the 1-based slide number, so a link or a reload opens that slide.
|
|
14
|
+
function fromHash(){
|
|
15
|
+
var m = location.hash.match(/^#(\d+)$/);
|
|
16
|
+
return m ? parseInt(m[1], 10) - 1 : 0;
|
|
17
|
+
}
|
|
18
|
+
window.addEventListener('hashchange', function(){ go(fromHash()); });
|
|
19
|
+
document.addEventListener('keydown', function(e){
|
|
20
|
+
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') { e.preventDefault(); go(i + 1); }
|
|
21
|
+
else if (e.key === 'ArrowLeft' || e.key === 'PageUp') { e.preventDefault(); go(i - 1); }
|
|
22
|
+
else if (e.key === 'Home') { go(0); }
|
|
23
|
+
else if (e.key === 'End') { go(slides.length - 1); }
|
|
24
|
+
else if (e.key === 'f' || e.key === 'F') {
|
|
25
|
+
if (!document.fullscreenElement) document.documentElement.requestFullscreen();
|
|
26
|
+
else document.exitFullscreen();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
document.addEventListener('click', function(e){
|
|
30
|
+
if (String(window.getSelection())) return;
|
|
31
|
+
if (e.clientX > window.innerWidth * 0.5) go(i + 1); else go(i - 1);
|
|
32
|
+
});
|
|
33
|
+
go(fromHash());
|
akceo/assets/diagrams.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Draw each Mermaid diagram as inline SVG in place of its source. The build has already checked
|
|
2
|
+
// every diagram's syntax, so a failure here is one that only shows when drawing, such as a bad date.
|
|
3
|
+
(function() {
|
|
4
|
+
var diagrams = Array.prototype.slice.call(document.querySelectorAll('.diagram'));
|
|
5
|
+
var theme = JSON.parse(document.getElementById('mermaid-theme').textContent);
|
|
6
|
+
// Mermaid sizes its SVG as width="100%" with an inline max-width. Give it a pixel size from its
|
|
7
|
+
// viewBox instead, so it scales to fit the frame the way an <img> does, under the same CSS rules.
|
|
8
|
+
function sized(markup, node) {
|
|
9
|
+
var holder = document.createElement('div');
|
|
10
|
+
holder.innerHTML = markup;
|
|
11
|
+
var svg = holder.querySelector('svg');
|
|
12
|
+
if (!svg) throw new Error('Mermaid returned no SVG');
|
|
13
|
+
var box = svg.viewBox && svg.viewBox.baseVal;
|
|
14
|
+
if (box && box.width && box.height) {
|
|
15
|
+
svg.setAttribute('width', Math.round(box.width));
|
|
16
|
+
svg.setAttribute('height', Math.round(box.height));
|
|
17
|
+
svg.style.maxWidth = '';
|
|
18
|
+
}
|
|
19
|
+
// The page's Content-Security-Policy blocks every outside load, and the build rejects outside
|
|
20
|
+
// links. Drop any link that still gets through, so a click can't leave the deck.
|
|
21
|
+
svg.querySelectorAll('*').forEach(function(el) {
|
|
22
|
+
Array.prototype.slice.call(el.attributes).forEach(function(attr) {
|
|
23
|
+
if (/^(xlink:)?href$/i.test(attr.name) && !/^\s*#/.test(attr.value)) el.removeAttributeNode(attr);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
node.classList.forEach(function(name){ svg.classList.add(name); });
|
|
27
|
+
svg.setAttribute('role', 'img');
|
|
28
|
+
svg.setAttribute('aria-label', node.getAttribute('aria-label'));
|
|
29
|
+
return svg;
|
|
30
|
+
}
|
|
31
|
+
function failed(node, e) {
|
|
32
|
+
// The image role and its label would hide this text from screen readers.
|
|
33
|
+
node.removeAttribute('role');
|
|
34
|
+
node.removeAttribute('aria-label');
|
|
35
|
+
node.classList.add('failed');
|
|
36
|
+
node.textContent = 'Mermaid could not draw this diagram: ' + ((e && e.message) || e);
|
|
37
|
+
}
|
|
38
|
+
// Never rejects: any error, from Mermaid or from sizing, marks this diagram failed and leaves the
|
|
39
|
+
// rest to draw.
|
|
40
|
+
function draw(node, n) {
|
|
41
|
+
return Promise.resolve().then(function() {
|
|
42
|
+
var source = node.querySelector('.diagram-src').textContent;
|
|
43
|
+
// An unframed diagram takes the theme's colors; one in a frame keeps Mermaid's defaults.
|
|
44
|
+
// initialize starts again from Mermaid's defaults each time, so one diagram can't leak into
|
|
45
|
+
// the next. suppressErrorRendering stops Mermaid leaving its own error graphic in the page.
|
|
46
|
+
var colors = node.dataset.mermaid === 'theme' ? theme : {};
|
|
47
|
+
mermaid.initialize(Object.assign({ startOnLoad: false, suppressErrorRendering: true }, colors));
|
|
48
|
+
return mermaid.render('diagram-' + n, source);
|
|
49
|
+
}).then(function(result) {
|
|
50
|
+
node.replaceWith(sized(result.svg, node));
|
|
51
|
+
}).catch(function(e) {
|
|
52
|
+
failed(node, e);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// The theme's fonts change the size of the text, so wait for them before Mermaid measures it.
|
|
56
|
+
// One at a time, because initialize sets Mermaid's config for the whole page.
|
|
57
|
+
diagrams.reduce(function(done, node, n) {
|
|
58
|
+
return done.then(function() { return draw(node, n); });
|
|
59
|
+
}, document.fonts.ready);
|
|
60
|
+
})();
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Markdown Viewer</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root{
|
|
9
|
+
--bg:#0b0f16; --panel:#111925; --line:#22314a; --text:#e8eef6; --muted:#93a4bd;
|
|
10
|
+
--accent:#5eead4; --accent2:#fbbf24; --warn:#f87171; --fs:18px;
|
|
11
|
+
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
|
12
|
+
--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
|
13
|
+
}
|
|
14
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
15
|
+
html,body{height:100%}
|
|
16
|
+
body{background:var(--bg);color:var(--text);font-family:var(--sans);display:flex;flex-direction:column}
|
|
17
|
+
header{display:flex;align-items:center;gap:14px;padding:10px 18px;border-bottom:1px solid var(--line);
|
|
18
|
+
background:var(--panel);position:sticky;top:0;z-index:5;flex:none}
|
|
19
|
+
header .name{color:var(--accent);font-family:var(--mono);font-size:13px;overflow:hidden;
|
|
20
|
+
text-overflow:ellipsis;white-space:nowrap;flex:1}
|
|
21
|
+
header .name .idle{color:var(--muted)}
|
|
22
|
+
button{font:inherit;font-size:13px;color:var(--text);background:#1b2740;border:1px solid var(--line);
|
|
23
|
+
border-radius:7px;padding:6px 12px;cursor:pointer}
|
|
24
|
+
button:hover{border-color:var(--accent);color:#fff}
|
|
25
|
+
button.accent{background:var(--accent);color:#06231d;border-color:var(--accent);font-weight:600}
|
|
26
|
+
.fsz{display:flex;gap:4px}
|
|
27
|
+
.fsz button{padding:6px 10px}
|
|
28
|
+
#status{color:var(--muted);font-size:12px;font-family:var(--mono)}
|
|
29
|
+
#status.on{color:var(--accent2)}
|
|
30
|
+
#status.warn{color:var(--warn)}
|
|
31
|
+
main{flex:1;overflow-y:auto}
|
|
32
|
+
#empty{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;
|
|
33
|
+
gap:14px;color:var(--muted);text-align:center;padding:40px}
|
|
34
|
+
#empty .big{font-size:22px;color:var(--text)}
|
|
35
|
+
#content{max-width:80ch;margin:0 auto;padding:40px 48px 120px;font-size:var(--fs);line-height:1.62;display:none}
|
|
36
|
+
#content h1{font-size:1.9em;margin:.2em 0 .5em;letter-spacing:-.01em;border-bottom:1px solid var(--line);padding-bottom:.2em}
|
|
37
|
+
#content h2{font-size:1.5em;margin:1.4em 0 .5em;letter-spacing:-.01em}
|
|
38
|
+
#content h3{font-size:1.2em;margin:1.5em 0 .4em;color:#fff}
|
|
39
|
+
#content h3 em{color:var(--accent2);font-style:normal;font-size:.82em;font-family:var(--mono)}
|
|
40
|
+
#content h4{font-size:1.02em;margin:1.2em 0 .3em;color:var(--muted);text-transform:uppercase;letter-spacing:.06em}
|
|
41
|
+
#content p{margin:.7em 0}
|
|
42
|
+
#content ul,#content ol{margin:.6em 0 .6em 1.5em}
|
|
43
|
+
#content li{margin:.32em 0}
|
|
44
|
+
#content li>ul,#content li>ol{margin:.2em 0 .2em 1.3em}
|
|
45
|
+
#content img{max-width:100%;height:auto;border-radius:8px}
|
|
46
|
+
#content strong{color:#fff}
|
|
47
|
+
#content em{color:var(--text)}
|
|
48
|
+
#content a{color:var(--accent);text-decoration:none;border-bottom:1px solid rgba(94,234,212,.3)}
|
|
49
|
+
#content a:hover{border-bottom-color:var(--accent)}
|
|
50
|
+
#content code{font-family:var(--mono);font-size:.88em;background:#182338;color:var(--accent2);
|
|
51
|
+
padding:2px 6px;border-radius:5px}
|
|
52
|
+
#content pre{background:#0e1626;border:1px solid var(--line);border-radius:10px;padding:16px;
|
|
53
|
+
overflow-x:auto;margin:1em 0}
|
|
54
|
+
#content pre code{background:none;color:#cdd8e6;padding:0;font-size:.86em}
|
|
55
|
+
#content blockquote{border-left:3px solid var(--accent);background:#0e1626;margin:1em 0;padding:.6em 1.1em;
|
|
56
|
+
color:var(--muted);border-radius:0 8px 8px 0}
|
|
57
|
+
#content hr{border:none;border-top:1px solid var(--line);margin:2em 0}
|
|
58
|
+
#content table{border-collapse:collapse;width:100%;margin:1em 0;font-size:.92em;display:block;overflow-x:auto}
|
|
59
|
+
#content th,#content td{border:1px solid var(--line);padding:9px 12px;text-align:left;vertical-align:top}
|
|
60
|
+
#content th{background:#0e1626;color:var(--accent);font-size:.85em;letter-spacing:.04em;text-transform:uppercase}
|
|
61
|
+
#drop{position:fixed;inset:0;background:rgba(11,15,22,.92);border:3px dashed var(--accent);
|
|
62
|
+
display:none;align-items:center;justify-content:center;font-size:26px;color:var(--accent);z-index:20}
|
|
63
|
+
#drop.show{display:flex}
|
|
64
|
+
</style>
|
|
65
|
+
</head>
|
|
66
|
+
<body>
|
|
67
|
+
<header>
|
|
68
|
+
<span class="name"><span class="idle">No file loaded</span></span>
|
|
69
|
+
<span id="status" role="status"></span>
|
|
70
|
+
<div class="fsz"><button id="minus" title="Smaller text">A−</button><button id="plus" title="Larger text">A+</button></div>
|
|
71
|
+
<button id="open" class="accent">Open .md</button>
|
|
72
|
+
<input id="file" type="file" accept=".md,.markdown,.txt,text/markdown" hidden>
|
|
73
|
+
</header>
|
|
74
|
+
<main>
|
|
75
|
+
<div id="empty">
|
|
76
|
+
<div class="big">Open or drop a Markdown file</div>
|
|
77
|
+
<div id="hint"></div>
|
|
78
|
+
</div>
|
|
79
|
+
<article id="content"></article>
|
|
80
|
+
</main>
|
|
81
|
+
<div id="drop">Drop to open</div>
|
|
82
|
+
|
|
83
|
+
<script>
|
|
84
|
+
(function(){
|
|
85
|
+
var nameEl=document.querySelector('.name'), statusEl=document.getElementById('status'),
|
|
86
|
+
main=document.querySelector('main'), content=document.getElementById('content'),
|
|
87
|
+
empty=document.getElementById('empty'), drop=document.getElementById('drop'),
|
|
88
|
+
input=document.getElementById('file');
|
|
89
|
+
// File System Access API: Chrome and Edge, including pages opened from file://.
|
|
90
|
+
var canWatch=!!window.showOpenFilePicker;
|
|
91
|
+
document.getElementById('hint').textContent=canWatch
|
|
92
|
+
?'It refreshes by itself whenever you save the file.'
|
|
93
|
+
:'This browser can\'t watch files, so drop the file again to refresh after edits.';
|
|
94
|
+
|
|
95
|
+
var fs=parseInt(localStorage.getItem('mdv_fs')||'18',10); applyFontSize();
|
|
96
|
+
function applyFontSize(){document.documentElement.style.setProperty('--fs',fs+'px');localStorage.setItem('mdv_fs',fs);}
|
|
97
|
+
document.getElementById('plus').onclick=function(){fs=Math.min(30,fs+1);applyFontSize();};
|
|
98
|
+
document.getElementById('minus').onclick=function(){fs=Math.max(12,fs-1);applyFontSize();};
|
|
99
|
+
|
|
100
|
+
var LIST_ITEM=/^([ \t]*)([-*+]|\d+\.)[ \t]+(.*)$/;
|
|
101
|
+
function esc(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
|
|
102
|
+
function safeUrl(url,schemes){
|
|
103
|
+
// URL parsers drop control characters and spaces, so "java\tscript:" would still run as javascript:.
|
|
104
|
+
url=url.replace(/[\u0000-\u0020]/g,'');
|
|
105
|
+
var scheme=url.match(/^([a-z][a-z0-9+.-]*):/i);
|
|
106
|
+
return scheme&&!schemes.test(scheme[1])?null:url.replace(/"/g,'"');
|
|
107
|
+
}
|
|
108
|
+
function link(text,url){
|
|
109
|
+
url=safeUrl(url,/^(https?|mailto)$/i);
|
|
110
|
+
return url===null?text:'<a href="'+url+'" target="_blank" rel="noopener">'+text+'</a>';
|
|
111
|
+
}
|
|
112
|
+
function image(alt,url){
|
|
113
|
+
url=safeUrl(url,/^(https?|data)$/i);
|
|
114
|
+
if(url===null||(/^data:/i.test(url)&&!/^data:image\//i.test(url)))return alt;
|
|
115
|
+
return '<img src="'+url+'" alt="'+alt.replace(/"/g,'"')+'">';
|
|
116
|
+
}
|
|
117
|
+
// [text](url) and . Parentheses inside the URL are matched in pairs.
|
|
118
|
+
function links(s,hold){
|
|
119
|
+
var out='',i=0;
|
|
120
|
+
while(i<s.length){
|
|
121
|
+
var open=s.indexOf('[',i);
|
|
122
|
+
if(open<0)break;
|
|
123
|
+
var close=s.indexOf(']',open);
|
|
124
|
+
if(close<0)break;
|
|
125
|
+
var j=close+2,depth=1;
|
|
126
|
+
if(s.charAt(close+1)==='('){
|
|
127
|
+
while(j<s.length){if(s[j]==='(')depth++;else if(s[j]===')'&&--depth===0)break;j++;}
|
|
128
|
+
}
|
|
129
|
+
if(s.charAt(close+1)!=='('||depth){out+=s.slice(i,open+1);i=open+1;continue;}
|
|
130
|
+
var isImage=s.charAt(open-1)==='!',text=s.slice(open+1,close),url=s.slice(close+2,j);
|
|
131
|
+
out+=s.slice(i,isImage?open-1:open)+hold(isImage?image(text,url):link(text,url));
|
|
132
|
+
i=j+1;
|
|
133
|
+
}
|
|
134
|
+
return out+s.slice(i);
|
|
135
|
+
}
|
|
136
|
+
// Items indented deeper than the one before nest under it.
|
|
137
|
+
function listHtml(items){
|
|
138
|
+
var out='',open=[];
|
|
139
|
+
items.forEach(function(it){
|
|
140
|
+
while(open.length&&it.indent<open[open.length-1].indent)out+='</li></'+open.pop().tag+'>';
|
|
141
|
+
var top=open[open.length-1];
|
|
142
|
+
if(top&&it.indent===top.indent&&it.tag===top.tag){out+='</li><li>';}
|
|
143
|
+
else{
|
|
144
|
+
if(top&&it.indent===top.indent)out+='</li></'+open.pop().tag+'>';
|
|
145
|
+
open.push({indent:it.indent,tag:it.tag});out+='<'+it.tag+'><li>';
|
|
146
|
+
}
|
|
147
|
+
out+=inline(it.text);
|
|
148
|
+
});
|
|
149
|
+
while(open.length)out+='</li></'+open.pop().tag+'>';
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
function inline(s){
|
|
153
|
+
var held=[];
|
|
154
|
+
function hold(html){held.push(html);return '\uE000'+(held.length-1)+'\uE000';}
|
|
155
|
+
s=esc(s);
|
|
156
|
+
s=s.replace(/`([^`]+)`/g,function(m,c){return hold('<code>'+c+'</code>');});
|
|
157
|
+
s=links(s,hold);
|
|
158
|
+
s=s.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>');
|
|
159
|
+
s=s.replace(/(^|[^*])\*([^*\s][^*]*?)\*/g,'$1<em>$2</em>');
|
|
160
|
+
s=s.replace(/(^|[^\w])_([^_\s][^_]*?)_/g,'$1<em>$2</em>');
|
|
161
|
+
return s.replace(/\uE000(\d+)\uE000/g,function(m,i){return held[i];});
|
|
162
|
+
}
|
|
163
|
+
function splitRow(l){return l.trim().replace(/^\|/,'').replace(/\|$/,'').split('|').map(function(c){return c.trim();});}
|
|
164
|
+
function md(src){
|
|
165
|
+
var lines=src.replace(/\r\n?/g,'\n').split('\n'),out='',i=0,n=lines.length,para=[];
|
|
166
|
+
function flush(){if(para.length){out+='<p>'+inline(para.join(' '))+'</p>';para=[];}}
|
|
167
|
+
while(i<n){
|
|
168
|
+
var line=lines[i];
|
|
169
|
+
if(/^```/.test(line)){flush();i++;var code=[];while(i<n&&!/^```/.test(lines[i])){code.push(lines[i]);i++;}i++;out+='<pre><code>'+esc(code.join('\n'))+'</code></pre>';continue;}
|
|
170
|
+
if(/^\s*$/.test(line)){flush();i++;continue;}
|
|
171
|
+
var h=line.match(/^(#{1,6})\s+(.*)$/);
|
|
172
|
+
if(h){flush();out+='<h'+h[1].length+'>'+inline(h[2])+'</h'+h[1].length+'>';i++;continue;}
|
|
173
|
+
if(/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line)){flush();out+='<hr>';i++;continue;}
|
|
174
|
+
if(/\|/.test(line)&&i+1<n&&/-/.test(lines[i+1])&&/^\s*\|?[\s:|-]+\|?\s*$/.test(lines[i+1])){
|
|
175
|
+
flush();var head=splitRow(line);i+=2;var rows=[];
|
|
176
|
+
while(i<n&&/\|/.test(lines[i])&&!/^\s*$/.test(lines[i])){rows.push(splitRow(lines[i]));i++;}
|
|
177
|
+
out+='<table><thead><tr>'+head.map(function(c){return '<th>'+inline(c)+'</th>';}).join('')+'</tr></thead><tbody>'+
|
|
178
|
+
rows.map(function(r){return '<tr>'+r.map(function(c){return '<td>'+inline(c)+'</td>';}).join('')+'</tr>';}).join('')+'</tbody></table>';continue;}
|
|
179
|
+
if(/^>\s?/.test(line)){flush();var bq=[];while(i<n&&/^>\s?/.test(lines[i])){bq.push(lines[i].replace(/^>\s?/,''));i++;}out+='<blockquote>'+md(bq.join('\n'))+'</blockquote>';continue;}
|
|
180
|
+
if(LIST_ITEM.test(line)){
|
|
181
|
+
flush();var items=[];
|
|
182
|
+
while(i<n){
|
|
183
|
+
var m=lines[i].match(LIST_ITEM);
|
|
184
|
+
if(m){items.push({indent:m[1].replace(/\t/g,' ').length,tag:/\d/.test(m[2])?'ol':'ul',text:m[3]});i++;}
|
|
185
|
+
else if(/^[ \t]+\S/.test(lines[i])){items[items.length-1].text+=' '+lines[i].trim();i++;}
|
|
186
|
+
else if(/^\s*$/.test(lines[i])&&i+1<n&&LIST_ITEM.test(lines[i+1])){i++;}
|
|
187
|
+
else break;
|
|
188
|
+
}
|
|
189
|
+
out+=listHtml(items);continue;
|
|
190
|
+
}
|
|
191
|
+
para.push(line);i++;
|
|
192
|
+
}
|
|
193
|
+
flush();return out;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function setStatus(text,cls){statusEl.textContent=text;statusEl.className=cls||'';}
|
|
197
|
+
function render(text,name,keepScroll){
|
|
198
|
+
var top=main.scrollTop;
|
|
199
|
+
content.innerHTML=md(text);
|
|
200
|
+
content.style.display='block';empty.style.display='none';
|
|
201
|
+
nameEl.textContent=name||'document';document.title=(name||'Markdown')+' — Viewer';
|
|
202
|
+
main.scrollTop=keepScroll?top:0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Reads finish out of order. Each open bumps the generation, and a read only renders if its
|
|
206
|
+
// generation is still current, so the latest selection always wins.
|
|
207
|
+
var handle=null,lastMod=0,generation=0,polling=false;
|
|
208
|
+
function openHandle(h){
|
|
209
|
+
var gen=++generation;
|
|
210
|
+
return h.getFile().then(function(f){
|
|
211
|
+
return f.text().then(function(t){
|
|
212
|
+
if(gen!==generation)return;
|
|
213
|
+
handle=h;lastMod=f.lastModified;render(t,f.name,false);setStatus('● watching','on');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function openFile(f){
|
|
218
|
+
var gen=++generation;handle=null;
|
|
219
|
+
f.text().then(function(t){
|
|
220
|
+
if(gen!==generation)return;
|
|
221
|
+
render(t,f.name,false);setStatus(canWatch?'not watching · use Open to watch':'drop again to refresh');
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
setInterval(function(){
|
|
225
|
+
if(!handle||polling)return;
|
|
226
|
+
var h=handle,gen=generation;polling=true;
|
|
227
|
+
h.getFile().then(function(f){
|
|
228
|
+
if(gen!==generation||f.lastModified===lastMod)return;
|
|
229
|
+
return f.text().then(function(t){if(gen!==generation)return;lastMod=f.lastModified;render(t,f.name,true);});
|
|
230
|
+
}).catch(function(){if(gen===generation){handle=null;setStatus('file unavailable · open it again','warn');}})
|
|
231
|
+
.then(function(){polling=false;});
|
|
232
|
+
},1500);
|
|
233
|
+
|
|
234
|
+
document.getElementById('open').onclick=function(){
|
|
235
|
+
if(!canWatch){input.click();return;}
|
|
236
|
+
window.showOpenFilePicker({types:[{description:'Markdown',accept:{'text/markdown':['.md','.markdown','.txt']}}]})
|
|
237
|
+
.then(function(picks){return openHandle(picks[0]);})
|
|
238
|
+
.catch(function(err){if(err&&err.name!=='AbortError')input.click();});
|
|
239
|
+
};
|
|
240
|
+
input.addEventListener('change',function(e){var f=e.target.files[0];if(f)openFile(f);input.value='';});
|
|
241
|
+
|
|
242
|
+
window.addEventListener('dragover',function(e){e.preventDefault();drop.classList.add('show');});
|
|
243
|
+
window.addEventListener('dragleave',function(e){if(e.relatedTarget===null)drop.classList.remove('show');});
|
|
244
|
+
window.addEventListener('drop',function(e){
|
|
245
|
+
e.preventDefault();drop.classList.remove('show');
|
|
246
|
+
// The drop's data is only readable during this event, so take the file and ask for its handle now.
|
|
247
|
+
var file=e.dataTransfer.files[0],item=e.dataTransfer.items&&e.dataTransfer.items[0];
|
|
248
|
+
if(!file)return;
|
|
249
|
+
var pending=item&&item.getAsFileSystemHandle?item.getAsFileSystemHandle():null;
|
|
250
|
+
if(!pending){openFile(file);return;}
|
|
251
|
+
pending.then(function(h){return h&&h.kind==='file'?openHandle(h):openFile(file);})
|
|
252
|
+
.catch(function(){openFile(file);});
|
|
253
|
+
});
|
|
254
|
+
})();
|
|
255
|
+
</script>
|
|
256
|
+
</body>
|
|
257
|
+
</html>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Loaded into V8 before mermaid.min.js so that mermaid.parse() can run without a browser. Only the
|
|
2
|
+
// build-time diagram check uses this file; the built page never includes it.
|
|
3
|
+
|
|
4
|
+
// DOMPurify, finding no DOM, returns a bare object and sets isSupported = false on it. Catch that
|
|
5
|
+
// assignment and give the object no-op methods: checking syntax never sanitizes anything.
|
|
6
|
+
Object.defineProperty(Function.prototype, "isSupported", {
|
|
7
|
+
configurable: true,
|
|
8
|
+
set(value) {
|
|
9
|
+
Object.defineProperty(this, "isSupported", { value, writable: true, configurable: true });
|
|
10
|
+
if (!value) {
|
|
11
|
+
const noop = () => {};
|
|
12
|
+
Object.assign(this, {
|
|
13
|
+
addHook: noop, removeHook: noop, removeHooks: noop, removeAllHooks: noop,
|
|
14
|
+
setConfig: noop, clearConfig: noop, isValidAttribute: () => true, sanitize: (dirty) => dirty,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// The parsers of the newer diagram types (pie, gitGraph, architecture and others) encode text.
|
|
21
|
+
globalThis.TextEncoder = class {
|
|
22
|
+
get encoding() { return "utf-8"; }
|
|
23
|
+
encode(text = "") {
|
|
24
|
+
const out = [];
|
|
25
|
+
for (const ch of text) {
|
|
26
|
+
const c = ch.codePointAt(0);
|
|
27
|
+
if (c < 0x80) out.push(c);
|
|
28
|
+
else if (c < 0x800) out.push(0xc0 | (c >> 6), 0x80 | (c & 63));
|
|
29
|
+
else if (c < 0x10000) out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 63), 0x80 | (c & 63));
|
|
30
|
+
else out.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 63), 0x80 | ((c >> 6) & 63), 0x80 | (c & 63));
|
|
31
|
+
}
|
|
32
|
+
return new Uint8Array(out);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Only plain parse results pass through here, so a recursive copy is enough.
|
|
37
|
+
globalThis.structuredClone = function clone(value) {
|
|
38
|
+
if (value === null || typeof value !== "object") return value;
|
|
39
|
+
if (Array.isArray(value)) return value.map(clone);
|
|
40
|
+
if (value instanceof Map) return new Map([...value].map(([k, v]) => [clone(k), clone(v)]));
|
|
41
|
+
if (value instanceof Set) return new Set([...value].map(clone));
|
|
42
|
+
if (value instanceof Date) return new Date(value);
|
|
43
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, clone(v)]));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// A click link's http(s) URL is tidied with new URL(). The check only needs that not to throw.
|
|
47
|
+
globalThis.URL = class URL {
|
|
48
|
+
constructor(href) {
|
|
49
|
+
const m = /^([a-z][a-z0-9+.-]*:)\/\/([^/?#]*)(.*)$/i.exec(String(href));
|
|
50
|
+
if (!m) throw new TypeError("Invalid URL: " + href);
|
|
51
|
+
[, this.protocol, this.hostname, this.rest] = m;
|
|
52
|
+
}
|
|
53
|
+
get href() { return this.protocol + "//" + this.hostname + this.rest; }
|
|
54
|
+
toString() { return this.href; }
|
|
55
|
+
static canParse(href) {
|
|
56
|
+
try { new URL(href); return true; } catch (e) { return false; }
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Resolves to null when the diagram parses, or to Mermaid's error as JSON. A YAML error from front
|
|
61
|
+
// matter or @{...} shape data counts its line within that snippet, not within the file.
|
|
62
|
+
globalThis.akceoCheck = async (source) => {
|
|
63
|
+
try {
|
|
64
|
+
await mermaid.parse(source);
|
|
65
|
+
return null;
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return JSON.stringify({
|
|
68
|
+
name: String((e && e.name) || "Error"),
|
|
69
|
+
message: String((e && e.message) || e),
|
|
70
|
+
yamlLine: e && e.mark && typeof e.mark.line === "number" ? e.mark.line : null,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Mermaid refuses to draw a longer diagram. parse() doesn't check it, so the build does.
|
|
76
|
+
globalThis.akceoMaxTextSize = () => mermaid.mermaidAPI.getConfig().maxTextSize;
|
akceo/assets/page.html
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<title>__TITLE__</title>
|
|
8
|
+
<style>
|
|
9
|
+
__STYLE__
|
|
10
|
+
</style>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="deck">
|
|
14
|
+
|
|
15
|
+
__SLIDES__
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div id="hud"><div id="bar"></div></div>
|
|
20
|
+
<div id="count"></div>
|
|
21
|
+
<div id="hint">← → move · F full screen</div>
|
|
22
|
+
|
|
23
|
+
<script>
|
|
24
|
+
__SCRIPT__
|
|
25
|
+
</script>
|
|
26
|
+
__DIAGRAMS__</body>
|
|
27
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014 - 2022 Knut Sveidqvist
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Vendored Mermaid
|
|
2
|
+
|
|
3
|
+
`mermaid.min.js` is the browser build of [Mermaid](https://mermaid.js.org), taken from the npm
|
|
4
|
+
package. akceo uses it twice:
|
|
5
|
+
|
|
6
|
+
- At build time, `akceo.mermaid` runs its `parse()` in V8 (through mini-racer) to check each `.mmd`
|
|
7
|
+
diagram, so syntax errors stop the build.
|
|
8
|
+
- In the built page, it draws each diagram as inline SVG when the deck opens.
|
|
9
|
+
|
|
10
|
+
`manifest.json` records the version, the tarball it came from, npm's integrity hash, the lockfile
|
|
11
|
+
the notices come from, and the SHA-256 of the file here. A test checks the file against that SHA-256.
|
|
12
|
+
|
|
13
|
+
## Licenses
|
|
14
|
+
|
|
15
|
+
Mermaid is MIT licensed; see `LICENSE`. The file also bundles other packages, listed with their
|
|
16
|
+
license texts in `THIRD_PARTY_NOTICES`. The list comes from `pnpm-lock.yaml` in Mermaid's
|
|
17
|
+
repository at the release tag, the lockfile the bundle was built from, so each version matches the
|
|
18
|
+
bundle. It covers Mermaid's production dependencies, optional ones included, plus the
|
|
19
|
+
devDependencies that the linked workspace package `@mermaid-js/parser` compiles into its own output,
|
|
20
|
+
such as langium.
|
|
21
|
+
|
|
22
|
+
Not covered yet: Mermaid's own devDependencies (about 40). If esbuild inlines one of them into the
|
|
23
|
+
bundle, it is missing from the notices, and the script doesn't notice.
|
|
24
|
+
|
|
25
|
+
One bundled package, elkjs, is EPL-2.0, which asks that recipients be told where its source is:
|
|
26
|
+
https://github.com/kieler/elkjs. Every built deck that has a diagram carries the notices, and that
|
|
27
|
+
line, in a comment.
|
|
28
|
+
|
|
29
|
+
## The one change from upstream
|
|
30
|
+
|
|
31
|
+
A raw control character inside a string literal is replaced with its `\xNN` escape. JavaScript reads
|
|
32
|
+
both forms the same way, but a raw control character is unsafe in an HTML page, and a test checks
|
|
33
|
+
that the packaged assets carry none. `manifest.json` records how many were escaped.
|
|
34
|
+
|
|
35
|
+
## Updating
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
uv run python scripts/update_mermaid.py 12.0.0 # the new version
|
|
39
|
+
uv run pytest
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The script downloads the tarball, checks it against npm's integrity hash, applies the change above
|
|
43
|
+
and builds the notices by downloading each locked package and checking it against the lockfile's
|
|
44
|
+
integrity hash. It writes the folder only after every step succeeds, so a failed update leaves it
|
|
45
|
+
as it was. It stops, and writes nothing, when:
|
|
46
|
+
|
|
47
|
+
- a linked workspace package (today only `@mermaid-js/parser`) has a devDependency the script
|
|
48
|
+
hasn't sorted. Check whether that package ends up in `mermaid.min.js`, then add it to
|
|
49
|
+
`BUNDLED_DEV_DEPENDENCIES` or `BUILD_ONLY_DEV_DEPENDENCIES` in the script.
|
|
50
|
+
- the walk misses a package the bundle certainly holds (`ANCHOR_PACKAGES`), which means the
|
|
51
|
+
lockfile's layout has changed under the script's reader.
|
|
52
|
+
- a package has no license file. Find its copyright notice, then add it to `NO_LICENSE_FILE`.
|
|
53
|
+
|
|
54
|
+
Then run the full test suite: `tests/test_mermaid.py` runs the real parser, so a Mermaid release
|
|
55
|
+
that no longer works with the stand-ins in `assets/mermaid-shims.js` fails there. Build
|
|
56
|
+
`examples/demo/deck.md` and look at the diagram slide too, since drawing isn't covered by the tests.
|