Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: paste links #353

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ CommandShortcutEventHandler _pasteCommandHandler = (editorState) {
if (html != null && html.isNotEmpty) {
pasteHTML(editorState, html);
} else if (text != null && text.isNotEmpty) {
handlePastePlainText(editorState, data.text!);
handlePastePlainText(editorState, text);
}
}();

Expand Down
70 changes: 43 additions & 27 deletions lib/src/plugins/markdown/decoder/document_markdown_decoder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ class DocumentMarkdownDecoder extends Converter<String, Document> {
final htmlRegex = RegExp('^(http|https)://');
final numberedlistRegex = RegExp(r'^\d+\. ');

// Reference: https://stackoverflow.com/a/6041965
final linkRegExp = RegExp(
r"(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])",
);

@override
Document convert(String input) {
final lines = input.split('\n');
Expand Down Expand Up @@ -62,36 +67,32 @@ class DocumentMarkdownDecoder extends Converter<String, Document> {
if (line.startsWith('### ')) {
return headingNode(
level: 3,
attributes: {'delta': decoder.convert(line.substring(4)).toJson()},
delta: decoder.convert(line.substring(4)),
);
} else if (line.startsWith('## ')) {
return headingNode(
level: 2,
attributes: {'delta': decoder.convert(line.substring(3)).toJson()},
delta: decoder.convert(line.substring(3)),
);
} else if (line.startsWith('# ')) {
return headingNode(
level: 1,
attributes: {'delta': decoder.convert(line.substring(2)).toJson()},
delta: decoder.convert(line.substring(2)),
);
} else if (line.startsWith('- [ ] ')) {
return todoListNode(
checked: false,
attributes: {'delta': decoder.convert(line.substring(6)).toJson()},
delta: decoder.convert(line.substring(6)),
);
} else if (line.startsWith('- [x] ')) {
return todoListNode(
checked: true,
attributes: {'delta': decoder.convert(line.substring(6)).toJson()},
delta: decoder.convert(line.substring(6)),
);
} else if (line.startsWith('> ')) {
return quoteNode(
attributes: {'delta': decoder.convert(line.substring(2)).toJson()},
);
return quoteNode(delta: decoder.convert(line.substring(2)));
} else if (line.startsWith('- ') || line.startsWith('* ')) {
return bulletedListNode(
attributes: {'delta': decoder.convert(line.substring(2)).toJson()},
);
return bulletedListNode(delta: decoder.convert(line.substring(2)));
} else if (line.isNotEmpty && RegExp('^-*').stringMatch(line) == line) {
return Node(type: 'divider');
} else if (line.startsWith('```') && line.endsWith('```')) {
Expand All @@ -112,22 +113,18 @@ class DocumentMarkdownDecoder extends Converter<String, Document> {
}
} else if (numberedlistRegex.hasMatch(line)) {
return numberedListNode(
attributes: {
'delta':
decoder.convert(line.substring(line.indexOf('.') + 1)).toJson()
},
delta: decoder.convert(line.substring(line.indexOf('.') + 1)),
);
} else if (linkRegExp.hasMatch(line)) {
final delta = deltaFromLineWithLinks(line);
return paragraphNode(delta: delta);
}

if (line.isNotEmpty) {
return paragraphNode(
attributes: {'delta': decoder.convert(line).toJson()},
);
return paragraphNode(delta: decoder.convert(line));
}

return paragraphNode(
attributes: {'delta': Delta().toJson()},
);
return paragraphNode(delta: Delta());
}

String? extractImagePath(String text) {
Expand All @@ -140,6 +137,29 @@ class DocumentMarkdownDecoder extends Converter<String, Document> {
return match?.group(1);
}

Delta deltaFromLineWithLinks(String line, [Delta? delta]) {
final matches = linkRegExp.allMatches(line).toList();
final nodeDelta = delta ?? Delta();

int lastUrlOffset = 0;
for (final match in matches) {
if (lastUrlOffset < match.start) {
nodeDelta.insert(line.substring(lastUrlOffset, match.start));
}

final link = line.substring(match.start, match.end);
nodeDelta.insert(link, attributes: {AppFlowyRichTextKeys.href: link});
lastUrlOffset = match.end;
}

final lastMatch = matches.last;
if (lastMatch.end - 1 < line.length - 1) {
nodeDelta.insert(line.substring(lastMatch.end));
}

return nodeDelta;
}

Node _codeBlockNodeFromMarkdown(
String markdown,
DeltaMarkdownDecoder decoder,
Expand All @@ -149,9 +169,7 @@ class DocumentMarkdownDecoder extends Converter<String, Document> {
// so this is treated like a normal paragraph
if (!markdown.contains('\n') &&
markdown.split('`').length - 1 == markdown.length) {
return paragraphNode(
attributes: {'delta': decoder.convert(markdown).toJson()},
);
return paragraphNode(delta: decoder.convert(markdown));
}
const codeMarker = '```';
int codeStartIndex = markdown.indexOf(codeMarker);
Expand All @@ -162,9 +180,7 @@ class DocumentMarkdownDecoder extends Converter<String, Document> {
// This if condition is for handling cases like ```\n`
// In this case codeStartIndex = 0 and codeEndIndex = -1
if (codeEndIndex < codeStartIndex) {
return paragraphNode(
attributes: {'delta': decoder.convert(markdown).toJson()},
);
return paragraphNode(delta: decoder.convert(markdown));
}
String codeBlock = markdown.substring(
codeStartIndex + codeMarker.length,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/plugins/markdown/decoder/document_markdown_decoder.dart';
import 'package:flutter/widgets.dart';

int _textLengthOfNode(Node node) => node.delta?.length ?? 0;
Expand All @@ -9,15 +10,27 @@ void _pasteSingleLine(
String line,
) {
assert(selection.isCollapsed);

final markdownDecoder = DocumentMarkdownDecoder();
final transaction = editorState.transaction;
final node = editorState.getNodeAtPath(selection.end.path)!;
final transaction = editorState.transaction
..insertText(node, selection.startIndex, line)
..afterSelection = (Selection.collapsed(
Position(
path: selection.end.path,
offset: selection.startIndex + line.length,
),
));

if (markdownDecoder.linkRegExp.hasMatch(line)) {
final delta = markdownDecoder.deltaFromLineWithLinks(line, node.delta);
transaction.insertNode(
node.path,
paragraphNode(delta: delta),
);
} else {
transaction.insertText(node, selection.startIndex, line);
}

transaction.afterSelection = Selection.collapsed(
Position(
path: selection.end.path,
offset: selection.startIndex + line.length,
),
);
editorState.apply(transaction);
}

Expand Down