Added markdown to chat and multiline support to the game hall
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user