Skip to content

adding link rendering component#56

Merged
ValwareIRC merged 3 commits into
obbyworld:mainfrom
anotherdoesnm:main
Aug 16, 2025
Merged

adding link rendering component#56
ValwareIRC merged 3 commits into
obbyworld:mainfrom
anotherdoesnm:main

Conversation

@anotherdoesnm
Copy link
Copy Markdown

@anotherdoesnm anotherdoesnm commented Aug 15, 2025

Working link rendering. Not sure if it's a right method because i write without React knowledge but... why not?

Summary by CodeRabbit

  • New Features

    • URLs in all message contexts (regular, system, and replies) are now auto-detected and rendered as clickable links.
    • Message items gain controls to toggle header visibility and set a reply target.
  • Style

    • Message header visuals updated with two new icons and minor layout polish.
  • Refactor

    • Unified message rendering so link detection and handling are consistent across message types.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 15, 2025

Walkthrough

Adds an EnhancedLinkWrapper that detects HTTP/HTTPS/IRC/IRCS links via regex and renders them as anchors by recursively processing React children. Replaces direct html content rendering with the wrapper for system, regular, and reply messages. MessageItem gains showHeader and setReplyTo props and imports updated.

Changes

Cohort / File(s) Summary of Changes
Chat rendering & link enhancement
src/components/layout/ChatArea.tsx
Added EnhancedLinkWrapper that detects HTTP/HTTPS/IRC/IRCS links (regex), traverses React children (Children, isValidElement, cloneElement, Fragment), splits text into text+link parts and renders anchors. Replaced direct htmlContent rendering with the wrapper for system messages, regular messages, and reply blocks.
MessageItem props & imports
src/components/layout/ChatArea.tsx
Extended MessageItem public props with showHeader: boolean and setReplyTo: (msg: MessageType) => void. Updated imports to include React children utilities and added icons FaPenAlt, FaUserPlus. Minor formatting cleanup.

Sequence Diagram(s)

sequenceDiagram
  participant UI as MessageItem
  participant Wrapper as EnhancedLinkWrapper
  participant React as React Children Utils

  UI->>Wrapper: render(content)
  Wrapper->>React: traverse children (Children.map / isValidElement)
  React-->>Wrapper: return text nodes / elements
  Wrapper->>Wrapper: split text by URL regex, create <a> for matches
  Wrapper-->>UI: Fragment containing text and anchor nodes
Loading
sequenceDiagram
  participant User
  participant MessageItem
  participant Handler as setReplyTo(msg)

  User->>MessageItem: click "Reply"
  MessageItem->>Handler: setReplyTo(message)
  Handler-->>MessageItem: acknowledge (state updated)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I nibble code like clover leaves,
Finding links with twitchy eaves.
I wrap each URL in a stitch,
Hop to headers, mark replies rich.
Little rabbit, regex-bright — click! 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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
Copy Markdown
Contributor

@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: 1

🧹 Nitpick comments (5)
src/components/layout/ChatArea.tsx (5)

85-91: Make URL detection regex less error-prone (reduce false positives/negatives)

The current regex can over/under-match (e.g., the unescaped dot after the host). Prefer a simpler, robust variant restricted to http/https, which covers most chat links reliably.

Apply this diff:

-  const urlRegex = /(?:(?:https?|ftp):\/\/[^\s/$.?#].[^\s]*)/g;
+  // Basic, robust URL matcher for http/https links
+  const urlRegex = /\bhttps?:\/\/[^\s<>"']+/gi;

If you need ftp and edge-cases (punycode, unicode TLDs), consider using linkify-it instead of maintaining a custom regex.


169-170: Avoid unnecessary wrapper div around linkified system content

Removing the extra

prevents layout shifts in the flex row. The wrapper already returns inline-friendly content.

Apply this diff:

-          <div><EnhancedLinkWrapper>{htmlContent}</EnhancedLinkWrapper></div>
+          <EnhancedLinkWrapper>{htmlContent}</EnhancedLinkWrapper>

254-262: Linkify reply preview content for consistency

Reply previews currently show raw text and won’t render links. Use the same mircToHtml + EnhancedLinkWrapper pipeline here.

Apply this diff:

                 ┌ Replying to{" "}
                 <strong>{message.replyMessage.userId.split("-")[0]}:</strong>{" "}
-                {message.replyMessage.content}
+                <EnhancedLinkWrapper>
+                  {mircToHtml(message.replyMessage.content)}
+                </EnhancedLinkWrapper>

263-264: Avoid extra wrapper div around main message content

Same rationale as system messages: keep DOM shallow and content inline where possible.

Apply this diff:

-            <div><EnhancedLinkWrapper>{htmlContent}</EnhancedLinkWrapper></div>
+            <EnhancedLinkWrapper>{htmlContent}</EnhancedLinkWrapper>

101-108: Use theme-consistent link colors

Hard-coded blue may clash with themes. Using existing palette keeps visual consistency across light/dark or custom themes.

Apply this diff (already included in the main fix above; keeping it here for clarity):

-              className="text-blue-500 underline hover:text-blue-700"
+              className="text-discord-text-link underline hover:opacity-80"
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 07be1d0 and e6ff303.

📒 Files selected for processing (1)
  • src/components/layout/ChatArea.tsx (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/layout/ChatArea.tsx (2)
src/lib/ircUtils.tsx (2)
  • part (212-267)
  • mircToHtml (167-279)
src/types/index.ts (1)
  • Message (48-59)
🪛 GitHub Actions: Deploy to Cloudflare Pages
src/components/layout/ChatArea.tsx

[error] 118-118: TS7023: 'processChildren' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.


[error] 119-119: TS7024: Function implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.


[error] 125-125: TS2769: No overload matches this call. The last overload gave the following error. Object literal may only specify known properties, and 'children' does not exist in type 'Partial & Attributes'.

🔇 Additional comments (1)
src/components/layout/ChatArea.tsx (1)

13-19: Icon additions: looks good

The added icons (FaPenAlt, FaUserPlus) are imported and used appropriately in the header actions.

Comment thread src/components/layout/ChatArea.tsx Outdated
@anotherdoesnm anotherdoesnm changed the title adding link rendering component WIP: adding link rendering component Aug 15, 2025
@anotherdoesnm anotherdoesnm marked this pull request as draft August 15, 2025 18:34
@anotherdoesnm anotherdoesnm marked this pull request as ready for review August 15, 2025 18:38
@anotherdoesnm anotherdoesnm changed the title WIP: adding link rendering component adding link rendering component Aug 15, 2025
@anotherdoesnm
Copy link
Copy Markdown
Author

@ValwareIRC pls review this (i can't found how to make via gh interface)

Copy link
Copy Markdown
Contributor

@matheusfillipe matheusfillipe left a comment

Choose a reason for hiding this comment

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

This is approved and lgtm code wise also works ok.

However this is going to break tauri apps as they just follow hyperlinks leaving the app itself by default and we need to better handle that.

Please open an issue if this is merged as it is. We could then try to handle it with something like https://v2.tauri.app/plugin/opener/

@ValwareIRC
Copy link
Copy Markdown
Contributor

ValwareIRC commented Aug 16, 2025

Thanks for this.
Please could we also render "ircs://" links

@anotherdoesnm
Copy link
Copy Markdown
Author

Added irc(s) to regex but for fully support we may need to add ability to specify query parameters and use https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler

Copy link
Copy Markdown
Contributor

@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: 0

🧹 Nitpick comments (4)
src/components/layout/ChatArea.tsx (4)

83-136: EnhancedLinkWrapper: solid recursive linkify (types + cloneElement usage are correct)

  • Explicit return types address TS7023/TS7024.
  • cloneElement usage is correct (children passed as the third arg).
  • Skips existing anchors to avoid nesting.
  • Returns a Fragment, preserving inline layout.

Nice work.

If you’d like, I can extract this into a reusable component file with lightweight unit tests.


104-104: Style consistency: avoid Tailwind’s blue-700; prefer theme-consistent hover

Use the theme token instead of hardcoding Tailwind blue. Suggest using hover:opacity-80 to keep the existing text-discord-text-link color while providing feedback.

Apply this diff:

-              className="text-discord-text-link underline hover:text-blue-700"
+              className="text-discord-text-link underline hover:opacity-80"

86-114: Optional: handle trailing punctuation and closing brackets in detected URLs

Current regex will include trailing punctuation like “),.” at the end of URLs, producing broken links. Consider iterating with RegExp.exec and trimming trailing punctuation from the match, while preserving it as text after the link.

Apply this diff to replace parseContent with a punctuation-aware version:

-  const parseContent = (content: string): React.ReactNode[] => {
-    // Split the content based on the URL regex
-    const parts = content.split(urlRegex);
-    const matches = content.match(urlRegex) || [];
-
-    return parts.map((part, index) => {
-      // Render the text part
-      const textPart = <span key={`text-${index}`}>{part}</span>;
-
-      // If there's a matching link for this part, render it
-      if (index < matches.length) {
-        return (
-          <Fragment key={`fragment-${index}`}>
-            {textPart}
-            <a
-              href={matches[index]}
-              target="_blank"
-              rel="noopener noreferrer"
-              className="text-discord-text-link underline hover:text-blue-700"
-            >
-              {matches[index]}
-            </a>
-          </Fragment>
-        );
-      }
-
-      return textPart;
-    });
-  };
+  const parseContent = (content: string): React.ReactNode[] => {
+    const nodes: React.ReactNode[] = [];
+    // Clone the regex to avoid shared lastIndex
+    const re = new RegExp(urlRegex.source, "gi");
+    let lastIndex = 0;
+    let match: RegExpExecArray | null;
+
+    while ((match = re.exec(content)) !== null) {
+      const start = match.index;
+      const raw = match[0];
+
+      // Push any text before the match
+      if (start > lastIndex) {
+        nodes.push(
+          <span key={`text-${lastIndex}`}>{content.slice(lastIndex, start)}</span>,
+        );
+      }
+
+      // Trim common trailing punctuation that shouldn't be part of the URL
+      const trimmed = raw.replace(/[)\]\.,;:!?]+$/, "");
+      const trailing = raw.slice(trimmed.length);
+
+      nodes.push(
+        <a
+          key={`link-${start}`}
+          href={trimmed}
+          target="_blank"
+          rel="noopener noreferrer"
+          className="text-discord-text-link underline hover:text-blue-700"
+        >
+          {trimmed}
+        </a>,
+      );
+
+      // Preserve any trimmed punctuation as plain text
+      if (trailing) {
+        nodes.push(<span key={`trail-${start}`}>{trailing}</span>);
+      }
+
+      lastIndex = start + raw.length;
+    }
+
+    // Push any remaining text after the last match
+    if (lastIndex < content.length) {
+      nodes.push(
+        <span key={`text-${lastIndex}`}>{content.slice(lastIndex)}</span>,
+      );
+    }
+
+    return nodes;
+  };

117-133: Flatten nested arrays from parseContent for predictable output

processChildren can return nested arrays when parseContent returns an array. Flattening keeps the structure predictable and avoids surprises in downstream renderers.

Apply this diff:

-  const processChildren = (node: React.ReactNode): React.ReactNode[] => {
-    return Children.map(node, child => {
-      if (typeof child === 'string') {
-        return parseContent(child); // Process string content
-      } else if (isValidElement(child)) {
-        // Skip already-linkified anchors to avoid nested <a>
-        if ((child as React.ReactElement).type === 'a') {
-          return child;
-        }
-        // Directly process the children of the React element
-        const processed = processChildren((child as React.ReactElement).props?.children);
-        return cloneElement(child as React.ReactElement, undefined, processed);
-      }
-      // For other types of children, return them as is
-      return child as React.ReactNode;
-    }) ?? [];
-  };
+  const processChildren = (node: React.ReactNode): React.ReactNode[] => {
+    const mapped = Children.map(node, (child) => {
+      if (typeof child === "string") {
+        return parseContent(child);
+      } else if (isValidElement(child)) {
+        // Skip already-linkified anchors to avoid nested <a>
+        if ((child as React.ReactElement).type === "a") {
+          return child as React.ReactNode;
+        }
+        const processed = processChildren(
+          (child as React.ReactElement).props?.children,
+        );
+        return cloneElement(child as React.ReactElement, undefined, processed);
+      }
+      return child as React.ReactNode;
+    }) ?? [];
+    // Flatten nested arrays from parseContent
+    return mapped.flat();
+  };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bfa0b92 and 394868e.

📒 Files selected for processing (1)
  • src/components/layout/ChatArea.tsx (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/layout/ChatArea.tsx (2)
src/lib/ircUtils.tsx (2)
  • mircToHtml (167-279)
  • part (212-267)
src/types/index.ts (1)
  • Message (48-59)
🔇 Additional comments (5)
src/components/layout/ChatArea.tsx (5)

4-4: Imports for React utilities are appropriate

Good call adding Children, isValidElement, cloneElement, and Fragment; they’re all used correctly by EnhancedLinkWrapper.


13-19: New icons added are used and scoped properly

FaPenAlt and FaUserPlus are imported and consumed in the header. No dead imports.


170-170: System messages now linkify correctly

Wrapping system htmlContent with EnhancedLinkWrapper ensures http/https/irc/ircs links are clickable. Good coverage.


261-261: Reply preview linkification looks good

Applying EnhancedLinkWrapper to the reply content addresses link-clickability in quoted previews.


264-264: Main message body linkification integrated

Wrapping the rendered htmlContent covers all normal messages. This matches the PR goal and includes ircs:// as requested.

Consider a quick manual check with messages containing:

  • http(s)://, irc://, ircs://
  • URLs followed by punctuation: “https://example.com),”
  • Long URLs to ensure they wrap correctly in the UI

@ValwareIRC
Copy link
Copy Markdown
Contributor

Great job

@ValwareIRC ValwareIRC merged commit 96b24eb into obbyworld:main Aug 16, 2025
1 of 2 checks passed
zocram4cc pushed a commit to zocram4cc/ObsidianIRC that referenced this pull request Feb 17, 2026
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.

3 participants