From 0181cbf743a03f05d0bbddf7c7c6b7dadffb439f Mon Sep 17 00:00:00 2001 From: Cory Gwin Date: Thu, 17 Mar 2022 16:18:10 +0000 Subject: [PATCH 1/2] feat(accessibility): add accessibility attributes to sequence diagrams --- demos/index.html | 7 +- demos/sequence.html | 71 +++++++++++++++++++ .../sequence/parser/sequenceDiagram.jison | 4 +- src/diagrams/sequence/sequenceDb.js | 11 +++ src/diagrams/sequence/sequenceDiagram.spec.js | 23 ++++++ src/diagrams/sequence/sequenceRenderer.js | 4 ++ 6 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 demos/sequence.html diff --git a/demos/index.html b/demos/index.html index 530e24965e..663a4cda66 100644 --- a/demos/index.html +++ b/demos/index.html @@ -37,7 +37,7 @@ axisFormat %m-%d %a excludes weekends, 2021-10-01,2021-10-04,2021-10-05,2021-10-06,2021-10-07 includes 2021-10-09 - + section Airworks 3.4.1 开发 :b, 2021-10-07, 5d 测试 :after b, 4d @@ -467,6 +467,7 @@
sequenceDiagram + accDescription Hello friends participant Alice participant Bob participant John as John
Second Line @@ -619,7 +620,7 @@
classDiagram - Class01 <|-- AveryLongClass : Cool + Class01 <|-- AveryLongClass : Cool <<interface>> Class01 Class03 "0" *-- "0..n" Class04 @@ -656,7 +657,7 @@
classDiagram - Class01~T~ <|-- AveryLongClass : Cool + Class01~T~ <|-- AveryLongClass : Cool <<interface>> Class01 Class03~T~ "0" *-- "0..n" Class04 Class05 "1" o-- "many" Class06 diff --git a/demos/sequence.html b/demos/sequence.html new file mode 100644 index 0000000000..6c477114d2 --- /dev/null +++ b/demos/sequence.html @@ -0,0 +1,71 @@ + + + + + + + Mermaid Quick Test Page + + + + + + + +
+ sequenceDiagram + accDescription Test a description + participant Alice + participant Bob + participant John as John
Second Line + rect rgb(200, 220, 100) + rect rgb(200, 255, 200) + Alice ->> Bob: Hello Bob, how are you? + Bob-->>John: How about you John? + end + Bob--x Alice: I am good thanks! + Bob-x John: I am good thanks! + Note right of John: John thinks a long
long time, so long
that the text does
not fit on a row. + Bob-->Alice: Checking with John... + Note over John:wrap: John looks like he's still thinking, so Bob prods him a bit. + Bob-x John: Hey John - we're still waiting to know
how you're doing + Note over John:nowrap: John's trying hard not to break his train of thought. + Bob-x John:wrap: John! Are you still debating about how you're doing? How long does it take?? + Note over John: After a few more moments, John
finally snaps out of it. + end + alt either this + Alice->>John: Yes + else or this + Alice->>John: No + else or this will happen + Alice->John: Maybe + end + par this happens in parallel + Alice -->> Bob: Parallel message 1 + and + Alice -->> John: Parallel message 2 + end +
+ + + + + + + \ No newline at end of file diff --git a/src/diagrams/sequence/parser/sequenceDiagram.jison b/src/diagrams/sequence/parser/sequenceDiagram.jison index 6608511f27..beadc9508b 100644 --- a/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -33,7 +33,7 @@ \%%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */ "participant" { this.begin('ID'); return 'participant'; } -"actor" { this.begin('ID'); return 'participant_actor'; } +"actor" { this.begin('ID'); return 'participant_actor'; } [^\->:\n,;]+?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } "as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } (?:) { this.popState(); this.popState(); return 'NEWLINE'; } @@ -57,6 +57,7 @@ "activate" { this.begin('ID'); return 'activate'; } "deactivate" { this.begin('ID'); return 'deactivate'; } "title" return 'title'; +"accDescription"\s[^#\n;]+ return 'accDescription'; "sequenceDiagram" return 'SD'; "autonumber" return 'autonumber'; "," return ','; @@ -122,6 +123,7 @@ statement | properties_statement 'NEWLINE' | details_statement 'NEWLINE' | title text2 'NEWLINE' {$$=[{type:'setTitle', text:$2}]} + | accDescription {yy.setAccDescription($1.substring(15));$$=$1.substring(15);} | 'loop' restOfLine document end { $3.unshift({type: 'loopStart', loopText:yy.parseMessage($2), signalType: yy.LINETYPE.LOOP_START}); diff --git a/src/diagrams/sequence/sequenceDb.js b/src/diagrams/sequence/sequenceDb.js index 7492e6d11f..fd8ae448b5 100644 --- a/src/diagrams/sequence/sequenceDb.js +++ b/src/diagrams/sequence/sequenceDb.js @@ -8,6 +8,7 @@ let actors = {}; let messages = []; const notes = []; let title = ''; +let description = ''; let titleWrapped = false; let sequenceNumbersEnabled = false; let wrapEnabled = false; @@ -408,6 +409,14 @@ export const apply = function (param) { } }; +const setAccDescription = function (description_lex) { + description = description_lex; +}; + +const getAccDescription = function () { + return description; +}; + export default { addActor, addMessage, @@ -436,4 +445,6 @@ export default { addNote, setTitle, apply, + setAccDescription, + getAccDescription, }; diff --git a/src/diagrams/sequence/sequenceDiagram.spec.js b/src/diagrams/sequence/sequenceDiagram.spec.js index 437e0e6e98..f305d45dea 100644 --- a/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/src/diagrams/sequence/sequenceDiagram.spec.js @@ -73,6 +73,7 @@ Bob-->Alice: I am good thanks!`; expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; + expect(parser.yy.getAccDescription()).toBe(''); const messages = parser.yy.getMessages(); const title = parser.yy.getTitle(); @@ -81,6 +82,28 @@ Bob-->Alice: I am good thanks!`; expect(messages[2].from).toBe('Bob'); expect(title).toBe('Diagram Title'); }); + it('it should handle a sequenceDiagram definition with a accDescription', function () { + const str = ` +sequenceDiagram +accDescription: Accessibility Description +Alice->Bob:Hello Bob, how are you? +Note right of Bob: Bob thinks +Bob-->Alice: I am good thanks!`; + + mermaidAPI.parse(str); + const actors = parser.yy.getActors(); + expect(actors.Alice.description).toBe('Alice'); + actors.Bob.description = 'Bob'; + + expect(parser.yy.getAccDescription()).toBe('Accessibility Description'); + const messages = parser.yy.getMessages(); + const title = parser.yy.getTitle(); + + expect(messages.length).toBe(3); + expect(messages[0].from).toBe('Alice'); + expect(messages[2].from).toBe('Bob'); + }); + it('it should space in actor names', function () { const str = ` sequenceDiagram diff --git a/src/diagrams/sequence/sequenceRenderer.js b/src/diagrams/sequence/sequenceRenderer.js index 138ca7c6bc..48507b9f70 100644 --- a/src/diagrams/sequence/sequenceRenderer.js +++ b/src/diagrams/sequence/sequenceRenderer.js @@ -6,6 +6,7 @@ import common from '../common/common'; import sequenceDb from './sequenceDb'; import * as configApi from '../../config'; import utils, { assignWithDepth, configureSvgSize } from '../../utils'; +import addSVGAccessibilityFields from '../../accessibility'; parser.yy = sequenceDb; @@ -727,6 +728,7 @@ export const draw = function (text, id) { log.error('error while drawing message', e); } } + // Increment sequence counter if msg.type is a line (and not another event like activation or note, etc) if ( [ @@ -802,6 +804,8 @@ export const draw = function (text, id) { ' ' + (height + extraVertForTitle) ); + + addSVGAccessibilityFields(parser.yy, diagram, id); log.debug(`models:`, bounds.models); }; From 50f522ae5c4cd8d3462893c9c16ac770b9c281fd Mon Sep 17 00:00:00 2001 From: Cory Gwin Date: Thu, 17 Mar 2022 19:48:22 +0000 Subject: [PATCH 2/2] feat: adding more accessibility tooling --- demos/er.html | 45 +++++++++++++++++++ demos/flowchart.html | 2 + demos/sequence.html | 1 + launch.json | 11 +++++ src/accessibility.js | 5 ++- src/diagrams/er/erDb.js | 11 +++++ src/diagrams/er/erRenderer.js | 3 ++ src/diagrams/er/parser/erDiagram.jison | 8 ++++ src/diagrams/er/parser/erDiagram.spec.js | 11 +++++ .../sequence/parser/sequenceDiagram.jison | 6 ++- src/diagrams/sequence/sequenceDb.js | 14 +++--- src/diagrams/sequence/sequenceDiagram.spec.js | 29 +++++++++++- 12 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 demos/er.html create mode 100644 launch.json diff --git a/demos/er.html b/demos/er.html new file mode 100644 index 0000000000..62285a2b45 --- /dev/null +++ b/demos/er.html @@ -0,0 +1,45 @@ + + + + + + +Mermaid Quick Test Page + + + + + + + +
+erDiagram +title This is a title +accDescription Test a description +CUSTOMER ||--o{ ORDER : places +ORDER ||--|{ LINE-ITEM : contains +CUSTOMER }|..|{ DELIVERY-ADDRESS : uses +
+ + + + + + + \ No newline at end of file diff --git a/demos/flowchart.html b/demos/flowchart.html index 0ab1f64813..864fac6877 100644 --- a/demos/flowchart.html +++ b/demos/flowchart.html @@ -230,6 +230,8 @@

graph

flowchart

flowchart TD + title Christmas + accDescription Get money A[Christmas] -->|Get money| B(Go shopping) B --> C{Let me thinksssssx
sssssssssssssssssssuuu
tttsssssssssssssssssssssss} C -->|One| D[Laptop] diff --git a/demos/sequence.html b/demos/sequence.html index 6c477114d2..b6930a7225 100644 --- a/demos/sequence.html +++ b/demos/sequence.html @@ -19,6 +19,7 @@
sequenceDiagram + title: FancySequenceDiagram accDescription Test a description participant Alice participant Bob diff --git a/launch.json b/launch.json new file mode 100644 index 0000000000..6a9ed413ae --- /dev/null +++ b/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach", + "port": 9229 + } + ] +} \ No newline at end of file diff --git a/src/accessibility.js b/src/accessibility.js index 2b1119580e..046fea6ad5 100644 --- a/src/accessibility.js +++ b/src/accessibility.js @@ -9,15 +9,16 @@ * @param id */ export default function addSVGAccessibilityFields(yy_parser, svg, id) { + if (typeof svg.insert == 'undefined') { + return; + } let title_string = yy_parser.getTitle(); let description = yy_parser.getAccDescription(); svg.attr('role', 'img').attr('aria-labelledby', 'chart-title-' + id + ' chart-desc-' + id); - svg .insert('desc', ':first-child') .attr('id', 'chart-desc-' + id) .text(description); - svg .insert('title', ':first-child') .attr('id', 'chart-title-' + id) diff --git a/src/diagrams/er/erDb.js b/src/diagrams/er/erDb.js index 9aa94aef14..8fe54ed83a 100644 --- a/src/diagrams/er/erDb.js +++ b/src/diagrams/er/erDb.js @@ -5,6 +5,7 @@ import * as configApi from '../../config'; let entities = {}; let relationships = []; let title = ''; +let description = ''; const Cardinality = { ZERO_OR_ONE: 'ZERO_OR_ONE', @@ -75,6 +76,14 @@ const getTitle = function () { return title; }; +const setAccDescription = function (txt) { + description = txt; +}; + +const getAccDescription = function () { + return description; +}; + const clear = function () { entities = {}; relationships = []; @@ -94,4 +103,6 @@ export default { clear, setTitle, getTitle, + setAccDescription, + getAccDescription, }; diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js index d2e2cc0d9f..a8589da72c 100644 --- a/src/diagrams/er/erRenderer.js +++ b/src/diagrams/er/erRenderer.js @@ -7,6 +7,7 @@ import { getConfig } from '../../config'; import { log } from '../../logger'; import erMarkers from './erMarkers'; import { configureSvgSize } from '../../utils'; +import addSVGAccessibilityFields from '../../accessibility'; const conf = {}; @@ -637,6 +638,8 @@ export const draw = function (text, id) { configureSvgSize(svg, height, width, conf.useMaxWidth); svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`); + + addSVGAccessibilityFields(parser.yy, svg, id); }; // draw export default { diff --git a/src/diagrams/er/parser/erDiagram.jison b/src/diagrams/er/parser/erDiagram.jison index 6e06fc2cec..7461cb0646 100644 --- a/src/diagrams/er/parser/erDiagram.jison +++ b/src/diagrams/er/parser/erDiagram.jison @@ -2,8 +2,14 @@ %options case-insensitive %x open_directive type_directive arg_directive block +%x title +%x accDescription %% +title { this.begin("title");return 'title'; } +(?!\n|;|#)*[^\n]* { this.popState(); return "title_value"; } +accDescription { this.begin("accDescription");return 'accDescription'; } +<accDescription>(?!\n|;|#)*[^\n]* { this.popState(); return "description_value"; } \%\%\{ { this.begin('open_directive'); return 'open_directive'; } <open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } <type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; } @@ -84,6 +90,8 @@ statement } | entityName BLOCK_START BLOCK_STOP { yy.addEntity($1); } | entityName { yy.addEntity($1); } + | title title_value { $$=$2.trim();yy.setTitle($$); } + | accDescription description_value { $$=$2.trim();yy.setAccDescription($$); } ; entityName diff --git a/src/diagrams/er/parser/erDiagram.spec.js b/src/diagrams/er/parser/erDiagram.spec.js index 746a091fad..edfec58d6e 100644 --- a/src/diagrams/er/parser/erDiagram.spec.js +++ b/src/diagrams/er/parser/erDiagram.spec.js @@ -181,6 +181,17 @@ describe('when parsing ER diagram it...', function () { expect(Object.keys(erDb.getEntities()).length).toBe(1); }); + it('should allow for a title and acc description', function () { + const teacherRole = 'is teacher of'; + const line1 = `TEACHER }o--o{ STUDENT : "${teacherRole}"`; + + erDiagram.parser.parse( + `erDiagram\ntitle graph title\n accDescription this graph is about stuff\n${line1}` + ); + expect(erDb.getTitle()).toBe('graph title'); + expect(erDb.getAccDescription()).toBe('this graph is about stuff'); + }); + it('should allow more than one relationship between the same two entities', function () { const line1 = 'CAR ||--o{ PERSON : "insured for"'; const line2 = 'CAR }o--|| PERSON : "owned by"'; diff --git a/src/diagrams/sequence/parser/sequenceDiagram.jison b/src/diagrams/sequence/parser/sequenceDiagram.jison index beadc9508b..b649408337 100644 --- a/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -56,7 +56,8 @@ "note" return 'note'; "activate" { this.begin('ID'); return 'activate'; } "deactivate" { this.begin('ID'); return 'deactivate'; } -"title" return 'title'; +"title"\s[^#\n;]+ return 'title'; +"title:"\s[^#\n;]+ return 'legacy_title'; "accDescription"\s[^#\n;]+ return 'accDescription'; "sequenceDiagram" return 'SD'; "autonumber" return 'autonumber'; @@ -122,7 +123,8 @@ statement | link_statement 'NEWLINE' | properties_statement 'NEWLINE' | details_statement 'NEWLINE' - | title text2 'NEWLINE' {$$=[{type:'setTitle', text:$2}]} + | title {yy.setTitle($1.substring(6));$$=$1.substring(6);} + | legacy_title {yy.setTitle($1.substring(7));$$=$1.substring(7);} | accDescription {yy.setAccDescription($1.substring(15));$$=$1.substring(15);} | 'loop' restOfLine document end { diff --git a/src/diagrams/sequence/sequenceDb.js b/src/diagrams/sequence/sequenceDb.js index fd8ae448b5..cb7238fece 100644 --- a/src/diagrams/sequence/sequenceDb.js +++ b/src/diagrams/sequence/sequenceDb.js @@ -9,7 +9,6 @@ let messages = []; const notes = []; let title = ''; let description = ''; -let titleWrapped = false; let sequenceNumbersEnabled = false; let wrapEnabled = false; @@ -123,9 +122,6 @@ export const getActorKeys = function () { export const getTitle = function () { return title; }; -export const getTitleWrapped = function () { - return titleWrapped; -}; export const enableSequenceNumbers = function () { sequenceNumbersEnabled = true; }; @@ -324,9 +320,9 @@ export const getActorProperty = function (actor, key) { return undefined; }; -export const setTitle = function (titleWrap) { - title = titleWrap.text; - titleWrapped = (titleWrap.wrap === undefined && autoWrap()) || !!titleWrap.wrap; +export const setTitle = function (txt) { + let sanitizedText = sanitizeText(txt, configApi.getConfig()); + title = sanitizedText; }; export const apply = function (param) { @@ -410,7 +406,8 @@ export const apply = function (param) { }; const setAccDescription = function (description_lex) { - description = description_lex; + let sanitizedText = sanitizeText(description_lex, configApi.getConfig()); + description = sanitizedText; }; const getAccDescription = function () { @@ -436,7 +433,6 @@ export default { getTitle, parseDirective, getConfig: () => configApi.getConfig().sequence, - getTitleWrapped, clear, parseMessage, LINETYPE, diff --git a/src/diagrams/sequence/sequenceDiagram.spec.js b/src/diagrams/sequence/sequenceDiagram.spec.js index f305d45dea..1016cb9bfc 100644 --- a/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/src/diagrams/sequence/sequenceDiagram.spec.js @@ -60,7 +60,7 @@ Bob-->Alice: I am good thanks!`; mermaidAPI.parse(str); expect(parser.yy.showSequenceNumbers()).toBe(true); }); - it('it should handle a sequenceDiagram definition with a title', function () { + it('it should handle a sequenceDiagram definition with a title:', function () { const str = ` sequenceDiagram title: Diagram Title @@ -82,10 +82,34 @@ Bob-->Alice: I am good thanks!`; expect(messages[2].from).toBe('Bob'); expect(title).toBe('Diagram Title'); }); + + it('it should handle a sequenceDiagram definition with a title without a :', function () { + const str = ` +sequenceDiagram +title Diagram Title +Alice->Bob:Hello Bob, how are you? +Note right of Bob: Bob thinks +Bob-->Alice: I am good thanks!`; + + mermaidAPI.parse(str); + const actors = parser.yy.getActors(); + expect(actors.Alice.description).toBe('Alice'); + actors.Bob.description = 'Bob'; + + expect(parser.yy.getAccDescription()).toBe(''); + const messages = parser.yy.getMessages(); + const title = parser.yy.getTitle(); + + expect(messages.length).toBe(3); + expect(messages[0].from).toBe('Alice'); + expect(messages[2].from).toBe('Bob'); + expect(title).toBe('Diagram Title'); + }); + it('it should handle a sequenceDiagram definition with a accDescription', function () { const str = ` sequenceDiagram -accDescription: Accessibility Description +accDescription Accessibility Description Alice->Bob:Hello Bob, how are you? Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; @@ -1609,6 +1633,7 @@ participant Alice renderer.bounds.init(); mermaidAPI.parse(str); + renderer.draw(str, 'tst'); const { bounds, models } = renderer.bounds.getBounds();