2018-02-27 16:20:49 +00:00
|
|
|
enum ChatType {
|
|
|
|
GENERAL,
|
|
|
|
SERVER,
|
|
|
|
CHANNEL,
|
|
|
|
CLIENT
|
|
|
|
}
|
|
|
|
|
2018-04-30 21:57:21 +00:00
|
|
|
namespace MessageHelper {
|
|
|
|
export function htmlEscape(message: string) : string {
|
|
|
|
const div = document.createElement('div');
|
|
|
|
div.innerText = message;
|
|
|
|
message = div.innerHTML;
|
|
|
|
return message.replace(/ /g, ' ');
|
|
|
|
}
|
|
|
|
|
|
|
|
export function formatElement(object: any) : JQuery[] {
|
|
|
|
if($.isArray(object)) {
|
|
|
|
let result = [];
|
|
|
|
for(let element of object)
|
|
|
|
result.push(...this.formatElement(element));
|
|
|
|
return result;
|
|
|
|
} else if(typeof(object) == "string") {
|
|
|
|
if(object.length == 0) return [];
|
|
|
|
return [$.spawn("a").html(this.htmlEscape(object))];
|
|
|
|
} else if(typeof(object) === "object") {
|
|
|
|
if(object instanceof jQuery)
|
|
|
|
return [object];
|
|
|
|
return this.formatElement("<unknwon object>");
|
|
|
|
} else if(typeof(object) === "function") return this.formatElement(object());
|
|
|
|
else if(typeof(object) === "undefined") return this.formatElement("<undefined>");
|
|
|
|
return this.formatElement("<unknown object type " + typeof object + ">");
|
|
|
|
}
|
|
|
|
|
|
|
|
export function formatMessage(pattern: string, ...objects: any[]) : JQuery[] {
|
|
|
|
let begin = 0, found = 0;
|
|
|
|
|
|
|
|
let result: JQuery[] = [];
|
|
|
|
do {
|
|
|
|
found = pattern.indexOf('{', found);
|
|
|
|
if(found == -1 || pattern.length <= found + 1) {
|
|
|
|
result.push(...this.formatElement(pattern.substr(begin)));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(found > 0 && pattern[found - 1] == '\\') {
|
|
|
|
//TODO remove the escape!
|
|
|
|
found++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
result.push(...this.formatElement(pattern.substr(begin, found - begin))); //Append the text
|
|
|
|
|
|
|
|
let number;
|
|
|
|
let offset = 0;
|
|
|
|
while ("0123456789".includes(pattern[found + 1 + offset])) offset++;
|
|
|
|
number = parseInt(offset > 0 ? pattern.substr(found + 1, offset) : "0");
|
|
|
|
|
|
|
|
if(pattern[found + offset + 1] != '}') {
|
|
|
|
found++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(objects.length < number)
|
|
|
|
console.warn("Message to format contains invalid index (" + number + ")");
|
|
|
|
|
|
|
|
result.push(...this.formatElement(objects[number]));
|
|
|
|
begin = found = found + 2 + offset;
|
|
|
|
console.log("Offset: " + offset + " Number: " + number);
|
|
|
|
} while(found++);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-27 16:20:49 +00:00
|
|
|
class ChatMessage {
|
|
|
|
date: Date;
|
2018-04-30 21:57:21 +00:00
|
|
|
message: JQuery[];
|
2018-02-27 16:20:49 +00:00
|
|
|
private _htmlTag: JQuery<HTMLElement>;
|
|
|
|
|
2018-04-30 21:57:21 +00:00
|
|
|
constructor(message: JQuery[]) {
|
2018-02-27 16:20:49 +00:00
|
|
|
this.date = new Date();
|
|
|
|
this.message = message;
|
|
|
|
}
|
|
|
|
|
|
|
|
private num(num: number) : string {
|
|
|
|
let str = num.toString();
|
|
|
|
while(str.length < 2) str = '0' + str;
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
get htmlTag() {
|
|
|
|
if(this._htmlTag) return this._htmlTag;
|
|
|
|
|
2018-03-07 19:14:36 +00:00
|
|
|
let tag = $.spawn("div");
|
2018-02-27 16:20:49 +00:00
|
|
|
tag.addClass("message");
|
|
|
|
|
2018-03-07 19:14:36 +00:00
|
|
|
let dateTag = $.spawn("div");
|
2018-02-27 16:20:49 +00:00
|
|
|
dateTag.text("<" + this.num(this.date.getUTCHours()) + ":" + this.num(this.date.getUTCMinutes()) + ":" + this.num(this.date.getUTCSeconds()) + "> ");
|
|
|
|
dateTag.css("margin-right", "4px");
|
|
|
|
dateTag.css("color", "dodgerblue");
|
|
|
|
|
|
|
|
this._htmlTag = tag;
|
|
|
|
tag.append(dateTag);
|
2018-04-30 21:57:21 +00:00
|
|
|
this.message.forEach(e => e.appendTo(tag));
|
2018-02-27 16:20:49 +00:00
|
|
|
tag.hide();
|
|
|
|
return tag;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ChatEntry {
|
|
|
|
handle: ChatBox;
|
|
|
|
type: ChatType;
|
|
|
|
key: string;
|
|
|
|
history: ChatMessage[];
|
|
|
|
|
|
|
|
private _name: string;
|
|
|
|
private _htmlTag: any;
|
|
|
|
private _closeable: boolean;
|
|
|
|
private _unread : boolean;
|
|
|
|
onMessageSend: (text: string) => void;
|
|
|
|
onClose: () => boolean;
|
|
|
|
|
|
|
|
constructor(handle, type : ChatType, key) {
|
|
|
|
this.handle = handle;
|
|
|
|
this.type = type;
|
|
|
|
this.key = key;
|
|
|
|
this._name = key;
|
|
|
|
this.history = [];
|
|
|
|
|
|
|
|
this.onClose = function () { return true; }
|
|
|
|
}
|
|
|
|
|
|
|
|
appendError(message: string, ...args) {
|
2018-04-30 21:57:21 +00:00
|
|
|
let entries = MessageHelper.formatMessage(message, ...args);
|
|
|
|
entries.forEach(e => e.css("color", "red"));
|
|
|
|
this.pushChatMessage(new ChatMessage(entries));
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
appendMessage(message : string, fmt: boolean = true, ...args) {
|
2018-04-30 21:57:21 +00:00
|
|
|
this.pushChatMessage(new ChatMessage(MessageHelper.formatMessage(message, ...args)));
|
|
|
|
}
|
|
|
|
|
|
|
|
private pushChatMessage(entry: ChatMessage) {
|
|
|
|
this.history.push(entry);
|
2018-02-27 16:20:49 +00:00
|
|
|
while(this.history.length > 100) {
|
|
|
|
let elm = this.history.pop_front();
|
|
|
|
elm.htmlTag.animate({opacity: 0}, 200, function () {
|
|
|
|
$(this).detach();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if(this.handle.activeChat === this) {
|
|
|
|
let box = $(this.handle.htmlTag).find(".messages");
|
|
|
|
let mbox = $(this.handle.htmlTag).find(".message_box");
|
|
|
|
let bottom : boolean = box.scrollTop() + box.height() + 1 >= mbox.height();
|
2018-04-30 21:57:21 +00:00
|
|
|
mbox.append(entry.htmlTag);
|
|
|
|
entry.htmlTag.show().css("opacity", "0").animate({opacity: 1}, 100);
|
2018-02-27 16:20:49 +00:00
|
|
|
if(bottom) box.scrollTop(mbox.height());
|
|
|
|
} else {
|
|
|
|
this.unread = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
displayHistory() {
|
|
|
|
this.unread = false;
|
|
|
|
let box = $(this.handle.htmlTag).find(".messages");
|
|
|
|
let mbox = $(this.handle.htmlTag).find(".message_box");
|
|
|
|
mbox.empty();
|
|
|
|
|
|
|
|
for(let e of this.history) {
|
|
|
|
mbox.append(e.htmlTag);
|
|
|
|
if(e.htmlTag.is(":hidden")) e.htmlTag.show();
|
|
|
|
}
|
|
|
|
|
|
|
|
box.scrollTop(mbox.height());
|
|
|
|
}
|
|
|
|
|
|
|
|
get htmlTag() {
|
|
|
|
if(this._htmlTag) return this._htmlTag;
|
|
|
|
|
2018-04-11 15:56:09 +00:00
|
|
|
let tag = $.spawn("div");
|
2018-02-27 16:20:49 +00:00
|
|
|
tag.addClass("chat");
|
|
|
|
|
|
|
|
tag.append("<div class=\"chatIcon icon clicon " + this.chatIcon() + "\"></div>");
|
|
|
|
tag.append("<a class='name'>" + this._name + "</a>");
|
|
|
|
|
|
|
|
let closeTag = $.spawn("div");
|
2018-05-08 18:20:12 +00:00
|
|
|
closeTag.addClass("btn_close icon manager-tab_close_button");
|
2018-02-27 16:20:49 +00:00
|
|
|
if(!this._closeable) closeTag.hide();
|
|
|
|
tag.append(closeTag);
|
|
|
|
|
|
|
|
const _this = this;
|
|
|
|
tag.click(function () {
|
|
|
|
_this.handle.activeChat = _this;
|
|
|
|
});
|
|
|
|
tag.on("contextmenu", function (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
let actions = [];
|
|
|
|
actions.push({
|
|
|
|
type: MenuEntryType.ENTRY,
|
|
|
|
icon: "",
|
|
|
|
name: "Clear",
|
|
|
|
callback: () => {
|
|
|
|
_this.history = [];
|
|
|
|
_this.displayHistory();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if(_this.closeable) {
|
|
|
|
actions.push({
|
|
|
|
type: MenuEntryType.ENTRY,
|
2018-05-08 18:20:12 +00:00
|
|
|
icon: "manager-tab_close_button",
|
2018-02-27 16:20:49 +00:00
|
|
|
name: "Close",
|
|
|
|
callback: () => {
|
|
|
|
chat.deleteChat(_this);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
actions.push({
|
|
|
|
type: MenuEntryType.ENTRY,
|
2018-05-08 18:20:12 +00:00
|
|
|
icon: "manager-tab_close_button",
|
2018-02-27 16:20:49 +00:00
|
|
|
name: "Close all private tabs",
|
|
|
|
callback: () => {
|
|
|
|
//TODO Implement this?
|
|
|
|
}
|
|
|
|
});
|
|
|
|
spawnMenu(e.pageX, e.pageY, ...actions);
|
|
|
|
});
|
|
|
|
|
|
|
|
closeTag.click(function () {
|
|
|
|
if($.isFunction(_this.onClose) && !_this.onClose()) return;
|
|
|
|
_this.handle.deleteChat(_this);
|
|
|
|
});
|
|
|
|
|
|
|
|
this._htmlTag = tag;
|
|
|
|
return tag;
|
|
|
|
}
|
|
|
|
|
|
|
|
set name(newName : string) {
|
2018-03-07 19:14:36 +00:00
|
|
|
console.log("Change name!");
|
2018-02-27 16:20:49 +00:00
|
|
|
this._name = newName;
|
|
|
|
this.htmlTag.find(".name").text(this._name);
|
|
|
|
}
|
|
|
|
|
|
|
|
set closeable(flag : boolean) {
|
|
|
|
if(this._closeable == flag) return;
|
|
|
|
|
|
|
|
this._closeable = flag;
|
|
|
|
console.log("Set closeable: " + this._closeable);
|
|
|
|
if(flag) this.htmlTag.find(".btn_close").show();
|
|
|
|
else this.htmlTag.find(".btn_close").hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
set unread(flag : boolean) {
|
|
|
|
if(this._unread == flag) return;
|
|
|
|
this._unread = flag;
|
|
|
|
this.htmlTag.find(".chatIcon").attr("class", "chatIcon icon clicon " + this.chatIcon());
|
|
|
|
if(flag) {
|
|
|
|
this.htmlTag.find(".name").css("color", "blue");
|
|
|
|
} else {
|
|
|
|
this.htmlTag.find(".name").css("color", "black");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private chatIcon() : string {
|
|
|
|
if(this._unread) {
|
|
|
|
switch (this.type) {
|
|
|
|
case ChatType.CLIENT:
|
2018-05-08 18:20:12 +00:00
|
|
|
return "manager-new_chat";
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
switch (this.type) {
|
|
|
|
case ChatType.SERVER:
|
2018-05-08 18:20:12 +00:00
|
|
|
return "manager-server_log";
|
2018-02-27 16:20:49 +00:00
|
|
|
case ChatType.CHANNEL:
|
2018-05-08 18:20:12 +00:00
|
|
|
return "manager-channel_chat";
|
2018-02-27 16:20:49 +00:00
|
|
|
case ChatType.CLIENT:
|
2018-05-08 18:20:12 +00:00
|
|
|
return "manager-player_chat";
|
2018-02-27 16:20:49 +00:00
|
|
|
case ChatType.GENERAL:
|
2018-05-08 18:20:12 +00:00
|
|
|
return "manager-channel_chat";
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class ChatBox {
|
2018-04-16 18:38:35 +00:00
|
|
|
htmlTag: JQuery;
|
2018-02-27 16:20:49 +00:00
|
|
|
chats: ChatEntry[];
|
|
|
|
private _activeChat: ChatEntry;
|
|
|
|
|
2018-04-16 18:38:35 +00:00
|
|
|
constructor(htmlTag: JQuery) {
|
2018-02-27 16:20:49 +00:00
|
|
|
this.htmlTag = htmlTag;
|
|
|
|
|
2018-04-16 18:38:35 +00:00
|
|
|
this.htmlTag.find(".input button").click(this.onSend.bind(this));
|
|
|
|
this.htmlTag.find(".input_box").keypress(event => {
|
2018-04-19 16:42:34 +00:00
|
|
|
if(event.keyCode == JQuery.Key.Enter && !event.shiftKey) {
|
2018-04-16 18:38:35 +00:00
|
|
|
this.onSend();
|
2018-02-27 16:20:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
2018-04-16 18:38:35 +00:00
|
|
|
}).on('input', (event) => {
|
|
|
|
let text = $(event.target).val().toString();
|
|
|
|
if(this.testMessage(text))
|
|
|
|
this.htmlTag.find(".input button").removeAttr("disabled");
|
2018-02-27 16:20:49 +00:00
|
|
|
else
|
2018-04-16 18:38:35 +00:00
|
|
|
this.htmlTag.find(".input button").attr("disabled", "true");
|
|
|
|
}).trigger("input");
|
2018-02-27 16:20:49 +00:00
|
|
|
|
|
|
|
this.chats = [];
|
|
|
|
this._activeChat = undefined;
|
|
|
|
|
|
|
|
this.createChat("chat_server", ChatType.SERVER).onMessageSend = (text: string) => {
|
|
|
|
if(!globalClient.serverConnection) {
|
|
|
|
chat.serverChat().appendError("Could not send chant message (Not connected)");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
globalClient.serverConnection.sendMessage(text, ChatType.SERVER);
|
|
|
|
};
|
|
|
|
this.createChat("chat_channel", ChatType.CHANNEL).onMessageSend = (text: string) => {
|
|
|
|
if(!globalClient.serverConnection) {
|
|
|
|
chat.channelChat().appendError("Could not send chant message (Not connected)");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
globalClient.serverConnection.sendMessage(text, ChatType.CHANNEL, globalClient.getClient().currentChannel());
|
|
|
|
};
|
2018-04-16 18:38:35 +00:00
|
|
|
|
|
|
|
globalClient.permissions.initializedListener.push(flag => {
|
|
|
|
if(flag) this.activeChat0(this._activeChat);
|
|
|
|
});
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
createChat(key, type : ChatType = ChatType.CLIENT) : ChatEntry {
|
|
|
|
let chat = new ChatEntry(this, type, key);
|
|
|
|
this.chats.push(chat);
|
2018-04-16 18:38:35 +00:00
|
|
|
this.htmlTag.find(".chats").append(chat.htmlTag);
|
2018-02-27 16:20:49 +00:00
|
|
|
if(!this._activeChat) this.activeChat = chat;
|
|
|
|
return chat;
|
|
|
|
}
|
|
|
|
|
|
|
|
findChat(key : string) : ChatEntry {
|
|
|
|
for(let e of this.chats)
|
|
|
|
if(e.key == key) return e;
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
deleteChat(chat : ChatEntry) {
|
|
|
|
this.chats.remove(chat);
|
|
|
|
chat.htmlTag.detach();
|
|
|
|
if(this._activeChat === chat) {
|
|
|
|
if(this.chats.length > 0)
|
|
|
|
this.activeChat = this.chats.last();
|
|
|
|
else
|
|
|
|
this.activeChat = undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onSend() {
|
|
|
|
let textBox = $(this.htmlTag).find(".input_box");
|
|
|
|
let text = textBox.val().toString();
|
|
|
|
if(!this.testMessage(text)) return;
|
|
|
|
textBox.val("");
|
|
|
|
$(this.htmlTag).find(".input_box").trigger("input");
|
|
|
|
|
|
|
|
if(this._activeChat && $.isFunction(this._activeChat.onMessageSend))
|
|
|
|
this._activeChat.onMessageSend(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
set activeChat(chat : ChatEntry) {
|
|
|
|
if(this.chats.indexOf(chat) === -1) return;
|
|
|
|
if(this._activeChat == chat) return;
|
2018-04-16 18:38:35 +00:00
|
|
|
this.activeChat0(chat);
|
|
|
|
}
|
2018-02-27 16:20:49 +00:00
|
|
|
|
2018-04-16 18:38:35 +00:00
|
|
|
private activeChat0(chat: ChatEntry) {
|
2018-02-27 16:20:49 +00:00
|
|
|
this._activeChat = chat;
|
|
|
|
for(let e of this.chats)
|
|
|
|
e.htmlTag.removeClass("active");
|
2018-04-16 18:38:35 +00:00
|
|
|
|
|
|
|
let flagAllowSend = false;
|
2018-02-27 16:20:49 +00:00
|
|
|
if(this._activeChat) {
|
|
|
|
this._activeChat.htmlTag.addClass("active");
|
|
|
|
this._activeChat.displayHistory();
|
2018-04-16 18:38:35 +00:00
|
|
|
|
|
|
|
if(globalClient && globalClient.permissions && globalClient.permissions.initialized())
|
|
|
|
switch (this._activeChat.type) {
|
|
|
|
case ChatType.CLIENT:
|
|
|
|
flagAllowSend = true;
|
|
|
|
break;
|
|
|
|
case ChatType.SERVER:
|
|
|
|
flagAllowSend = globalClient.permissions.neededPermission(PermissionType.B_CLIENT_SERVER_TEXTMESSAGE_SEND).granted(1);
|
|
|
|
break;
|
|
|
|
case ChatType.CHANNEL:
|
|
|
|
flagAllowSend = globalClient.permissions.neededPermission(PermissionType.B_CLIENT_CHANNEL_TEXTMESSAGE_SEND).granted(1);
|
|
|
|
break;
|
|
|
|
}
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|
2018-04-16 18:38:35 +00:00
|
|
|
this.htmlTag.find(".input_box").prop("disabled", !flagAllowSend);
|
2018-02-27 16:20:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get activeChat(){ return this._activeChat; }
|
|
|
|
|
|
|
|
channelChat() : ChatEntry {
|
|
|
|
return this.findChat("chat_channel");
|
|
|
|
}
|
|
|
|
|
|
|
|
serverChat() {
|
|
|
|
return this.findChat("chat_server");
|
|
|
|
}
|
|
|
|
|
|
|
|
focus(){
|
|
|
|
$(this.htmlTag).find(".input_box").focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
private testMessage(message: string) : boolean {
|
|
|
|
message = message
|
|
|
|
.replace(/ /gi, "")
|
|
|
|
.replace(/<br>/gi, "")
|
|
|
|
.replace(/\n/gi, "")
|
|
|
|
.replace(/<br\/>/gi, "");
|
|
|
|
return message.length > 0;
|
|
|
|
}
|
|
|
|
}
|