Skip to content

Conversation

FredrikWallstrom
Copy link
Contributor

@FredrikWallstrom FredrikWallstrom commented May 20, 2025

Fixes https://github.com/Lundalogik/crm-client/issues/24

Summary by CodeRabbit

  • New Features

    • Enhanced link handling in the text editor and Markdown parser, including improved detection and processing of multiple links when pasting text.
    • Added support for security-related link attributes (target, rel, referrerpolicy) for external links in both the editor and rendered Markdown.
  • Bug Fixes

    • Improved accuracy in distinguishing between internal and external links, ensuring correct behavior and security attributes.
  • Refactor

    • Streamlined link command logic and centralized link attribute creation for consistency.
    • Replaced external dependency for link processing with a custom solution.
  • Tests

    • Added comprehensive tests for link attribute generation, covering various URL scenarios.
  • Chores

    • Removed an unused external package related to link processing.

How to Test

This PR improves the link handling in the text editor, particularly when pasting content with URLs. Here's how to test these enhancements:

  1. Multiple links in one paste operation

  2. Links with surrounding text

    • Copy text with URLs embedded in sentences
    • Paste into the editor
    • Confirm that only the URLs become links while surrounding text remains plain
  3. Test URLs with query parameters, hashes, and paths (e.g., "https://example.com/path?query=value#section")

    • Verify the entire URL becomes a link with no truncation
  4. Trailing text after links

    • Copy text ending with plain text after a URL
    • Confirm the text after the link remains unlinked
  5. Testing Email Links

    • Copy text containing mailto links (e.g., "Contact us at mailto:[email protected]")
    • Verify they become clickable email links
  6. Testing Phone links

    • Copy text with tel links (e.g., "Call us at tel:+1234567890")
    • Verify they become clickable phone links
  7. Testing Colon Handling

    • Copy text containing colons but not as part of a URL (e.g., "Time: 3:00 PM")
    • Paste into the editor
    • Verify the text with colons is not incorrectly converted to links
  8. External vs. Internal links

  9. Command/Ctrl+Click behavior

    • Test Cmd/Ctrl+clicking on newly pasted links
    • Verify they open in a new tab as expected
  10. Paste link over selection

    • If you mark a word in the text-editor and paste a single link, your selection should be kept and converted to a link.
  11. Play around on your own with pasting/creating links, make sure everything works as you would expect.

Review:

  • Commits are atomic
  • Commits have the correct type for the changes made
  • Commits with breaking changes are marked as such

Browsers tested:

(Check any that applies, it's ok to leave boxes unchecked if testing something didn't seem relevant.)

Windows:

  • Chrome
  • Edge
  • Firefox

Linux:

  • Chrome
  • Firefox

macOS:

  • Chrome
  • Firefox
  • Safari

Mobile:

  • Chrome on Android
  • iOS

@Copilot Copilot AI review requested due to automatic review settings May 20, 2025 12:46
Copy link

coderabbitai bot commented May 20, 2025

📝 Walkthrough

Walkthrough

This update refactors and extends link handling across the text editor and markdown parser. It introduces a centralized link attribute utility, enhances link mark specification and paste handling in ProseMirror, replaces an external markdown plugin with a custom one, and updates schema integration and tests. Redundant code and dependencies are removed.

Changes

Files/Paths Change Summary
src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts Removed the isValidUrl utility, the copyPasteLinkCommand, and its keymap binding. Refactored createInsertLinkCommand to use the new getLinkAttributes utility for link mark attribute creation.
package.json Removed rehype-external-links from devDependencies.
src/components/markdown/link-markdown-plugin.ts Added a custom unified.js plugin (createLinksPlugin) to enhance anchor elements with security attributes using getLinkAttributes.
src/components/markdown/markdown-parser.ts Replaced rehype-external-links with the custom createLinksPlugin. Updated the sanitization whitelist to allow the referrerpolicy attribute on <a> tags.
src/components/text-editor/prosemirror-adapter/plugins/link/link-mark-spec.ts Introduced a ProseMirror mark specification for links, defining supported attributes and logic for parsing and serializing link marks with security attributes.
src/components/text-editor/prosemirror-adapter/plugins/link/link-plugin.ts Refactored paste event handling to detect and process multiple links in pasted text, using new helper functions and getLinkAttributes. Improved modularity and expanded link handling capabilities.
src/components/text-editor/prosemirror-adapter/plugins/link/utils.ts Added a utility module providing getLinkAttributes for consistent link attribute generation and isExternalUrl for external link detection.
src/components/text-editor/prosemirror-adapter/plugins/link/utils.spec.ts Added tests for getLinkAttributes, covering various URL types and ensuring correct attribute assignment for internal and external links.
src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx Updated imports for link plugin and mark spec. Integrated the new link mark specification into the ProseMirror schema.
src/design-guidelines/text-editor/menu-command.md Refactored the createInsertLinkCommand implementation to centralize link mark creation, improving code clarity without changing external behavior.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Editor
    participant LinkUtils
    participant MarkdownPlugin

    User->>Editor: Paste text (may contain links)
    Editor->>LinkUtils: Parse text, detect links, generate attributes
    LinkUtils-->>Editor: Return nodes with appropriate link marks
    Editor->>Editor: Insert nodes (linked text and plain text) into document

    User->>Editor: Insert or edit link
    Editor->>LinkUtils: getLinkAttributes(url, title)
    LinkUtils-->>Editor: Return link attributes (target, rel, etc.)
    Editor->>Editor: Apply link mark to selection

    User->>MarkdownPlugin: Render markdown with links
    MarkdownPlugin->>LinkUtils: getLinkAttributes(href, title)
    LinkUtils-->>MarkdownPlugin: Return attributes
    MarkdownPlugin->>MarkdownPlugin: Add attributes to <a> elements
Loading

Possibly related PRs

Suggested labels

bug

Suggested reviewers

  • jgroth

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm warn config production Use --omit=dev instead.
npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-05-23T06_40_35_737Z-debug-0.log

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR aims to improve link handling in the text editor by refactoring and enhancing paste event processing. Key changes include:

  • Replacing the basic URL validation with a regular expression that supports http, mailto, and tel schemes.
  • Refactoring paste event handling to generate text and link nodes dynamically.
  • Removing redundant URL validation and copy-paste command logic from the menu commands.

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/components/text-editor/prosemirror-adapter/plugins/link-plugin.ts Updated paste event processing with new helper functions and regex for detecting URLs, along with improved link node insertion logic.
src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts Removed the obsolete isValidUrl function and the copyPasteLinkCommand, updating the command mapping accordingly.
Comments suppressed due to low confidence (1)

src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts:214

  • The removal of the isValidUrl function and copyPasteLinkCommand may affect workflows related to paste handling. Ensure that the new paste handling in the link plugin comprehensively covers these cases and that any necessary updates to documentation are made.
export const isValidUrl = (text: string): boolean => {

@FredrikWallstrom FredrikWallstrom changed the title Link improvements fix(text-editor): don't interpret : as links when pasting May 20, 2025
Copy link

Documentation has been published to https://lundalogik.github.io/lime-elements/versions/PR-3562/

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/components/text-editor/prosemirror-adapter/plugins/link-plugin.ts (1)

190-198: Use Schema instead of any for stronger type-safety

The helper functions still accept schema: any; switching to schema: Schema (from prosemirror-model) surfaces typing errors at compile-time and fulfils a previous review suggestion.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce6db71 and 02708fe.

📒 Files selected for processing (2)
  • src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts (0 hunks)
  • src/components/text-editor/prosemirror-adapter/plugins/link-plugin.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: Imports from other files in the same module (lime-elements) must use relative paths. Using absolute paths for imports will cause the production build to fail.

**/*.{ts,tsx}: Imports from other files in the same module (lime-elements) must use relative paths. Using absolute paths for imports will cause the production build to fail.

  • src/components/text-editor/prosemirror-adapter/plugins/link-plugin.ts

Comment on lines 198 to 203
const linkMark = schema.marks.link.create({
href: url,
title: url,
// Only set _blank for http/https links, not for mailto/tel
target: url.startsWith('http') && isExternalLink(url) ? '_blank' : null,
});

return schema.text(url, [linkMark]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add rel="noopener noreferrer" to external links opened in a new tab

Opening external URLs with target="_blank" without the rel attribute allows the new page to access window.opener, posing a phishing/vector risk.
Extend the attributes so the rendered <a> element gets the protective rel.

-        target: url.startsWith('http') && isExternalLink(url) ? '_blank' : null,
+        target: url.startsWith('http') && isExternalLink(url) ? '_blank' : null,
+        rel:
+            url.startsWith('http') && isExternalLink(url)
+                ? 'noopener noreferrer'
+                : null,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const linkMark = schema.marks.link.create({
href: url,
title: url,
// Only set _blank for http/https links, not for mailto/tel
target: url.startsWith('http') && isExternalLink(url) ? '_blank' : null,
});
return schema.text(url, [linkMark]);
const linkMark = schema.marks.link.create({
href: url,
title: url,
// Only set _blank for http/https links, not for mailto/tel
target: url.startsWith('http') && isExternalLink(url) ? '_blank' : null,
rel:
url.startsWith('http') && isExternalLink(url)
? 'noopener noreferrer'
: null,
});
return schema.text(url, [linkMark]);
🤖 Prompt for AI Agents
In src/components/text-editor/prosemirror-adapter/plugins/link-plugin.ts around
lines 198 to 205, the link mark sets target="_blank" for external http/https
links but does not include the rel="noopener noreferrer" attribute. To fix this,
add the rel attribute with the value "noopener noreferrer" alongside
target="_blank" for external links to prevent security risks from window.opener
access.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has not been introduced in this PR. I will fix this in a follow up PR.

@FredrikWallstrom FredrikWallstrom force-pushed the link-improvements branch 3 times, most recently from 6c25b3c to 5d52323 Compare May 20, 2025 13:19
omaralweli
omaralweli previously approved these changes May 22, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (1)
src/components/text-editor/prosemirror-adapter/plugins/link/link-plugin.ts (1)

146-146: 🧹 Nitpick (assertive)

Add type annotation for better code quality.

The clickTimeout variable should be typed to improve code maintainability and prevent potential type-related issues.

-let clickTimeout;
+let clickTimeout: ReturnType<typeof setTimeout> | undefined;
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4950c1a and 09dcdcd.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • package.json (0 hunks)
  • src/components/markdown/link-markdown-plugin.ts (1 hunks)
  • src/components/markdown/markdown-parser.ts (3 hunks)
  • src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts (2 hunks)
  • src/components/text-editor/prosemirror-adapter/plugins/link/link-mark-spec.ts (1 hunks)
  • src/components/text-editor/prosemirror-adapter/plugins/link/link-plugin.ts (2 hunks)
  • src/components/text-editor/prosemirror-adapter/plugins/link/utils.spec.ts (1 hunks)
  • src/components/text-editor/prosemirror-adapter/plugins/link/utils.ts (1 hunks)
  • src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx (2 hunks)
  • src/design-guidelines/text-editor/menu-command.md (2 hunks)
💤 Files with no reviewable changes (1)
  • package.json
🧰 Additional context used
📓 Path-based instructions (3)
`**/*.{ts,tsx}`: Imports from other files in the same module (lime-elements) must use relative paths. Using absolute paths for imports will cause the production build to fail.

**/*.{ts,tsx}: Imports from other files in the same module (lime-elements) must use relative paths. Using absolute paths for imports will cause the production build to fail.

  • src/components/markdown/markdown-parser.ts
  • src/components/markdown/link-markdown-plugin.ts
  • src/components/text-editor/prosemirror-adapter/plugins/link/utils.spec.ts
  • src/components/text-editor/prosemirror-adapter/plugins/link/utils.ts
  • src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx
  • src/components/text-editor/prosemirror-adapter/plugins/link/link-mark-spec.ts
  • src/components/text-editor/prosemirror-adapter/plugins/link/link-plugin.ts
  • src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts
`**/*.tsx`: Our `.tsx` files are typically using StencilJS, not React. When a developer wants to return multiple top-level JSX elements from the `render` method, they will sometime...

**/*.tsx: Our .tsx files are typically using StencilJS, not React. When a developer wants to return multiple top-level JSX elements from the render method, they will sometimes wrap them in an array literal. In these cases, rather than recommending they add key properties to the elements, recommend removing the hardcoded array literal. Recommend wrapping the elements in StencilJS's special <Host> element.

  • src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx
`**/*.{tsx,scss}`: Almost all our components use shadow-DOM. Therefore, we have no need of BEM-style class names in our CSS.

**/*.{tsx,scss}: Almost all our components use shadow-DOM. Therefore, we have no need of BEM-style class names in our CSS.

  • src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx
🧬 Code Graph Analysis (6)
src/components/markdown/markdown-parser.ts (1)
src/components/markdown/link-markdown-plugin.ts (1)
  • createLinksPlugin (12-35)
src/components/markdown/link-markdown-plugin.ts (1)
src/components/text-editor/prosemirror-adapter/plugins/link/utils.ts (1)
  • getLinkAttributes (3-24)
src/components/text-editor/prosemirror-adapter/plugins/link/utils.spec.ts (1)
src/components/text-editor/prosemirror-adapter/plugins/link/utils.ts (1)
  • getLinkAttributes (3-24)
src/components/text-editor/prosemirror-adapter/plugins/link/utils.ts (1)
src/components/text-editor/prosemirror-adapter/plugins/link/link-mark-spec.ts (1)
  • LinkMarkAttrs (3-9)
src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx (1)
src/components/text-editor/prosemirror-adapter/plugins/link/link-mark-spec.ts (1)
  • linkMarkSpec (11-44)
src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts (1)
src/components/text-editor/prosemirror-adapter/plugins/link/utils.ts (1)
  • getLinkAttributes (3-24)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Docs / Publish Docs
  • GitHub Check: Build
  • GitHub Check: Test
🔇 Additional comments (28)
src/components/text-editor/prosemirror-adapter/plugins/link/utils.ts (2)

3-24: LGTM! Solid implementation with proper security attributes.

The function correctly distinguishes between external and internal URLs and applies appropriate security attributes (target="_blank", rel="noopener noreferrer", referrerpolicy="noreferrer") for external links while keeping internal links clean.


46-48: LGTM! Simple and effective helper function.

The protocol-relative URL detection is straightforward and correct.

src/components/text-editor/prosemirror-adapter/prosemirror-adapter.tsx (2)

37-38: LGTM! Import paths correctly follow coding guidelines.

The import paths use relative paths as required by the coding guidelines for files in the same module. The organization into a plugins/link/ directory structure improves code organization.


394-394: Excellent integration of the new link mark specification.

Adding linkMarkSpec to the schema marks properly integrates the enhanced link handling capabilities into the ProseMirror editor. This will enable the centralized link attribute logic across the editor.

src/components/markdown/markdown-parser.ts (3)

15-15: LGTM! Proper use of relative import path.

The import correctly uses a relative path as required by the coding guidelines for same-module imports.


44-44: Excellent replacement of external dependency with custom solution.

Replacing rehype-external-links with the custom createLinksPlugin() provides better control over link attribute handling and ensures consistency with the ProseMirror editor's link processing logic.


110-110: Good addition of referrerpolicy to sanitization whitelist.

Adding referrerpolicy to the allowed attributes for <a> elements ensures that the security attributes set by the custom link plugin are preserved during HTML sanitization.

src/components/markdown/link-markdown-plugin.ts (2)

6-11: LGTM! Clear and comprehensive documentation.

The JSDoc documentation clearly explains the plugin's purpose and return type. Good documentation practices.


12-35: Excellent unified.js plugin implementation.

The plugin implementation follows unified.js best practices:

  • Proper plugin structure returning a transformer function
  • Efficient AST traversal using visit
  • Appropriate type checking (node.tagName === 'a')
  • Null safety check for href before processing
  • Consistent attribute application using the centralized getLinkAttributes utility

The reuse of getLinkAttributes ensures consistency between markdown processing and ProseMirror editor link handling.

src/components/text-editor/prosemirror-adapter/plugins/link/utils.spec.ts (3)

1-1: Import path needs to be relative

The import statement uses a relative path which is correct for this module. This follows the coding guideline for imports within the same module.


3-23: Well-implemented test setup and teardown

The test setup properly mocks window.location with a realistic internal domain configuration and restores it after each test. This ensures test isolation and prevents side effects between tests.


25-132: Comprehensive test coverage with parameterized tests

The test suite provides excellent coverage of various URL scenarios including:

  • Relative URLs, fragment URLs, protocol-relative URLs
  • Internal vs external domain handling
  • URLs with query parameters
  • Malformed URLs
  • Null title preservation

The assertions correctly verify that:

  • Internal links have target, rel, and referrerpolicy set to null
  • External links have security attributes set (target="_blank", rel="noopener noreferrer", referrerpolicy="noreferrer")
  • URL and title values are preserved correctly
src/design-guidelines/text-editor/menu-command.md (3)

108-110: Good refactoring to centralize link mark creation

Moving the linkMark creation outside the conditional branches eliminates code duplication and leverages the centralized getLinkAttributes utility. This makes the code more maintainable and consistent with the broader link handling refactor.


113-113: Clear documentation with helpful comments

The added comment clearly explains when this code path is executed (no text selected), making the documentation more accessible to developers.


118-118: Clear documentation with helpful comments

The added comment clearly explains when this code path is executed (text is selected), improving code comprehension.

src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts (2)

6-6: Import path should be relative

The import uses a relative path '../plugins/link/utils' which is correct according to the coding guidelines for imports within the same module (lime-elements).


86-88: Excellent refactoring to use centralized link attributes

The refactoring to use getLinkAttributes(link.href, link.href) centralizes link attribute creation and ensures consistent handling of internal vs external links with appropriate security attributes. This eliminates the previous manual attribute setting and aligns with the broader link handling improvements.

Note: Using link.href as both the URL and title parameter seems intentional based on the function signature, where title can be null and defaults to the URL.

src/components/text-editor/prosemirror-adapter/plugins/link/link-mark-spec.ts (5)

1-1: Import statement is correct

The import uses absolute imports from the ProseMirror library, which is appropriate for external dependencies.


3-9: Well-defined interface for link attributes

The LinkMarkAttrs interface comprehensively covers all necessary link attributes including security-related ones (rel, referrerpolicy). The nullable types for optional attributes are correctly specified.


11-19: Proper mark specification with sensible defaults

The mark specification correctly defines all attributes with appropriate defaults. Setting inclusive: false is correct for link marks to prevent them from extending to adjacent text.


20-33: Comprehensive DOM parsing implementation

The parseDOM configuration correctly extracts all link attributes from anchor elements. The getAttrs function properly handles missing attributes by defaulting href to an empty string and allowing other attributes to be null.


34-44: Excellent security-aware DOM serialization

The toDOM method implements important security measures by automatically adding rel="noopener noreferrer" and referrerpolicy="noreferrer" when target="_blank" is set. This prevents potential security vulnerabilities when opening external links in new tabs.

The approach of spreading both mark.attrs and securityAttrs ensures that security attributes take precedence over any potentially unsafe attributes from the mark.

src/components/text-editor/prosemirror-adapter/plugins/link/link-plugin.ts (6)

3-6: LGTM! Imports align with the new link handling functionality.

The added imports for ProseMirror model types and the centralized getLinkAttributes utility support the enhanced paste handling implementation.


175-189: Well-designed URL detection that addresses the PR objective.

The URL regex pattern correctly matches HTTP(S), mailto, and tel links while avoiding false positives on isolated colons (like timestamps). Good practice resetting the regex lastIndex before use.


193-227: Clean utility functions with proper regex handling.

The node creation utilities are well-structured, and findLinkMatches correctly manages the regex state by resetting lastIndex. The structured return data makes it easy to process matches.


232-264: Excellent implementation of text-to-nodes conversion.

The function correctly handles all scenarios: text without links, links with surrounding text, multiple links, and proper text segmentation. The logic ensures no text is lost during the conversion.


271-337: Well-designed paste handling with good UX considerations.

The implementation correctly distinguishes between single link and multi-node pastes. The single link logic thoughtfully preserves selected text when applying a link mark, providing intuitive behavior for users.


342-355: Clean refactoring of paste event handling.

The simplified implementation effectively uses the new utility functions to handle URL detection and node creation, making the code more maintainable while addressing the PR's objective of proper link detection.

Comment on lines +26 to +44
function isExternalUrl(url: string): boolean {
try {
let urlObj: URL;

if (isProtocolRelativeUrl(url)) {
urlObj = new URL(window.location.protocol + url);
} else {
urlObj = new URL(url, window.location.origin);
}

return (
urlObj.protocol.startsWith('http') &&
urlObj.hostname !== window.location.hostname
);
} catch {
// Malformed URLs → internal
return false;
}
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify edge case handling for malformed URLs and review protocol logic.

The URL parsing logic handles most cases well, but there are a few considerations:

  1. Malformed URL handling: Defaulting to internal (line 42) for malformed URLs is generally safe, but consider if this could mask legitimate external URLs that are slightly malformed.

  2. Protocol restriction: The check urlObj.protocol.startsWith('http') (line 37) excludes other protocols like https://, ftp://, mailto:, tel:, etc. This seems intentional for security, but verify this aligns with requirements.

Let me verify how this handles various URL formats:


🏁 Script executed:

#!/bin/bash
# Description: Test URL parsing logic with various formats to verify behavior

# Test: Check for existing tests that cover edge cases
rg -A 10 -B 2 "isExternalUrl|getLinkAttributes" --type ts | head -50

Length of output: 5064


Verify isExternalUrl protocol logic & add edge-case tests

Please confirm that defaulting malformed URLs to “internal” and only treating http* protocols as external aligns with product/security requirements. Also add unit tests for these cases:

  • Absolute HTTP/HTTPS URLs (external vs same-host)
  • Relative URLs (e.g. /path/to/resource)
  • Protocol-relative URLs (e.g. //cdn.example.com)
  • Malformed URLs (e.g. ht tp://, empty strings)
  • Other schemes (e.g. ftp://, mailto:, tel:, data:)

Ensure any intended external schemes are correctly detected or explicitly excluded.

🤖 Prompt for AI Agents
In src/components/text-editor/prosemirror-adapter/plugins/link/utils.ts around
lines 26 to 44, confirm with the product/security team that treating malformed
URLs as internal and restricting external URLs to those with protocols starting
with 'http' meets requirements. Then, add comprehensive unit tests covering
absolute HTTP/HTTPS URLs (both external and same-host), relative URLs,
protocol-relative URLs, malformed URLs (like invalid syntax and empty strings),
and other schemes such as ftp, mailto, tel, and data. Ensure the tests verify
that only intended external schemes are detected as external and others are
excluded or treated as internal accordingly.

import { visit } from 'unist-util-visit';
import { Node } from 'unist';
import { Plugin, Transformer } from 'unified';
import { getLinkAttributes } from '../text-editor/prosemirror-adapter/plugins/link/utils';
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify import path compliance with coding guidelines.

The import uses a relative path ../text-editor/prosemirror-adapter/plugins/link/utils, which follows the coding guidelines for same-module imports. However, this creates a dependency from the markdown component to the text-editor component.

Let me verify if this cross-component dependency is acceptable in the codebase:


🏁 Script executed:

#!/bin/bash
# Description: Check for similar cross-component imports in markdown files
# Expected: Find patterns of imports between different component directories

rg "from '\.\./[^/]+/" src/components/markdown/ || echo "No cross-component imports found"

Length of output: 540


Extract getLinkAttributes into a shared utilities module

Importing getLinkAttributes directly from the text-editor component is valid but introduces unnecessary coupling between the markdown and text-editor modules. To preserve clear boundaries, please:

  • Move the getLinkAttributes function into a shared location (for example, src/shared/link-utils.ts or src/utils/link.ts).
  • Update this file’s import (and the text-editor plugin) to reference the new shared module path.

• File: src/components/markdown/link-markdown-plugin.ts (line 4)

This refactor ensures both markdown and text-editor plugins can consume the utility without cross-component dependencies.

🤖 Prompt for AI Agents
In src/components/markdown/link-markdown-plugin.ts at line 4, the import of
getLinkAttributes from the text-editor component creates an unwanted
cross-component dependency. To fix this, move the getLinkAttributes function to
a shared utilities module such as src/shared/link-utils.ts or src/utils/link.ts.
Then update the import in this file and in the text-editor plugin to reference
the new shared module path, ensuring both components use the utility without
coupling.

@FredrikWallstrom FredrikWallstrom merged commit e42123b into main May 23, 2025
13 checks passed
@FredrikWallstrom FredrikWallstrom deleted the link-improvements branch May 23, 2025 06:45
@lime-opensource
Copy link
Collaborator

🎉 This PR is included in version 38.13.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants