fix issue mention links in markdown
This commit is contained in:
@@ -8797,6 +8797,7 @@ private fun IssueCommentCard(
|
|||||||
members: List<Models.Member> = emptyList(),
|
members: List<Models.Member> = emptyList(),
|
||||||
agents: List<Models.Agent> = emptyList(),
|
agents: List<Models.Agent> = emptyList(),
|
||||||
currentUser: Models.User? = null,
|
currentUser: Models.User? = null,
|
||||||
|
onIssueIdClick: ((String) -> Unit)? = null,
|
||||||
onIssueReferenceClick: ((String) -> Unit)? = null,
|
onIssueReferenceClick: ((String) -> Unit)? = null,
|
||||||
expandedActions: Boolean,
|
expandedActions: Boolean,
|
||||||
contentExpanded: Boolean,
|
contentExpanded: Boolean,
|
||||||
@@ -8890,6 +8891,7 @@ private fun IssueCommentCard(
|
|||||||
members = members,
|
members = members,
|
||||||
agents = agents,
|
agents = agents,
|
||||||
currentUser = currentUser,
|
currentUser = currentUser,
|
||||||
|
onIssueIdClick = onIssueIdClick,
|
||||||
onIssueReferenceClick = onIssueReferenceClick,
|
onIssueReferenceClick = onIssueReferenceClick,
|
||||||
)
|
)
|
||||||
if (collapseContent) {
|
if (collapseContent) {
|
||||||
@@ -9891,6 +9893,7 @@ private fun PilotIssueDetail(
|
|||||||
members = data.members,
|
members = data.members,
|
||||||
agents = data.agents,
|
agents = data.agents,
|
||||||
currentUser = currentUser,
|
currentUser = currentUser,
|
||||||
|
onIssueIdClick = { onOpenIssue(it) },
|
||||||
onIssueReferenceClick = { openIssueReference(it) },
|
onIssueReferenceClick = { openIssueReference(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -10037,6 +10040,7 @@ private fun PilotIssueDetail(
|
|||||||
members = data.members,
|
members = data.members,
|
||||||
agents = data.agents,
|
agents = data.agents,
|
||||||
currentUser = currentUser,
|
currentUser = currentUser,
|
||||||
|
onIssueIdClick = { onOpenIssue(it) },
|
||||||
onIssueReferenceClick = { openIssueReference(it) },
|
onIssueReferenceClick = { openIssueReference(it) },
|
||||||
expandedActions = expandedCommentActionsId == comment.id,
|
expandedActions = expandedCommentActionsId == comment.id,
|
||||||
contentExpanded = comment.id in expandedCommentContentIds,
|
contentExpanded = comment.id in expandedCommentContentIds,
|
||||||
@@ -11448,12 +11452,13 @@ private fun MarkdownBlock(
|
|||||||
members: List<Models.Member> = emptyList(),
|
members: List<Models.Member> = emptyList(),
|
||||||
agents: List<Models.Agent> = emptyList(),
|
agents: List<Models.Agent> = emptyList(),
|
||||||
currentUser: Models.User? = null,
|
currentUser: Models.User? = null,
|
||||||
|
onIssueIdClick: ((String) -> Unit)? = null,
|
||||||
onIssueReferenceClick: ((String) -> Unit)? = null,
|
onIssueReferenceClick: ((String) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val textColor = MulticaColors.Text.toArgb()
|
val textColor = MulticaColors.Text.toArgb()
|
||||||
val mutedColor = MulticaColors.Muted.toArgb()
|
val mutedColor = MulticaColors.Muted.toArgb()
|
||||||
val borderColor = MulticaColors.Border.toArgb()
|
val borderColor = MulticaColors.Border.toArgb()
|
||||||
val linkHandler = remember(members, agents, currentUser, onIssueReferenceClick) {
|
val linkHandler = remember(members, agents, currentUser, onIssueIdClick, onIssueReferenceClick) {
|
||||||
object : MarkdownRenderer.LinkHandler {
|
object : MarkdownRenderer.LinkHandler {
|
||||||
override fun resolveMentionLabel(
|
override fun resolveMentionLabel(
|
||||||
mentionType: String,
|
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) {
|
override fun openIssueIdentifier(identifier: String) {
|
||||||
if (identifier.isNotBlank()) onIssueReferenceClick?.invoke(identifier)
|
if (identifier.isNotBlank()) onIssueReferenceClick?.invoke(identifier)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package ai.multica.app;
|
|||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.text.Layout;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
@@ -12,6 +13,7 @@ import android.text.style.ForegroundColorSpan;
|
|||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
import android.text.style.TypefaceSpan;
|
import android.text.style.TypefaceSpan;
|
||||||
import android.text.style.UnderlineSpan;
|
import android.text.style.UnderlineSpan;
|
||||||
|
import android.view.MotionEvent;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.HorizontalScrollView;
|
import android.widget.HorizontalScrollView;
|
||||||
@@ -30,6 +32,7 @@ final class MarkdownRenderer {
|
|||||||
|
|
||||||
interface LinkHandler {
|
interface LinkHandler {
|
||||||
String resolveMentionLabel(String mentionType, String mentionId, String fallbackLabel);
|
String resolveMentionLabel(String mentionType, String mentionId, String fallbackLabel);
|
||||||
|
void openIssueId(String issueId);
|
||||||
void openIssueIdentifier(String identifier);
|
void openIssueIdentifier(String identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +127,7 @@ final class MarkdownRenderer {
|
|||||||
TextView bullet = text(context, "• " + trimmed.substring(2), 15, textColor);
|
TextView bullet = text(context, "• " + trimmed.substring(2), 15, textColor);
|
||||||
bullet.setText(applyInline(bullet.getText().toString(), linkHandler));
|
bullet.setText(applyInline(bullet.getText().toString(), linkHandler));
|
||||||
bullet.setMovementMethod(LinkMovementMethod.getInstance());
|
bullet.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
enableClickableSpans(bullet);
|
||||||
bullet.setPadding(dp(context, 8), dp(context, 2), 0, dp(context, 2));
|
bullet.setPadding(dp(context, 8), dp(context, 2), 0, dp(context, 2));
|
||||||
parent.addView(bullet);
|
parent.addView(bullet);
|
||||||
} else {
|
} else {
|
||||||
@@ -148,6 +152,7 @@ final class MarkdownRenderer {
|
|||||||
TextView p = text(context, String.join("\n", paragraph), 15, textColor);
|
TextView p = text(context, String.join("\n", paragraph), 15, textColor);
|
||||||
p.setText(applyInline(p.getText().toString(), linkHandler));
|
p.setText(applyInline(p.getText().toString(), linkHandler));
|
||||||
p.setMovementMethod(LinkMovementMethod.getInstance());
|
p.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
enableClickableSpans(p);
|
||||||
p.setPadding(0, dp(context, 2), 0, dp(context, 6));
|
p.setPadding(0, dp(context, 2), 0, dp(context, 6));
|
||||||
parent.addView(p);
|
parent.addView(p);
|
||||||
paragraph.clear();
|
paragraph.clear();
|
||||||
@@ -194,6 +199,7 @@ final class MarkdownRenderer {
|
|||||||
TextView tv = text(context, "", 14, r == 0 ? textColor : mutedColor);
|
TextView tv = text(context, "", 14, r == 0 ? textColor : mutedColor);
|
||||||
tv.setText(applyInline(cellText, linkHandler));
|
tv.setText(applyInline(cellText, linkHandler));
|
||||||
tv.setMovementMethod(LinkMovementMethod.getInstance());
|
tv.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
enableClickableSpans(tv);
|
||||||
tv.setTypeface(r == 0 ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
|
tv.setTypeface(r == 0 ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
|
||||||
tv.setPadding(dp(context, 10), dp(context, 8), dp(context, 10), dp(context, 8));
|
tv.setPadding(dp(context, 10), dp(context, 8), dp(context, 10), dp(context, 8));
|
||||||
tv.setBackgroundColor(r == 0 ? palette.tableHeaderBackground : palette.tableCellBackground);
|
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) {
|
private static void appendMarkdownLink(SpannableStringBuilder out, ParsedMarkdownLink link, LinkHandler linkHandler) {
|
||||||
MentionUri mention = MentionUri.parse(link.url);
|
MentionUri mention = MentionUri.parse(link.url);
|
||||||
if (mention != null) {
|
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
|
String label = linkHandler == null
|
||||||
? IssueCommentRichText.mentionDisplayLabel(mention.type, mention.id, link.label, new ArrayList<>(), new ArrayList<>(), null)
|
? IssueCommentRichText.mentionDisplayLabel(mention.type, mention.id, link.label, new ArrayList<>(), new ArrayList<>(), null)
|
||||||
: linkHandler.resolveMentionLabel(mention.type, mention.id, link.label);
|
: 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) {
|
private static ParsedMarkdownLink parseMarkdownLink(String value, int start) {
|
||||||
int labelEnd = value.indexOf("](", start + 1);
|
int labelEnd = value.indexOf("](", start + 1);
|
||||||
if (labelEnd <= start + 1) return null;
|
if (labelEnd <= start + 1) return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user