Tabbed chats with BBCode rendering and clickable identities
Replace the chat target dropdown with a tab strip along the bottom of the chat panel: a Server and a Channel tab plus one closable tab per private chat, each with its own log and an unread marker. Render logs and channel/client descriptions as HTML through a new BBCode renderer in core, supporting b/i/u/s, color, size, url, center and lists, with auto-linked bare URLs. Input is escaped and tag values validated, so server-supplied text cannot inject markup or unsafe schemes. Sender names and TeamSpeak's own client:// links open the same context menu as the server tree (menus extracted into ClientMenu/ChannelMenu); channel:// links open the channel menu. Links are styled by CSS class: identities bold in the author colour, other TS protocols plain, and links leaving the client underlined so they cannot be mistaken for an identity. Dragging a client or channel out of the tree yields the TS3 link BBCode. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package com.ts3client.ui;
|
||||
import com.ts3client.net.ChannelNode;
|
||||
import com.ts3client.net.ClientEntry;
|
||||
import com.ts3client.net.ServerModel;
|
||||
import com.ts3client.text.BBCode;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JEditorPane;
|
||||
@@ -19,10 +20,26 @@ public final class InfoPanel extends JScrollPane {
|
||||
private final JEditorPane pane = new JEditorPane();
|
||||
|
||||
public InfoPanel() {
|
||||
pane.setContentType("text/html");
|
||||
javax.swing.text.html.HTMLEditorKit kit = new javax.swing.text.html.HTMLEditorKit();
|
||||
javax.swing.text.html.StyleSheet css = new javax.swing.text.html.StyleSheet();
|
||||
css.addStyleSheet(kit.getStyleSheet());
|
||||
css.addRule("a { color:" + hex(Theme.CHAT_NAME) + "; text-decoration:none; }");
|
||||
// Client references look exactly like a message author's name.
|
||||
css.addRule("a." + BBCode.IDENTITY_LINK_CLASS
|
||||
+ " { color:" + hex(Theme.CHAT_NAME) + "; font-weight:bold; text-decoration:none; }");
|
||||
// External links are underlined so they can't be mistaken for an identity.
|
||||
css.addRule("a." + BBCode.EXTERNAL_LINK_CLASS
|
||||
+ " { color:" + hex(Theme.LINK) + "; text-decoration:underline; }");
|
||||
kit.setStyleSheet(css);
|
||||
pane.setEditorKit(kit);
|
||||
pane.setEditable(false);
|
||||
pane.setBackground(Theme.CHAT_BG);
|
||||
pane.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8));
|
||||
pane.addHyperlinkListener(e -> {
|
||||
if (e.getEventType() == javax.swing.event.HyperlinkEvent.EventType.ACTIVATED) {
|
||||
Links.open(e.getDescription(), this);
|
||||
}
|
||||
});
|
||||
setViewportView(pane);
|
||||
setBorder(BorderFactory.createLineBorder(new java.awt.Color(0xD0D0D0)));
|
||||
clear();
|
||||
@@ -89,12 +106,16 @@ public final class InfoPanel extends JScrollPane {
|
||||
.append(label).append(":</span> ").append(value).append("</div>");
|
||||
}
|
||||
|
||||
/** Descriptions may carry BBCode markup. */
|
||||
private static String multiline(String text) {
|
||||
return esc(text).replace("\n", "<br>");
|
||||
return BBCode.toHtml(text);
|
||||
}
|
||||
|
||||
private static String esc(String s) {
|
||||
if (s == null) return "";
|
||||
return s.replace("&", "&").replace("<", "<").replace(">", ">");
|
||||
return BBCode.escape(s);
|
||||
}
|
||||
|
||||
private static String hex(java.awt.Color c) {
|
||||
return String.format("#%06X", c.getRGB() & 0xFFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user