Add markdown support [#29]
This commit is contained in:
parent
0f22dc7f94
commit
f8a757c508
14
README.md
14
README.md
|
@ -199,6 +199,20 @@ A -> B: this is much easier
|
||||||
A <- B: than writing the whole name
|
A <- B: than writing the whole name
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Markdown
|
||||||
|
|
||||||
|
<img src="screenshots/Markdown.png" alt="Markdown preview" width="200" align="right" />
|
||||||
|
|
||||||
|
```
|
||||||
|
define 'Name with
|
||||||
|
**bold** and _italic_' as A
|
||||||
|
define 'Also `code`
|
||||||
|
and ~strikeout~' as B
|
||||||
|
|
||||||
|
A -> B: '_**basic markdown
|
||||||
|
is supported!**_'
|
||||||
|
```
|
||||||
|
|
||||||
### Alternative Agent Ordering
|
### Alternative Agent Ordering
|
||||||
|
|
||||||
<img src="screenshots/AlternativeAgentOrdering.png" alt="Alternative Agent Ordering preview" width="200" align="right" />
|
<img src="screenshots/AlternativeAgentOrdering.png" alt="Alternative Agent Ordering preview" width="200" align="right" />
|
||||||
|
|
|
@ -1334,16 +1334,105 @@ define('sequence/Tokeniser',['./CodeMirrorMode'], (CMMode) => {
|
||||||
define('sequence/MarkdownParser',[],() => {
|
define('sequence/MarkdownParser',[],() => {
|
||||||
'use strict';
|
'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) {
|
function parseMarkdown(text) {
|
||||||
if(!text) {
|
if(!text) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const active = STYLES.map(() => false);
|
||||||
|
let activeCount = 0;
|
||||||
|
let attrs = null;
|
||||||
const lines = text.split('\n');
|
const lines = text.split('\n');
|
||||||
const result = [];
|
const result = [];
|
||||||
const attrs = null;
|
|
||||||
lines.forEach((line) => {
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
11
library.htm
11
library.htm
|
@ -301,6 +301,17 @@ A -> B: this is much easier
|
||||||
A <- B: than writing the whole name
|
A <- B: than writing the whole name
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
<h3 id="Markdown">Markdown</h3>
|
||||||
|
<pre class="example" data-lang="sequence">
|
||||||
|
define 'Name with
|
||||||
|
**bold** and _italic_' as A
|
||||||
|
define 'Also `code`
|
||||||
|
and ~strikeout~' as B
|
||||||
|
|
||||||
|
A -> B: '_**basic markdown
|
||||||
|
is supported!**_'
|
||||||
|
</pre>
|
||||||
|
|
||||||
<h3 id="AlternativeAgentOrdering">Alternative Agent Ordering</h3>
|
<h3 id="AlternativeAgentOrdering">Alternative Agent Ordering</h3>
|
||||||
<pre class="example" data-lang="sequence">
|
<pre class="example" data-lang="sequence">
|
||||||
define Baz, Foo
|
define Baz, Foo
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
|
@ -199,6 +199,26 @@
|
||||||
code: 'title {Title}',
|
code: 'title {Title}',
|
||||||
preview: 'headers box\ntitle Title\nA -> B',
|
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',
|
title: 'Monospace theme',
|
||||||
code: 'theme monospace',
|
code: 'theme monospace',
|
||||||
|
|
|
@ -1,16 +1,105 @@
|
||||||
define(() => {
|
define(() => {
|
||||||
'use strict';
|
'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) {
|
function parseMarkdown(text) {
|
||||||
if(!text) {
|
if(!text) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const active = STYLES.map(() => false);
|
||||||
|
let activeCount = 0;
|
||||||
|
let attrs = null;
|
||||||
const lines = text.split('\n');
|
const lines = text.split('\n');
|
||||||
const result = [];
|
const result = [];
|
||||||
const attrs = null;
|
|
||||||
lines.forEach((line) => {
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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', () => {
|
describe('SVGTextBlock interaction', () => {
|
||||||
let hold = null;
|
let hold = null;
|
||||||
let block = null;
|
let block = null;
|
||||||
|
|
Loading…
Reference in New Issue