From b98a42543e3dc89e9a9726a07fbd839e492d957d Mon Sep 17 00:00:00 2001 From: Vishesh Handa Date: Mon, 10 Feb 2020 15:43:48 +0100 Subject: [PATCH] Add a Checklist class --- lib/core/checklist.dart | 101 +++++++++++++++++++++++++++++++++++++++ test/checklist_test.dart | 60 +++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 lib/core/checklist.dart create mode 100644 test/checklist_test.dart diff --git a/lib/core/checklist.dart b/lib/core/checklist.dart new file mode 100644 index 00000000..c95cf2b0 --- /dev/null +++ b/lib/core/checklist.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:markdown/markdown.dart' as md; + +import 'package:gitjournal/core/note.dart'; + +class ChecklistItem { + bool checked; + String text; + md.Element element; + + ChecklistItem({ + @required this.checked, + @required this.text, + @required this.element, + }); + + @override + String toString() => 'ChecklistItem: $checked $text'; +} + +class Checklist { + Note note; + List items; + + Checklist(this.note) { + items = ChecklistBuilder().parse(note.body); + } +} + +/// Copied from flutter-markdown - cannot be merged as we added xUpperCase and changed the regexp +/// Parse [task list items](https://github.github.com/gfm/#task-list-items-extension-). +class TaskListSyntax extends md.InlineSyntax { + // FIXME: Waiting for dart-lang/markdown#269 to land + static final String _pattern = r'^ *\[([ xX])\] +(.*)$'; + + TaskListSyntax() : super(_pattern); + + @override + bool onMatch(md.InlineParser parser, Match match) { + md.Element el = md.Element.withTag('input'); + el.attributes['type'] = 'checkbox'; + el.attributes['checked'] = '${match[1].trim().isNotEmpty}'; + var m = match[1].trim(); + if (m.isNotEmpty) { + el.attributes['xUpperCase'] = (m[0] == 'X').toString(); + } + el.attributes['text'] = '${match[2]}'; + parser.addNode(el); + return true; + } +} + +class ChecklistBuilder implements md.NodeVisitor { + List list; + + @override + bool visitElementBefore(md.Element element) { + return true; + } + + @override + void visitText(md.Text text) {} + + @override + void visitElementAfter(md.Element element) { + final String tag = element.tag; + + if (tag == 'input') { + var el = element; + if (el is md.Element && el.attributes['type'] == 'checkbox') { + bool val = el.attributes['checked'] != 'false'; + var item = ChecklistItem( + checked: val, + text: el.attributes['text'], + element: el, + ); + list.add(item); + } + } + } + + List build(List nodes) { + list = []; + for (md.Node node in nodes) { + node.accept(this); + } + + return list; + } + + List parse(String text) { + var doc = md.Document( + encodeHtml: false, + inlineSyntaxes: [TaskListSyntax()], + extensionSet: md.ExtensionSet.gitHubFlavored, + ); + + var nodes = doc.parseInline(text); + return build(nodes); + } +} diff --git a/test/checklist_test.dart b/test/checklist_test.dart new file mode 100644 index 00000000..4d5f0ded --- /dev/null +++ b/test/checklist_test.dart @@ -0,0 +1,60 @@ +import 'dart:io'; + +import 'package:gitjournal/core/checklist.dart'; +import 'package:gitjournal/core/note.dart'; +import 'package:gitjournal/core/notes_folder.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +void main() { + group('Note', () { + Directory tempDir; + + setUpAll(() async { + tempDir = await Directory.systemTemp.createTemp('__notes_test__'); + }); + + tearDownAll(() async { + tempDir.deleteSync(recursive: true); + }); + + test('Should parse simple checklists', () async { + var content = """--- +title: Foo +modified: 2017-02-15T22:41:19+01:00 +--- + +# Title 1 + +How are you doing? + +[ ] item 1 +[x] item 2 +[X] item 3 +[ ] item 4 + +Booga Wooga +"""; + + var notePath = p.join(tempDir.path, "note.md"); + File(notePath).writeAsString(content); + + var parentFolder = NotesFolder(null, tempDir.path); + var note = Note(parentFolder, notePath); + await note.load(); + + var checklist = Checklist(note); + expect(checklist.items.length, equals(4)); + + expect(checklist.items[0].checked, false); + expect(checklist.items[1].checked, true); + expect(checklist.items[2].checked, true); + expect(checklist.items[3].checked, false); + + expect(checklist.items[0].text, "item 1"); + expect(checklist.items[1].text, "item 2"); + expect(checklist.items[2].text, "item 3"); + expect(checklist.items[3].text, "item 4"); + }); + }); +}