feat: added support for markdown to link an image (#1277)

* feat: added support for markdown to link an image

#1064

* fix: fixed failing FlowyEditor test

#1064

* chore: add documentation

* chore: add documentation

* chore: update locale

* Refactor/rename crate (#1275)

* chore: addressed review comments

#1064

* chore: removed unused import

* fix: compile error and wrong image attributes

Co-authored-by: appflowy <annie@appflowy.io>
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com>
This commit is contained in:
Jed Aureus Gonzales 2022-10-15 17:04:02 +08:00 committed by GitHub
parent f53c2567fc
commit 8a94bbd602
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 62 additions and 42 deletions

View File

@ -188,9 +188,7 @@ ShortcutEventHandler doubleTildeToStrikethrough = (editorState, event) {
return KeyEventResult.handled;
};
/// To create a link, enclose the link text in brackets (e.g., [link text]).
/// Then, immediately follow it with the URL in parentheses (e.g., (https://example.com)).
ShortcutEventHandler markdownLinkToLinkHandler = (editorState, event) {
ShortcutEventHandler markdownLinkOrImageHandler = (editorState, event) {
final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value;
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
@ -198,48 +196,72 @@ ShortcutEventHandler markdownLinkToLinkHandler = (editorState, event) {
return KeyEventResult.ignored;
}
// find all of the indexs for important characters
// Find all of the indexes of the relevant characters
final textNode = textNodes.first;
final text = textNode.toPlainText();
final firstExclamation = text.indexOf('!');
final firstOpeningBracket = text.indexOf('[');
final firstClosingBracket = text.indexOf(']');
// use regex to validate the format of the link
// note: this enforces that the link has http or https
final regexp = RegExp(r'\[([\w\s\d]+)\]\(((?:\/|https?:\/\/)[\w\d./?=#]+)$');
final match = regexp.firstMatch(text);
if (match == null) {
// Use RegEx to determine whether it's an image or a link
// Difference between image and link syntax is that image
// has an exclamation point at the beginning.
// Note: The RegEx enforces that the URL has http or https
final imgRegEx =
RegExp(r'\!\[([\w\s\d]+)\]\(((?:\/|https?:\/\/)[\w\d-./?=#%&]+)$');
final lnkRegEx =
RegExp(r'\[([\w\s\d]+)\]\(((?:\/|https?:\/\/)[\w\d-./?=#%&]+)$');
if (imgRegEx.firstMatch(text) != null) {
// Extract the alt text and the URL of the image
final match = lnkRegEx.firstMatch(text);
final imgUrl = match?.group(2);
// Delete the text and replace it with the image pointed to by the URL
final transaction = editorState.transaction
..deleteText(textNode, firstExclamation, text.length)
..insertNode(
textNode.path,
Node.fromJson({
'type': 'image',
'attributes': {
'image_src': imgUrl,
'align': 'center',
}
}));
editorState.apply(transaction);
} else if (lnkRegEx.firstMatch(text) != null) {
// Extract the text and the URL of the link
final match = lnkRegEx.firstMatch(text);
final linkText = match?.group(1);
final linkUrl = match?.group(2);
// Delete the initial opening bracket,
// update the href attribute of the text surrounded by [ ] to the url,
// delete everything after the text,
// and update the cursor position.
final transaction = editorState.transaction
..deleteText(textNode, firstOpeningBracket, 1)
..formatText(
textNode,
firstOpeningBracket,
firstClosingBracket - firstOpeningBracket - 1,
{
BuiltInAttributeKey.href: linkUrl,
},
)
..deleteText(textNode, firstClosingBracket - 1,
selection.end.offset - firstClosingBracket)
..afterSelection = Selection.collapsed(
Position(
path: textNode.path,
offset: firstOpeningBracket + linkText!.length,
),
);
editorState.apply(transaction);
} else {
return KeyEventResult.ignored;
}
// extract the text and the url of the link
final linkText = match.group(1);
final linkUrl = match.group(2);
// Delete the initial opening bracket,
// update the href attribute of the text surrounded by [ ] to the url,
// delete everything after the text,
// and update the cursor position.
final transaction = editorState.transaction
..deleteText(textNode, firstOpeningBracket, 1)
..formatText(
textNode,
firstOpeningBracket,
firstClosingBracket - firstOpeningBracket - 1,
{
BuiltInAttributeKey.href: linkUrl,
},
)
..deleteText(textNode, firstClosingBracket - 1,
selection.end.offset - firstClosingBracket)
..afterSelection = Selection.collapsed(
Position(
path: textNode.path,
offset: firstOpeningBracket + linkText!.length,
),
);
editorState.apply(transaction);
return KeyEventResult.handled;
};
@ -369,6 +391,5 @@ ShortcutEventHandler doubleUnderscoresToBold = (editorState, event) {
),
);
editorState.apply(transaction);
return KeyEventResult.handled;
};

View File

@ -16,7 +16,6 @@ import 'package:appflowy_editor/src/service/internal_key_event_handlers/whitespa
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event.dart';
import 'package:flutter/foundation.dart';
//
List<ShortcutEvent> builtInShortcutEvents = [
ShortcutEvent(
key: 'Move cursor up',
@ -273,9 +272,9 @@ List<ShortcutEvent> builtInShortcutEvents = [
handler: doubleTildeToStrikethrough,
),
ShortcutEvent(
key: 'Markdown link to link',
key: 'Markdown link or image',
command: 'shift+parenthesis right',
handler: markdownLinkToLinkHandler,
handler: markdownLinkOrImageHandler,
),
// https://github.com/flutter/flutter/issues/104944
// Workaround: Using space editing on the web platform often results in errors,