From eb7b3caf691685cef757102faa8da6780b80fa24 Mon Sep 17 00:00:00 2001 From: Amir Panahandeh Date: Thu, 9 May 2024 11:11:57 +0330 Subject: [PATCH] Add checkbox support to markdown codec (#345) --- .../parchment/lib/src/codecs/markdown.dart | 45 ++++++++++++++++++- .../parchment/test/codecs/markdown_test.dart | 37 ++++++++++++++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/packages/parchment/lib/src/codecs/markdown.dart b/packages/parchment/lib/src/codecs/markdown.dart index b3eca6aa..4e286101 100644 --- a/packages/parchment/lib/src/codecs/markdown.dart +++ b/packages/parchment/lib/src/codecs/markdown.dart @@ -40,6 +40,7 @@ class _ParchmentMarkdownDecoder extends Converter { RegExp(r'\[(?.+)\]\((?[^ ]+)(?: "(?.+)")?\)'); static final _ulRegExp = RegExp(r'^( *)\* +(.*)'); static final _olRegExp = RegExp(r'^( *)\d+[.)] +(.*)'); + static final _clRegExp = RegExp(r'^( *)- +\[( |x|X)\] +(.*)'); static final _bqRegExp = RegExp(r'^> *(.*)'); static final _codeRegExpTag = RegExp(r'^( *)```'); @@ -101,7 +102,8 @@ class _ParchmentMarkdownDecoder extends Converter<String, ParchmentDocument> { } if (_handleOrderedList(line, delta, style) || - _handleUnorderedList(line, delta, style)) { + _handleUnorderedList(line, delta, style) || + _handleCheckList(line, delta, style)) { return true; } @@ -163,6 +165,31 @@ class _ParchmentMarkdownDecoder extends Converter<String, ParchmentDocument> { return false; } + bool _handleCheckList(String line, Delta delta, [ParchmentStyle? style]) { + // we do not support nested blocks + if (style?.contains(ParchmentAttribute.block) ?? false) { + return false; + } + + ParchmentStyle newStyle = + (style ?? ParchmentStyle()).put(ParchmentAttribute.cl); + + final match = _clRegExp.matchAsPrefix(line); + final span = match?.group(3); + final isChecked = match?.group(2) != ' '; + if (isChecked) { + newStyle = newStyle.put(ParchmentAttribute.checked); + } + if (span != null) { + _handleSpan(span, delta, false, + ParchmentStyle().putAll(newStyle.inlineAttributes)); + _handleSpan( + '\n', delta, false, ParchmentStyle().putAll(newStyle.lineAttributes)); + return true; + } + return false; + } + bool _handleHeading(String line, Delta delta, [ParchmentStyle? style]) { final match = _headingRegExp.matchAsPrefix(line); final levelTag = match?.group(1); @@ -411,6 +438,16 @@ class _ParchmentMarkdownEncoder extends Converter<ParchmentDocument, String> { for (final lineNode in node.children) { if (node.style.containsSame(ParchmentAttribute.ol)) { lineBuffer.write(currentItemOrder); + } else if (node.style.containsSame(ParchmentAttribute.cl)) { + lineBuffer.write('- ['); + if ((lineNode as LineNode) + .style + .contains(ParchmentAttribute.checked)) { + lineBuffer.write('X'); + } else { + lineBuffer.write(' '); + } + lineBuffer.write('] '); } handleLine(lineNode as LineNode); if (!lineNode.isLast) { @@ -454,6 +491,8 @@ class _ParchmentMarkdownEncoder extends Converter<ParchmentDocument, String> { } else if (attribute?.key == ParchmentAttribute.block.key) { _writeBlockTag(buffer, attribute as ParchmentAttribute<String>, close: close); + } else if (attribute?.key == ParchmentAttribute.checked.key) { + // no-op } else { throw ArgumentError('Cannot handle $attribute'); } @@ -501,7 +540,9 @@ class _ParchmentMarkdownEncoder extends Converter<ParchmentDocument, String> { if (close) return; // no close tag needed for simple blocks. final tag = simpleBlocks[block]; - buffer.write(tag); + if (tag != null) { + buffer.write(tag); + } } } } diff --git a/packages/parchment/test/codecs/markdown_test.dart b/packages/parchment/test/codecs/markdown_test.dart index 97c09f82..1883662e 100644 --- a/packages/parchment/test/codecs/markdown_test.dart +++ b/packages/parchment/test/codecs/markdown_test.dart @@ -337,6 +337,19 @@ void main() { expect(act, exp); }); + test('cl', () { + var markdown = '- [ ] Hello\n- [X] This is a\n- [ ] Checklist\n\n'; + final act = parchmentMarkdown.decode(markdown).toDelta(); + final exp = Delta() + ..insert('Hello') + ..insert('\n', {'block': 'cl'}) + ..insert('This is a') + ..insert('\n', {'block': 'cl', 'checked': true}) + ..insert('Checklist') + ..insert('\n', {'block': 'cl'}); + expect(act, exp); + }); + test('simple bq', () { // var markdown = '> quote\n> > nested\n>#Heading\n>**bold**\n>_italics_\n>* bullet\n>1. 1st point\n>1. 2nd point\n\n'; var markdown = @@ -543,6 +556,25 @@ void main() { expect(result, expected); }); + test('cl', () { + final delta = Delta() + ..insert('Hello') + ..insert('\n', ParchmentAttribute.cl.toJson()) + ..insert( + 'This is a', + ) + ..insert('\n', { + ...ParchmentAttribute.cl.toJson(), + ...ParchmentAttribute.checked.toJson(), + }) + ..insert('Checklist') + ..insert('\n', ParchmentAttribute.cl.toJson()); + final result = + parchmentMarkdown.encode(ParchmentDocument.fromDelta(delta)); + final expected = '- [ ] Hello\n- [X] This is a\n- [ ] Checklist\n\n'; + expect(result, expected); + }); + test('multiline blocks', () { void runFor(ParchmentAttribute<String> attribute, String source, String expected) { @@ -571,7 +603,7 @@ void main() { } final doc = - r'[{"insert":"Fleather"},{"insert":"\n","attributes":{"heading":1}},{"insert":"Soft and gentle rich text editing for Flutter applications.","attributes":{"i":true}},{"insert":"\nFleather is an "},{"insert":"early preview","attributes":{"b":true}},{"insert":" open source library.\nDocumentation"},{"insert":"\n","attributes":{"heading":3}},{"insert":"Quick Start"},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Data format and Document Model"},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Style attributes"},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Heuristic rules"},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Clean and modern look"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Fleather’s rich text editor is built with "},{"insert": "simplicity and flexibility", "attributes":{"i":true}},{"insert":" in mind. It provides clean interface for distraction-free editing. Think "},{"insert": "Medium.com", "attributes":{"c":true}},{"insert": "-like experience.\nimport ‘package:flutter/material.dart’;"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"import ‘package:parchment/parchment.dart’;"},{"insert":"\n\n","attributes":{"block":"code"}},{"insert":"void main() {"},{"insert":"\n","attributes":{"block":"code"}},{"insert":" print(“Hello world!”);"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"}"},{"insert":"\n","attributes":{"block":"code"}}]'; + r'[{"insert":"Fleather"},{"insert":"\n","attributes":{"heading":1}},{"insert":"Soft and gentle rich text editing for Flutter applications.","attributes":{"i":true}},{"insert":"\nFleather is an "},{"insert":"early preview","attributes":{"b":true}},{"insert":" open source library.\n"},{"insert":"That even supports"},{"insert":"\n","attributes":{"block":"cl"}},{"insert":"Checklists"},{"insert":"\n","attributes":{"checked":true,"block":"cl"}},{"insert":"Documentation"},{"insert":"\n","attributes":{"heading":3}},{"insert":"Quick Start"},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Data format and Document Model"},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Style attributes"},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Heuristic rules"},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Clean and modern look"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Fleather’s rich text editor is built with "},{"insert": "simplicity and flexibility", "attributes":{"i":true}},{"insert":" in mind. It provides clean interface for distraction-free editing. Think "},{"insert": "Medium.com", "attributes":{"c":true}},{"insert": "-like experience.\nimport ‘package:flutter/material.dart’;"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"import ‘package:parchment/parchment.dart’;"},{"insert":"\n\n","attributes":{"block":"code"}},{"insert":"void main() {"},{"insert":"\n","attributes":{"block":"code"}},{"insert":" print(“Hello world!”);"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"}"},{"insert":"\n","attributes":{"block":"code"}}]'; final delta = Delta.fromJson(json.decode(doc) as List); final markdown = ''' @@ -581,6 +613,9 @@ _Soft and gentle rich text editing for Flutter applications._ Fleather is an **early preview** open source library. +- [ ] That even supports +- [X] Checklists + ### Documentation * Quick Start