From d1108867e235c9df43d3a6cb9c63d664df20490d Mon Sep 17 00:00:00 2001 From: Alexander Piskun <13381981+bigcat88@users.noreply.github.com> Date: Sat, 1 Nov 2025 10:00:56 +0200 Subject: [PATCH] feat(TextPreviewWidget): add minimal support for [[label|url]] links (#6482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR adds a minimal, backward-compatible way to render **labeled hyperlinks** in node text previews without enabling full Markdown rendering. The syntax is: ``` [[Label|https://example.com]] ``` Links open in a new tab and preserve the existing look and behavior of the widget. ## Motivation I first implemented a `Markdown`-based version that correctly rendered `[label](url)` and other inline Markdown. After some consideration, I have decided against shipping Markdown here because it risks breaking existing custom nodes that already rely on `PromptServer.instance.send_progress_text` with the current `linkifyHtml` --> `nl2br` behavior. The token approach is **smaller**, safer, and avoids surprises. ## Changes * No global behavior change. * No new dependencies. * Token parsing is opt-in. If a node does not emit `[[label|url]]`, behavior is unchanged. ## ComfyUI backend changes PR to ComfyUI with these will come later(as first version of ComfyUI with these frontend changes should be released), and it will contain: ```python def _display_text( node_cls: type[IO.ComfyNode], text: Optional[str], *, status: Optional[Union[str, int]] = None, price: Optional[float] = None, results: Optional[Union[list[str], str]] = None, ) -> None: display_lines: list[str] = [] if status: display_lines.append(f"Status: {status.capitalize() if isinstance(status, str) else status}") if price is not None: display_lines.append(f"Price: ${float(price):,.4f}") if results: # New code starts if isinstance(results, str): display_lines.append(f"Result link: [[1|{results}]]") elif len(results) == 1: display_lines.append(f"Result link: [[1|{results[0]}]]") else: links = ", ".join(f"[[{i}|{u}]]" for i, u in enumerate(results, start=1)) display_lines.append(f"Result links: {links}") # New code ends if text is not None: display_lines.append(text) if display_lines: PromptServer.instance.send_progress_text("\n".join(display_lines), get_node_id(node_cls)) ``` ## Screenshots (if applicable) Screenshot From 2025-10-31 13-12-54 Screenshot From 2025-10-31 13-14-15 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6482-feat-TextPreviewWidget-add-minimal-support-for-label-url-links-29d6d73d365081e9ac97dd7f41e85d8f) by [Unito](https://www.unito.io) --- .../graph/widgets/TextPreviewWidget.vue | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/components/graph/widgets/TextPreviewWidget.vue b/src/components/graph/widgets/TextPreviewWidget.vue index 33ab748748..68c2c09550 100644 --- a/src/components/graph/widgets/TextPreviewWidget.vue +++ b/src/components/graph/widgets/TextPreviewWidget.vue @@ -27,7 +27,33 @@ const props = defineProps<{ const executionStore = useExecutionStore() const isParentNodeExecuting = ref(true) -const formattedText = computed(() => nl2br(linkifyHtml(modelValue.value))) +const formattedText = computed(() => { + const src = modelValue.value + // Turn [[label|url]] into placeholders to avoid interfering with linkifyHtml + const tokens: { label: string; url: string }[] = [] + const holed = src.replace( + /\[\[([^|\]]+)\|([^\]]+)\]\]/g, + (_m, label, url) => { + tokens.push({ label: String(label), url: String(url) }) + return `__LNK${tokens.length - 1}__` + } + ) + + // Keep current behavior (auto-link bare URLs + \n ->
) + let html = nl2br(linkifyHtml(holed)) + + // Restore placeholders as ... (minimal escaping + http default) + html = html.replace(/__LNK(\d+)__/g, (_m, i) => { + const { label, url } = tokens[+i] + const safeHref = url.replace(/"/g, '"') + const safeLabel = label.replace(//g, '>') + return /^https?:\/\//i.test(url) + ? `${safeLabel}` + : safeLabel + }) + + return html +}) let parentNodeId: NodeId | null = null onMounted(() => {