mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #1424 from LucasXu0/markdown
Implement appflowy editor document to markdown
This commit is contained in:
commit
cdf6f1b38a
@ -1,14 +1,14 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:app_flowy/plugins/doc/application/share_service.dart';
|
import 'package:app_flowy/plugins/doc/application/share_service.dart';
|
||||||
import 'package:app_flowy/workspace/application/markdown/document_markdown.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart' show Document;
|
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||||
|
show Document, documentToMarkdown;
|
||||||
part 'share_bloc.freezed.dart';
|
part 'share_bloc.freezed.dart';
|
||||||
|
|
||||||
class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
|
class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
|
||||||
|
@ -33,3 +33,10 @@ export 'src/render/selection_menu/selection_menu_widget.dart';
|
|||||||
export 'src/l10n/l10n.dart';
|
export 'src/l10n/l10n.dart';
|
||||||
export 'src/render/style/plugin_styles.dart';
|
export 'src/render/style/plugin_styles.dart';
|
||||||
export 'src/render/style/editor_style.dart';
|
export 'src/render/style/editor_style.dart';
|
||||||
|
export 'src/plugins/markdown/encoder/delta_markdown_encoder.dart';
|
||||||
|
export 'src/plugins/markdown/encoder/document_markdown_encoder.dart';
|
||||||
|
export 'src/plugins/markdown/encoder/parser/node_parser.dart';
|
||||||
|
export 'src/plugins/markdown/encoder/parser/text_node_parser.dart';
|
||||||
|
export 'src/plugins/markdown/encoder/parser/image_node_parser.dart';
|
||||||
|
export 'src/plugins/markdown/decoder/delta_markdown_decoder.dart';
|
||||||
|
export 'src/plugins/markdown/document_markdown.dart';
|
||||||
|
@ -50,7 +50,7 @@ class TextInsert extends TextOperation {
|
|||||||
final result = <String, dynamic>{
|
final result = <String, dynamic>{
|
||||||
'insert': text,
|
'insert': text,
|
||||||
};
|
};
|
||||||
if (_attributes != null) {
|
if (_attributes != null && _attributes!.isNotEmpty) {
|
||||||
result['attributes'] = attributes;
|
result['attributes'] = attributes;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -62,7 +62,7 @@ class TextInsert extends TextOperation {
|
|||||||
|
|
||||||
return other is TextInsert &&
|
return other is TextInsert &&
|
||||||
other.text == text &&
|
other.text == text &&
|
||||||
mapEquals(_attributes, other._attributes);
|
_mapEquals(_attributes, other._attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -87,7 +87,7 @@ class TextRetain extends TextOperation {
|
|||||||
final result = <String, dynamic>{
|
final result = <String, dynamic>{
|
||||||
'retain': length,
|
'retain': length,
|
||||||
};
|
};
|
||||||
if (_attributes != null) {
|
if (_attributes != null && _attributes!.isNotEmpty) {
|
||||||
result['attributes'] = attributes;
|
result['attributes'] = attributes;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -99,7 +99,7 @@ class TextRetain extends TextOperation {
|
|||||||
|
|
||||||
return other is TextRetain &&
|
return other is TextRetain &&
|
||||||
other.length == length &&
|
other.length == length &&
|
||||||
mapEquals(_attributes, other._attributes);
|
_mapEquals(_attributes, other._attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -181,7 +181,7 @@ class Delta extends Iterable<TextOperation> {
|
|||||||
lastOp.length += textOperation.length;
|
lastOp.length += textOperation.length;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mapEquals(lastOp.attributes, textOperation.attributes)) {
|
if (_mapEquals(lastOp.attributes, textOperation.attributes)) {
|
||||||
if (lastOp is TextInsert && textOperation is TextInsert) {
|
if (lastOp is TextInsert && textOperation is TextInsert) {
|
||||||
lastOp.text += textOperation.text;
|
lastOp.text += textOperation.text;
|
||||||
return;
|
return;
|
||||||
@ -539,3 +539,10 @@ class _OpIterator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _mapEquals<T, U>(Map<T, U>? a, Map<T, U>? b) {
|
||||||
|
if ((a == null || a.isEmpty) && (b == null || b.isEmpty)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return mapEquals(a, b);
|
||||||
|
}
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:appflowy_editor/src/core/document/attributes.dart';
|
||||||
|
import 'package:appflowy_editor/src/core/document/text_delta.dart';
|
||||||
|
import 'package:appflowy_editor/src/core/legacy/built_in_attribute_keys.dart';
|
||||||
|
import 'package:markdown/markdown.dart' as md;
|
||||||
|
|
||||||
|
class DeltaMarkdownDecoder extends Converter<String, Delta>
|
||||||
|
with md.NodeVisitor {
|
||||||
|
final _delta = Delta();
|
||||||
|
final Attributes _attributes = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Delta convert(String input) {
|
||||||
|
final document =
|
||||||
|
md.Document(extensionSet: md.ExtensionSet.gitHubWeb).parseInline(input);
|
||||||
|
for (final node in document) {
|
||||||
|
node.accept(this);
|
||||||
|
}
|
||||||
|
return _delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitElementAfter(md.Element element) {
|
||||||
|
_removeAttributeKey(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool visitElementBefore(md.Element element) {
|
||||||
|
_addAttributeKey(element);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void visitText(md.Text text) {
|
||||||
|
_delta.add(TextInsert(text.text, attributes: {..._attributes}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addAttributeKey(md.Element element) {
|
||||||
|
if (element.tag == 'strong') {
|
||||||
|
_attributes[BuiltInAttributeKey.bold] = true;
|
||||||
|
} else if (element.tag == 'em') {
|
||||||
|
_attributes[BuiltInAttributeKey.italic] = true;
|
||||||
|
} else if (element.tag == 'code') {
|
||||||
|
_attributes[BuiltInAttributeKey.code] = true;
|
||||||
|
} else if (element.tag == 'del') {
|
||||||
|
_attributes[BuiltInAttributeKey.strikethrough] = true;
|
||||||
|
} else if (element.tag == 'a') {
|
||||||
|
_attributes[BuiltInAttributeKey.href] = element.attributes['href'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _removeAttributeKey(md.Element element) {
|
||||||
|
if (element.tag == 'strong') {
|
||||||
|
_attributes.remove(BuiltInAttributeKey.bold);
|
||||||
|
} else if (element.tag == 'em') {
|
||||||
|
_attributes.remove(BuiltInAttributeKey.italic);
|
||||||
|
} else if (element.tag == 'code') {
|
||||||
|
_attributes.remove(BuiltInAttributeKey.code);
|
||||||
|
} else if (element.tag == 'del') {
|
||||||
|
_attributes.remove(BuiltInAttributeKey.strikethrough);
|
||||||
|
} else if (element.tag == 'a') {
|
||||||
|
_attributes.remove(BuiltInAttributeKey.href);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
|
||||||
|
class DocumentMarkdownDecoder extends Converter<String, Document> {
|
||||||
|
@override
|
||||||
|
Document convert(String input) {
|
||||||
|
final lines = input.split('\n');
|
||||||
|
final document = Document.empty();
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
for (final line in lines) {
|
||||||
|
document.insert([i++], [_convertLineToNode(line)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node _convertLineToNode(String text) {
|
||||||
|
final decoder = DeltaMarkdownDecoder();
|
||||||
|
// Heading Style
|
||||||
|
if (text.startsWith('### ')) {
|
||||||
|
return TextNode(
|
||||||
|
delta: decoder.convert(text.substring(4)),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||||
|
BuiltInAttributeKey.heading: BuiltInAttributeKey.h3,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (text.startsWith('## ')) {
|
||||||
|
return TextNode(
|
||||||
|
delta: decoder.convert(text.substring(3)),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||||
|
BuiltInAttributeKey.heading: BuiltInAttributeKey.h2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (text.startsWith('# ')) {
|
||||||
|
return TextNode(
|
||||||
|
delta: decoder.convert(text.substring(2)),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||||
|
BuiltInAttributeKey.heading: BuiltInAttributeKey.h1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (text.startsWith('- [ ] ')) {
|
||||||
|
return TextNode(
|
||||||
|
delta: decoder.convert(text.substring(6)),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
|
||||||
|
BuiltInAttributeKey.checkbox: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (text.startsWith('- [x] ')) {
|
||||||
|
return TextNode(
|
||||||
|
delta: decoder.convert(text.substring(6)),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
|
||||||
|
BuiltInAttributeKey.checkbox: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (text.startsWith('> ')) {
|
||||||
|
return TextNode(
|
||||||
|
delta: decoder.convert(text.substring(2)),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.quote,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (text.startsWith('- ') || text.startsWith('* ')) {
|
||||||
|
return TextNode(
|
||||||
|
delta: decoder.convert(text.substring(2)),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (text.startsWith('> ')) {
|
||||||
|
return TextNode(
|
||||||
|
delta: decoder.convert(text.substring(2)),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.quote,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.isNotEmpty) {
|
||||||
|
return TextNode(delta: decoder.convert(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
return TextNode(delta: Delta());
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,9 @@ library delta_markdown;
|
|||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart' show Document;
|
import 'package:appflowy_editor/src/core/document/document.dart';
|
||||||
import 'package:app_flowy/workspace/application/markdown/src/parser/markdown_encoder.dart';
|
import 'package:appflowy_editor/src/plugins/markdown/decoder/document_markdown_decoder.dart';
|
||||||
|
import 'package:appflowy_editor/src/plugins/markdown/encoder/document_markdown_encoder.dart';
|
||||||
|
|
||||||
/// Codec used to convert between Markdown and AppFlowy Editor Document.
|
/// Codec used to convert between Markdown and AppFlowy Editor Document.
|
||||||
const AppFlowyEditorMarkdownCodec _kCodec = AppFlowyEditorMarkdownCodec();
|
const AppFlowyEditorMarkdownCodec _kCodec = AppFlowyEditorMarkdownCodec();
|
||||||
@ -20,10 +21,8 @@ class AppFlowyEditorMarkdownCodec extends Codec<Document, String> {
|
|||||||
const AppFlowyEditorMarkdownCodec();
|
const AppFlowyEditorMarkdownCodec();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Converter<String, Document> get decoder => throw UnimplementedError();
|
Converter<String, Document> get decoder => DocumentMarkdownDecoder();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Converter<Document, String> get encoder {
|
Converter<Document, String> get encoder => DocumentMarkdownEncoder();
|
||||||
return AppFlowyEditorMarkdownEncoder();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
|
||||||
|
/// A [Delta] encoder that encodes a [Delta] to Markdown.
|
||||||
|
///
|
||||||
|
/// Only support inline styles, like bold, italic, underline, strike, code.
|
||||||
|
class DeltaMarkdownEncoder extends Converter<Delta, String> {
|
||||||
|
@override
|
||||||
|
String convert(Delta input) {
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
final iterator = input.iterator;
|
||||||
|
while (iterator.moveNext()) {
|
||||||
|
final op = iterator.current;
|
||||||
|
if (op is TextInsert) {
|
||||||
|
final attributes = op.attributes;
|
||||||
|
if (attributes != null) {
|
||||||
|
buffer.write(_prefixSyntax(attributes));
|
||||||
|
buffer.write(op.text);
|
||||||
|
buffer.write(_suffixSyntax(attributes));
|
||||||
|
} else {
|
||||||
|
buffer.write(op.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String _prefixSyntax(Attributes attributes) {
|
||||||
|
var syntax = '';
|
||||||
|
|
||||||
|
if (attributes[BuiltInAttributeKey.bold] == true &&
|
||||||
|
attributes[BuiltInAttributeKey.italic] == true) {
|
||||||
|
syntax += '***';
|
||||||
|
} else if (attributes[BuiltInAttributeKey.bold] == true) {
|
||||||
|
syntax += '**';
|
||||||
|
} else if (attributes[BuiltInAttributeKey.italic] == true) {
|
||||||
|
syntax += '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes[BuiltInAttributeKey.strikethrough] == true) {
|
||||||
|
syntax += '~~';
|
||||||
|
}
|
||||||
|
if (attributes[BuiltInAttributeKey.underline] == true) {
|
||||||
|
syntax += '<u>';
|
||||||
|
}
|
||||||
|
if (attributes[BuiltInAttributeKey.code] == true) {
|
||||||
|
syntax += '`';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes[BuiltInAttributeKey.href] != null) {
|
||||||
|
syntax += '[';
|
||||||
|
}
|
||||||
|
|
||||||
|
return syntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _suffixSyntax(Attributes attributes) {
|
||||||
|
var syntax = '';
|
||||||
|
|
||||||
|
if (attributes[BuiltInAttributeKey.href] != null) {
|
||||||
|
syntax += '](${attributes[BuiltInAttributeKey.href]})';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes[BuiltInAttributeKey.code] == true) {
|
||||||
|
syntax += '`';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes[BuiltInAttributeKey.underline] == true) {
|
||||||
|
syntax += '</u>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes[BuiltInAttributeKey.strikethrough] == true) {
|
||||||
|
syntax += '~~';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes[BuiltInAttributeKey.bold] == true &&
|
||||||
|
attributes[BuiltInAttributeKey.italic] == true) {
|
||||||
|
syntax += '***';
|
||||||
|
} else if (attributes[BuiltInAttributeKey.bold] == true) {
|
||||||
|
syntax += '**';
|
||||||
|
} else if (attributes[BuiltInAttributeKey.italic] == true) {
|
||||||
|
syntax += '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
return syntax;
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:app_flowy/workspace/application/markdown/src/parser/image_node_parser.dart';
|
import 'package:appflowy_editor/src/core/document/document.dart';
|
||||||
import 'package:app_flowy/workspace/application/markdown/src/parser/node_parser.dart';
|
import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/image_node_parser.dart';
|
||||||
import 'package:app_flowy/workspace/application/markdown/src/parser/text_node_parser.dart';
|
import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/node_parser.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/text_node_parser.dart';
|
||||||
|
|
||||||
class AppFlowyEditorMarkdownEncoder extends Converter<Document, String> {
|
class DocumentMarkdownEncoder extends Converter<Document, String> {
|
||||||
AppFlowyEditorMarkdownEncoder({
|
DocumentMarkdownEncoder({
|
||||||
this.parsers = const [
|
this.parsers = const [
|
||||||
TextNodeParser(),
|
TextNodeParser(),
|
||||||
ImageNodeParser(),
|
ImageNodeParser(),
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:app_flowy/workspace/application/markdown/src/parser/node_parser.dart';
|
import 'package:appflowy_editor/src/core/document/node.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/node_parser.dart';
|
||||||
|
|
||||||
class ImageNodeParser extends NodeParser {
|
class ImageNodeParser extends NodeParser {
|
||||||
const ImageNodeParser();
|
const ImageNodeParser();
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/src/core/document/node.dart';
|
||||||
|
|
||||||
abstract class NodeParser {
|
abstract class NodeParser {
|
||||||
const NodeParser();
|
const NodeParser();
|
@ -1,8 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'package:appflowy_editor/src/core/document/node.dart';
|
||||||
|
import 'package:appflowy_editor/src/core/legacy/built_in_attribute_keys.dart';
|
||||||
import 'package:app_flowy/workspace/application/markdown/delta_markdown.dart';
|
import 'package:appflowy_editor/src/plugins/markdown/encoder/delta_markdown_encoder.dart';
|
||||||
import 'package:app_flowy/workspace/application/markdown/src/parser/node_parser.dart';
|
import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/node_parser.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
|
||||||
|
|
||||||
class TextNodeParser extends NodeParser {
|
class TextNodeParser extends NodeParser {
|
||||||
const TextNodeParser();
|
const TextNodeParser();
|
||||||
@ -14,20 +13,15 @@ class TextNodeParser extends NodeParser {
|
|||||||
String transform(Node node) {
|
String transform(Node node) {
|
||||||
assert(node is TextNode);
|
assert(node is TextNode);
|
||||||
final textNode = node as TextNode;
|
final textNode = node as TextNode;
|
||||||
final delta = jsonEncode(
|
final markdown = DeltaMarkdownEncoder().convert(textNode.delta);
|
||||||
textNode.delta
|
|
||||||
..add(TextInsert('\n'))
|
|
||||||
..toJson(),
|
|
||||||
);
|
|
||||||
final markdown = deltaToMarkdown(delta);
|
|
||||||
final attributes = textNode.attributes;
|
final attributes = textNode.attributes;
|
||||||
var result = markdown;
|
var result = markdown;
|
||||||
var suffix = '';
|
var suffix = '\n';
|
||||||
if (attributes.isNotEmpty &&
|
if (attributes.isNotEmpty &&
|
||||||
attributes.containsKey(BuiltInAttributeKey.subtype)) {
|
attributes.containsKey(BuiltInAttributeKey.subtype)) {
|
||||||
final subtype = attributes[BuiltInAttributeKey.subtype];
|
final subtype = attributes[BuiltInAttributeKey.subtype];
|
||||||
if (node.next?.subtype != subtype) {
|
if (node.next == null) {
|
||||||
suffix = '\n';
|
suffix = '';
|
||||||
}
|
}
|
||||||
if (subtype == 'heading') {
|
if (subtype == 'heading') {
|
||||||
final heading = attributes[BuiltInAttributeKey.heading];
|
final heading = attributes[BuiltInAttributeKey.heading];
|
||||||
@ -46,12 +40,10 @@ class TextNodeParser extends NodeParser {
|
|||||||
}
|
}
|
||||||
} else if (subtype == 'quote') {
|
} else if (subtype == 'quote') {
|
||||||
result = '> $markdown';
|
result = '> $markdown';
|
||||||
} else if (subtype == 'code') {
|
|
||||||
result = '`$markdown`';
|
|
||||||
} else if (subtype == 'code-block') {
|
} else if (subtype == 'code-block') {
|
||||||
result = '```\n$markdown\n```';
|
result = '```\n$markdown\n```';
|
||||||
} else if (subtype == 'bulleted-list') {
|
} else if (subtype == 'bulleted-list') {
|
||||||
result = '- $markdown';
|
result = '* $markdown';
|
||||||
} else if (subtype == 'number-list') {
|
} else if (subtype == 'number-list') {
|
||||||
final number = attributes['number'];
|
final number = attributes['number'];
|
||||||
result = '$number. $markdown';
|
result = '$number. $markdown';
|
||||||
@ -62,6 +54,10 @@ class TextNodeParser extends NodeParser {
|
|||||||
result = '- [ ] $markdown';
|
result = '- [ ] $markdown';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (node.next == null) {
|
||||||
|
suffix = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return '$result$suffix';
|
return '$result$suffix';
|
||||||
}
|
}
|
@ -27,6 +27,7 @@ dependencies:
|
|||||||
intl:
|
intl:
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
markdown: ^6.0.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
group('delta_markdown_decoder.dart', () {
|
||||||
|
test('bold', () {
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('Welcome to '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownDecoder().convert('Welcome to **AppFlowy**');
|
||||||
|
expect(result, delta);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('italic', () {
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('Welcome to '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.italic: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownDecoder().convert('Welcome to _AppFlowy_');
|
||||||
|
expect(result, delta);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('strikethrough', () {
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('Welcome to '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.strikethrough: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownDecoder().convert('Welcome to ~~AppFlowy~~');
|
||||||
|
expect(result, delta);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('href', () {
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('Welcome to '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.href: 'https://appflowy.io',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownDecoder()
|
||||||
|
.convert('Welcome to [AppFlowy](https://appflowy.io)');
|
||||||
|
expect(result, delta);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('code', () {
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('Welcome to '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.code: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownDecoder().convert('Welcome to `AppFlowy`');
|
||||||
|
expect(result, delta);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('bold', () {
|
||||||
|
const markdown =
|
||||||
|
'***<u>`Welcome`</u>*** ***~~to~~*** ***[AppFlowy](https://appflowy.io)***';
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('<u>', attributes: {
|
||||||
|
BuiltInAttributeKey.italic: true,
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
}),
|
||||||
|
TextInsert('Welcome', attributes: {
|
||||||
|
BuiltInAttributeKey.code: true,
|
||||||
|
BuiltInAttributeKey.italic: true,
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
}),
|
||||||
|
TextInsert('</u>', attributes: {
|
||||||
|
BuiltInAttributeKey.italic: true,
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
}),
|
||||||
|
TextInsert(' '),
|
||||||
|
TextInsert('to', attributes: {
|
||||||
|
BuiltInAttributeKey.italic: true,
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
BuiltInAttributeKey.strikethrough: true,
|
||||||
|
}),
|
||||||
|
TextInsert(' '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.href: 'https://appflowy.io',
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
BuiltInAttributeKey.italic: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownDecoder().convert(markdown);
|
||||||
|
expect(result, delta);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:appflowy_editor/src/plugins/markdown/decoder/document_markdown_decoder.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
group('document_markdown_decoder.dart', () {
|
||||||
|
const example = '''
|
||||||
|
{
|
||||||
|
"document": {
|
||||||
|
"type": "editor",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {"subtype": "heading", "heading": "h2"},
|
||||||
|
"delta": [
|
||||||
|
{"insert": "👋 "},
|
||||||
|
{"insert": "Welcome to", "attributes": {"bold": true}},
|
||||||
|
{"insert": " "},
|
||||||
|
{
|
||||||
|
"insert": "AppFlowy Editor",
|
||||||
|
"attributes": {"italic": true, "bold": true, "href": "appflowy.io"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{"type": "text", "delta": []},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"delta": [
|
||||||
|
{"insert": "AppFlowy Editor is a "},
|
||||||
|
{"insert": "highly customizable", "attributes": {"bold": true}},
|
||||||
|
{"insert": " "},
|
||||||
|
{"insert": "rich-text editor", "attributes": {"italic": true}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {"subtype": "checkbox", "checkbox": true},
|
||||||
|
"delta": [{"insert": "Customizable"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {"subtype": "checkbox", "checkbox": true},
|
||||||
|
"delta": [{"insert": "Test-covered"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {"subtype": "checkbox", "checkbox": false},
|
||||||
|
"delta": [{"insert": "more to come!"}]
|
||||||
|
},
|
||||||
|
{"type": "text", "delta": []},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {"subtype": "quote"},
|
||||||
|
"delta": [{"insert": "Here is an example you can give a try"}]
|
||||||
|
},
|
||||||
|
{"type": "text", "delta": []},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"delta": [
|
||||||
|
{"insert": "You can also use "},
|
||||||
|
{
|
||||||
|
"insert": "AppFlowy Editor",
|
||||||
|
"attributes": {"italic": true, "bold": true}
|
||||||
|
},
|
||||||
|
{"insert": " as a component to build your own app."}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{"type": "text", "delta": []},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {"subtype": "bulleted-list"},
|
||||||
|
"delta": [{"insert": "Use / to insert blocks"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {"subtype": "bulleted-list"},
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "Select text to trigger to the toolbar to format your notes."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{"type": "text", "delta": []},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{"type": "text", "delta": []},
|
||||||
|
{"type": "text", "delta": [{"insert": ""}]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
setUpAll(() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parser document', () async {
|
||||||
|
const markdown = '''
|
||||||
|
## 👋 **Welcome to** ***[AppFlowy Editor](appflowy.io)***
|
||||||
|
|
||||||
|
AppFlowy Editor is a **highly customizable** _rich-text editor_
|
||||||
|
- [x] Customizable
|
||||||
|
- [x] Test-covered
|
||||||
|
- [ ] more to come!
|
||||||
|
|
||||||
|
> Here is an example you can give a try
|
||||||
|
|
||||||
|
You can also use ***AppFlowy Editor*** as a component to build your own app.
|
||||||
|
|
||||||
|
* Use / to insert blocks
|
||||||
|
* Select text to trigger to the toolbar to format your notes.
|
||||||
|
|
||||||
|
If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!
|
||||||
|
''';
|
||||||
|
final result = DocumentMarkdownDecoder().convert(markdown);
|
||||||
|
final data = Map<String, Object>.from(json.decode(example));
|
||||||
|
expect(result.toJson(), data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
group('delta_markdown_encoder.dart', () {
|
||||||
|
test('bold', () {
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('Welcome to '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownEncoder().convert(delta);
|
||||||
|
expect(result, 'Welcome to **AppFlowy**');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('italic', () {
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('Welcome to '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.italic: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownEncoder().convert(delta);
|
||||||
|
expect(result, 'Welcome to _AppFlowy_');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('underline', () {
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('Welcome to '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.underline: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownEncoder().convert(delta);
|
||||||
|
expect(result, 'Welcome to <u>AppFlowy</u>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('strikethrough', () {
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('Welcome to '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.strikethrough: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownEncoder().convert(delta);
|
||||||
|
expect(result, 'Welcome to ~~AppFlowy~~');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('href', () {
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('Welcome to '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.href: 'https://appflowy.io',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownEncoder().convert(delta);
|
||||||
|
expect(result, 'Welcome to [AppFlowy](https://appflowy.io)');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('code', () {
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('Welcome to '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.code: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownEncoder().convert(delta);
|
||||||
|
expect(result, 'Welcome to `AppFlowy`');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('composition', () {
|
||||||
|
final delta = Delta(operations: [
|
||||||
|
TextInsert('Welcome', attributes: {
|
||||||
|
BuiltInAttributeKey.code: true,
|
||||||
|
BuiltInAttributeKey.italic: true,
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
BuiltInAttributeKey.underline: true,
|
||||||
|
}),
|
||||||
|
TextInsert(' '),
|
||||||
|
TextInsert('to', attributes: {
|
||||||
|
BuiltInAttributeKey.italic: true,
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
BuiltInAttributeKey.strikethrough: true,
|
||||||
|
}),
|
||||||
|
TextInsert(' '),
|
||||||
|
TextInsert('AppFlowy', attributes: {
|
||||||
|
BuiltInAttributeKey.href: 'https://appflowy.io',
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
BuiltInAttributeKey.italic: true,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
final result = DeltaMarkdownEncoder().convert(delta);
|
||||||
|
expect(
|
||||||
|
result,
|
||||||
|
'***<u>`Welcome`</u>*** ***~~to~~*** ***[AppFlowy](https://appflowy.io)***',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
group('document_markdown_encoder.dart', () {
|
||||||
|
const example = '''
|
||||||
|
{
|
||||||
|
"document": {
|
||||||
|
"type": "editor",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": {
|
||||||
|
"subtype": "heading",
|
||||||
|
"heading": "h2"
|
||||||
|
},
|
||||||
|
"delta": [
|
||||||
|
{ "insert": "👋 " },
|
||||||
|
{ "insert": "Welcome to", "attributes": { "bold": true } },
|
||||||
|
{ "insert": " " },
|
||||||
|
{
|
||||||
|
"insert": "AppFlowy Editor",
|
||||||
|
"attributes": {
|
||||||
|
"href": "appflowy.io",
|
||||||
|
"italic": true,
|
||||||
|
"bold": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "type": "text", "delta": [] },
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"delta": [
|
||||||
|
{ "insert": "AppFlowy Editor is a " },
|
||||||
|
{ "insert": "highly customizable", "attributes": { "bold": true } },
|
||||||
|
{ "insert": " " },
|
||||||
|
{ "insert": "rich-text editor", "attributes": { "italic": true } },
|
||||||
|
{ "insert": " for " },
|
||||||
|
{ "insert": "Flutter", "attributes": { "underline": true } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "checkbox": true, "subtype": "checkbox" },
|
||||||
|
"delta": [{ "insert": "Customizable" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "checkbox": true, "subtype": "checkbox" },
|
||||||
|
"delta": [{ "insert": "Test-covered" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "checkbox": false, "subtype": "checkbox" },
|
||||||
|
"delta": [{ "insert": "more to come!" }]
|
||||||
|
},
|
||||||
|
{ "type": "text", "delta": [] },
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "quote" },
|
||||||
|
"delta": [{ "insert": "Here is an example you can give a try" }]
|
||||||
|
},
|
||||||
|
{ "type": "text", "delta": [] },
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"delta": [
|
||||||
|
{ "insert": "You can also use " },
|
||||||
|
{
|
||||||
|
"insert": "AppFlowy Editor",
|
||||||
|
"attributes": {
|
||||||
|
"italic": true,
|
||||||
|
"bold": true,
|
||||||
|
"backgroundColor": "0x6000BCF0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "insert": " as a component to build your own app." }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "type": "text", "delta": [] },
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "bulleted-list" },
|
||||||
|
"delta": [{ "insert": "Use / to insert blocks" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"attributes": { "subtype": "bulleted-list" },
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "Select text to trigger to the toolbar to format your notes."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "type": "text", "delta": [] },
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"delta": [
|
||||||
|
{
|
||||||
|
"insert": "If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
setUpAll(() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parser document', () async {
|
||||||
|
final data = Map<String, Object>.from(json.decode(example));
|
||||||
|
final document = Document.fromJson(data);
|
||||||
|
final result = DocumentMarkdownEncoder().convert(document);
|
||||||
|
expect(result, '''
|
||||||
|
## 👋 **Welcome to** ***[AppFlowy Editor](appflowy.io)***
|
||||||
|
|
||||||
|
AppFlowy Editor is a **highly customizable** _rich-text editor_ for <u>Flutter</u>
|
||||||
|
- [x] Customizable
|
||||||
|
- [x] Test-covered
|
||||||
|
- [ ] more to come!
|
||||||
|
|
||||||
|
> Here is an example you can give a try
|
||||||
|
|
||||||
|
You can also use ***AppFlowy Editor*** as a component to build your own app.
|
||||||
|
|
||||||
|
* Use / to insert blocks
|
||||||
|
* Select text to trigger to the toolbar to format your notes.
|
||||||
|
|
||||||
|
If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!''');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
group('image_node_parser.dart', () {
|
||||||
|
test('parser image node', () {
|
||||||
|
final node = Node(
|
||||||
|
type: 'image',
|
||||||
|
attributes: {
|
||||||
|
'image_src': 'https://appflowy.io',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final result = const ImageNodeParser().transform(node);
|
||||||
|
expect(result, '');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
group('text_node_parser.dart', () {
|
||||||
|
const text = 'Welcome to AppFlowy';
|
||||||
|
|
||||||
|
test('heading style', () {
|
||||||
|
final h1 = TextNode(
|
||||||
|
delta: Delta(operations: [TextInsert(text)]),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||||
|
BuiltInAttributeKey.heading: BuiltInAttributeKey.h1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final h2 = TextNode(
|
||||||
|
delta: Delta(operations: [TextInsert(text)]),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||||
|
BuiltInAttributeKey.heading: BuiltInAttributeKey.h2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final h3 = TextNode(
|
||||||
|
delta: Delta(operations: [TextInsert(text)]),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||||
|
BuiltInAttributeKey.heading: BuiltInAttributeKey.h3,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(const TextNodeParser().transform(h1), '# $text');
|
||||||
|
expect(const TextNodeParser().transform(h2), '## $text');
|
||||||
|
expect(const TextNodeParser().transform(h3), '### $text');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('bulleted list style', () {
|
||||||
|
final node = TextNode(
|
||||||
|
delta: Delta(operations: [TextInsert(text)]),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(const TextNodeParser().transform(node), '* $text');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('number list style', () {
|
||||||
|
final node = TextNode(
|
||||||
|
delta: Delta(operations: [TextInsert(text)]),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.numberList,
|
||||||
|
BuiltInAttributeKey.number: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(const TextNodeParser().transform(node), '1. $text');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('checkbox style', () {
|
||||||
|
final checkbox = TextNode(
|
||||||
|
delta: Delta(operations: [TextInsert(text)]),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
|
||||||
|
BuiltInAttributeKey.checkbox: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final unCheckbox = TextNode(
|
||||||
|
delta: Delta(operations: [TextInsert(text)]),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
|
||||||
|
BuiltInAttributeKey.checkbox: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(const TextNodeParser().transform(checkbox), '- [x] $text');
|
||||||
|
expect(const TextNodeParser().transform(unCheckbox), '- [ ] $text');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('quote style', () {
|
||||||
|
final node = TextNode(
|
||||||
|
delta: Delta(operations: [TextInsert(text)]),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.quote,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(const TextNodeParser().transform(node), '> $text');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('code block style', () {
|
||||||
|
final node = TextNode(
|
||||||
|
delta: Delta(operations: [TextInsert(text)]),
|
||||||
|
attributes: {
|
||||||
|
BuiltInAttributeKey.subtype: 'code-block',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(const TextNodeParser().transform(node), '```\n$text\n```');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -773,6 +773,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
markdown:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: markdown
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.1"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user