c(e))}})}),e(["sequence/SequenceDiagram"],e=>{"use strict";const t=window.define;t&&t.amd?t(()=>e):(document.addEventListener("DOMContentLoaded",()=>{e.convertAll()},{once:!0}),window.CodeMirror&&e.registerCodeMirrorMode(window.CodeMirror),window.SequenceDiagram=e)},null,!0),n("standalone",function(){})}();
\ No newline at end of file
diff --git a/library.htm b/library.htm
index 008e20b..4b2b47d 100644
--- a/library.htm
+++ b/library.htm
@@ -301,6 +301,17 @@ A -> B: this is much easier
A <- B: than writing the whole name
+Markdown
+
+define 'Name with
+**bold** and _italic_' as A
+define 'Also `code`
+and ~strikeout~' as B
+
+A -> B: '_**basic markdown
+is supported!**_'
+
+
Alternative Agent Ordering
define Baz, Foo
diff --git a/screenshots/Markdown.png b/screenshots/Markdown.png
new file mode 100644
index 0000000..41487f9
Binary files /dev/null and b/screenshots/Markdown.png differ
diff --git a/scripts/editor.js b/scripts/editor.js
index 327d96e..43f7fc4 100644
--- a/scripts/editor.js
+++ b/scripts/editor.js
@@ -199,6 +199,26 @@
code: 'title {Title}',
preview: 'headers box\ntitle Title\nA -> B',
},
+ {
+ title: 'Bold markdown',
+ code: '**{text}**',
+ preview: 'A -> B: **bold**',
+ },
+ {
+ title: 'Italic markdown',
+ code: '_{text}_',
+ preview: 'A -> B: _italic_',
+ },
+ {
+ title: 'Strikeout markdown',
+ code: '~{text}~',
+ preview: 'A -> B: ~strikeout~',
+ },
+ {
+ title: 'Monospace markdown',
+ code: '`{text}`',
+ preview: 'A -> B: `mono`',
+ },
{
title: 'Monospace theme',
code: 'theme monospace',
diff --git a/scripts/sequence/MarkdownParser.js b/scripts/sequence/MarkdownParser.js
index 1a9ca7d..ecb96b3 100644
--- a/scripts/sequence/MarkdownParser.js
+++ b/scripts/sequence/MarkdownParser.js
@@ -1,16 +1,105 @@
define(() => {
'use strict';
+ const STYLES = [
+ {
+ begin: /[\s_~`]\*(?=\S)/g,
+ end: /\S\*(?=[\s_~`])/g,
+ attrs: {'font-style': 'italic'},
+ }, {
+ begin: /[\s*~`]_(?=\S)/g,
+ end: /\S_(?=[\s*~`])/g,
+ attrs: {'font-style': 'italic'},
+ }, {
+ begin: /[\s_~`]\*\*(?=\S)/g,
+ end: /\S\*\*(?=[\s_~`])/g,
+ attrs: {'font-weight': 'bolder'},
+ }, {
+ begin: /[\s*~`]__(?=\S)/g,
+ end: /\S__(?=[\s*~`])/g,
+ attrs: {'font-weight': 'bolder'},
+ }, {
+ begin: /[\s_*`]~(?=\S)/g,
+ end: /\S~(?=[\s_*`])/g,
+ attrs: {'text-decoration': 'line-through'},
+ }, {
+ begin: /[\s_*~.]`(?=\S)/g,
+ end: /\S`(?=[\s_*~.])/g,
+ attrs: {'font-family': 'monospace'},
+ },
+ ];
+
+ function findNext(line, p, active) {
+ const virtLine = ' ' + line + ' ';
+ let styleIndex = -1;
+ let bestStart = virtLine.length;
+ let bestEnd = 0;
+
+ STYLES.forEach(({begin, end, attrs}, ind) => {
+ const search = active[ind] ? end : begin;
+ search.lastIndex = p;
+ const m = search.exec(virtLine);
+ if(m && (
+ m.index < bestStart ||
+ (m.index === bestStart && search.lastIndex > bestEnd)
+ )) {
+ styleIndex = ind;
+ bestStart = m.index;
+ bestEnd = search.lastIndex;
+ }
+ });
+
+ return {styleIndex, start: bestStart, end: bestEnd - 1};
+ }
+
+ function combineAttrs(activeCount, active) {
+ if(!activeCount) {
+ return null;
+ }
+ const attrs = {};
+ active.forEach((on, ind) => {
+ if(on) {
+ Object.assign(attrs, STYLES[ind].attrs);
+ }
+ });
+ return attrs;
+ }
+
function parseMarkdown(text) {
if(!text) {
return [];
}
+ const active = STYLES.map(() => false);
+ let activeCount = 0;
+ let attrs = null;
const lines = text.split('\n');
const result = [];
- const attrs = null;
lines.forEach((line) => {
- result.push([{text: line, attrs}]);
+ const parts = [];
+ let p = 0;
+ while(true) {
+ const {styleIndex, start, end} = findNext(line, p, active);
+ if(styleIndex === -1) {
+ break;
+ }
+ if(active[styleIndex]) {
+ active[styleIndex] = false;
+ -- activeCount;
+ } else {
+ active[styleIndex] = true;
+ ++ activeCount;
+ }
+ if(start > p) {
+ parts.push({text: line.substring(p, start), attrs});
+ }
+ attrs = combineAttrs(activeCount, active);
+ p = end;
+ }
+ if(p < line.length) {
+ parts.push({text: line.substr(p), attrs});
+ }
+ result.push(parts);
});
return result;
}
diff --git a/scripts/sequence/MarkdownParser_spec.js b/scripts/sequence/MarkdownParser_spec.js
index a12541e..9ba91f9 100644
--- a/scripts/sequence/MarkdownParser_spec.js
+++ b/scripts/sequence/MarkdownParser_spec.js
@@ -27,6 +27,101 @@ defineDescribe('Markdown Parser', [
]);
});
+ it('recognises bold styling', () => {
+ const formatted = parser('a **b** c __d__ e');
+ expect(formatted).toEqual([[
+ {text: 'a ', attrs: null},
+ {text: 'b', attrs: {'font-weight': 'bolder'}},
+ {text: ' c ', attrs: null},
+ {text: 'd', attrs: {'font-weight': 'bolder'}},
+ {text: ' e', attrs: null},
+ ]]);
+ });
+
+ it('ignores styling marks inside words', () => {
+ const formatted = parser('a**b**c__d__e');
+ expect(formatted).toEqual([[
+ {text: 'a**b**c__d__e', attrs: null},
+ ]]);
+ });
+
+ it('continues styling across lines', () => {
+ const formatted = parser('a **b\nc** d');
+ expect(formatted).toEqual([[
+ {text: 'a ', attrs: null},
+ {text: 'b', attrs: {'font-weight': 'bolder'}},
+ ], [
+ {text: 'c', attrs: {'font-weight': 'bolder'}},
+ {text: ' d', attrs: null},
+ ]]);
+ });
+
+ it('recognises italic styling', () => {
+ const formatted = parser('a *b* c _d_ e');
+ expect(formatted).toEqual([[
+ {text: 'a ', attrs: null},
+ {text: 'b', attrs: {'font-style': 'italic'}},
+ {text: ' c ', attrs: null},
+ {text: 'd', attrs: {'font-style': 'italic'}},
+ {text: ' e', attrs: null},
+ ]]);
+ });
+
+ it('recognises strikethrough styling', () => {
+ const formatted = parser('a ~b~ c');
+ expect(formatted).toEqual([[
+ {text: 'a ', attrs: null},
+ {text: 'b', attrs: {'text-decoration': 'line-through'}},
+ {text: ' c', attrs: null},
+ ]]);
+ });
+
+ it('recognises monospace styling', () => {
+ const formatted = parser('a `b` c');
+ expect(formatted).toEqual([[
+ {text: 'a ', attrs: null},
+ {text: 'b', attrs: {'font-family': 'monospace'}},
+ {text: ' c', attrs: null},
+ ]]);
+ });
+
+ it('allows dots around monospace styling', () => {
+ const formatted = parser('a.`b`.c');
+ expect(formatted).toEqual([[
+ {text: 'a.', attrs: null},
+ {text: 'b', attrs: {'font-family': 'monospace'}},
+ {text: '.c', attrs: null},
+ ]]);
+ });
+
+ it('recognises combined styling', () => {
+ const formatted = parser('a **_b_ c**');
+ expect(formatted).toEqual([[
+ {text: 'a ', attrs: null},
+ {text: 'b', attrs: {
+ 'font-weight': 'bolder',
+ 'font-style': 'italic',
+ }},
+ {text: ' c', attrs: {'font-weight': 'bolder'}},
+ ]]);
+ });
+
+ it('allows complex interactions between styles', () => {
+ const formatted = parser('_a **b_ ~c~**');
+ expect(formatted).toEqual([[
+ {text: 'a ', attrs: {'font-style': 'italic'}},
+ {text: 'b', attrs: {
+ 'font-weight': 'bolder',
+ 'font-style': 'italic',
+ }},
+ {text: ' ', attrs: {'font-weight': 'bolder'}},
+ {text: 'c', attrs: {
+ 'font-weight': 'bolder',
+ 'text-decoration': 'line-through',
+ }},
+ ]]);
+ });
+
describe('SVGTextBlock interaction', () => {
let hold = null;
let block = null;