1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-25 03:58:30 +00:00

3.5.51 to 3.5.57. Changes to how chat history is implemented. Made the Actions and Metadata overlay for theater mode optional. Added support for the new Conversation UI. Increased socket server rollout. Mod cards update history in real time. Fixed global moderator badges. Refactored emote clicking code. Moved CSS to external again.

This commit is contained in:
SirStendec 2015-11-07 22:56:15 -05:00
parent 10da2b4fd5
commit c40b3ba337
20 changed files with 459 additions and 2291 deletions

111
dark.css
View file

@ -164,6 +164,7 @@
/* Popups */ /* Popups */
.ffz-dark .conversation-settings-menu,
.ffz-dark .ember-chat .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box, .ffz-dark .ember-chat .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box,
.ffz-dark .card, .ffz-dark .card,
.ffz-dark #flyout .content, .ffz-dark #flyout .content,
@ -925,4 +926,114 @@
.ffz-dark .playlist-container:not(.playlist-enabled) .playlist-item:hover, .ffz-dark .playlist-container:not(.playlist-enabled) .playlist-item:hover,
.ffz-dark .playlist-container:not(.playlist-enabled) .ui-sortable-helper { .ffz-dark .playlist-container:not(.playlist-enabled) .ui-sortable-helper {
background-color: rgba(255,255,255,0.2); background-color: rgba(255,255,255,0.2);
}
/* Conversations */
.ffz-dark .conversation-settings-menu .options-divider {
border-bottom-color: rgba(255,255,255,0.2);
}
.ffz-dark .conversation-settings-menu:before {
border-color: transparent;
border-bottom-color: #32323e;
}
.ffz-dark .conversation-settings-menu:after {
border-color: transparent;
border-bottom-color: rgb(16,16,16);
}
.ffz-dark .conversations-list-icon {
background: #19191f;
color: #8c8c9c;
border-color: #19191f
}
.ffz-dark .conversations-list-icon:hover {
color: #fff
}
.ffz-dark .conversations-list {
background: #19191f;
border: 1px solid #32323e;
color: #fff
}
.ffz-dark .conversations-list:after {
border-color: rgba(25,25,31,0);
border-top-color: #19191f;
border-width: 10px;
margin-left: -10px
}
.ffz-dark .conversations-list:before {
right: 9px;
border-color: rgba(50,50,62,0);
border-top-color: #32323e
}
.ffz-dark .conversations-list .conversations-list-header {
background: #19191f;
border-bottom: 1px solid #32323e;
color: #fff
}
.ffz-dark .conversations-list .conversation-preview-line {
color: #8c8c9c
}
.ffz-dark .conversations-list .conversations-list-item {
border-bottom: 1px solid #32323e
}
.ffz-dark .conversations-list .conversations-list-item:hover {
background-color: #121217
}
.ffz-dark .conversation-window {
background: #19191f;
box-shadow: none;
color: #8c8c9c;
}
.ffz-dark .conversations-list-icon,
.ffz-dark .conversation-window {
border: 1px solid rgba(255,255,255,0.2);
border-bottom: none;
}
.ffz-dark .conversation-header {
background: #121217;
box-shadow: none;
border-bottom: 1px solid rgba(255,255,255,0.2);
}
.ffz-dark .conversation-input-bar textarea {
border-color: rgba(255,255,255,0.1);
background-color: rgba(255,255,255,0.05);
color: #b6b6b6
}
.ffz-dark .conversation-input-actions .button,
.ffz-dark .conversation-input-actions .follow-button:not(.ember-follow) .follow {
background-color: #444
}
.ffz-dark .conversation-window.has-focus .conversation-header {
background-color: #121217
}
.ffz-dark .conversation-window.has-focus .conversation-header-name {
color: #fff
}
.ffz-dark .conversation-window.has-focus .conversation-input-bar textarea:focus {
border-color: rgba(255,255,255,0.2)
}
.ffz-dark .conversation-window.has-focus .conversation-input-actions .button,
.ffz-dark .conversation-window.has-focus .conversation-input-actions .follow-button:not(.ember-follow) .follow {
background-color: #6441a5
} }

View file

@ -254,7 +254,7 @@ FFZ.prototype.render_badges = function(component, badges) {
if ( ! this.settings.show_badges ) if ( ! this.settings.show_badges )
return badges; return badges;
var user = component.get('msgObject.from'), var user = component.get('msgObject.from') || component.get('message.from.username'),
room_id = component.get('msgObject.room') || App.__container__.lookup('controller:chat').get('currentRoom.id'); room_id = component.get('msgObject.room') || App.__container__.lookup('controller:chat').get('currentRoom.id');
var data = this.users[user]; var data = this.users[user];

View file

@ -581,6 +581,18 @@ FFZ.prototype._update_colors = function(darkness_only) {
bit.style.color = is_dark ? colors[1] : colors[0]; bit.style.color = is_dark ? colors[1] : colors[0];
} }
colored_bits = document.querySelectorAll('.conversation-chat-line .has-color');
for(var i=0, l=colored_bits.length; i < l; i++) {
var bit = colored_bits[i],
color = bit.getAttribute('data-color'),
colors = color && this._handle_color(color);
if ( ! colors )
continue;
bit.style.color = is_dark ? colors[1] : colors[0];
}
} }

View file

@ -2,10 +2,13 @@ var SVGPATH = '<path d="m120.95 1.74c4.08-0.09 8.33-0.84 12.21 0.82 3.61 1.8 7 4
DEBUG = localStorage.ffzDebugMode == "true" && document.body.classList.contains('ffz-dev'), DEBUG = localStorage.ffzDebugMode == "true" && document.body.classList.contains('ffz-dev'),
SERVER = DEBUG ? "//localhost:8000/" : "//cdn.frankerfacez.com/"; SERVER = DEBUG ? "//localhost:8000/" : "//cdn.frankerfacez.com/";
DIRECT_SERVER = DEBUG ? "//localhost:8000/" : "//direct-cdn.frankerfacez.com/";
module.exports = { module.exports = {
DEBUG: DEBUG, DEBUG: DEBUG,
SERVER: SERVER, SERVER: SERVER,
DIRECT_SERVER: DIRECT_SERVER,
API_SERVER: "//api.frankerfacez.com/", API_SERVER: "//api.frankerfacez.com/",
API_SERVER_2: "//direct-api.frankerfacez.com/", API_SERVER_2: "//direct-api.frankerfacez.com/",

View file

@ -16,6 +16,7 @@ FFZ.prototype.setup_channel = function() {
// Settings stuff! // Settings stuff!
document.body.classList.toggle("ffz-hide-view-count", !this.settings.channel_views); document.body.classList.toggle("ffz-hide-view-count", !this.settings.channel_views);
document.body.classList.toggle('ffz-theater-stats', this.settings.theater_stats);
this.log("Creating channel style element."); this.log("Creating channel style element.");
var s = this._channel_style = document.createElement('style'); var s = this._channel_style = document.createElement('style');
@ -790,6 +791,20 @@ FFZ.settings_info.stream_title = {
}; };
FFZ.settings_info.theater_stats = {
type: "boolean",
value: true,
no_mobile: true,
category: "Channel Metadata",
name: "Display on Theater Mode Hover",
help: "Show the channel metadata and actions over the video player in theater mode when you hover it with your mouse.",
on_update: function(val) {
document.body.classList.toggle('ffz-theater-stats', val);
}
};
FFZ.basic_settings.channel_info = { FFZ.basic_settings.channel_info = {
type: "select", type: "select",
options: { options: {

View file

@ -280,6 +280,20 @@ FFZ.settings_info.group_tabs = {
}; };
FFZ.settings_info.top_conversations = {
type: "boolean",
value: false,
no_mobile: true,
category: "Chat Appearance",
name: "Conversations on Top",
help: "Display the new conversation-style whisper UI at the top of the window instead of the bottom.",
on_update: function(val) {
document.body.classList.toggle('ffz-top-conversations', val);
}
};
FFZ.settings_info.pinned_rooms = { FFZ.settings_info.pinned_rooms = {
value: [], value: [],
visible: false, visible: false,
@ -298,6 +312,7 @@ FFZ.settings_info.visible_rooms = {
FFZ.prototype.setup_chatview = function() { FFZ.prototype.setup_chatview = function() {
document.body.classList.toggle("ffz-minimal-chat-head", this.settings.minimal_chat === 1 || this.settings.minimal_chat === 3); document.body.classList.toggle("ffz-minimal-chat-head", this.settings.minimal_chat === 1 || this.settings.minimal_chat === 3);
document.body.classList.toggle("ffz-minimal-chat-input", this.settings.minimal_chat === 2 || this.settings.minimal_chat === 3); document.body.classList.toggle("ffz-minimal-chat-input", this.settings.minimal_chat === 2 || this.settings.minimal_chat === 3);
document.body.classList.toggle('ffz-top-conversations', this.settings.top_conversations);
this.log("Hooking the Ember Chat controller."); this.log("Hooking the Ember Chat controller.");

View file

@ -30,45 +30,44 @@ FFZ.prototype.setup_directory = function() {
if ( ChannelView ) if ( ChannelView )
this._modify_directory_live(ChannelView); this._modify_directory_live(ChannelView);
var CreativeChannel = App.__container__.resolve('view:creative-channel');
if ( CreativeChannel )
this._modify_directory_live(CreativeChannel);
var CSGOChannel = App.__container__.resolve('view:cs-go-channel');
if ( CSGOChannel )
this._modify_directory_live(CSGOChannel, true);
var HostView = App.__container__.resolve('view:host'); var HostView = App.__container__.resolve('view:host');
if ( HostView ) if ( HostView )
this._modify_directory_host(HostView); this._modify_directory_host(HostView);
var VideoView = App.__container__.resolve('view:video'); // Initialize existing views.
if ( VideoView ) for(var key in Ember.View.views) {
this._modify_directory_video(VideoView); var view = Ember.View.views[key];
if ( view instanceof ChannelView || view instanceof CreativeChannel || view instanceof CSGOChannel || view instanceof HostView )
// TODO: Process existing views. view.ffzInit();
}
} }
FFZ.prototype._modify_directory_video = function(dir) { FFZ.prototype._modify_directory_live = function(dir, is_csgo) {
var f = this;
dir.reopen({
didInsertElement: function() {
this._super();
}
});
try {
dir.create().destroy();
} catch(err) { }
}
FFZ.prototype._modify_directory_live = function(dir) {
var f = this; var f = this;
dir.reopen({ dir.reopen({
didInsertElement: function() { didInsertElement: function() {
this._super(); this._super();
this.ffzInit();
},
ffzInit: function() {
var el = this.get('element'), var el = this.get('element'),
meta = el && el.querySelector('.meta'), meta = el && el.querySelector('.meta'),
thumb = el && el.querySelector('.thumb'), thumb = el && el.querySelector('.thumb'),
cap = thumb && thumb.querySelector('.cap'); cap = thumb && thumb.querySelector('.cap');
if ( f.settings.stream_uptime && f.settings.stream_uptime < 3 && cap ) { // CSGO doesn't provide the actual uptime information...
if ( !is_csgo && f.settings.stream_uptime && f.settings.stream_uptime < 3 && cap ) {
var t_el = this._ffz_uptime = document.createElement('div'); var t_el = this._ffz_uptime = document.createElement('div');
t_el.className = 'overlay_info length live'; t_el.className = 'overlay_info length live';
@ -88,6 +87,8 @@ FFZ.prototype._modify_directory_live = function(dir) {
target = this.get('context.model.channel.name'); target = this.get('context.model.channel.name');
logo.className = 'profile-photo'; logo.className = 'profile-photo';
logo.classList.toggle('is-csgo', is_csgo);
logo.src = this.get('context.model.channel.logo') || "http://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_150x150.png"; logo.src = this.get('context.model.channel.logo') || "http://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_150x150.png";
logo.alt = this.get('context.model.channel.display_name'); logo.alt = this.get('context.model.channel.display_name');

View file

@ -47,6 +47,12 @@ FFZ.settings_info.portrait_mode = {
} }
} }
FFZ.settings_info.portrait_warning = {
value: false,
visible: false
}
FFZ.settings_info.swap_sidebars = { FFZ.settings_info.swap_sidebars = {
type: "boolean", type: "boolean",
value: false, value: false,
@ -177,7 +183,7 @@ FFZ.prototype.setup_layout = function() {
height = size[1]; height = size[1];
// Make sure we have at least a bit of room for the chat. // Make sure we have at least a bit of room for the chat.
return this.get("windowHeight") < (height + 120 + 60 + 100); return this.get("windowHeight") < (height + 120 + 60 + 200);
} else } else
return this.get("windowWidth") < (1090 - this.get('rightColumnWidth')) return this.get("windowWidth") < (1090 - this.get('rightColumnWidth'))
@ -217,27 +223,14 @@ FFZ.prototype.setup_layout = function() {
return "<style>.dynamic-player, .dynamic-player object, .dynamic-player video{width:" + width + "px !important;height:" + height + "px !important} .dynamic-target-player,.dynamic-target-player object, .dynamic-target-player video{width:" + width + "px !important;height:" + host_height + "px !important}</style><style>.dynamic-player .player object{width:100% !important; height:100% !important}</style>"; return "<style>.dynamic-player, .dynamic-player object, .dynamic-player video{width:" + width + "px !important;height:" + height + "px !important} .dynamic-target-player,.dynamic-target-player object, .dynamic-target-player video{width:" + width + "px !important;height:" + host_height + "px !important}</style><style>.dynamic-player .player object{width:100% !important; height:100% !important}</style>";
}.property("playerSize"), }.property("playerSize"),
/*ffzUpdateWidth: _.throttle(function() { ffzPortraitWarning: function() {
var rc = document.querySelector('#right_close'); if ( ! f.settings.portrait_mode || f._portrait_warning || f.settings.portrait_warning || ! this.get('isTooSmallForRightColumn') )
if ( ! rc )
return; return;
var left_width = this.get("isLeftColumnClosed") ? 50 : 240, f._portrait_warning = true;
right_width; f.show_message('Twitch\'s Chat Sidebar has been hidden as a result of FrankerFaceZ\'s Portrait Mode because the window is too wide.<br><br>Please <a href="#" onclick="ffz.settings.set(\'portrait_mode\',0);jQuery(this).parents(\'.ffz-noty\').remove();ffz._portrait_warning = false;return false">disable Portrait Mode</a> or make your window narrower.<br><br><a href="#" onclick="onclick="ffz.settings.set(\'portrait_warning\',true);jQuery(this).parents(\'.ffz-noty\').remove();return false">Do not show this message again</a>');
if ( f.settings.swap_sidebars ) }.observes("isTooSmallForRightColumn"),
right_width = rc.offsetLeft; // + this.get('rightColumnWidth') - 5;
else
right_width = document.body.offsetWidth - rc.offsetLeft - left_width - 25;
if ( right_width < 250 ) {
// Close it!
}
this.set('rightColumnWidth', right_width);
Ember.propertyDidChange(Layout, 'contentWidth');
}, 200),*/
ffzUpdateCss: function() { ffzUpdateCss: function() {
// TODO: Fix this mess of duplicate code. // TODO: Fix this mess of duplicate code.
@ -332,6 +325,6 @@ FFZ.prototype.setup_layout = function() {
Layout.set('rawPortraitMode', this.settings.portrait_mode); Layout.set('rawPortraitMode', this.settings.portrait_mode);
// Force re-calculation of everything. // Force re-calculation of everything.
Ember.propertyDidChange(Layout, 'contentWidth'); Ember.propertyDidChange(Layout, 'windowWidth');
Ember.propertyDidChange(Layout, 'windowHeight'); Ember.propertyDidChange(Layout, 'windowHeight');
} }

View file

@ -555,6 +555,13 @@ FFZ.prototype.setup_line = function() {
if ( Line ) if ( Line )
this._modify_line(Line); this._modify_line(Line);
this.log("Hooking the Ember Conversation Line component.");
var Conversation = App.__container__.resolve('component:conversation-line');
if ( Conversation )
this._modify_conversation_line(Conversation);
// Store the capitalization of our own name. // Store the capitalization of our own name.
var user = this.get_user(); var user = this.get_user();
if ( user && user.name ) if ( user && user.name )
@ -568,6 +575,67 @@ FFZ.prototype.save_aliases = function() {
} }
FFZ.prototype._modify_conversation_line = function(component) {
var f = this,
Layout = App.__container__.lookup('controller:layout'),
Settings = App.__container__.lookup('controller:settings');
component.reopen({
tokenizedMessage: function() {
try {
return f.tokenize_conversation_line(this.get('message'));
} catch(err) {
f.error("convo-line tokenizedMessage: " + err);
return this._super();
}
}.property("message", "currentUsername"),
click: function(e) {
if ( e.target && e.target.classList.contains('deleted-link') )
return f._deleted_link_click.bind(e.target)(e);
if ( f._click_emote(e.target, e) )
return;
return this._super(e);
},
render: function(e) {
var user = this.get('message.from.username'),
raw_color = this.get('message.from.color'),
colors = raw_color && f._handle_color(raw_color),
is_dark = (Layout && Layout.get('isTheatreMode')) || f.settings.dark_twitch;
e.push('<div class="indicator"></div>');
var alias = f.aliases[user],
name = this.get('message.from.displayName') || (user && user.capitalize()) || "unknown user",
style = colors && 'color:' + (is_dark ? colors[1] : colors[0]),
colored = style ? ' has-color' : '';
if ( alias )
e.push('<span class="from ffz-alias tooltip' + colored + '" style="' + style + (colors ? '" data-colors="' + raw_color : '') + '" title="' + utils.sanitize(name) + '">' + utils.sanitize(alias) + '</span>');
else
e.push('<span class="from' + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '') + '">' + utils.sanitize(name) + '</span>');
e.push('<span class="colon">:</span> ');
if ( ! this.get('isActionMessage') ) {
style = '';
colored = '';
}
e.push('<span class="message' + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '') + '">');
e.push(f.render_tokens(this.get('tokenizedMessage'), true));
e.push('</span>');
}
});
}
FFZ.prototype._modify_line = function(component) { FFZ.prototype._modify_line = function(component) {
var f = this, var f = this,
@ -577,46 +645,13 @@ FFZ.prototype._modify_line = function(component) {
component.reopen({ component.reopen({
tokenizedMessage: function() { tokenizedMessage: function() {
// Add our own step to the tokenization procedure. try {
var tokens = this.get("msgObject.cachedTokens"); return f.tokenize_chat_line(this.get('msgObject'));
if ( tokens ) } catch(err) {
return tokens; f.error("chat-line tokenizedMessage: " + err);
return this._super();
tokens = this._super();
var start = performance.now(),
user = f.get_user(),
from_me = user && this.get("msgObject.from") === user.login;
tokens = f._remove_banned(tokens);
tokens = f._emoticonize(this, tokens);
if ( f.settings.parse_emoji )
tokens = f.tokenize_emoji(tokens);
// Store the capitalization.
var display = this.get("msgObject.tags.display-name");
if ( display && display.length )
FFZ.capitalization[this.get("msgObject.from")] = [display.trim(), Date.now()];
if ( ! from_me )
tokens = f.tokenize_mentions(tokens);
for(var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if ( ! _.isString(token) && token.mentionedUser && ! token.own ) {
this.set('msgObject.ffz_has_mention', true);
break;
}
} }
var end = performance.now();
if ( end - start > 5 )
f.log("Tokenizing Message Took Too Long - " + (end-start) + "ms", tokens, false, true);
this.set("msgObject.cachedTokens", tokens);
return tokens;
}.property("msgObject.message", "isChannelLinksDisabled", "currentUserNick", "msgObject.from", "msgObject.tags.emotes"), }.property("msgObject.message", "isChannelLinksDisabled", "currentUserNick", "msgObject.from", "msgObject.tags.emotes"),
ffzUpdated: Ember.observer("msgObject.ffz_deleted", "msgObject.ffz_old_messages", function() { ffzUpdated: Ember.observer("msgObject.ffz_deleted", "msgObject.ffz_old_messages", function() {
@ -633,18 +668,6 @@ FFZ.prototype._modify_line = function(component) {
if ( e.target && e.target.classList.contains('mod-icon') ) { if ( e.target && e.target.classList.contains('mod-icon') ) {
jQuery(e.target).trigger('mouseout'); jQuery(e.target).trigger('mouseout');
/*if ( e.target.classList.contains('purge') ) {
var i = this.get('msgObject.from'),
room_id = this.get('msgObject.room'),
room = room_id && f.rooms[room_id] && f.rooms[room_id].room;
if ( room ) {
room.send("/timeout " + i + " 1", true);
room.clearMessages(i);
}
return;
}*/
if ( e.target.classList.contains('custom') ) { if ( e.target.classList.contains('custom') ) {
var room_id = this.get('msgObject.room'), var room_id = this.get('msgObject.room'),
room = room_id && f.rooms[room_id] && f.rooms[room_id].room, room = room_id && f.rooms[room_id] && f.rooms[room_id].room,
@ -660,30 +683,8 @@ FFZ.prototype._modify_line = function(component) {
} }
} }
if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons && e.target && e.target.classList.contains('emoticon') ) { if ( f._click_emote(e.target, e) )
var eid = e.target.getAttribute('data-emote'); return;
if ( eid )
window.open("https://twitchemotes.com/emote/" + eid);
else {
eid = e.target.getAttribute("data-ffz-emote");
var es = e.target.getAttribute("data-ffz-set"),
set = es && f.emote_sets[es],
url;
if ( ! set )
return;
if ( set.hasOwnProperty('source_ext') ) {
var api = f._apis[set.source_ext];
if ( api && api.emote_url_generator )
url = api.emote_url_generator(set.source_id, eid);
} else
url = "https://www.frankerfacez.com/emoticons/" + eid;
if ( url )
window.open(url);
}
}
return this._super(e); return this._super(e);
}, },
@ -768,7 +769,7 @@ FFZ.prototype._modify_line = function(component) {
else if ( this.get('isAdmin') ) else if ( this.get('isAdmin') )
badges[0] = {klass: 'admin', title: 'Admin'}; badges[0] = {klass: 'admin', title: 'Admin'};
else if ( this.get('isGlobalMod') ) else if ( this.get('isGlobalMod') )
badges[0] = {klass: 'global-mod', title: 'Global Moderator'}; badges[0] = {klass: 'global-moderator', title: 'Global Moderator'};
else if ( ! is_whisper && this.get('isModerator') ) else if ( ! is_whisper && this.get('isModerator') )
badges[0] = {klass: 'moderator', title: 'Moderator'}; badges[0] = {klass: 'moderator', title: 'Moderator'};
@ -835,7 +836,7 @@ FFZ.prototype._modify_line = function(component) {
if ( deleted ) if ( deleted )
e.push('<span class="deleted"><a class="undelete" href="#">&lt;message deleted&gt;</a></span>'); e.push('<span class="deleted"><a class="undelete" href="#">&lt;message deleted&gt;</a></span>');
else { else {
e.push('<span class="message' + colored + '" style="' + style + '">'); e.push('<span class="message' + colored + '" style="' + style + (colors ? '" data-color="' + raw_color : '') + '">');
e.push(f.render_tokens(this.get('tokenizedMessage'), true)); e.push(f.render_tokens(this.get('tokenizedMessage'), true));
var old_messages = this.get('msgObject.ffz_old_messages'); var old_messages = this.get('msgObject.ffz_old_messages');

View file

@ -435,13 +435,20 @@ FFZ.prototype.setup_mod_card = function() {
return alias || this.get("cardInfo.user.display_name") || user_id.capitalize(); return alias || this.get("cardInfo.user.display_name") || user_id.capitalize();
}), }),
willDestroy: function() {
if ( f._mod_card === this )
f._mod_card = undefined;
this._super();
},
didInsertElement: function() { didInsertElement: function() {
this._super(); this._super();
window._card = this;
try { try {
if ( f.has_bttv ) if ( f.has_bttv )
return; return;
f._mod_card = this;
var el = this.get('element'), var el = this.get('element'),
controller = this.get('controller'), controller = this.get('controller'),
line, line,
@ -450,11 +457,14 @@ FFZ.prototype.setup_mod_card = function() {
chat = window.App && App.__container__.lookup('controller:chat'), chat = window.App && App.__container__.lookup('controller:chat'),
user = f.get_user(), user = f.get_user(),
is_broadcaster = user && chat && chat.get('currentRoom.id') === user.login, room_id = chat && chat.get('currentRoom.id'),
is_broadcaster = user && room_id === user.login,
user_id = controller.get('cardInfo.user.id'), user_id = controller.get('cardInfo.user.id'),
alias = f.aliases[user_id]; alias = f.aliases[user_id];
this.ffz_room_id = room_id;
// Alias Display // Alias Display
if ( alias ) { if ( alias ) {
var name = el.querySelector('h3.name'), var name = el.querySelector('h3.name'),
@ -735,17 +745,19 @@ FFZ.prototype.setup_mod_card = function() {
l_el.innerHTML = (helpers ? '<span class="timestamp float-left">' + helpers.getTime(line.date) + '</span> ' : '') + '<span class="message">' + (line.style === 'action' ? '*' + line.from + ' ' : '') + f.render_tokens(line.cachedTokens) + '</span>'; l_el.innerHTML = (helpers ? '<span class="timestamp float-left">' + helpers.getTime(line.date) + '</span> ' : '') + '<span class="message">' + (line.style === 'action' ? '*' + line.from + ' ' : '') + f.render_tokens(line.cachedTokens) + '</span>';
// Banned Links // Interactivity
var bad_links = l_el.querySelectorAll('a.deleted-link'); jQuery('a.deleted-link', l_el).click(f._deleted_link_click);
for(var x=0; x < bad_links.length; x++) jQuery('img.emoticon', l_el).click(function(e) { f._click_emote(this, e) });
bad_links[x].addEventListener("click", f._deleted_link_click);
jQuery('.html-tooltip', l_el).tipsy({html:true}); jQuery('.html-tooltip', l_el).tipsy({html:true});
// Append
history.appendChild(l_el); history.appendChild(l_el);
} }
el.appendChild(history); el.appendChild(history);
this.ffz_alternate = alternate;
// Lazy scroll-to-bottom // Lazy scroll-to-bottom
history.scrollTop = history.scrollHeight; history.scrollTop = history.scrollHeight;
} }

View file

@ -5,6 +5,7 @@ var FFZ = window.FrankerFaceZ,
HOSTED_SUB = / subscribed to /, HOSTED_SUB = / subscribed to /,
constants = require('../constants'), constants = require('../constants'),
utils = require('../utils'), utils = require('../utils'),
helpers,
// StrimBagZ Support // StrimBagZ Support
is_android = navigator.userAgent.indexOf('Android') !== -1, is_android = navigator.userAgent.indexOf('Android') !== -1,
@ -14,7 +15,12 @@ var FFZ = window.FrankerFaceZ,
return ""; return "";
return '.chat-line[data-room="' + room.id + '"] .badges .moderator:not(.ffz-badge-replacement) { background-image:url("' + room.moderator_badge + '") !important; }'; return '.chat-line[data-room="' + room.id + '"] .badges .moderator:not(.ffz-badge-replacement) { background-image:url("' + room.moderator_badge + '") !important; }';
} };
try {
helpers = window.require && window.require("ember-twitch-chat/helpers/chat-line-helpers");
} catch(err) { }
// -------------------- // --------------------
@ -723,10 +729,11 @@ FFZ.prototype._insert_history = function(room_id, data) {
tmiRoom = r.tmiRoom, tmiRoom = r.tmiRoom,
inserted = 0, inserted = 0,
purged = {},
last_msg = data[data.length - 1], last_msg = data[data.length - 1],
now = new Date(), now = new Date(),
last_date = typeof last_msg.date === "string" ? utils.parse_date(last_msg.date) : last_msg.date, last_date = (typeof last_msg.date === "string" || typeof last_msg.date === "number") ? (last_msg.date = utils.parse_date(last_msg.date)) : last_msg.date,
age = (now - last_date) / 1000, age = (now - last_date) / 1000,
is_old = age > 300, is_old = age > 300,
@ -738,9 +745,13 @@ FFZ.prototype._insert_history = function(room_id, data) {
var i = data.length; var i = data.length;
while(i--) { while(i--) {
var msg = data[i]; var msg = data[i],
is_deleted = msg.ffz_deleted = purged[msg.from] || false;
if ( typeof msg.date === "string" ) if ( is_deleted && ! this.settings.prevent_clear )
msg.deleted = true;
if ( typeof msg.date === "string" || typeof msg.date === "number" )
msg.date = utils.parse_date(msg.date); msg.date = utils.parse_date(msg.date);
msg.ffz_alternate = alternation = ! alternation; msg.ffz_alternate = alternation = ! alternation;
@ -774,6 +785,14 @@ FFZ.prototype._insert_history = function(room_id, data) {
msg.style = "notification"; msg.style = "notification";
} }
if ( msg.tags && typeof msg.tags.emotes === "string" )
try {
msg.tags.emotes = JSON.parse(msg.tags.emotes);
} catch(err) {
f.log("Error Parsing JSON Emotes: " + err);
msg.tags.emotes = {};
}
if ( ! msg.cachedTokens || ! msg.cachedTokens.length ) if ( ! msg.cachedTokens || ! msg.cachedTokens.length )
this.tokenize_chat_line(msg, true, r.get('roomProperties.hide_chat_links')); this.tokenize_chat_line(msg, true, r.get('roomProperties.hide_chat_links'));
@ -791,6 +810,14 @@ FFZ.prototype._insert_history = function(room_id, data) {
} else } else
break; break;
} }
// If there was a CLEARCHAT, stop processing.
if ( msg.tags && msg.tags.target === '@@' )
break;
// If there was a purge, just track the name.
else if ( msg.tags && msg.tags.target )
purged[msg.tags.target] = true;
} }
if ( is_old ) { if ( is_old ) {
@ -1190,6 +1217,38 @@ FFZ.prototype._modify_room = function(room) {
if ( user_history.length > 20 ) if ( user_history.length > 20 )
user_history.shift(); user_history.shift();
if ( f._mod_card && f._mod_card.ffz_room_id === msg.room && f._mod_card.get('cardInfo.user.id') === msg.from ) {
var el = f._mod_card.get('element'),
history = el && el.querySelector('.chat-history'),
was_at_top = history && history.scrollTop >= (history.scrollHeight - history.clientHeight);
if ( history ) {
var l_el = document.createElement('li');
l_el.className = 'message-line chat-line clearfix';
l_el.classList.toggle('ffz-alternate', f._mod_card.ffz_alternate);
f._mod_card.ffz_alternate = !f._mod_card.ffz_alternate;
if ( msg.style )
l_el.classList.add(msg.style);
l_el.innerHTML = (helpers ? '<span class="timestamp float-left">' + helpers.getTime(msg.date) + '</span> ' : '') + '<span class="message">' + (msg.style === 'action' ? '*' + msg.from + ' ' : '') + f.render_tokens(msg.cachedTokens) + '</span>';
// Interactivity
jQuery('a.deleted-link', l_el).click(f._deleted_link_click);
jQuery('img.emoticon', l_el).click(function(e) { f._click_emote(this, e) });
jQuery('.html-tooltip', l_el).tipsy({html:true});
history.appendChild(l_el);
if ( was_at_top )
setTimeout(function() { history.scrollTop = history.scrollHeight; })
// Don't do infinite scrollback.
if ( history.childElementCount > 50 )
history.removeChild(history.firstElementChild);
}
}
} }
} }

View file

@ -141,6 +141,41 @@ FFZ.prototype._report_emotes = function() {
} }
// ------------------------
// Emote Click Handler
// ------------------------
FFZ.prototype._click_emote = function(target, event) {
if ( ! this.settings.clickable_emoticons || (event && !((event.shiftKey || event.shiftLeft) && target && target.classList.contains('emoticon'))) )
return;
var eid = target.getAttribute('data-emote');
if ( eid )
window.open("https://twitchemotes.com/emote/" + eid);
else {
eid = target.getAttribute("data-ffz-emote");
var es = target.getAttribute("data-ffz-set"),
emote_set = es && this.emote_sets[es],
url;
if ( ! emote_set )
return;
if ( emote_set.hasOwnProperty('source_ext') ) {
var api = this._apis[emote_set.source_ext];
if ( api && api.emote_url_generator )
url = api.emote_url_generator(emote_set.source_id, eid);
} else
url = "https://www.frankerfacez.com/emoticons/" + eid;
if ( url ) {
window.open(url);
return true;
}
}
}
// ------------------------ // ------------------------
// Twitch Emoticon Checker // Twitch Emoticon Checker
// ------------------------ // ------------------------

View file

@ -22,7 +22,7 @@ FFZ.get = function() { return FFZ.instance; }
// Version // Version
var VER = FFZ.version_info = { var VER = FFZ.version_info = {
major: 3, minor: 5, revision: 50, major: 3, minor: 5, revision: 57,
toString: function() { toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || ""); return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
} }

View file

@ -35,7 +35,7 @@ FFZ.settings_info.socket_server_pool = {
2: "Development" 2: "Development"
}, },
value: ffz_socket_seed > 0.65 ? 1 : 0, value: ffz_socket_seed > 0.4 ? 1 : 0,
process_value: function(val) { process_value: function(val) {
if ( typeof val === "string" ) if ( typeof val === "string" )

File diff suppressed because it is too large Load diff

View file

@ -401,14 +401,58 @@ FFZ.prototype.load_twitch_emote_data = function(tries) {
// Tokenization // Tokenization
// --------------------- // ---------------------
FFZ.prototype.tokenize_conversation_line = function(message, prevent_notification) {
var msg = message.get('body'),
user = this.get_user(),
from_user = message.get('from.username'),
from_me = user && from_user === user.login,
emotes = message.get('tags.emotes'),
tokens = [msg];
// Standard Tokenization
if ( helpers && helpers.linkifyMessage )
tokens = helpers.linkifyMessage(tokens);
if ( user && user.login && helpers && helpers.mentionizeMessage )
tokens = helpers.mentionizeMessage(tokens, user.login, from_me);
if ( helpers && helpers.emoticonizeMessage && emotes )
tokens = helpers.emoticonizeMessage(tokens, emotes);
if ( this.settings.replace_bad_emotes )
tokens = this.tokenize_replace_emotes(tokens);
// FrankerFaceZ Extras
tokens = this._remove_banned(tokens);
tokens = this.tokenize_emotes(from_user, undefined, tokens, from_me);
if ( this.settings.parse_emoji )
tokens = this.tokenize_emoji(tokens);
// Capitalization
var display_name = message.get('from.displayName');
if ( display_name && display_name.length )
FFZ.capitalization[from_user] = [display_name.trim(), Date.now()];
// Mentions!
if ( ! from_me )
tokens = this.tokenize_mentions(tokens);
// TODO: Notifications?
return tokens;
}
FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, delete_links) { FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, delete_links) {
if ( msgObject.cachedTokens ) if ( msgObject.cachedTokens )
return msgObject.cachedTokens; return msgObject.cachedTokens;
var msg = msgObject.message, var msg = msgObject.message || msgObject.get('body'),
user = this.get_user(), user = this.get_user(),
room_id = msgObject.room, room_id = msgObject.room,
from_me = user && msgObject.from === user.login, from_user = msgObject.from,
from_me = user && from_user === user.login,
emotes = msgObject.tags && msgObject.tags.emotes, emotes = msgObject.tags && msgObject.tags.emotes,
tokens = [msg]; tokens = [msg];
@ -438,7 +482,7 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
// FrankerFaceZ Extras // FrankerFaceZ Extras
tokens = this._remove_banned(tokens); tokens = this._remove_banned(tokens);
tokens = this.tokenize_emotes(msgObject.from, room_id, tokens, from_me); tokens = this.tokenize_emotes(from_user, room_id, tokens, from_me);
if ( this.settings.parse_emoji ) if ( this.settings.parse_emoji )
tokens = this.tokenize_emoji(tokens); tokens = this.tokenize_emoji(tokens);
@ -446,7 +490,7 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
// Capitalization // Capitalization
var display = msgObject.tags && msgObject.tags['display-name']; var display = msgObject.tags && msgObject.tags['display-name'];
if ( display && display.length ) if ( display && display.length )
FFZ.capitalization[msgObject.from] = [display.trim(), Date.now()]; FFZ.capitalization[from_user] = [display.trim(), Date.now()];
// Mentions! // Mentions!
@ -482,7 +526,7 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
else else
room_name = FFZ.get_capitalization(room_id); room_name = FFZ.get_capitalization(room_id);
display = display || Twitch.display.capitalize(msgObject.from); display = display || Twitch.display.capitalize(from_user);
if ( msgObject.style === 'action' ) if ( msgObject.style === 'action' )
msg = '* ' + display + ' ' + msg; msg = '* ' + display + ' ' + msg;
@ -602,7 +646,7 @@ FFZ.prototype.render_tokens = function(tokens, render_links) {
var mirror_url = utils.quote_attr(constants.EMOTE_MIRROR_BASE + id + '.png'); var mirror_url = utils.quote_attr(constants.EMOTE_MIRROR_BASE + id + '.png');
extra = ' data-emote="' + id + '" onerror="FrankerFaceZ._emote_mirror_swap(this)"'; extra = ' data-emote="' + id + '"'; // onerror="FrankerFaceZ._emote_mirror_swap(this)"'; // Disable error checking for now.
if ( ! constants.EMOTE_REPLACEMENTS[id] ) if ( ! constants.EMOTE_REPLACEMENTS[id] )
srcset = build_srcset(id); srcset = build_srcset(id);

View file

@ -1,6 +1,6 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
constants = require("../constants"), constants = require("../constants");
styles = require("../styles"); //styles = require("../styles");
// --------------------- // ---------------------
@ -211,7 +211,6 @@ FFZ.prototype._load_dark_css = function() {
s.id = "ffz-dark-css"; s.id = "ffz-dark-css";
s.setAttribute('rel', 'stylesheet'); s.setAttribute('rel', 'stylesheet');
s.setAttribute('href', constants.SERVER + "script/dark.css?_=" + (constants.DEBUG ? Date.now() : FFZ.version_info)); s.setAttribute('href', constants.DIRECT_SERVER + "script/dark.css?_=" + (constants.DEBUG ? Date.now() : FFZ.version_info));
s.onerror = "this.href = this.href + '_';"
document.head.appendChild(s); document.head.appendChild(s);
} }

View file

@ -181,6 +181,11 @@ FFZ.prototype.show_notification = function(message, title, tag, timeout, on_clic
// --------------------- // ---------------------
FFZ.prototype.show_message = function(message) { FFZ.prototype.show_message = function(message) {
if ( ! window.jQuery || ! window.jQuery.noty || ! jQuery.noty.themes.ffzTheme ) {
setTimeout(this.show_message.bind(this, message), 50);
return;
}
window.noty({ window.noty({
text: message, text: message,
theme: "ffzTheme", theme: "ffzTheme",

View file

@ -1,18 +1,24 @@
var FFZ = window.FrankerFaceZ, var FFZ = window.FrankerFaceZ,
constants = require('../constants'), constants = require('../constants');
styles = require('../styles'); //styles = require('../styles');
FFZ.prototype.setup_css = function() { FFZ.prototype.setup_css = function() {
document.body.classList.toggle('ffz-flip-dashboard', this.settings.flip_dashboard); document.body.classList.toggle('ffz-flip-dashboard', this.settings.flip_dashboard);
this.log("Injecting main FrankerFaceZ CSS."); this.log("Injecting main FrankerFaceZ CSS.");
var s = this._main_style = document.createElement('style'); var s = this._main_style = document.createElement('link');
s.id = "ffz-main-css";
s.setAttribute('rel', 'stylesheet');
s.setAttribute('href', constants.DIRECT_SERVER + "script/style.css?_=" + (constants.DEBUG ? Date.now() : FFZ.version_info));
document.head.appendChild(s);
/*var s = this._main_style = document.createElement('style');
s.textContent = styles.style; s.textContent = styles.style;
s.id = "ffz-ui-css"; s.id = "ffz-ui-css";
document.head.appendChild(s); document.head.appendChild(s);*/
if ( window.jQuery && jQuery.noty ) if ( window.jQuery && jQuery.noty )
jQuery.noty.themes.ffzTheme = { jQuery.noty.themes.ffzTheme = {

View file

@ -42,6 +42,9 @@ var sanitize_el = document.createElement('span'),
date_regex = /^(\d{4}|\+\d{6})(?:-?(\d{2})(?:-?(\d{2})(?:T(\d{2})(?::?(\d{2})(?::?(\d{2})(?:(?:\.|,)(\d{1,}))?)?)?(Z|([\-+])(\d{2})(?::?(\d{2}))?)?)?)?)?$/, date_regex = /^(\d{4}|\+\d{6})(?:-?(\d{2})(?:-?(\d{2})(?:T(\d{2})(?::?(\d{2})(?::?(\d{2})(?:(?:\.|,)(\d{1,}))?)?)?(Z|([\-+])(\d{2})(?::?(\d{2}))?)?)?)?)?$/,
parse_date = function(str) { parse_date = function(str) {
if ( typeof str === "number" )
return new Date(str);
var parts = str.match(date_regex); var parts = str.match(date_regex);
if ( ! parts ) if ( ! parts )
return null; return null;