Commit 17bb0e4e authored by Eugene Shen's avatar Eugene Shen

Poll refresh and set styles in onStateChange

parent 0d9586e0
......@@ -17,22 +17,19 @@
<ul class="contact-list">
</ul>
<div class="chat-form-panel">
<form class="sync-form">
<input type="submit" value="Synchronize!" />
</form>
<form class="edit-form">
<input type="submit" value="Edit contact!" />
</form>
<form class="join-form">
<input type="text" name="content" />
<input type="submit" value="Join room!" />
</form>
<form class="edit-form">
<input type="submit" value="Edit contact!" />
</form>
</div>
</div>
<div class="chat-right-panel">
<div class="chat-max-height-wrapper">
<h3 class="chat-title center"></h3>
<div class="chat-max-height-wrapper">
<div class="chat-right-panel-chat">
<h3 class="chat-title center"></h3>
<ul class="chat-list">
</ul>
<form class="send-form">
......
......@@ -51,74 +51,15 @@
}
/* Set the display style of the element given by a query.
/* Get the contact element given by a room.
* Parameters:
* - query: the query for which to find the element using querySelector
* - style: the display style to set the element to, example: "block"
* Effects: set the display style of the element to the given style
*/
function styleElementByQuery(gadget, query, style) {
if (gadget.element.querySelector(query)) {
gadget.element.querySelector(query).style.display = style;
}
}
/* Get the element given by a contact.
* Parameters:
* - contact: the contact for which to find the element
* - room: the room for which to find the element in the contact list
* Effects: nothing
* Returns: the element corresponding to the given contact
*/
function getElementFromContact(gadget, contact) {
return gadget.element.querySelector("#chat-contact-" + nameToId(contact));
}
/* Highlight the current contact.
* Parameters:
* - contact: the current contact to be highlighted
* - previous_contact: the previously highlighted contact
* Effects:
* - highlight contact by adding the "current" class on its contact element
* - unhighlight previous_contact by removing the "current class"
*/
function highlightContact(gadget, contact) {
var contact_element = getElementFromContact(gadget, contact),
previous_element = getElementFromContact(gadget, gadget.state.contact);
contact_element.classList.add("current");
if (previous_element) {
previous_element.classList.remove("current");
}
}
/* Notify the user.
* Parameters:
* - room: the room that triggers the notification
* - notify: true to notify, false to remove notification
* Effects:
* - toggle "notify" class on the contact element corresponding to room
* - toggle the favicon between alert and default icons
* Returns: the contact element corresponding to the given room
*/
function notifyStatus(gadget, room, notify) {
var contact_element = getElementFromContact(gadget, room),
favicon_element = document.querySelector("link[rel*='icon']")
|| document.createElement("link");
if (notify) {
contact_element.classList.add("notify");
favicon_element.href = gadget.state.alert_icon_url;
} else {
contact_element.classList.remove("notify");
favicon_element.href = gadget.state.default_icon_url;
}
favicon_element.type = "image/x-icon";
favicon_element.rel = "shortcut icon";
document.head.appendChild(favicon_element);
function getContactFromRoom(gadget, room) {
return gadget.element.querySelector("#chat-contact-" + nameToId(room));
}
......@@ -158,13 +99,13 @@
chat.appendChild(document.createTextNode("["
+ new Date(message.time).toLocaleString() + "] " + message.name + ": "
));
// Loop through all potential URLs in the message
// matches[i] is the non-link content, matches[i + 1] is the actual link
matches = message.content.split(url_regex);
for (i = 0; i < matches.length - 1; i += 2) {
chat.appendChild(document.createTextNode(matches[i]));
// If 2d + 1 >= L, then the potential URL has only single letters,
// so ignore it to eliminate acronyms like e.g., i.e., U.S.A., etc.
link_string = matches[i + 1];
......@@ -173,14 +114,14 @@
chat.appendChild(document.createTextNode(link_string));
} else {
is_image = false;
// Add a protocol to transform relative URLs into absolute URLs
if (link_string.indexOf(":") !== -1) {
absolute_url = link_string;
} else {
absolute_url = "http://" + link_string;
}
// Create an image if the URL ends with one of the image extensions
for (j = 0; j < image_extensions.length; j += 1) {
if (stringEndsWith(link_string, image_extensions[j])) {
......@@ -191,7 +132,7 @@
break;
}
}
// Otherwise, create a link
if (!is_image) {
link = document.createElement("a");
......@@ -214,12 +155,35 @@
.setState({
name: null,
// the currently active room, to show in the right panel
room: null,
contact: null,
// true if the room is a chat box, false if the room is a contact panel
is_chat: false,
// a set of the names of all active rooms with number of messages in each
// e.g. {small_room: 3, large_room: 1337}
room_set: {},
// a set of the names of all rooms that have unread messages with boolean
// e.g. {read_room: false, unread_room: true}
unread_room_set: {},
// a set of XXX
delay_refresh_dict: {},
// true to use alert_icon_url, false to use default_icon_url
favicon_alert: false,
alert_icon_url: "https://pricespy.co.nz/favicon.ico",
default_icon_url: "https://softinst75770.host.vifib.net/"
+ "erp5/web_site_module/web_chat/favicon.ico"
+ "erp5/web_site_module/web_chat/favicon.ico",
// toggle refresh_chat to force call delayRefresh
refresh_chat: false,
// toggle update to force call onStateChange
update: false
})
......@@ -238,24 +202,86 @@
})
// Set the chat title when the current room changes.
/* Render everything again when the current state changes.
* Parameters: all properties in gadget.state
* Effects:
* - in right panel, only display the currently active room or contact
* - in right panel, set the title based on the room or contact
* - in contact list, only add styles for unread messages and current room
* - set favicon depending on whether there are new unread messages
*/
.onStateChange(function (modification_dict) {
var gadget = this;
if (modification_dict.hasOwnProperty("room")) {
styleElementByQuery(gadget,
"#room-gadget-" + nameToId(gadget.state.contact), "none");
styleElementByQuery(gadget, ".chat-right-panel-chat", "flex");
gadget.element.querySelector(".chat-title")
.textContent = "Room: " + modification_dict.room;
} else if (modification_dict.hasOwnProperty("contact")) {
styleElementByQuery(gadget,
"#room-gadget-" + nameToId(gadget.state.contact), "none");
styleElementByQuery(gadget,
"#room-gadget-" + nameToId(modification_dict.contact), "block");
styleElementByQuery(gadget, ".chat-right-panel-chat", "none");
gadget.element.querySelector(".chat-title")
.textContent = "Contact: " + modification_dict.contact;
var gadget = this,
i,
room_list = gadget.element.querySelector(".chat-right-panel").children,
contact_list = gadget.element.querySelector(".contact-list").children,
favicon_element = document.querySelector("link[rel*='icon']")
|| document.createElement("link");
// hide all room gadget elements
for (i = 0; i < room_list.length; i += 1) {
if (room_list[i].getAttribute("data-gadget-url")) {
room_list[i].style.display = "none";
}
}
// remove all styles from contact elements
for (i = 0; i < contact_list.length; i += 1) {
if (contact_list[i].classList.contains("current")) {
contact_list[i].classList.remove("current");
contact_list[i].classList.remove("notify");
}
// add styles to contacts with unread messages
if (gadget.state.unread_room_set[contact_list[i].id]) {
contact_list[i].classList.add("notify");
}
}
// set favicon depending on whether there are new unread messages
if (gadget.state.favicon_alert) {
favicon_element.href = gadget.state.alert_icon_url;
} else {
favicon_element.href = gadget.state.default_icon_url;
}
favicon_element.type = "image/x-icon";
favicon_element.rel = "shortcut icon";
document.head.appendChild(favicon_element);
// add style to the current contact
if (modification_dict.hasOwnProperty("room") ||
modification_dict.hasOwnProperty("is_chat")) {
getContactFromRoom(gadget, gadget.state.room).classList.add("current");
// if a chat room is active, show chat panel
if (gadget.state.is_chat) {
gadget.element.querySelector(".chat-right-panel-chat")
.style.display = "flex";
gadget.element.querySelector(".chat-title")
.textContent = "Room: " + gadget.state.room;
} else {
// otherwise, hide chat panel and show contact panel
gadget.element.querySelector(".chat-right-panel-chat")
.style.display = "none";
gadget.element.querySelector("#room-gadget-" +
nameToId(gadget.state.room)).style.display = "block";
gadget.element.querySelector(".chat-title")
.textContent = "Contact: " + gadget.state.room;
}
}
// set update to false so that setting update to true calls onStateChange
gadget.state.update = false;
// refresh the current room
if (gadget.state.refresh_chat) {
gadget.state.refresh_chat = false;
if (gadget.state.delay_refresh_dict[gadget.state.room]) {
gadget.state.delay_refresh_dict[gadget.state.room].cancel();
}
return gadget.delayRefresh(gadget.state.room, 0);
}
})
......@@ -271,7 +297,8 @@
*/
.declareMethod("render", function () {
var gadget = this;
var gadget = this,
user_email;
return gadget.requireSetting(
"jio_storage_description",
"jio_configurator",
......@@ -291,9 +318,9 @@
]);
})
.push(function (setting_list) {
user_email = setting_list[0];
return gadget.changeState({
name: setting_list[0],
contact: setting_list[0],
name: user_email,
local_sub_storage: setting_list[1],
default_jio_type: setting_list[2] === "none" ?
null : setting_list[2],
......@@ -306,9 +333,13 @@
.push(function () {
gadget.element.querySelector(".send-form input[type='text']")
.onfocus = function () {
return notifyStatus(gadget, gadget.state.room, false);
gadget.state.unread_room_set[gadget.state.room] = false;
return gadget.changeState({update: true});
};
return gadget.createContact(gadget.state.contact);
return gadget.createContact(user_email);
})
.push(function () {
return gadget.changeState({room: user_email});
})
);
})
......@@ -316,63 +347,59 @@
/* Create a new contact.
* Parameters:
* - contact: the name of the contact
* - room: the name of the contact
* Effects:
* - append a new contact to the contact list
* - create a new room gadget
* - show the chat box
*/
.declareMethod("createContact", function (contact) {
.declareMethod("createContact", function (room) {
var gadget = this;
return gadget.appendContact(contact)
return gadget.appendContact(room)
.push(function () {
return gadget.createRoom(contact);
return gadget.createRoom(room);
})
.push(function () {
return gadget.changeState({contact: contact});
return gadget.changeState({room: room, is_chat: false});
});
})
/* Append a new contact element to the contact list.
* Parameters:
* - contact: the name of the contact
* - room: the name of the contact
* Effects:
* - if the contact is not blank and not a duplicate, then:
* - if the name is not blank and not a duplicate, then:
* - create and append a new contact element to the contact list
* - set its ID to a querySelector-safe translation of its name
* - highlight it and add an event listener to highlight it on click
* and either change to the room or the contact
* and either change to the room or the contact panel
*/
.declareMethod("appendContact", function (contact) {
.declareMethod("appendContact", function (room) {
var gadget = this,
contact_element;
if (!contact.trim()) {
if (!room.trim()) {
throw "An invisible name is not allowed! You couldn't click on it!";
}
if (getElementFromContact(gadget, contact)) {
if (getContactFromRoom(gadget, room)) {
throw "A contact with the same name already exists!";
}
contact_element = document.createElement("li");
contact_element.appendChild(document.createTextNode(contact));
contact_element.setAttribute("id", "chat-contact-" + nameToId(contact));
contact_element.appendChild(document.createTextNode(room));
contact_element.setAttribute("id", "chat-contact-" + nameToId(room));
gadget.element.querySelector(".contact-list")
.appendChild(contact_element);
highlightContact(gadget, contact);
contact_element.addEventListener("click", function () {
return notifyStatus(gadget, contact, false)
.push(function () {
highlightContact(gadget, contact);
if (gadget.state.room_set[contact]) {
return gadget.changeRoom(contact);
}
return gadget.changeState({contact: contact});
});
gadget.state.unread_room_set[room] = false;
if (gadget.state.room_set[room] !== undefined) {
return gadget.changeRoom(room);
}
return gadget.changeState({room: room, is_chat: false, update: true});
}, false);
})
......@@ -430,12 +457,15 @@
.declareMethod("changeRoom", function (room) {
var gadget = this;
return gadget.changeState({room: room})
return gadget.changeState({room: room, is_chat: true})
.push(function () {
if (!gadget.state.room_set[room]) {
gadget.state.room_set[room] = true;
if (gadget.state.room_set[room] === undefined) {
gadget.state.room_set[room] = 0;
return gadget.deployMessage({
content: gadget.state.name + " has joined.",
color: "orange"
});
}
return gadget.refreshChat();
});
})
......@@ -459,7 +489,10 @@
})
.push(function () {
return gadget.storeArchive(message);
});
})
.push(function () {
return gadget.changeState({refresh_chat: true});
})
})
......@@ -547,21 +580,53 @@
})
/* Refresh the chat by polling with increasing delays.
* Parameters:
* - room: the room to refresh
* - delay: the time in milliseconds to wait before refreshing again
* Effects:
* - call refreshChat and wait delay milliseconds before calling it again
*/
.declareMethod("delayRefresh", function (room, delay) {
var gadget = this;
console.log(room, delay);
return new RSVP.Queue()
.push(function () {
return RSVP.all([
RSVP.delay(delay),
// gadget.refreshChat(room)
]);
})
.push(function () {
gadget.state.delay_refresh_dict[room] = new RSVP.Queue()
.push(function () {
return gadget.delayRefresh(room, delay + 500);
});
})
})
/* Overwrite the chat box with chats from the jIO storage.
* Parameters: nothing
* Parameters:
* - room: the room to refresh
* Effects:
* - get a sorted list of all chats in the current room from jIO storage
* - remove the entire chat box and append all chats from jIO storage
* - scroll the chat list down to show the chat element
*/
.declareMethod("refreshChat", function () {
var gadget = this;
return gadget.getDeclaredGadget("room-gadget-" + gadget.state.room)
.push(function (room_gadget) {
.declareMethod("refreshChat", function (room) {
var gadget = this,
room_gadget;
return gadget.getDeclaredGadget("room-gadget-" + room)
.push(function (sub_gadget) {
room_gadget = sub_gadget;
return room_gadget.wrapJioCall("repair");
})
.push(function () {
return room_gadget.wrapJioCall("allDocs", [{
query: 'portal_type: "Text Post" AND room: "'
+ gadget.state.room + '"',
query: 'portal_type: "Text Post" AND room: "' + room + '"',
limit: [0, 1000000],
sort_on: [["date_ms", "ascending"]],
select_list: ["content"]
......@@ -570,23 +635,31 @@
.push(function (result_list) {
var i, message, message_list = [],
container = gadget.element.querySelector(".chat-list");
for (i = 0; i < result_list.data.total_rows; i += 1) {
try {
message = JSON.parse(result_list.data.rows[i].value.content);
if (message && typeof message === "object") {
message_list.push(message);
}
} catch (ignore) {}
}
while (container.firstChild) {
container.removeChild(container.firstChild);
}
for (i = 0; i < message_list.length; i += 1) {
container.appendChild(messageToChat(message_list[i]));
if (result_list.data.total_rows > gadget.state.room_set[room]) {
gadget.state.room_set[room] = result_list.data.total_rows;
gadget.state.unread_room_set[room] = true;
for (i = 0; i < result_list.data.total_rows; i += 1) {
try {
message = JSON.parse(result_list.data.rows[i].value.content);
if (message && typeof message === "object") {
message_list.push(message);
}
} catch (ignore) {}
}
if (room === gadget.state.room) {
while (container.firstChild) {
container.removeChild(container.firstChild);
}
for (i = 0; i < message_list.length; i += 1) {
container.appendChild(messageToChat(message_list[i]));
}
container.scrollTop = container.scrollHeight;
}
return gadget.changeState({refresh_chat: true});
}
container.scrollTop = container.scrollHeight;
});
})
})
......@@ -602,6 +675,7 @@
split = chat.slice(1).split(" "),
command = split.shift(),
argument = split.join(" "),
room_element = getContactFromRoom(gadget, gadget.state.room),
promise_list = [],
help_list = [
"Available commands:",
......@@ -614,7 +688,7 @@
// change to a room that has already been joined
case "join":
if (gadget.state.room_set[argument]) {
if (gadget.state.room_set[argument] !== undefined) {
return gadget.changeRoom(argument);
}
return gadget.deployNotification({
......@@ -633,14 +707,15 @@
color: "red"
});
}
gadget.state.room_set[gadget.state.room] = false;
gadget.state.room_set[gadget.state.room] = undefined;
room_element.parentNode.removeChild(room_element);
return gadget.deployMessage({
content: gadget.state.name + " has quit.",
color: "orange"
});
})
.push(function () {
return gadget.changeRoom(gadget.state.name);
})
.push(function () {
return gadget.changeRoom(gadget.state.name);
});
});
// quit the entire chat
......@@ -656,6 +731,12 @@
}));
}
return RSVP.all(promise_list);
default:
return gadget.deployNotification({
content: "'" + argument + "' is not a valid command.",
color: "red"
});
}
})
......@@ -666,17 +747,9 @@
var gadget = this,
content;
switch (event.target.className) {
case "sync-form":
return gadget.getDeclaredGadget("room-gadget-" + gadget.state.room)
.push(function (room_gadget) {
return room_gadget.wrapJioCall("repair");
})
.push(function () {
return gadget.refreshChat();
});
case "edit-form":
content = resetInputValue(event.target.elements.content);
return gadget.changeState({contact: content});
return gadget.changeState({room: content});
case "join-form":
content = resetInputValue(event.target.elements.content);
return gadget.createContact(content);
......@@ -685,16 +758,7 @@
if (content.indexOf("/") === 0) {
return gadget.parseCommand(content);
}
return gadget.deployMessage({content: content})
.push(function () {
return gadget.getDeclaredGadget("room-gadget-" + gadget.state.room);
})
.push(function (room_gadget) {
return room_gadget.wrapJioCall("repair");
})
.push(function () {
return gadget.refreshChat();
})
return gadget.deployMessage({content: content});
}
})
......@@ -714,7 +778,7 @@
var promise_list = [], room;
for (room in gadget.state.room_set) {
if (gadget.state.room_set.hasOwnProperty(room)
&& gadget.state.room_set[room]) {
&& gadget.state.room_set[room] !== undefined) {
promise_list.push(gadget.deployMessage({
content: gadget.state.name + " has quit.",
room: room,
......
......@@ -10,9 +10,11 @@
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
......@@ -22,6 +24,7 @@
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
......@@ -40,6 +43,8 @@
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
......@@ -48,9 +53,11 @@
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
......
......@@ -10,9 +10,11 @@
<key> <string>_Access_contents_information_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
......@@ -22,6 +24,7 @@
<key> <string>_Add_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
......@@ -40,6 +43,8 @@
<key> <string>_Modify_portal_content_Permission</string> </key>
<value>
<tuple>
<string>Assignee</string>
<string>Assignor</string>
<string>Manager</string>
</tuple>
</value>
......@@ -48,9 +53,11 @@
<key> <string>_View_Permission</string> </key>
<value>
<tuple>
<string>Anonymous</string>
<string>Assignee</string>
<string>Assignor</string>
<string>Associate</string>
<string>Auditor</string>
<string>Manager</string>
<string>Owner</string>
</tuple>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment