Merge pull request #759 from thelounge/yamanickill/condense-joins

Initial part/join condensing
This commit is contained in:
Pavel Djundik 2017-08-14 11:18:51 +03:00 committed by GitHub
commit 28e32dc558
16 changed files with 287 additions and 128 deletions

View File

@ -194,6 +194,7 @@ kbd {
#chat .title:before, #chat .title:before,
#footer .icon, #footer .icon,
#chat .count:before, #chat .count:before,
#settings .extra-help,
#settings #play:before, #settings #play:before,
#form #submit:before, #form #submit:before,
#chat .invite .from:before, #chat .invite .from:before,
@ -317,6 +318,10 @@ kbd {
line-height: 50px; line-height: 50px;
} }
#settings .extra-help:before {
content: "\f059"; /* http://fontawesome.io/icon/question-circle/ */
}
#settings #play:before { #settings #play:before {
content: "\f028"; /* http://fontawesome.io/icon/volume-up/ */ content: "\f028"; /* http://fontawesome.io/icon/volume-up/ */
margin-right: 9px; margin-right: 9px;
@ -801,6 +806,31 @@ kbd {
display: block; display: block;
} }
#chat .condensed {
flex-wrap: wrap;
}
#chat .condensed .content {
flex: 1;
}
#chat .condensed-text {
cursor: pointer;
transition: opacity .2s;
}
#chat .condensed-text:hover {
opacity: .6;
}
#chat .condensed-text .toggle-button:hover {
opacity: 1;
}
#chat .condensed.closed .msg {
display: none;
}
#windows .header .topic, #windows .header .topic,
.messages .msg, .messages .msg,
.sidebar { .sidebar {
@ -1092,15 +1122,17 @@ kbd {
color: #555; color: #555;
} }
#chat.hide-join .join, #chat.hide-status-messages .join,
#chat.hide-mode .mode, #chat.hide-status-messages .mode,
#chat.hide-motd .motd, #chat.hide-status-messages .nick,
#chat.hide-nick .nick, #chat.hide-status-messages .part,
#chat.hide-part .part, #chat.hide-status-messages .quit,
#chat.hide-quit .quit { #chat.hide-status-messages .condensed,
#chat.hide-motd .motd {
display: none !important; display: none !important;
} }
#chat .condensed .content,
#chat .join .content, #chat .join .content,
#chat .kick .content, #chat .kick .content,
#chat .mode .content, #chat .mode .content,
@ -1138,19 +1170,14 @@ kbd {
#chat .toggle-button { #chat .toggle-button {
display: inline-block; display: inline-block;
color: #666; transition: opacity .2s, transform .2s;
transition: color .2s, transform .2s;
} }
#chat .toggle-button.opened { #chat .toggle-button.opened, /* Thumbnail toggle */
#chat .msg.condensed:not(.closed) .toggle-button { /* Expanded status message toggle */
transform: rotate(90deg); transform: rotate(90deg);
} }
#chat .toggle-button:hover {
/* transform and opacity together glitch, so need to use RGBA transition */
color: rgba(102, 102, 102, .8); /* #666 x .8 opacity */
}
#chat .toggle-content { #chat .toggle-content {
background: #f5f5f5; background: #f5f5f5;
border-radius: 2px; border-radius: 2px;
@ -1361,18 +1388,26 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
#settings .opt { #settings .opt {
display: block; display: block;
padding: 5px 0 10px 1px; padding: 5px 0 5px 1px;
} }
#settings .opt input { #settings .opt input {
float: left; margin-right: 6px;
margin: 4px 10px 0 0;
} }
#settings .extra-help,
#settings #play { #settings #play {
color: #7f8c8d; color: #7f8c8d;
} }
#settings .extra-help {
cursor: help;
}
#settings h2 .extra-help {
font-size: .8em;
}
#settings #play { #settings #play {
font-size: 14px; font-size: 14px;
transition: opacity .2s; transition: opacity .2s;
@ -1694,6 +1729,16 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
animation-delay: .4s; animation-delay: .4s;
} }
.tooltipped-no-delay:hover:before,
.tooltipped-no-delay:hover:after,
.tooltipped-no-delay:active:before,
.tooltipped-no-delay:active:after,
.tooltipped-no-delay:focus:before,
.tooltipped-no-delay:focus:after {
-webkit-animation-delay: 0s;
animation-delay: 0s;
}
.tooltipped-s:after, .tooltipped-s:after,
.tooltipped-se:after, .tooltipped-se:after,
.tooltipped-sw:after { .tooltipped-sw:after {
@ -2130,3 +2175,7 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
#chat table.channel-list .topic { #chat table.channel-list .topic {
white-space: pre-wrap; white-space: pre-wrap;
} }
.hide-text {
color: transparent !important;
}

View File

@ -216,48 +216,40 @@
<div class="col-sm-12"> <div class="col-sm-12">
<h2>Messages</h2> <h2>Messages</h2>
</div> </div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="join">
Show joins
</label>
</div>
<div class="col-sm-6"> <div class="col-sm-6">
<label class="opt"> <label class="opt">
<input type="checkbox" name="motd"> <input type="checkbox" name="motd">
Show <abbr title="Message Of The Day">MOTD</abbr> Show <abbr title="Message Of The Day">MOTD</abbr>
</label> </label>
</div> </div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="part">
Show parts
</label>
</div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="nick">
Show nick changes
</label>
</div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="mode">
Show mode
</label>
</div>
<div class="col-sm-6">
<label class="opt">
<input type="checkbox" name="quit">
Show quits
</label>
</div>
<div class="col-sm-6"> <div class="col-sm-6">
<label class="opt"> <label class="opt">
<input type="checkbox" name="showSeconds"> <input type="checkbox" name="showSeconds">
Show seconds in timestamp Show seconds in timestamp
</label> </label>
</div> </div>
<div class="col-sm-12">
<h2>
Status messages
<span class="tooltipped tooltipped-n tooltipped-no-delay" aria-label="Joins, parts, kicks, nick changes, and mode changes">
<button class="extra-help" aria-label="Joins, parts, kicks, nick changes, and mode changes"></button>
</span>
</h2>
</div>
<div class="col-sm-12">
<label class="opt">
<input type="radio" name="statusMessages" value="shown">
Show all status messages individually
</label>
<label class="opt">
<input type="radio" name="statusMessages" value="condensed">
Condense status messages together
</label>
<label class="opt">
<input type="radio" name="statusMessages" value="hidden">
Hide all status messages
</label>
</div>
<div class="col-sm-12"> <div class="col-sm-12">
<h2>Visual Aids</h2> <h2>Visual Aids</h2>
</div> </div>

55
client/js/condensed.js Normal file
View File

@ -0,0 +1,55 @@
"use strict";
const constants = require("./constants");
const templates = require("../views");
module.exports = {
updateText
};
function updateText(condensed, addedTypes) {
const obj = {};
constants.condensedTypes.forEach((type) => {
obj[type] = condensed.data(type) || 0;
});
addedTypes.forEach((type) => {
obj[type]++;
condensed.data(type, obj[type]);
});
const strings = [];
constants.condensedTypes.forEach((type) => {
if (obj[type]) {
switch (type) {
case "join":
strings.push(obj[type] + (obj[type] > 1 ? " users have joined the channel" : " user has joined the channel"));
break;
case "part":
strings.push(obj[type] + (obj[type] > 1 ? " users have left the channel" : " user has left the channel"));
break;
case "quit":
strings.push(obj[type] + (obj[type] > 1 ? " users have quit" : " user has quit"));
break;
case "nick":
strings.push(obj[type] + (obj[type] > 1 ? " users have changed nick" : " user has changed nick"));
break;
case "kick":
strings.push(obj[type] + (obj[type] > 1 ? " users were kicked" : " user was kicked"));
break;
case "mode":
strings.push(obj[type] + (obj[type] > 1 ? " modes were set" : " mode was set"));
break;
}
}
});
let text = strings.pop();
if (strings.length) {
text = strings.join(", ") + ", and " + text;
}
condensed.find(".condensed-text")
.html(text + templates.msg_condensed_toggle());
}

View File

@ -56,6 +56,32 @@ const commands = [
"/whois" "/whois"
]; ];
const actionTypes = [
"ban_list",
"invite",
"join",
"mode",
"kick",
"nick",
"part",
"quit",
"topic",
"topic_set_by",
"action",
"whois",
"ctcp",
"channel_list",
];
const condensedTypes = [
"join",
"part",
"quit",
"nick",
"kick",
"mode",
];
const timeFormats = { const timeFormats = {
msgDefault: "HH:mm", msgDefault: "HH:mm",
msgWithSeconds: "HH:mm:ss" msgWithSeconds: "HH:mm:ss"
@ -63,6 +89,8 @@ const timeFormats = {
module.exports = { module.exports = {
colorCodeMap: colorCodeMap, colorCodeMap: colorCodeMap,
timeFormats: timeFormats, commands: commands,
commands: commands condensedTypes: condensedTypes,
actionTypes: actionTypes,
timeFormats: timeFormats
}; };

View File

@ -439,6 +439,10 @@ $(function() {
} }
}); });
chat.on("click", ".condensed-text", function() {
$(this).closest(".msg.condensed").toggleClass("closed");
});
chat.on("click", ".user", function() { chat.on("click", ".user", function() {
var name = $(this).data("name"); var name = $(this).data("name");
var chan = findCurrentNetworkChan(name); var chan = findCurrentNetworkChan(name);
@ -707,6 +711,9 @@ $(function() {
chat.on("click", ".show-more-button", function() { chat.on("click", ".show-more-button", function() {
var self = $(this); var self = $(this);
var lastMessage = self.parent().next(".messages").children(".msg").first(); var lastMessage = self.parent().next(".messages").children(".msg").first();
if (lastMessage.is(".condensed")) {
lastMessage = lastMessage.children(".msg").first();
}
var lastMessageId = parseInt(lastMessage[0].id.replace("msg-", ""), 10); var lastMessageId = parseInt(lastMessage[0].id.replace("msg-", ""), 10);
self self

View File

@ -9,25 +9,29 @@ const tz = require("./libs/handlebars/tz");
const windows = $("#windows"); const windows = $("#windows");
const chat = $("#chat"); const chat = $("#chat");
const options = $.extend({ // Default options
const options = {
autocomplete: true,
coloredNicks: true, coloredNicks: true,
desktopNotifications: false, desktopNotifications: false,
join: true, highlights: [],
links: true, links: true,
mode: true,
motd: true, motd: true,
nick: true,
notification: true, notification: true,
notifyAllMessages: false, notifyAllMessages: false,
part: true,
quit: true,
showSeconds: false, showSeconds: false,
statusMessages: "condensed",
theme: $("#theme").attr("href").replace(/^themes\/(.*).css$/, "$1"), // Extracts default theme name, set on the server configuration theme: $("#theme").attr("href").replace(/^themes\/(.*).css$/, "$1"), // Extracts default theme name, set on the server configuration
thumbnails: true, thumbnails: true,
userStyles: userStyles.text(), userStyles: userStyles.text(),
highlights: [], };
autocomplete: true const userOptions = JSON.parse(storage.get("settings")) || {};
}, JSON.parse(storage.get("settings")));
for (const key in options) {
if (userOptions[key]) {
options[key] = userOptions[key];
}
}
module.exports = options; module.exports = options;
@ -43,6 +47,9 @@ for (var i in options) {
settings.find("#user-specified-css-input").val(options[i]); settings.find("#user-specified-css-input").val(options[i]);
} else if (i === "highlights") { } else if (i === "highlights") {
settings.find("input[name=" + i + "]").val(options[i]); settings.find("input[name=" + i + "]").val(options[i]);
} else if (i === "statusMessages") {
settings.find(`input[name=${i}][value=${options[i]}]`)
.prop("checked", true);
} else if (i === "theme") { } else if (i === "theme") {
$("#theme").attr("href", "themes/" + options[i] + ".css"); $("#theme").attr("href", "themes/" + options[i] + ".css");
settings.find("select[name=" + i + "]").val(options[i]); settings.find("select[name=" + i + "]").val(options[i]);
@ -58,6 +65,10 @@ settings.on("change", "input, select, textarea", function() {
if (type === "password") { if (type === "password") {
return; return;
} else if (type === "radio") {
if (self.prop("checked")) {
options[name] = self.val();
}
} else if (type === "checkbox") { } else if (type === "checkbox") {
options[name] = self.prop("checked"); options[name] = self.prop("checked");
} else { } else {
@ -66,16 +77,10 @@ settings.on("change", "input, select, textarea", function() {
storage.set("settings", JSON.stringify(options)); storage.set("settings", JSON.stringify(options));
if ([ if (name === "motd") {
"join",
"mode",
"motd",
"nick",
"part",
"quit",
"notifyAllMessages",
].indexOf(name) !== -1) {
chat.toggleClass("hide-" + name, !self.prop("checked")); chat.toggleClass("hide-" + name, !self.prop("checked"));
} else if (name === "statusMessages") {
chat.toggleClass("hide-status-messages", options[name] === "hidden");
} else if (name === "coloredNicks") { } else if (name === "coloredNicks") {
chat.toggleClass("colored-nicks", self.prop("checked")); chat.toggleClass("colored-nicks", self.prop("checked"));
} else if (name === "theme") { } else if (name === "theme") {

View File

@ -6,11 +6,14 @@ const options = require("./options");
const renderPreview = require("./renderPreview"); const renderPreview = require("./renderPreview");
const utils = require("./utils"); const utils = require("./utils");
const sorting = require("./sorting"); const sorting = require("./sorting");
const constants = require("./constants");
const condense = require("./condensed");
const chat = $("#chat"); const chat = $("#chat");
const sidebar = $("#sidebar"); const sidebar = $("#sidebar");
module.exports = { module.exports = {
appendMessage,
buildChannelMessages, buildChannelMessages,
buildChatMessage, buildChatMessage,
renderChannel, renderChannel,
@ -19,16 +22,39 @@ module.exports = {
renderNetworks, renderNetworks,
}; };
function buildChannelMessages(channel, messages) { function buildChannelMessages(data) {
return messages.reduce(function(docFragment, message) { return data.messages.reduce(function(docFragment, message) {
docFragment.append(buildChatMessage({ appendMessage(docFragment, data.id, data.type, message.type, buildChatMessage({
chan: channel, chan: data.id,
msg: message msg: message
})); }));
return docFragment; return docFragment;
}, $(document.createDocumentFragment())); }, $(document.createDocumentFragment()));
} }
function appendMessage(container, chan, chanType, messageType, msg) {
if (constants.condensedTypes.indexOf(messageType) !== -1 && chanType !== "lobby") {
var condensedTypesClasses = "." + constants.condensedTypes.join(", .");
var lastChild = container.children("div.msg").last();
var lastDate = (new Date(lastChild.attr("data-time"))).toDateString();
var msgDate = (new Date(msg.attr("data-time"))).toDateString();
if (lastChild && $(lastChild).hasClass("condensed") && !$(msg).hasClass("message") && lastDate === msgDate) {
lastChild.append(msg);
condense.updateText(lastChild, [messageType]);
} else if (lastChild && $(lastChild).is(condensedTypesClasses) && options.statusMessages === "condensed") {
var condensed = buildChatMessage({msg: {type: "condensed", time: msg.attr("data-time"), previews: []}, chan: chan});
condensed.append(lastChild);
condensed.append(msg);
container.append(condensed);
condense.updateText(condensed, [messageType, lastChild.attr("data-type")]);
} else {
container.append(msg);
}
} else {
container.append(msg);
}
}
function buildChatMessage(data) { function buildChatMessage(data) {
const type = data.msg.type; const type = data.msg.type;
let target = "#chan-" + data.chan; let target = "#chan-" + data.chan;
@ -45,25 +71,13 @@ function buildChatMessage(data) {
data.msg.highlight = true; data.msg.highlight = true;
} }
if ([ if (constants.actionTypes.indexOf(type) !== -1) {
"invite", data.msg.template = "actions/" + type;
"join",
"mode",
"kick",
"nick",
"part",
"quit",
"topic",
"topic_set_by",
"action",
"whois",
"ctcp",
"channel_list",
"ban_list",
].indexOf(type) !== -1) {
template = "msg_action"; template = "msg_action";
} else if (type === "unhandled") { } else if (type === "unhandled") {
template = "msg_unhandled"; template = "msg_unhandled";
} else if (type === "condensed") {
template = "msg_condensed";
} }
const msg = $(templates[template](data.msg)); const msg = $(templates[template](data.msg));
@ -100,7 +114,7 @@ function renderChannel(data) {
} }
function renderChannelMessages(data) { function renderChannelMessages(data) {
const documentFragment = buildChannelMessages(data.id, data.messages); const documentFragment = buildChannelMessages(data);
const channel = chat.find("#chan-" + data.id + " .messages").append(documentFragment); const channel = chat.find("#chan-" + data.id + " .messages").append(documentFragment);
if (data.firstUnread > 0) { if (data.firstUnread > 0) {
@ -109,6 +123,8 @@ function renderChannelMessages(data) {
// TODO: If the message is far off in the history, we still need to append the marker into DOM // TODO: If the message is far off in the history, we still need to append the marker into DOM
if (!first.length) { if (!first.length) {
channel.prepend(templates.unread_marker()); channel.prepend(templates.unread_marker());
} else if (first.parent().hasClass("condensed")) {
first.parent().before(templates.unread_marker());
} else { } else {
first.before(templates.unread_marker()); first.before(templates.unread_marker());
} }
@ -129,6 +145,10 @@ function renderChannelMessages(data) {
} }
if (lastDate.toDateString() !== msgDate.toDateString()) { if (lastDate.toDateString() !== msgDate.toDateString()) {
var parent = msg.parent();
if (parent.hasClass("condensed")) {
msg.insertAfter(parent);
}
msg.before(templates.date_marker({msgDate: msgDate})); msg.before(templates.date_marker({msgDate: msgDate}));
} }

View File

@ -47,7 +47,7 @@ function renderPreview(preview, msg) {
container.trigger("keepToBottom"); container.trigger("keepToBottom");
} }
$("#chat").on("click", ".toggle-button", function() { $("#chat").on("click", ".text .toggle-button", function() {
const self = $(this); const self = $(this);
const container = self.closest(".chat"); const container = self.closest(".chat");
const content = self.closest(".content") const content = self.closest(".content")

View File

@ -7,7 +7,7 @@ const chat = $("#chat");
const templates = require("../../views"); const templates = require("../../views");
socket.on("more", function(data) { socket.on("more", function(data) {
const documentFragment = render.buildChannelMessages(data.chan, data.messages); const documentFragment = render.buildChannelMessages(data);
const chan = chat const chan = chat
.find("#chan-" + data.chan) .find("#chan-" + data.chan)
.find(".messages"); .find(".messages");
@ -24,6 +24,8 @@ socket.on("more", function(data) {
} else if (children.eq(1).hasClass("date-marker-container")) { } else if (children.eq(1).hasClass("date-marker-container")) {
// The unread-marker could be at index 0, which will cause the date-marker to become "stuck" // The unread-marker could be at index 0, which will cause the date-marker to become "stuck"
children.eq(1).remove(); children.eq(1).remove();
} else if (children.eq(0).hasClass("condensed") && children.eq(0).children(".date-marker-container").eq(0).hasClass("date-marker-container")) {
children.eq(0).children(".date-marker-container").eq(0).remove();
} }
// Add the older messages // Add the older messages
@ -52,6 +54,10 @@ socket.on("more", function(data) {
} }
if (lastDate.toDateString() !== msgDate.toDateString()) { if (lastDate.toDateString() !== msgDate.toDateString()) {
var parent = msg.parent();
if (parent.hasClass("condensed")) {
msg.insertAfter(parent);
}
msg.before(templates.date_marker({msgDate: msgDate})); msg.before(templates.date_marker({msgDate: msgDate}));
} }

View File

@ -8,8 +8,9 @@ const templates = require("../../views");
socket.on("msg", function(data) { socket.on("msg", function(data) {
const msg = render.buildChatMessage(data); const msg = render.buildChatMessage(data);
const target = data.chan; const targetId = data.chan;
const channel = chat.find("#chan-" + target); const target = "#chan-" + targetId;
const channel = chat.find(target);
const container = channel.find(".messages"); const container = channel.find(".messages");
const activeChannelId = chat.find(".chan.active").data("id"); const activeChannelId = chat.find(".chan.active").data("id");
@ -19,7 +20,7 @@ socket.on("msg", function(data) {
} }
// Check if date changed // Check if date changed
const prevMsg = $(container.find(".msg")).last(); let prevMsg = $(container.find(".msg")).last();
const prevMsgTime = new Date(prevMsg.attr("data-time")); const prevMsgTime = new Date(prevMsg.attr("data-time"));
const msgTime = new Date(msg.attr("data-time")); const msgTime = new Date(msg.attr("data-time"));
@ -29,17 +30,26 @@ socket.on("msg", function(data) {
} }
if (prevMsgTime.toDateString() !== msgTime.toDateString()) { if (prevMsgTime.toDateString() !== msgTime.toDateString()) {
var parent = prevMsg.parent();
if (parent.hasClass("condensed")) {
prevMsg = parent;
}
prevMsg.after(templates.date_marker({msgDate: msgTime})); prevMsg.after(templates.date_marker({msgDate: msgTime}));
} }
// Add message to the container // Add message to the container
container render.appendMessage(
.append(msg) container,
.trigger("msg", [ data.chan,
"#chan-" + target, $(target).attr("data-type"),
data data.msg.type,
]) msg
.trigger("keepToBottom"); );
container.trigger("msg", [
target,
data
]).trigger("keepToBottom");
var lastVisible = container.find("div:visible").last(); var lastVisible = container.find("div:visible").last();
if (data.msg.self if (data.msg.self
@ -52,7 +62,7 @@ socket.on("msg", function(data) {
} }
// Message arrived in a non active channel, trim it to 100 messages // Message arrived in a non active channel, trim it to 100 messages
if (activeChannelId !== target && container.find(".msg").slice(0, -100).remove().length) { if (activeChannelId !== targetId && container.find(".msg").slice(0, -100).remove().length) {
channel.find(".show-more").addClass("show"); channel.find(".show-more").addClass("show");
// Remove date-seperators that would otherwise // Remove date-seperators that would otherwise

View File

@ -101,10 +101,6 @@ body {
color: #428bca; color: #428bca;
} }
#chat button:hover {
opacity: 1;
}
/* Increase contrast of some IRC colors */ /* Increase contrast of some IRC colors */
.irc-fg2 { color: #0074d9; } .irc-fg2 { color: #0074d9; }
.irc-fg5 { color: #e969a7; } .irc-fg5 { color: #e969a7; }
@ -208,17 +204,9 @@ body {
} }
/* Embeds */ /* Embeds */
#chat .toggle-content,
#chat .toggle-button {
color: #f3f3f3;
}
#chat .toggle-button:hover {
color: rgba(243, 243, 243, .8);
}
#chat .toggle-content { #chat .toggle-content {
background: #242a33; background: #242a33;
color: #f3f3f3;
} }
#chat .toggle-content .body { #chat .toggle-content .body {

View File

@ -127,10 +127,6 @@ body {
color: #8c8cbc; color: #8c8cbc;
} }
#chat button:hover {
opacity: 1;
}
/* Increase contrast of some IRC colors */ /* Increase contrast of some IRC colors */
.irc-fg2 { color: #1b94ff; } .irc-fg2 { color: #1b94ff; }
.irc-fg5 { color: #e969a7; } .irc-fg5 { color: #e969a7; }
@ -235,17 +231,9 @@ body {
/* Previews */ /* Previews */
#chat .toggle-content,
#chat .toggle-button {
color: #dcdccc;
}
#chat .toggle-button:hover {
color: rgba(220, 220, 204, .8);
}
#chat .toggle-content { #chat .toggle-content {
background: #333; background: #333;
color: #dcdccc;
} }
#chat .toggle-content .body { #chat .toggle-content .body {

View File

@ -25,6 +25,8 @@ module.exports = {
date_marker: require("./date-marker.tpl"), date_marker: require("./date-marker.tpl"),
msg: require("./msg.tpl"), msg: require("./msg.tpl"),
msg_action: require("./msg_action.tpl"), msg_action: require("./msg_action.tpl"),
msg_condensed_toggle: require("./msg_condensed_toggle.tpl"),
msg_condensed: require("./msg_condensed.tpl"),
msg_preview: require("./msg_preview.tpl"), msg_preview: require("./msg_preview.tpl"),
msg_preview_toggle: require("./msg_preview_toggle.tpl"), msg_preview_toggle: require("./msg_preview_toggle.tpl"),
msg_unhandled: require("./msg_unhandled.tpl"), msg_unhandled: require("./msg_unhandled.tpl"),

View File

@ -1,4 +1,5 @@
<div class="msg {{type}}{{#if self}} self{{/if}}{{#if highlight}} highlight{{/if}}" id="msg-{{id}}" data-time="{{time}}"> <div class="msg {{type}}{{#if self}} self{{/if}}{{#if highlight}} highlight{{/if}}"
data-type="{{type}}" id="msg-{{id}}" data-time="{{time}}">
<span class="time tooltipped tooltipped-e" aria-label="{{localetime time}}"> <span class="time tooltipped tooltipped-e" aria-label="{{localetime time}}">
{{tz time}} {{tz time}}
</span> </span>

View File

@ -0,0 +1,7 @@
<div class="msg {{type}} closed" data-time="{{time}}">
<span class="time hide-text">{{tz time}}</span>
<span class="from"></span>
<span class="content">
<span class="condensed-text"></span>
</span>
</div>

View File

@ -0,0 +1 @@
<button class="toggle-button" aria-label="Toggle status messages"></button>