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

feat: revamp link preivew #7692

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

asjqkkkk
Copy link
Collaborator

@asjqkkkk asjqkkkk commented Apr 7, 2025

  • Type Selection
    • Paste as menu (URL, Mention, Bookmark, Embed)
  • URL
    • Convert to menu(Mention, Bookmark, Embed)
  • Mention
    • Mention link UI(loading, complete)
    • Hover to show the preview menu
      • Internal links preview
      • External links preview
    • Implement Convert to menu(URL, Bookmark, Embed, Remove Link)
  • Bookmark
    • Bookmark UI(default, complete)
    • More options menu(Mention, URL, Embed, Copy Link, Replace, Reload, Remove Link)
  • Embed
    • Embed UI(default, complete)
    • More options menu(Alignment, Open link, Replace, Reload, Remove Link)
      • Alignment
    • Convert to menu(Mention, URL, Bookmark)
    • Fullscreen

Feature Preview


PR Checklist

  • My code adheres to AppFlowy's Conventions
  • I've listed at least one issue that this PR fixes in the description above.
  • I've added a test(s) to validate changes in this PR, or this PR only contains semantic changes.
  • All existing tests are passing.

Summary by Sourcery

Revamp the link preview functionality in the document editor, introducing a more flexible and feature-rich link preview system with enhanced UI and interaction capabilities.

New Features:

  • Implement a new link preview menu with multiple conversion options
  • Add a 'Paste as' menu for link types
  • Introduce enhanced link preview UI with loading and error states

Enhancements:

  • Improve link preview component with more robust handling of different link states
  • Add more interactive menu options for link preview
  • Enhance link preview styling and responsiveness

Chores:

  • Refactor link preview and embed block components
  • Update theme and styling configurations

Summary by Sourcery

Revamp the link preview functionality in the document editor, introducing a more flexible and feature-rich link preview system with enhanced UI and interaction capabilities.

New Features:

  • Implement a comprehensive link preview menu with multiple conversion options
  • Add 'Paste as' menu for different link types (URL, Mention, Bookmark, Embed)
  • Introduce enhanced link preview UI with loading and error states
  • Add support for external link mentions

Enhancements:

  • Improve link preview component with more robust handling of different link states
  • Add interactive menu options for link preview
  • Enhance link preview styling and responsiveness
  • Implement flexible link conversion between different types

Chores:

  • Refactor link preview and embed block components
  • Update theme and styling configurations
  • Improve link parsing and caching mechanisms

Copy link

sourcery-ai bot commented Apr 7, 2025

Reviewer's Guide by Sourcery

This pull request revamps the link preview functionality in the document editor, introducing a more flexible and feature-rich link preview system with enhanced UI and interaction capabilities. It includes new features such as a 'Paste as' menu for link types, a new link preview menu with multiple conversion options, and enhanced link preview UI with loading and error states. Enhancements include improved link preview component with more robust handling of different link states, more interactive menu options for link preview, and enhanced link preview styling and responsiveness. Chores include refactoring link preview and embed block components and updating theme and styling configurations.

Sequence diagram for converting URL preview to Mention

sequenceDiagram
    participant User
    participant EditorState
    participant Transaction
    participant Node

    User->>EditorState: Initiates convertUrlPreviewNodeToMention(editorState, node)
    EditorState->>Transaction: Creates a new transaction
    Transaction->>Transaction: Inserts MentionBlock node with URL
    Transaction->>Transaction: Deletes the original URL preview node
    EditorState->>Transaction: Applies the transaction
    Transaction-->>EditorState: Transaction applied
    EditorState-->>User: Updates the editor with the MentionBlock
Loading

Sequence diagram for converting URL to Link Preview

sequenceDiagram
    participant User
    participant EditorState
    participant Transaction
    participant Node

    User->>EditorState: Initiates convertUrlToLinkPreview(editorState, selection, url)
    EditorState->>Node: getNodeAtPath(selection.end.path)
    EditorState->>Transaction: Creates a new transaction
    Transaction->>Transaction: Deletes the original node
    Transaction->>Transaction: Inserts linkPreviewNode with URL
    EditorState->>Transaction: Applies the transaction
    Transaction-->>EditorState: Transaction applied
    EditorState-->>User: Updates the editor with the Link Preview
Loading

File-Level Changes

Change Details Files
Implements a custom link preview menu with options to convert the link to a mention, URL, or embed, copy the link, replace the link, reload the preview, or remove the link.
  • Created a new CustomLinkPreviewMenu widget.
  • Added LinkPreviewMenuCommand enum to define available menu options.
  • Implemented the onTap method to handle menu item selection and execute corresponding actions.
  • Added AppFlowyPopover to display the menu.
  • Added FlowyIconButton to trigger the menu.
  • Added methods to show and close the popover menu.
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_preview_menu.dart
Adds functionality to convert URLs to link previews, mentions, or remove them entirely, including updating the editor state and applying transactions.
  • Added convertUrlPreviewNodeToLink function to convert a URL preview node to a regular link.
  • Added convertUrlPreviewNodeToMention function to convert a URL preview node to a mention.
  • Added removeUrlPreviewLink function to remove a URL preview link and insert the URL as plain text.
  • Added convertUrlToLinkPreview function to convert a URL to a link preview node.
  • Added convertUrlToMention function to convert a URL to a mention node.
  • Added convertLinkBlockToOtherLinkBlock function to convert a link block to another type of link block.
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/shared.dart
Enhances the link hover menu with a convert to option, allowing users to change links to mentions or bookmarks.
  • Added onConvertTo callback to the LinkHoverMenu widget.
  • Implemented convertLinkTo method in _LinkHoverTriggerState to handle link conversion.
  • Added LinkConvertMenuCommand enum to define available conversion options.
  • Added a convert button to the link hover menu.
  • Added a convert menu to the link hover menu.
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart
Improves the UI of the link preview widget with enhanced styling, loading/error states, and hover effects.
  • Added isHovering and status parameters to CustomLinkPreviewWidget.
  • Updated the UI to display loading and error states.
  • Enhanced the styling and responsiveness of the link preview.
  • Added a border to the link preview.
  • Added an image to the link preview.
  • Added a title and description to the link preview.
  • Added a URL to the link preview.
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart
Adds a paste as menu to allow users to select the type of content to paste (URL, Mention, Bookmark, Embed).
  • Created a PasteAsMenuService to manage the display and dismissal of the menu.
  • Implemented a PasteAsMenu widget to display the available paste options.
  • Added PasteMenuType enum to define the available paste options.
  • Added logic to convert the pasted content based on the selected option.
  • Added keyboard navigation to the paste as menu.
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart
Introduces a mention link block to display external links as mentions with preview information.
  • Created a MentionLinkBlock widget to display external links as mentions.
  • Implemented logic to fetch and display preview information for the link.
  • Added a popover menu to the mention link block with options to copy, convert, or remove the link.
  • Added loading and error states to the mention link block.
  • Added a custom link parser to fetch link information.
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_block.dart
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_link_block.dart
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_link_error_preview.dart
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_link_preview.dart
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_parser.dart
Adds support for embedding links, allowing users to display a rich preview of the linked content directly within the editor.
  • Created a LinkEmbedBlockComponent to handle the display of embedded links.
  • Implemented a LinkEmbedMenu to provide options for interacting with embedded links.
  • Added logic to fetch and display preview information for the embedded link.
  • Added loading and error states to the embed block.
  • Added a custom link parser to fetch link information.
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_embed/link_embed_menu.dart
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart
Implements a replace menu for links, allowing users to easily update the URL associated with a link.
  • Created a LinkReplaceMenu widget to display the replace menu.
  • Added logic to validate the new URL.
  • Added a button to submit the new URL.
  • Added a callback to handle the submission of the new URL.
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_replace_menu.dart
Refactors the link preview block component to support both standard link previews and embedded links.
  • Created a CustomLinkPreviewBlockComponentBuilder to build the appropriate link preview component based on the node attributes.
  • Updated the build method to return either a LinkEmbedBlockComponent or a CustomLinkPreviewBlockComponent based on the previewType attribute.
  • Updated the validate method to ensure that the URL attribute is not empty.
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview_block_component.dart

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!
  • Generate a plan of action for an issue: Comment @sourcery-ai plan on
    an issue to generate a plan of action for it.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@asjqkkkk asjqkkkk marked this pull request as draft April 7, 2025 01:33
Copy link

github-actions bot commented Apr 7, 2025

🥷 Ninja i18n – 🛎️ Translations need to be updated

Project /project.inlang

lint rule new reports level link
Missing translation 464 warning contribute (via Fink 🐦)

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @asjqkkkk - I've reviewed your changes - here's some feedback:

Overall Comments:

  • Consider adding comments to the new methods to explain their purpose.
  • The diff is quite large; consider breaking it down into smaller, more manageable PRs.
Here's what I looked at during the review
  • 🟢 General issues: all looks good
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@asjqkkkk asjqkkkk marked this pull request as ready for review April 8, 2025 14:07
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @asjqkkkk - I've reviewed your changes - here's some feedback:

Overall Comments:

  • Consider extracting some of the complex widget building logic into separate methods for better readability.
  • The number of popover controllers might be excessive; consider consolidating them where possible.
Here's what I looked at during the review
  • 🟡 General issues: 3 issues found
  • 🟢 Security: all looks good
  • 🟡 Testing: 1 issue found
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@@ -41,6 +43,13 @@ extension PasteFromPlainText on EditorState {
}
if (nodes.length == 1) {
await pasteSingleLineNode(nodes.first);
final href = _getLinkFromNode(nodes.first);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Centralize link extraction logic in paste handling.

Introducing the _getLinkFromNode helper improves modularity when obtaining a hyperlink from a node. Check that this logic covers all necessary URL formats for pasted content.

Suggested implementation:

String? _getLinkFromNode(DocumentNode node) {
  // Extract trimmed content from the node
  final text = node.content.trim();
  // Try to parse the content as a valid URL
  final uri = Uri.tryParse(text);
  if (uri != null && (uri.hasScheme || uri.host.isNotEmpty)) {
    return text;
  }
  // Fallback: use regex extraction to cover common URL formats (e.g., if the scheme is missing)
  final regex = RegExp(r'(https?://[^\s]+)');
  final match = regex.firstMatch(text);
  if (match != null) {
    return match.group(0);
  }
  // Return null if no URL is found
  return null;
}

      final href = _getLinkFromNode(nodes.first); // Centralized link extraction for pasted content

Ensure that:

  1. The DocumentNode type (or similar) used in _getLinkFromNode has a proper "content" property representing its text.
  2. The regular expression used covers all necessary URL formats in your application’s context; update the regex if additional URL patterns need to be supported.
  3. This helper is placed in a location in the file where it is accessible by pastePlainText.

),
tooltipText: LocaleKeys.editor_copyLink.tr(),
preferBelow: false,
onPressed: () => copyLink(context),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Review command mapping in LinkEmbedMenu for clarity.

The LinkEmbedMenu now offers multiple actions such as copy, turn into, and more options. Double-check that the menus (turn-into and more options) are intuitive for users and that their corresponding commands are clearly mapped.

import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';

extension MenuExtension on EditorState {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Validate flexible menu positioning logic.

The MenuExtension now calculates menu offsets based on the editor's dimensions and selection rectangles. Please review the calculations to ensure they produce the intended positioning across various screen sizes.

Suggested implementation:

  late Rect startRect;
  if (rect != null) {
    startRect = rect;
  } else {
    // Fallback to the first selection rect if available; otherwise, use Rect.zero
    startRect = selectionRects.isNotEmpty ? selectionRects.first : Rect.zero;
  }

  // Obtain the screen size from context for flexible positioning
  final BuildContext context = this.context;
  final Size screenSize = MediaQuery.of(context).size;

    final double dxCandidate = startRect.left + menuOffset.dx;
    final double dyCandidate = startRect.bottom + menuOffset.dy;

    // Adjust horizontal position if the menu exceeds screen boundaries
    final double dx = (dxCandidate + menuWidth > screenSize.width)
        ? screenSize.width - menuWidth
        : dxCandidate;

    // Adjust vertical position: if the menu overshoots the bottom, try positioning it above the selection
    double dy = dyCandidate;
    if (dyCandidate + menuHeight > screenSize.height) {
      dy = (startRect.top - menuHeight - menuOffset.dy >= 0)
          ? startRect.top - menuHeight - menuOffset.dy
          : screenSize.height - menuHeight;
    }

    return MenuPosition(offset: Offset(dx, dy));

Note:

  1. Ensure that EditorState indeed has access to a BuildContext (as is the case with State subclasses in Flutter). If this is not true, you may need to pass down a BuildContext to this method.
  2. Test the menu positioning on various screen sizes to verify that the fallback logic works as intended.

// ignore: depend_on_referenced_packages
import 'package:flutter_link_previewer/flutter_link_previewer.dart' hide Size;

class LinkParser {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Caching link preview data to optimize performance.

The implementation of LinkParser now leverages a caching mechanism (using KeyValueStorage) to reduce redundant network calls. Ensure that cache invalidation or staleness checks are handled appropriately in long-running sessions.

Suggested implementation:

  Future<void> start(String url) async {
    final data = await _cache.get(url);
    if (data != null && !_isStale(data)) {
      refreshLinkInfo(data);
    }
    await _getLinkInfo(url);
  }

  // Add a helper to check if the cached LinkInfo is stale.
  // Adjust this method if your LinkInfo model uses a different timestamp property.
  bool _isStale(LinkInfo data) {
    const Duration cacheExpiryDuration = Duration(minutes: 10);
    return DateTime.now().difference(data.timestamp) > cacheExpiryDuration;
  }

  Future<LinkInfo?> _getLinkInfo(String url) async {

Ensure that your LinkInfo class includes a timestamp (for example, a property named "timestamp" of type DateTime) that records when it was fetched. You may need to update the LinkInfoCache to store this timestamp when caching data. Adjust the cacheExpiryDuration as needed for your application.

@asjqkkkk asjqkkkk force-pushed the feat/link_preview branch from 8ddb486 to c5fe9fc Compare April 8, 2025 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant