Added markdown to chat and multiline support to the game hall

This commit is contained in:
Christian 'ketura' McCarty
2022-08-23 15:49:00 -05:00
parent 2a0ec7f6e5
commit 71fa96a367
7 changed files with 223 additions and 52 deletions

View File

@@ -32,6 +32,21 @@
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.19.0</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark-ext-gfm-strikethrough</artifactId>
<version>0.19.0</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark-ext-autolink</artifactId>
<version>0.19.0</version>
</dependency>
</dependencies>
<build>
<plugins>

View File

@@ -8,7 +8,6 @@ import com.gempukku.lotro.chat.ChatCommandErrorException;
import com.gempukku.lotro.chat.ChatMessage;
import com.gempukku.lotro.chat.ChatRoomMediator;
import com.gempukku.lotro.chat.ChatServer;
import com.gempukku.lotro.game.CardSets;
import com.gempukku.lotro.game.ChatCommunicationChannel;
import com.gempukku.lotro.game.Player;
import com.gempukku.polling.LongPollingResource;
@@ -17,7 +16,18 @@ import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import org.apache.commons.lang.StringEscapeUtils;
import org.commonmark.Extension;
import org.commonmark.ext.autolink.AutolinkExtension;
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
import org.commonmark.node.Image;
import org.commonmark.node.Link;
import org.commonmark.node.Node;
import org.commonmark.node.Text;
import org.commonmark.parser.Parser;
import org.commonmark.renderer.NodeRenderer;
import org.commonmark.renderer.html.HtmlNodeRendererContext;
import org.commonmark.renderer.html.HtmlRenderer;
import org.commonmark.renderer.html.HtmlWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -25,18 +35,32 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;
public class ChatRequestHandler extends LotroServerRequestHandler implements UriRequestHandler {
private final ChatServer _chatServer;
private final LongPollingSystem longPollingSystem;
private final Parser _markdownParser;
private final HtmlRenderer _markdownRenderer;
public ChatRequestHandler(Map<Type, Object> context, LongPollingSystem longPollingSystem) {
super(context);
_chatServer = extractObject(context, ChatServer.class);
this.longPollingSystem = longPollingSystem;
List<Extension> adminExt = Arrays.asList(StrikethroughExtension.create(), AutolinkExtension.create());
_markdownParser = Parser.builder()
.extensions(adminExt)
.build();
_markdownRenderer = HtmlRenderer.builder()
.nodeRendererFactory(htmlContext -> new LinkShredder(htmlContext))
.extensions(adminExt)
.escapeHtml(true)
.sanitizeUrls(true)
.softbreak("<br />")
.build();
}
@Override
@@ -50,6 +74,8 @@ public class ChatRequestHandler extends LotroServerRequestHandler implements Uri
}
}
private Pattern QuoteExtender = Pattern.compile("^([ \t]*>[ \t]*.+)(?=\n[ \t]*[^>])", Pattern.MULTILINE);
private void postMessages(HttpRequest request, String room, ResponseWriter responseWriter) throws Exception {
HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder(request);
try {
@@ -64,8 +90,17 @@ public class ChatRequestHandler extends LotroServerRequestHandler implements Uri
try {
final boolean admin = resourceOwner.getType().contains("a");
final boolean leagueAdmin = resourceOwner.getType().contains("l");
if (message != null && message.trim().length() > 0) {
chatRoom.sendMessage(resourceOwner.getName(), StringEscapeUtils.escapeHtml(message), admin);
String newMsg;
newMsg = message.trim().replaceAll("\n\n\n+", "\n\n\n");
newMsg = QuoteExtender.matcher(newMsg).replaceAll("$1\n");
//Escaping underscores so that URLs with lots of underscores (i.e. wiki links) aren't mangled
// Besides, who uses _this_ instead of *this*?
newMsg = newMsg.replace("_", "\\_");
newMsg = _markdownRenderer.render(_markdownParser.parse(newMsg));
chatRoom.sendMessage(resourceOwner.getName(), newMsg, admin);
responseWriter.writeXmlResponse(null);
} else {
ChatCommunicationChannel pollableResource = chatRoom.getChatRoomListener(resourceOwner.getName());
@@ -84,6 +119,57 @@ public class ChatRequestHandler extends LotroServerRequestHandler implements Uri
}
}
//Processing to implement:
// + quotes restricted to one line
// - triple quote to avoid this??
// + remove url text processing
// + remove image processing
// - re-enable bare url linking
private class LinkShredder implements NodeRenderer {
private final HtmlWriter html;
LinkShredder(HtmlNodeRendererContext context) {
this.html = context.getWriter();
}
@Override
public Set<Class<? extends Node>> getNodeTypes() {
// Return the node types we want to use this renderer for.
return new HashSet<>(Arrays.asList(
Link.class,
Image.class
));
}
@Override
public void render(Node node) {
if(node instanceof Link link) {
if(link.getTitle() != null) {
html.text(link.getTitle() + ": " + link.getDestination());
}
else {
if(link.getFirstChild() != null
&& link.getFirstChild() instanceof Text text
&& !text.getLiteral().equals(link.getDestination()))
{
html.text(text.getLiteral() + ": " + link.getDestination());
}
else {
html.tag("a", Collections.singletonMap("href", link.getDestination()));
html.text(link.getDestination());
html.tag("/a");
}
}
}
else if(node instanceof Image image){
html.text(image.getTitle() + ": " + image.getDestination());
}
}
}
private class ChatUpdateLongPollingResource implements LongPollingResource {
private final ChatRoomMediator chatRoom;
private final String room;

View File

@@ -122,7 +122,6 @@ html, body {
}
.timestamp {
display: inline;
color: #cfcfcf;
}
@@ -235,6 +234,8 @@ tr.played {
#chatTalk {
flex-grow: 1;
resize: none;
max-height: 60px;
}
#discordChat {
@@ -257,11 +258,13 @@ tr.played {
.flex-horiz {
display: flex;
flex-direction: row;
gap: 2px;
}
.flex-vert {
display: flex;
flex-direction: column;
gap: 2px;
}
#tabs{
position: sticky;
@@ -279,26 +282,22 @@ tr.played {
gap: 4px;
}
#gameHall {
.hall-tab {
display: flex;
flex-direction: column;
flex-grow: 1;
gap: 5px;
}
#hall {
flex-grow: 1;
overflow: scroll;
}
#main {
top: 5px;
height: auto;
height: 100%;
min-height: 100%;
max-height: 100%;
width: 100%;
display: flex;
flex-direction: column;
}
#disclaimer {
@@ -314,3 +313,62 @@ tr.played {
#tablesDiv {
overflow: auto;
}
a {
color: orange !important;
}
.chatMessage h1,
.chatMessage h2,
.chatMessage h3,
.chatMessage h4,
.chatMessage h5,
.chatMessage h6 {
display: inline;
margin-top: 0;
margin-bottom: 0;
}
.chatMessage p {
margin-top: 0;
margin-bottom: 0;
}
.chatMessage blockquote {
display: inline-block;
border-left: 5px solid #505050;
margin-left: 5px;
margin-top: 0px;
margin-bottom: 5px;
font-style: italic;
}
.chatMessage blockquote br {
line-height: 0px;
margin: 0px;
content: "";
}
.user-mention {
background-color: #FFFF0044;
}
.user-ping {
background-color: #FF000044;
}
.message {
display: flex;
flex-direction: row;
gap: 2px;
align-items: flex-start;
}
.msg-content p {
margin-left: 5px;
margin-top: 0px;
margin-bottom: 7px;
}
.msg-identifier {
min-width: 200px;
}

View File

@@ -44,6 +44,7 @@
<script type="text/javascript" src="js/gemp-022/merchantUi.js"></script>
<script type="text/javascript" src="js/lib/json2html/json2html.js"></script>
<!-- <script type="text/javascript" src="js/lib/fingerprintJS/fingerprintjspro.mini.js"></script> -->
<audio id="gamestart" src="/gemp-lotr/fanfare_x.mp3" type="audio/mpeg" preload="metadata"></audio>
@@ -60,7 +61,7 @@
chat = new ChatBoxUI("Game Hall", $("#chat"), "/gemp-lotr-server", true, null, false, true, null, true);
chat.showTimestamps = true;
hall = new GempLotrHallUI($("#hall"), "/gemp-lotr-server", chat);
hall = new GempLotrHallUI("/gemp-lotr-server", chat);
$("#main").tabs();
@@ -123,9 +124,9 @@
</script>
<script>
<!-- <script >
// Initialize the agent at application startup.
const fpPromise = import('https://fpcdn.io/v3/1nM2YaiFE7fxWB2cvWRA')
const fpPromise = import('./js/lib/fingerprintJS/fingerprintjspro.mini.js')
.then(FingerprintJS => FingerprintJS.load())
// Get the visitor identifier when you need it.
@@ -136,12 +137,12 @@
const visitorId = result.visitorId
console.log(visitorId)
})
</script>
</script> -->
</head>
<body><script>0</script>
<div id="main" >
<div id="tabs" width="100%">
<div id="tabs">
<ul>
<li><a href="#gameHall">Game Hall</a></li>
<li><a href="includes/help.html">Help</a></li>
@@ -151,7 +152,7 @@
<li><a href="includes/user.html">User Profile</a></li>
</ul>
</div>
<div id="gameHall" class="flex-horiz" style="flex-grow:1">
<div id="gameHall" class="flex-horiz hall-tab">
<div id="serverInfo" class="flex-horiz" style="align-items:stretch">
<div id="news" class="flex-vert">
<div id="bugReport">
@@ -237,7 +238,8 @@
</div>
</div>
<div id="buttonsDiv" class="flex-horiz">
</div>
<div id="buttonsDiv" class="flex-horiz">
<a href="deckBuild.html">Deck builder</a>
|
<a href='merchant.html' style="text-decoration: line-through underline;">Merchant</a>
@@ -258,7 +260,7 @@
<div id="legacyChat" class="grid-container">
<div id="chatMessages" class="grid-item"></div>
<div id="legacyChatInput" class="grid-item">
<input type="text" id="chatTalk" class=""/>
<textarea type="text" id="chatTalk" class="" oninput='this.style.height = "";this.style.height = this.scrollHeight + "px"'></textarea>
<button id="showSystemButton" class="ui-icon-zoomin"></button>
<button id="lockChatButton" class="ui-icon-locked">Toggle lock chat</button>
@@ -277,7 +279,6 @@
<button id='toggleChatButt' class="grid-item">Switch to Discord</button>
</div>
</div>
</div>
<div id="disclaimer">
The information presented on this site about LotR TCG, both literal and graphical, is copyrighted by Decipher Inc.
This website is not produced, endorsed, supported, or affiliated with Decipher.

View File

@@ -1,6 +1,9 @@
var ChatBoxUI = Class.extend({
name:null,
userName:null,
pingRegex:null,
mentionRegex:null,
everyoneRegex:/(@everyone|@anyone)/,
div:null,
comm:null,
@@ -59,6 +62,13 @@ var ChatBoxUI = Class.extend({
that.appendMessage("Unknown chat problem occured (error=" + xhr.status + ")", "warningMessage");
});
this.enableDiscord = allowDiscord;
this.comm.getPlayerInfo(function(json)
{
that.userName = json;
that.pingRegex = new RegExp("@" + json);
that.mentionRegex = new RegExp("(?<!<b>)" + json );
}, this.chatErrorMap());
@@ -66,13 +76,7 @@ var ChatBoxUI = Class.extend({
if(this.name == "Game Hall")
{
this.discordDiv = $("#discordChat");
this.comm.getPlayerInfo(function(json)
{
that.userName = json;
}, this.chatErrorMap());
this.chatTalkDiv = $("#chatTalk");
@@ -128,13 +132,16 @@ var ChatBoxUI = Class.extend({
that.processMessages(xml, true);
}, this.chatErrorMap());
this.chatTalkDiv.bind("keypress", function (e) {
var code = (e.keyCode ? e.keyCode : e.which);
if (code == 13) {
var value = $(this).val();
if (value != "")
that.sendMessage(value);
$(this).val("");
this.chatTalkDiv.keydown(function (e) {
if (e.keyCode == 13) {
if(!e.shiftKey)
{
e.preventDefault();
var value = $(this).val();
if (value != "")
that.sendMessage(value);
$(this).val("").trigger("oninput");
}
}
});
@@ -153,8 +160,6 @@ var ChatBoxUI = Class.extend({
}
else
{
this.chatTalkDiv = $("<input type='text' class='chatTalk'>");
if (showHideSystemButton) {
@@ -242,10 +247,6 @@ var ChatBoxUI = Class.extend({
}
},
escapeHtml:function (text) {
return $('<div/>').text(text).html();
},
setBounds:function (x, y, width, height) {
if(this.name != "Game Hall")
@@ -359,10 +360,21 @@ var ChatBoxUI = Class.extend({
}
}
},
appendMessage:function (message, msgClass) {
if (msgClass == undefined)
msgClass = "chatMessage";
if(this.pingRegex != null && this.pingRegex.test(message))
{
msgClass += " user-ping";
}
else if((this.mentionRegex != null && this.mentionRegex.test(message)) || this.everyoneRegex.test(message))
{
msgClass += " user-mention";
}
var messageDiv = $("<div class='message " + msgClass + "'>" + message + "</div>");
this.chatMessagesDiv.append(messageDiv);
@@ -402,13 +414,17 @@ var ChatBoxUI = Class.extend({
var msgClass = "chatMessage";
if (from == "System")
msgClass = "systemMessage";
var prefix = "<div class='msg-identifier'>";
if (this.showTimestamps) {
var date = new Date(parseInt(message.getAttribute("date")));
var dateStr = this.monthNames[date.getMonth()] + " " + date.getDate() + " " + this.formatToTwoDigits(date.getHours()) + ":" + this.formatToTwoDigits(date.getMinutes()) + ":" + this.formatToTwoDigits(date.getSeconds());
this.appendMessage("<div class='timestamp'>[" + dateStr + "]</div> <b>" + from + ":</b> " + text, msgClass);
} else {
this.appendMessage("<b>" + from + ":</b> " + text, msgClass);
prefix += "<span class='timestamp'>[" + dateStr + "]</span>";
}
prefix += "<span> <b>" + from + ": </b></span></div>";
var postfix = "<div class='msg-content'>" + text + "</div>";
this.appendMessage(prefix + postfix, msgClass);
}
var users = root.getElementsByTagName("user");

View File

@@ -1,5 +1,4 @@
var GempLotrHallUI = Class.extend({
div:null,
comm:null,
chat:null,
supportedFormatsInitialized:false,
@@ -15,8 +14,7 @@ var GempLotrHallUI = Class.extend({
pocketValue:null,
hallChannelId: null,
init:function (div, url, chat) {
this.div = div;
init:function (url, chat) {
this.comm = new GempLotrCommunication(url, function (xhr, ajaxOptions, thrownError) {
if (thrownError != "abort") {
if (xhr != null) {
@@ -35,9 +33,6 @@ var GempLotrHallUI = Class.extend({
});
this.chat = chat;
var width = $(div).width();
var height = $(div).height();
this.tablesDiv = $("#tablesDiv");
var hallSettingsStr = $.cookie("hallSettings");

View File

@@ -90,8 +90,8 @@ public class ChatRoomMediator {
}
public void sendMessage(String playerId, String message, boolean admin) throws PrivateInformationException, ChatCommandErrorException {
if (message.startsWith("/")) {
processIfKnownCommand(playerId, message.substring(1), admin);
if (message.trim().startsWith("/")) {
processIfKnownCommand(playerId, message.trim().substring(1), admin);
return;
}