fix issue mention links in markdown

This commit is contained in:
Xisheng-Zhao
2026-05-17 18:48:20 +08:00
parent 832a7e0ea0
commit 27250ed42d
2 changed files with 51 additions and 1 deletions
@@ -8797,6 +8797,7 @@ private fun IssueCommentCard(
members: List<Models.Member> = emptyList(),
agents: List<Models.Agent> = emptyList(),
currentUser: Models.User? = null,
onIssueIdClick: ((String) -> Unit)? = null,
onIssueReferenceClick: ((String) -> Unit)? = null,
expandedActions: Boolean,
contentExpanded: Boolean,
@@ -8890,6 +8891,7 @@ private fun IssueCommentCard(
members = members,
agents = agents,
currentUser = currentUser,
onIssueIdClick = onIssueIdClick,
onIssueReferenceClick = onIssueReferenceClick,
)
if (collapseContent) {
@@ -9891,6 +9893,7 @@ private fun PilotIssueDetail(
members = data.members,
agents = data.agents,
currentUser = currentUser,
onIssueIdClick = { onOpenIssue(it) },
onIssueReferenceClick = { openIssueReference(it) },
)
}
@@ -10037,6 +10040,7 @@ private fun PilotIssueDetail(
members = data.members,
agents = data.agents,
currentUser = currentUser,
onIssueIdClick = { onOpenIssue(it) },
onIssueReferenceClick = { openIssueReference(it) },
expandedActions = expandedCommentActionsId == comment.id,
contentExpanded = comment.id in expandedCommentContentIds,
@@ -11448,12 +11452,13 @@ private fun MarkdownBlock(
members: List<Models.Member> = emptyList(),
agents: List<Models.Agent> = emptyList(),
currentUser: Models.User? = null,
onIssueIdClick: ((String) -> Unit)? = null,
onIssueReferenceClick: ((String) -> Unit)? = null,
) {
val textColor = MulticaColors.Text.toArgb()
val mutedColor = MulticaColors.Muted.toArgb()
val borderColor = MulticaColors.Border.toArgb()
val linkHandler = remember(members, agents, currentUser, onIssueReferenceClick) {
val linkHandler = remember(members, agents, currentUser, onIssueIdClick, onIssueReferenceClick) {
object : MarkdownRenderer.LinkHandler {
override fun resolveMentionLabel(
mentionType: String,
@@ -11470,6 +11475,10 @@ private fun MarkdownBlock(
)
}
override fun openIssueId(issueId: String) {
if (issueId.isNotBlank()) onIssueIdClick?.invoke(issueId)
}
override fun openIssueIdentifier(identifier: String) {
if (identifier.isNotBlank()) onIssueReferenceClick?.invoke(identifier)
}
@@ -3,6 +3,7 @@ package ai.multica.app;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.Typeface;
import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
@@ -12,6 +13,7 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.text.style.UnderlineSpan;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.View;
import android.widget.HorizontalScrollView;
@@ -30,6 +32,7 @@ final class MarkdownRenderer {
interface LinkHandler {
String resolveMentionLabel(String mentionType, String mentionId, String fallbackLabel);
void openIssueId(String issueId);
void openIssueIdentifier(String identifier);
}
@@ -124,6 +127,7 @@ final class MarkdownRenderer {
TextView bullet = text(context, "" + trimmed.substring(2), 15, textColor);
bullet.setText(applyInline(bullet.getText().toString(), linkHandler));
bullet.setMovementMethod(LinkMovementMethod.getInstance());
enableClickableSpans(bullet);
bullet.setPadding(dp(context, 8), dp(context, 2), 0, dp(context, 2));
parent.addView(bullet);
} else {
@@ -148,6 +152,7 @@ final class MarkdownRenderer {
TextView p = text(context, String.join("\n", paragraph), 15, textColor);
p.setText(applyInline(p.getText().toString(), linkHandler));
p.setMovementMethod(LinkMovementMethod.getInstance());
enableClickableSpans(p);
p.setPadding(0, dp(context, 2), 0, dp(context, 6));
parent.addView(p);
paragraph.clear();
@@ -194,6 +199,7 @@ final class MarkdownRenderer {
TextView tv = text(context, "", 14, r == 0 ? textColor : mutedColor);
tv.setText(applyInline(cellText, linkHandler));
tv.setMovementMethod(LinkMovementMethod.getInstance());
enableClickableSpans(tv);
tv.setTypeface(r == 0 ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
tv.setPadding(dp(context, 10), dp(context, 8), dp(context, 10), dp(context, 8));
tv.setBackgroundColor(r == 0 ? palette.tableHeaderBackground : palette.tableCellBackground);
@@ -325,6 +331,12 @@ final class MarkdownRenderer {
private static void appendMarkdownLink(SpannableStringBuilder out, ParsedMarkdownLink link, LinkHandler linkHandler) {
MentionUri mention = MentionUri.parse(link.url);
if (mention != null) {
if ("issue".equals(mention.type)) {
appendLinkedText(out, link.label, view -> {
if (linkHandler != null) linkHandler.openIssueId(mention.id);
});
return;
}
String label = linkHandler == null
? IssueCommentRichText.mentionDisplayLabel(mention.type, mention.id, link.label, new ArrayList<>(), new ArrayList<>(), null)
: linkHandler.resolveMentionLabel(mention.type, mention.id, link.label);
@@ -364,6 +376,35 @@ final class MarkdownRenderer {
}
}
private static void enableClickableSpans(TextView textView) {
CharSequence text = textView.getText();
if (!(text instanceof Spanned)) return;
Spanned spanned = (Spanned) text;
if (spanned.getSpans(0, spanned.length(), ClickableSpan.class).length == 0) return;
textView.setOnTouchListener((view, event) -> {
if (event.getAction() != MotionEvent.ACTION_UP && event.getAction() != MotionEvent.ACTION_DOWN) return false;
TextView touchedTextView = (TextView) view;
ClickableSpan span = clickableSpanAt(touchedTextView, event);
if (span == null) return false;
if (event.getAction() == MotionEvent.ACTION_UP) span.onClick(touchedTextView);
return true;
});
}
private static ClickableSpan clickableSpanAt(TextView textView, MotionEvent event) {
CharSequence text = textView.getText();
Layout layout = textView.getLayout();
if (!(text instanceof Spanned) || layout == null) return null;
int x = (int) event.getX() - textView.getTotalPaddingLeft() + textView.getScrollX();
int y = (int) event.getY() - textView.getTotalPaddingTop() + textView.getScrollY();
if (x < 0 || y < 0 || y > layout.getHeight()) return null;
int line = layout.getLineForVertical(y);
if (x < layout.getLineLeft(line) || x > layout.getLineRight(line)) return null;
int offset = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] spans = ((Spanned) text).getSpans(offset, offset, ClickableSpan.class);
return spans.length == 0 ? null : spans[0];
}
private static ParsedMarkdownLink parseMarkdownLink(String value, int start) {
int labelEnd = value.indexOf("](", start + 1);
if (labelEnd <= start + 1) return null;