From 8a94bbd60285f0319fa584bc7809e027e8d6289a Mon Sep 17 00:00:00 2001 From: Jed Aureus Gonzales Date: Sat, 15 Oct 2022 17:04:02 +0800 Subject: [PATCH] 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 Co-authored-by: Lucas.Xu Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com> --- .../markdown_syntax_to_styled_text.dart | 99 +++++++++++-------- .../built_in_shortcut_events.dart | 5 +- 2 files changed, 62 insertions(+), 42 deletions(-) diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart index 259299b964..1b37e5fa11 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart @@ -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(); @@ -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; }; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart index b07e8bde17..15cbb42517 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart @@ -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 builtInShortcutEvents = [ ShortcutEvent( key: 'Move cursor up', @@ -273,9 +272,9 @@ List 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,