define(function(require, exports, module) { "use strict"; main.consumes = [ "CollabPanel", "ui", "panels", "collab.util", "collab.workspace", "collab", "Menu", "MenuItem" ]; main.provides = ["chat"]; return main; function main(options, imports, register) { var CollabPanel = imports.CollabPanel; var ui = imports.ui; var panels = imports.panels; var util = imports["collab.util"]; var workspace = imports["collab.workspace"]; var collab = imports.collab; var Menu = imports.Menu; var MenuItem = imports.MenuItem; var html = require("text!./chat.html"); var css = require("text!./chat.css"); var timeago = require("timeago"); var staticPrefix = options.staticPrefix; var toDeleteMessage; var ROLE_NONE = "n"; var ROLE_VISITOR = "v"; var ROLE_COLLABORATOR = "c"; var ROLE_ADMIN = "a"; var plugin = new CollabPanel("Ajax.org", main.consumes, { name: "chat", index: 200, caption: "Group Chat", textselect: true, style: "flex:1;" }); // var emit = plugin.getEmitter(); var emoji = require("./my_emoji"); // panel-relared UI elements var chatInput, chatText, mnuCtxTreeEl, parent; // non-panel related UI elements var chatThrob, chatCounter, chatNotif; function $(id) { return document.getElementById(id); } var loaded = false; function load() { if (loaded) return; loaded = true; collab.on("chatMessage", onChatMessage); collab.on("chatClear", onChatClear); } var drawn = false; function draw(options) { if (drawn) return; drawn = true; drawNonPanelElements(); parent = options.aml; var parentExt = parent.$int; parentExt.className += " chatContainer"; chatText = parentExt.appendChild(document.createElement("div")); chatText.setAttribute("class", "chatText"); chatInput = new apf.codebox({ htmlNode: parentExt, skin: "codebox", "initial-message": "Enter your message here", // clearbutton : "true", focusselect: "true" }); chatInput.$ext.classList.remove("ace_searchbox"); chatInput.ace.setOptions({ wrap: "free", indentedSoftWrap: false, maxLines: 5, minLines: 2, fontFamily: "inherit" }); chatInput.ace.session.$setFontMetrics(chatInput.ace.renderer.$fontMetrics); plugin.addElement(chatInput); function onWorkspaceSync() { var accessInfo = workspace.accessInfo; if (accessInfo.private && (!accessInfo.member || accessInfo.pending)) return console.warn("Don't have read access - You can't use chat"); var chatHistory = workspace.chatHistory || []; chatHistory.forEach(addMessage); scrollDown(); chatCounter.textContent = chatHistory.length; } chatInput.ace.commands.addCommands([ { bindKey: "ESC", exec: function() { if (chatInput.getValue()) chatInput.setValue(""); else collab.hide(); } }, { bindKey: "Enter", exec: send } ]); function listenWorkspaceSync() { workspace.off("sync", onWorkspaceSync); workspace.on("sync", onWorkspaceSync); } if (panels.isActive("collab")) listenWorkspaceSync(); collab.on("show", function() { chatInput.focus(); listenWorkspaceSync(); }); collab.on("hide", function() { workspace.off("sync", onWorkspaceSync); }); var deleteMsgItem = new MenuItem({ caption: "Delete Message", match: "rw", onclick: clearChatMessage, disabled: true }); var clearHistoryItem = new MenuItem({ caption: "Clear history", match: "rw", onclick: clearChatHistory, disabled: true }); var mnuCtxTree = new Menu({ id: "mnuChat", items: [ deleteMsgItem, clearHistoryItem ] }, plugin); mnuCtxTreeEl = mnuCtxTree.aml; parent.setAttribute("contextmenu", mnuCtxTreeEl); mnuCtxTree.on("show", function() { setTimeout(function() { var hasReadWrite = workspace.fs === "rw"; var collabConnected = collab.connected; clearHistoryItem.disabled = !hasReadWrite || !collabConnected || !chatText.firstChild; toDeleteMessage = findMessageToDelete(hasReadWrite); deleteMsgItem.disabled = !toDeleteMessage || !collabConnected; }, 10); }); parent.on("resize", function() { if (isOpen()) { pending.forEach(addMessage); pending = []; updateCaption(); } }); } function findMessageToDelete(hasReadWrite) { var menuRect = mnuCtxTreeEl.$int.getBoundingClientRect(); var msg; [].slice.apply(chatText.getElementsByTagName("p")).forEach(function(msgP) { var rect = msgP.getBoundingClientRect(); if (rect.left < menuRect.left && (rect.left + rect.width) > menuRect.left && rect.top < menuRect.top && (rect.top + rect.height) > menuRect.top && (hasReadWrite || msgP.userId == workspace.myUserId)) msg = msgP; }); return msg; } function clearChatMessage() { if (!toDeleteMessage || !toDeleteMessage.id) return console.error("[OT] Chat: no message found to delete!"); var msgId = toDeleteMessage.id.match(/ot_chat_(\d+)/)[1]; collab.send("CLEAR_CHAT", { id: msgId }); } function clearChatHistory() { collab.send("CLEAR_CHAT", { clear: true }); } var seenMsgs = {}; var pending = []; var throbTimeout; function scrollDown() { chatText.scrollTop = chatText.scrollHeight; } function isOpen() { return panels.isActive("collab") && parent.state[0] != "m"; } function send() { var text = chatInput.getValue().trim(); if (!text) return; text = emoji.toEmojiUnicode(text); collab.send("CHAT_MESSAGE", { text: text }); chatInput.setValue(""); } function getAuthorName(userId) { if (userId == workspace.myUserId) return "You"; var user = workspace.users[userId]; return util.escapeHTML(user.fullname); } function getAuthorColor(userId) { var color = workspace.colorPool[userId]; return util.formatColor(color); } function formatMessageText(text) { text = util.escapeHtmlWithClickableLinks(text.trim(), "_blank"); text = text.replace(/\n/g, "
"); text = emoji.emoji(text, staticPrefix); return text; } function onChatMessage(msg) { drawNonPanelElements(); workspace.addChatMessage(msg); if (isOpen()) { addMessage(msg); } else { pending.push(msg); updateCaption(); var throbText = "" + getAuthorName(msg.userId) + " "; var text = formatMessageText(msg.text); var notif = msg.notification; if (notif) { throbText += text + " " + notif.linkText; } else { throbText += ": " + text; } chatThrob.innerHTML = throbText; chatThrob.style.display = "block"; clearTimeout(throbTimeout); throbTimeout = setTimeout(function () { chatThrob.style.display = "none"; }, 5000); } if (msg.increment) { var count = Number(chatCounter.textContent); chatCounter.textContent = count + 1; } var inputFocussed = chatInput && chatInput.ace.isFocused(); if (!inputFocussed) chatNotif.play(); } function onChatClear(data) { if (data.clear) { // fast way to delete all children - not innerHtml = '' while (chatText.firstChild) chatText.removeChild(chatText.firstChild); seenMsgs = {}; // sql truncate table would lead to same msg ids workspace.chatHistory = []; } else if (data.id) { var msgHtml = $("ot_chat_" + data.id); msgHtml && msgHtml.parentNode.removeChild(msgHtml); } } function addMessage(msg) { if (seenMsgs[msg.id]) return; seenMsgs[msg.id] = true; // correct the time // msg.timestamp += clientTimeOffset; var msgDate = new Date(msg.timestamp); // create the time string var text = formatMessageText(msg.text); var authorName = getAuthorName(msg.userId); var authorColor = getAuthorColor(msg.userId); var authorNameEl = document.createElement("a"); authorNameEl.href = "javascript:void(0)"; authorNameEl.className = "authorName"; authorNameEl.innerHTML = "" + authorName + ""; // authorNameEl.addEventListener("click", function () { // }); var html = document.createElement("p"); html.id = "ot_chat_" + msg.id; html.userId = msg.userId; if (msg.userId == workspace.myUserId) html.className = "you"; var borderEl = document.createElement("span"); html.appendChild(borderEl); borderEl.className = "chatBorder"; borderEl.style.borderLeftColor = authorColor; html.appendChild(authorNameEl); var textEl = document.createElement("span"); textEl.className = "chatmessage"; textEl.innerHTML = text + "
"; html.appendChild(textEl); var timeEl = document.createElement("span"); timeEl.className = "chattime"; timeEl.title = msgDate.toISOString(); timeEl.innerHTML = msgDate; timeago(timeEl); html.appendChild(timeEl); chatText.appendChild(html); scrollDown(); } function updateCaption() { var caption = "Group Chat"; if (pending.length) caption += "(" + pending.length + ")"; if (parent) parent.setAttribute("caption", caption); } var nonPanelDrawn = false; function drawNonPanelElements () { if (nonPanelDrawn) return; nonPanelDrawn = true; ui.insertHtml(null, html, plugin); ui.insertCss(css, staticPrefix, plugin); chatThrob = $("chatThrob"); chatCounter = $("chatCounter"); chatNotif = $("chatNotif"); chatNotif.src = staticPrefix + "/sounds/chat_notif.mp3"; chatThrob.addEventListener("click", function () { chatThrob.style.display = "none"; plugin.show(); }); } /***** Lifecycle *****/ plugin.on("load", function() { load(); plugin.once("draw", draw); }); plugin.on("enable", function() { }); plugin.on("disable", function() { }); plugin.on("unload", function() { loaded = false; drawn = nonPanelDrawn = false; }); /***** Register and define API *****/ /** * Adds File->New File and File->New Folder menu items as well as the * commands for opening a new file as well as an API. * @singleton **/ plugin.freezePublicAPI({ }); register(null, { chat: plugin }); } });