1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-31 10:20:55 +00:00

Merge branch 'master' into socketdev

This commit is contained in:
Kane York 2015-11-16 22:09:48 -08:00
commit 715a3a993c
51 changed files with 2832 additions and 989 deletions

175
dark.css
View file

@ -63,35 +63,6 @@
color: #fff !important;
}
/* moderation card */
.ember-chat-container.dark .ember-chat .ffz-moderation-card,
.chat-container.dark .ember-chat .ffz-moderation-card,
.app-main.theatre .ember-chat .ffz-moderation-card {
border-color: #1b1b20;
}
.ember-chat-container.dark .ember-chat .moderation-card:focus,
.chat-container.dark .ember-chat .moderation-card:focus,
.app-main.theatre .ember-chat .moderation-card:focus {
border-color: #cbcbcb;
}
.ember-chat-container.dark .ember-chat .moderation-card .interface,
.chat-container.dark .ember-chat .moderation-card .interface,
.app-main.theatre .ember-chat .moderation-card .interface {
background-color: #232329;
}
.ffz-no-blue .ember-chat-container.dark .ember-chat .moderation-card .interface,
.ffz-no-blue .chat-container.dark .ember-chat .moderation-card .interface,
.ffz-no-blue .app-main.theatre .ember-chat .moderation-card .interface {
background-color: #232323;
}
.moderation-card h3.name a { color: #fff !important; }
/* stats */
.ffz-dark .stats-and-actions,
.ffz-dark #main_col .content #stats_and_actions {
@ -164,6 +135,7 @@
/* Popups */
.ffz-dark .conversation-settings-menu,
.ffz-dark .ember-chat .chat-interface .ffz-ui-popup.emoticon-selector .emoticon-selector-box,
.ffz-dark .card,
.ffz-dark #flyout .content,
@ -530,7 +502,7 @@
/* FrankerFaceZ Menu */
.ffz-dark .ember-chat .chat-menu .list-header {
/*.ffz-dark .ember-chat .chat-menu .list-header {
border-top-color: rgba(255,255,255,0.2);
}
@ -552,7 +524,7 @@
.ffz-dark .ffz-ui-popup ul.menu li.active a {
border-top-color: rgb(16,16,16);
}
}*/
/* New User Prompt */
@ -925,4 +897,143 @@
.ffz-dark .playlist-container:not(.playlist-enabled) .playlist-item:hover,
.ffz-dark .playlist-container:not(.playlist-enabled) .ui-sortable-helper {
background-color: rgba(255,255,255,0.2);
}
}
/* Conversations */
.ffz-dark .conversation-input-bar .emoticon-selector-toggle svg path {
fill: rgba(255,255,255,0.2);
}
.ffz-dark .conversation-input-bar .emoticon-selector-toggle:hover svg path {
fill: rgba(255,255,255,0.5);
}
.ffz-dark .conversation-input-bar .emoticon-selector-box .emote-set {
border-color: #323232;
}
.ffz-dark .conversation-input-bar .emoticon-selector-box .emoticon-grid {
background-color: #191919;
}
.ffz-dark .ember-chat .chat-settings .experimental-options {
border-top-color: rgba(255,255,255, 0.2);
}
.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: #121218;
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: #121218;
}
.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;
}
.ffz-dark .conversation-window:not(.collapsed) .conversation-header {
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
}
.ffz-dark .conversation-window .timestamp-line span,
.ffz-dark .conversation-window .new-message-divider span { background: transparent; }
.ffz-dark .conversation-window .timestamp-line:after,
.ffz-dark .conversation-window .new-message-divider:after { display: none; }

View file

@ -44,26 +44,26 @@ gulp.task('prepare', ['clean'], function() {
});
gulp.task('templates', ['prepare'], function() {
gulp.src(['build/templates/**/*.hbs'])
.pipe(jsEscape())
.pipe(wrap('Handlebars.compile(<%= contents %>)'))
.pipe(declare({
root: 'exports',
noRedeclare: true,
processName: function(filePath) {
var match = filePath.match(/build[\\\/]templates[\\\/](.*)\.hbs$/);
return declare.processNameByPath((match && match.length > 1) ? match[1] : filePath);
}
}))
.pipe(concat('templates.js'))
.pipe(gulp.dest('build/'))
.on('error', util.log);
});
//gulp.task('templates', ['prepare'], function() {
// return gulp.src(['build/templates/**/*.hbs'])
// .pipe(jsEscape())
// .pipe(wrap('Handlebars.compile(<%= contents %>)'))
// .pipe(declare({
// root: 'exports',
// noRedeclare: true,
// processName: function(filePath) {
// var match = filePath.match(/build[\\\/]templates[\\\/](.*)\.hbs$/);
// return declare.processNameByPath((match && match.length > 1) ? match[1] : filePath);
// }
// }))
// .pipe(concat('templates.js'))
// .pipe(gulp.dest('build/'))
// .on('error', util.log);
//});
gulp.task('styles', ['prepare'], function() {
gulp.src(['build/styles/**/*.css'])
return gulp.src(['build/styles/**/*.css'])
.pipe(minifyCss())
.pipe(jsEscape())
.pipe(declare({
@ -74,14 +74,14 @@ gulp.task('styles', ['prepare'], function() {
return declare.processNameByPath((match && match.length > 1) ? match[1] : filePath);
}
}))
.pipe(concat('styles.js'))
.pipe(concat('compiled_styles.js'))
.pipe(gulp.dest('build/'))
.on('error', util.log)
});
gulp.task('scripts', ['prepare', 'templates', 'styles'], function() {
gulp.src(['build/main.js'])
gulp.task('scripts', ['styles'], function() {
return gulp.src(['build/main.js'])
.pipe(browserify())
.pipe(concat('script.js'))
.pipe(header('(function(window) {'))
@ -94,7 +94,7 @@ gulp.task('scripts', ['prepare', 'templates', 'styles'], function() {
});
gulp.task('watch', ['default', 'server'], function() {
gulp.watch('src/**/*', ['default']);
return gulp.watch('src/**/*', ['default']);
});
gulp.task('default', ['scripts']);
@ -130,7 +130,7 @@ gulp.task('upload', ['default'], function() {
.on('error', util.log);
});
gulp.task('clear_cache', ['upload'], function() {
gulp.task('clear_cache', ['upload'], function(cb) {
// Load credentials from an external file.
var contents = fs.readFileSync('credentials.json', 'utf8'),
cred = JSON.parse(contents);
@ -167,6 +167,7 @@ gulp.task('clear_cache', ['upload'], function() {
return util.log("[FAIL] Non-200 Status: " + request.statusCode);
util.log("[SUCCESS] Cache cleared.");
cb();
});
});

View file

@ -43,9 +43,9 @@ FFZ.settings_info.legacy_badges = {
},
on_update: function(val) {
document.body.classList.toggle("ffz-legacy-mod-badges", val !== 0);
document.body.classList.toggle("ffz-legacy-turbo-badges", val > 1);
document.body.classList.toggle("ffz-legacy-badges", val === 3);
this.toggle_style('badges-legacy', val === 3);
this.toggle_style('badges-legacy-mod', val !== 0);
this.toggle_style('badges-legacy-turbo', val > 1);
}
};
@ -83,11 +83,12 @@ FFZ.settings_info.transparent_badges = {
if ( this.has_bttv )
return;
document.body.classList.toggle("ffz-rounded-badges", val === 1);
document.body.classList.toggle("ffz-circular-badges", val === 2);
document.body.classList.toggle("ffz-circular-blank-badges", val === 3);
document.body.classList.toggle("ffz-circular-small-badges", val === 4);
document.body.classList.toggle("ffz-transparent-badges", val === 5);
this.toggle_style('badges-rounded', val === 1);
this.toggle_style('badges-circular', val === 2 || val === 3 || val === 4);
this.toggle_style('badges-blank', val === 3 || val === 4);
this.toggle_style('badges-circular-small', val === 4);
this.toggle_style('badges-transparent', val === 5);
document.body.classList.toggle('ffz-transparent-badges', val === 5);
}
};
@ -98,16 +99,18 @@ FFZ.settings_info.transparent_badges = {
FFZ.prototype.setup_badges = function() {
if ( ! this.has_bttv ) {
document.body.classList.toggle("ffz-rounded-badges", this.settings.transparent_badges === 1);
document.body.classList.toggle("ffz-circular-badges", this.settings.transparent_badges === 2);
document.body.classList.toggle("ffz-circular-blank-badges", this.settings.transparent_badges === 3);
document.body.classList.toggle("ffz-circular-small-badges", this.settings.transparent_badges === 4);
document.body.classList.toggle("ffz-transparent-badges", this.settings.transparent_badges === 5);
var val = this.settings.transparent_badges;
this.toggle_style('badges-rounded', val === 1);
this.toggle_style('badges-circular', val === 2 || val === 3 || val === 4);
this.toggle_style('badges-blank', val === 3 || val === 4);
this.toggle_style('badges-circular-small', val === 4);
this.toggle_style('badges-transparent', val === 5);
document.body.classList.toggle('ffz-transparent-badges', val === 5);
}
document.body.classList.toggle("ffz-legacy-mod-badges", this.settings.legacy_badges !== 0);
document.body.classList.toggle("ffz-legacy-turbo-badges", this.settings.legacy_badges > 1);
document.body.classList.toggle("ffz-legacy-badges", this.settings.legacy_badges === 3);
this.toggle_style('badges-legacy', this.settings.legacy_badges === 3);
this.toggle_style('badges-legacy-mod', this.settings.legacy_badges !== 0);
this.toggle_style('badges-legacy-turbo', this.settings.legacy_badges > 1);
this.log("Preparing badge system.");
this.badges = {};
@ -254,9 +257,13 @@ FFZ.prototype.render_badges = function(component, badges) {
if ( ! this.settings.show_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');
return this._render_badges(user, room_id, badges, component);
}
FFZ.prototype._render_badges = function(user, room_id, badges, component) {
var data = this.users[user];
if ( ! data || ! data.badges )
return badges;

View file

@ -45,7 +45,7 @@ FFZ.settings_info.fix_color = {
},
on_update: function(val) {
document.body.classList.toggle("ffz-chat-colors-gray", !this.has_bttv && (val === '-1'));
this.toggle_style('chat-colors-gray', !this.has_bttv && val === '-1');
if ( ! this.has_bttv && val !== '-1' )
this._rebuild_colors();
@ -114,6 +114,8 @@ FFZ.settings_info.color_blind = {
// --------------------
FFZ.prototype.setup_colors = function() {
this.toggle_style('chat-colors-gray', !this.has_bttv && this.settings.fix_color === '-1');
this._colors = {};
this._rebuild_contrast();
@ -185,6 +187,43 @@ RGBColor.prototype.eq = function(rgb) {
return rgb.r === this.r && rgb.g === this.g && rgb.b === this.b;
}
RGBColor.fromCSS = function(rgb) {
rgb = rgb.trim();
if ( rgb.charAt(0) === '#' )
return RGBColor.fromHex(rgb);
var match = /rgba?\( *(\d+%?) *, *(\d+%?) *, *(\d+%?) *(?:,[^\)]+)?\)/.exec(rgb);
if ( match ) {
var r = match[1],
g = match[2],
b = match[3];
if ( r.charAt(r.length-1) === '%' )
r = 255 * (parseInt(r) / 100);
else
r = parseInt(r);
if ( g.charAt(g.length-1) === '%' )
g = 255 * (parseInt(g) / 100);
else
g = parseInt(g);
if ( b.charAt(b.length-1) === '%' )
b = 255 * (parseInt(b) / 100);
else
b = parseInt(b);
return new RGBColor(
Math.min(Math.max(0, r), 255),
Math.min(Math.max(0, g), 255),
Math.min(Math.max(0, b), 255)
);
}
return null;
}
RGBColor.fromHex = function(code) {
var raw = parseInt(code.charAt(0) === '#' ? code.substr(1) : code, 16);
return new RGBColor(
@ -570,7 +609,7 @@ FFZ.prototype._update_colors = function(darkness_only) {
this._color_old_darkness = is_dark;
var colored_bits = document.querySelectorAll('.chat-line .has-color');
var colored_bits = document.querySelectorAll('.has-color');
for(var i=0, l=colored_bits.length; i < l; i++) {
var bit = colored_bits[i],
color = bit.getAttribute('data-color'),
@ -585,6 +624,9 @@ FFZ.prototype._update_colors = function(darkness_only) {
FFZ.prototype._handle_color = function(color) {
if ( color instanceof RGBColor )
color = color.toHex();
if ( ! color || this._colors.hasOwnProperty(color) )
return this._colors[color];

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'),
SERVER = DEBUG ? "//localhost:8000/" : "//cdn.frankerfacez.com/";
DIRECT_SERVER = DEBUG ? "//localhost:8000/" : "//direct-cdn.frankerfacez.com/";
module.exports = {
DEBUG: DEBUG,
SERVER: SERVER,
DIRECT_SERVER: DIRECT_SERVER,
API_SERVER: "//api.frankerfacez.com/",
API_SERVER_2: "//direct-api.frankerfacez.com/",
@ -14,6 +17,8 @@ module.exports = {
2: ["ws://localhost:8001/"]
},
TOOLTIP_DISTANCE: 50,
KNOWN_CODES: {
"#-?[\\\\/]": "#-/",
":-?(?:7|L)": ":-7",
@ -39,6 +44,7 @@ module.exports = {
"Gr(a|e)yFace": "GrayFace"
},
TWITCH_BASE: 'http://static-cdn.jtvnw.net/emoticons/v1/',
EMOTE_MIRROR_BASE: SERVER + "twitch-emote-mirror/",
EMOTE_REPLACEMENT_BASE: SERVER + "script/replacements/",
@ -74,6 +80,7 @@ module.exports = {
CLOCK: '<svg class="svg-glyph_views ffz-svg svg-clock" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path fill-rule="evenodd" clip-rule="evenodd" fill="#888888" d="M8,15c-3.866,0-7-3.134-7-7s3.134-7,7-7s7,3.134,7,7 S11.866,15,8,15z M8,3C5.238,3,3,5.238,3,8s2.238,5,5,5s5-2.238,5-5S10.762,3,8,3z M7.293,8.707L7,8l1-4l0.902,3.607L11,11 L7.293,8.707z"/></svg>',
GEAR: '<svg class="svg-gear" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M15,7v2h-2.115c-0.125,0.615-0.354,1.215-0.713,1.758l1.484,1.484l-1.414,1.414l-1.484-1.484C10.215,12.531,9.615,12.76,9,12.885V15H7v-2.12c-0.614-0.126-1.21-0.356-1.751-0.714l-1.491,1.49l-1.414-1.414l1.491-1.49C3.477,10.211,3.247,9.613,3.12,9H1V7h2.116C3.24,6.384,3.469,5.785,3.829,5.242L2.343,3.757l1.414-1.414l1.485,1.485C5.785,3.469,6.384,3.24,7,3.115V1h2v2.12c0.613,0.126,1.211,0.356,1.752,0.714l1.49-1.491l1.414,1.414l-1.49,1.492C12.523,5.79,12.754,6.387,12.88,7H15z M8,6C6.896,6,6,6.896,6,8s0.896,2,2,2s2-0.896,2-2S9.104,6,8,6z" fill-rule="evenodd"></path></svg>',
HEART: '<svg class="svg-heart" height="16px" version="1.1" viewBox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M8,13.5L1.5,7V4l2-2h3L8,3.5L9.5,2h3l2,2v3L8,13.5z" fill-rule="evenodd"></path></svg>',
UNHEART: '<svg class="svg-unheart" height="16px" version="1.1" viewbox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M1,9V7h14v2H1z M1,4l2-2h3l2,2l2-2h3l2,2v2H1V4z M8,14l-4.667-4h9.333L8,14z" fill-rule="evenodd"></path></svg>',
EMOTE: '<svg class="svg-emote" height="16px" version="1.1" viewBox="0 0 18 18" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M9,18c-4.971,0-9-4.029-9-9s4.029-9,9-9s9,4.029,9,9S13.971,18,9,18z M14,4.111V4h-0.111C12.627,2.766,10.904,2,9,2C7.095,2,5.373,2.766,4.111,4H4v0.111C2.766,5.373,2,7.096,2,9s0.766,3.627,2,4.889V14l0.05-0.051C5.317,15.217,7.067,16,9,16c1.934,0,3.684-0.783,4.949-2.051L14,14v-0.111c1.234-1.262,2-2.984,2-4.889S15.234,5.373,14,4.111zM11,6h2v4h-2V6z M12.535,12.535C11.631,13.44,10.381,14,9,14s-2.631-0.56-3.536-1.465l0.707-0.707C6.896,12.553,7.896,13,9,13s2.104-0.447,2.828-1.172L12.535,12.535z M5,6h2v4H5V6z" fill-rule="evenodd"></path></svg>',
STAR: '<svg class="svg-star" height="16px" version="1.1" viewbox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M15,6l-4.041,2.694L13,14l-5-3.333L3,14l2.041-5.306L1,6h5.077L8,1l1.924,5H15z" fill-rule="evenodd"></path></svg>',
CLOSE: '<svg class="svg-close_small" height="16px" version="1.1" viewbox="0 0 16 16" width="16px" x="0px" y="0px"><path clip-rule="evenodd" d="M12.657,4.757L9.414,8l3.243,3.242l-1.415,1.415L8,9.414l-3.243,3.243l-1.414-1.415L6.586,8L3.343,4.757l1.414-1.414L8,6.586l3.242-3.243L12.657,4.757z" fill-rule="evenodd"></path></svg>',

View file

@ -16,6 +16,7 @@ FFZ.prototype.setup_channel = function() {
// Settings stuff!
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.");
var s = this._channel_style = document.createElement('style');
@ -214,7 +215,13 @@ FFZ.prototype._modify_cindex = function(view) {
el.classList.add('ffz-channel');
// Try changing the theater mode tooltip.
this.$('.theatre-button a').attr('title', 'Theater Mode (Alt+T)');
var tb = this.$('.theatre-button > a'),
opts = tb.data('tipsy');
tb.attr('title', 'Theater Mode (Alt+T)');
if ( opts && opts.options && typeof opts.options.gravity !== "function" )
opts.options.gravity = utils.tooltip_placement(constants.TOOLTIP_DISTANCE, opts.options.gravity || 'n');
this.ffzFixTitle();
this.ffzUpdateUptime();
@ -282,7 +289,7 @@ FFZ.prototype._modify_cindex = function(view) {
if ( ! btn ) {
btn = document.createElement('span');
btn.id = 'ffz-ui-host-button';
btn.className = 'button action tooltip';
btn.className = 'button action';
btn.addEventListener('click', this.ffzClickHost.bind(btn, this, false));
@ -294,6 +301,8 @@ FFZ.prototype._modify_cindex = function(view) {
container.insertBefore(btn, before);
else
container.appendChild(btn);
jQuery(btn).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
btn.classList.remove('disabled');
@ -320,7 +329,7 @@ FFZ.prototype._modify_cindex = function(view) {
if ( ! btn ) {
btn = document.createElement('span');
btn.id = 'ffz-ui-host-button';
btn.className = 'button action tooltip';
btn.className = 'button action';
btn.addEventListener('click', this.ffzClickHost.bind(btn, this, true));
@ -332,6 +341,8 @@ FFZ.prototype._modify_cindex = function(view) {
container.insertBefore(btn, before);
else
container.appendChild(btn);
jQuery(btn).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
btn.classList.remove('disabled');
@ -405,7 +416,7 @@ FFZ.prototype._modify_cindex = function(view) {
else
cont.appendChild(stat);
jQuery(stat).tipsy();
jQuery(stat).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
el.innerHTML = utils.number_commas(chatter_count);
@ -437,7 +448,7 @@ FFZ.prototype._modify_cindex = function(view) {
else
cont.appendChild(stat);
jQuery(stat).tipsy();
jQuery(stat).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
el.innerHTML = utils.number_commas(ffz_viewers) + " (" + utils.number_commas(ffz_chatters) + ")";
@ -472,7 +483,7 @@ FFZ.prototype._modify_cindex = function(view) {
if ( ! stat_el ) {
stat_el = document.createElement('span');
stat_el.id = 'ffz-ui-player-stats';
stat_el.className = 'ffz stat tooltip';
stat_el.className = 'ffz stat';
stat_el.innerHTML = constants.GRAPH + " ";
el = document.createElement('span');
@ -483,16 +494,18 @@ FFZ.prototype._modify_cindex = function(view) {
container.insertBefore(stat_el, other.nextSibling);
else
container.appendChild(stat_el);
jQuery(stat_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
var delay = parseFloat(stats.hlsLatencyBroadcaster);
if ( delay > 180 ) {
delay = Math.floor(delay);
stat_el.setAttribute('original-title', 'Video Information\nBroadcast ' + utils.time_to_string(delay, true) + ' Ago\n\nVideo: ' + stats.videoResolution + 'p @ ' + stats.fps + '\nPlayback Rate: ' + stats.playbackRate + ' Kbps')
stat_el.setAttribute('original-title', 'Video Information<br>Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps')
el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old';
} else {
stat_el.setAttribute('original-title', 'Stream Latency\nVideo: ' + stats.videoResolution + 'p @ ' + stats.fps + '\nPlayback Rate: ' + stats.playbackRate + ' Kbps');
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps');
delay = stats.hlsLatencyBroadcaster;
var pos = delay.lastIndexOf('.');
@ -531,7 +544,7 @@ FFZ.prototype._modify_cindex = function(view) {
if ( ! stat_el ) {
stat_el = document.createElement('span');
stat_el.id = 'ffz-ui-player-stats';
stat_el.className = 'ffz stat tooltip';
stat_el.className = 'ffz stat';
stat_el.innerHTML = constants.GRAPH + " ";
el = document.createElement('span');
@ -542,16 +555,18 @@ FFZ.prototype._modify_cindex = function(view) {
container.insertBefore(stat_el, other.nextSibling);
else
container.appendChild(stat_el);
jQuery(stat_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
var delay = parseFloat(stats.hlsLatencyBroadcaster);
if ( delay > 180 ) {
delay = Math.floor(delay);
stat_el.setAttribute('original-title', 'Video Information\nBroadcast ' + utils.time_to_string(delay, true) + ' Ago\n\nVideo: ' + stats.videoResolution + 'p @ ' + stats.fps + '\nPlayback Rate: ' + stats.playbackRate + ' Kbps')
stat_el.setAttribute('original-title', 'Video Information<br>Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps')
el.textContent = utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old';
} else {
stat_el.setAttribute('original-title', 'Stream Latency\nVideo: ' + stats.videoResolution + 'p @ ' + stats.fps + '\nPlayback Rate: ' + stats.playbackRate + ' Kbps');
stat_el.setAttribute('original-title', 'Stream Latency<br>Video: ' + stats.videoResolution + 'p @ ' + stats.fps + '<br>Playback Rate: ' + stats.playbackRate + ' Kbps');
delay = stats.hlsLatencyBroadcaster;
var pos = delay.lastIndexOf('.');
@ -623,7 +638,7 @@ FFZ.prototype._modify_cindex = function(view) {
}
}
jQuery(stat).tipsy({html: true});
jQuery(stat).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
el.innerHTML = utils.time_to_string(uptime, false, false, false, f.settings.stream_uptime === 1 || f.settings.stream_uptime === 3);
@ -790,6 +805,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 = {
type: "select",
options: {

View file

@ -415,7 +415,7 @@ FFZ.prototype._modify_cview = function(view) {
ffzInit: function() {
f._chatv = this;
this.$('.textarea-contain').append(f.build_ui_link(this));
this.$('.chat-messages').find('.html-tooltip').tipsy({live: true, html: true, gravity: jQuery.fn.tipsy.autoNS});
this.$('.chat-messages').find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
if ( !f.has_bttv && f.settings.group_tabs )
this.ffzEnableTabs();
@ -447,23 +447,32 @@ FFZ.prototype._modify_cview = function(view) {
var room = this.get('controller.currentRoom'), rows;
room && room.resetUnreadCount();
if ( room && room._ffz_was_unread ) {
room._ffz_was_unread = false;
var el = this.get('element'),
unread_display = el && el.querySelector('#ffz-group-tabs .button .notifications'),
unread_count = unread_display ? parseInt(unread_display.textContent) : 0;
unread_count--;
if ( unread_display )
unread_display.textContent = unread_count || '';
}
if ( this._ffz_chan_table ) {
rows = jQuery(this._ffz_chan_table);
rows.children('.ffz-room-row').removeClass('active');
if ( room )
rows.children('.ffz-room-row[data-room="' + room.get('id') + '"]').addClass('active').children('span').text('');
}
if ( this._ffz_group_table ) {
rows = jQuery(this._ffz_group_table);
rows.children('.ffz-room-row').removeClass('active');
if ( room )
rows.children('.ffz-room-row[data-room="' + room.get('id') + '"]').addClass('active').children('span').text('');
}
if ( !f.has_bttv && f.settings.group_tabs && this._ffz_tabs ) {
var tabs = jQuery(this._ffz_tabs);
tabs.children('.ffz-chat-tab').removeClass('active');
if ( room && room._ffz_tab ) {
room._ffz_tab.classList.remove('tab-mentioned');
room._ffz_tab.classList.remove('hidden');
@ -473,6 +482,15 @@ FFZ.prototype._modify_cview = function(view) {
sp.innerHTML = '';
}
if ( room && room._ffz_row ) {
room._ffz_row.classList.remove('row-mentioned');
room._ffz_row.classList.remove('hidden');
room._ffz_row.classList.add('active');
var sp = room._ffz_row.querySelector('span');
if ( sp )
sp.innerHTML = '';
}
// Invite Link
var can_invite = room && room.get('canInvite');
this._ffz_invite && this._ffz_invite.classList.toggle('hidden', !can_invite);
@ -486,9 +504,7 @@ FFZ.prototype._modify_cview = function(view) {
// Better Menu
ffzRebuildMenu: function() {
return;
var el = this.get('element'),
/*var el = this.get('element'),
room_list = el && el.querySelector('.chat-rooms .tse-content');
if ( ! room_list )
@ -518,7 +534,7 @@ FFZ.prototype._modify_cview = function(view) {
tbl.setAttribute('cellspacing', 0);
tbl.id = 'ffz-channel-table';
tbl.className = 'ffz';
tbl.innerHTML = '<thead><tr><th colspan="2">Channels</th><th class="ffz-row-switch">Join</th><th class="ffz-row-switch">Pin</th></tr></thead><tbody></tbody>';
tbl.innerHTML = '<thead><tr><th colspan="2">Channels</th><th class="ffz-row-switch">Pin</th></tr></thead><tbody></tbody>';
room_list.insertBefore(tbl, room_list.firstChild);
chan_table = this._ffz_chan_table = tbl.querySelector('tbody');
@ -556,7 +572,7 @@ FFZ.prototype._modify_cview = function(view) {
tbl.setAttribute('cellspacing', 0);
tbl.id = 'ffz-group-table';
tbl.className = 'ffz';
tbl.innerHTML = '<thead><tr><th colspan="2">Group Chats</th><th class="ffz-row-switch">Pin</th></tr></thead><tbody></tbody>';
tbl.innerHTML = '<thead><tr><th colspan="2">Group Chats</th></tr></thead><tbody></tbody>';
var before = room_list.querySelector('#ffz-channel-table');
room_list.insertBefore(tbl, before.nextSibling);
@ -575,30 +591,29 @@ FFZ.prototype._modify_cview = function(view) {
// Change Create Tooltip
var create_btn = el.querySelector('.button.create');
if ( create_btn )
create_btn.title = 'Create a Group Room';
create_btn.title = 'Create a Group Room';*/
},
ffzBuildRow: function(view, room, current_channel, host_channel) {
/*ffzBuildRow: function(view, room, current_channel, host_channel) {
var row = document.createElement('tr'),
icon = document.createElement('td'),
name_el = document.createElement('td'),
btn,
toggle_pinned = document.createElement('td'),
toggle_visible = document.createElement('td'),
group = room.get('isGroupRoom'),
current = room === view.get('controller.currentRoom'),
//unread = format_unread(current ? 0 : room.get('unreadCount')),
unread = utils.format_unread(current ? 0 : room.get('unreadCount')),
name = room.get('tmiRoom.displayName') || (group ? room.get('tmiRoom.name') : FFZ.get_capitalization(room.get('id'), function(name) {
f.log("Name for Row: " + name);
//unread = format_unread(current ? 0 : room.get('unreadCount'));
name_el.innerHTML = utils.sanitize(name);
unread = utils.format_unread(current ? 0 : room.get('unreadCount'));
name_el.innerHTML = utils.sanitize(name) + ' <span>' + unread + '</span>';
}));
name_el.className = 'ffz-room';
name_el.innerHTML = utils.sanitize(name);
name_el.innerHTML = utils.sanitize(name) + ' <span>' + unread + '</span>';
if ( current_channel ) {
icon.innerHTML = constants.CAMERA;
@ -610,10 +625,9 @@ FFZ.prototype._modify_cview = function(view) {
icon.className = name_el.className = 'tooltip';
}
toggle_pinned.className = toggle_visible.className = 'ffz-row-switch';
toggle_pinned.className = 'ffz-row-switch';
toggle_pinned.innerHTML = '<a class="switch' + (f.settings.pinned_rooms.indexOf(room.get('id')) !== -1 ? ' active' : '') + '"><span></span></a>';
toggle_visible.innerHTML = '<a class="switch' + (f.settings.visible_rooms.indexOf(room.get('id')) !== -1 ? ' active' : '') + '"><span></span></a>';
row.setAttribute('data-room', room.get('id'));
@ -662,34 +676,16 @@ FFZ.prototype._modify_cview = function(view) {
});
}
row.appendChild(toggle_visible);
btn = toggle_visible.querySelector('a.switch');
btn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation && e.stopPropagation();
var room_id = room.get('id'),
visible_rooms = f.settings.visible_rooms,
is_visible = visible_rooms.indexOf(room_id) !== -1;
if ( is_visible )
visible_rooms.removeObject(room_id);
else
visible_rooms.push(room_id);
f.settings.set('visible_rooms', visible_rooms);
this.classList.toggle('active', !is_visible);
view.ffzRebuildTabs();
});
row.addEventListener('click', function() {
var controller = view.get('controller');
controller.focusRoom(room);
controller.set('showList', false);
});
room._ffz_row = row;
return row;
},
},*/
// Group Tabs~!
@ -721,10 +717,22 @@ FFZ.prototype._modify_cview = function(view) {
var link = document.createElement('a'),
view = this;
//total_unread = 0;
/*for(var room_id in f.rooms) {
var room = f.rooms[room_id] && f.rooms[room_id].room,
is_unread = room && room.get('unreadCount') > 0;
if ( is_unread ) {
room._ffz_was_unread = true;
total_unread++;
} else if ( room )
room._ffz_was_unread = false;
}*/
link.className = 'button glyph-only tooltip';
link.title = "Chat Room Management";
link.innerHTML = constants.ROOMS;
link.innerHTML = constants.ROOMS; // + '<span class="notifications">' + (total_unread || '') + '</span>';
link.addEventListener('click', function() {
var controller = view.get('controller');
@ -806,38 +814,64 @@ FFZ.prototype._modify_cview = function(view) {
},
ffzTabUnread: function(room_id) {
// TODO: Update menu.
if ( f.has_bttv || ! f.settings.group_tabs )
return;
var tabs = this._ffz_tabs || this.get('element').querySelector('#ffz-group-tabs'),
current_id = this.get('controller.currentRoom.id');
if ( ! tabs )
return;
var current_id = this.get('controller.currentRoom.id');
if ( room_id ) {
var room = f.rooms && f.rooms[room_id] && f.rooms[room_id].room,
tab = room && room._ffz_tab;
row = room && room._ffz_row,
tab = room && room._ffz_tab,
if ( tab ) {
var unread = utils.format_unread(room_id === current_id ? 0 : room.get('unreadCount'));
tab.querySelector('span').innerHTML = unread;
unread_count = room_id === current_id ? 0 : room.get('unreadCount'),
is_unread = unread_count > 0,
unread = utils.format_unread(unread_count);
if ( ! room._ffz_was_unread && is_unread ) {
room._ffz_was_unread = true;
var el = this.get('element'),
unread_display = el && el.querySelector('#ffz-group-tabs .button .notifications'),
unread_count = unread_display ? parseInt(unread_display.textContent) : 0;
unread_count++;
if ( unread_display )
unread_display.textContent = unread_count || '';
}
if ( row )
row.querySelector('span').innerHTML = unread;
if ( tab )
tab.querySelector('span').innerHTML = unread;
return;
}
var children = tabs.querySelectorAll('.ffz-chat-tab');
for(var i=0; i < children.length; i++) {
var tab = children[i],
room_id = tab.getAttribute('data-room'),
room = f.rooms && f.rooms[room_id] && f.rooms[room_id];
for(var room_id in f.rooms) {
var room = f.rooms[room_id] && f.rooms[room_id].room,
row = room && room._ffz_row,
tab = room && room._ffz_tab,
if ( ! room )
continue;
unread_count = room_id === current_id ? 0 : room.get('unreadCount'),
is_unread = unread_count > 0,
unread = utils.format_unread(unread_count);
var unread = utils.format_unread(room_id === current_id ? 0 : room.room.get('unreadCount'));
tab.querySelector('span').innerHTML = unread;
if ( ! room._ffz_was_unread && is_unread ) {
room._ffz_was_unread = true;
var el = this.get('element'),
unread_display = el && el.querySelector('#ffz-group-tabs .button .notifications'),
unread_count = unread_display ? parseInt(unread_display.textContent) : 0;
unread_count++;
if ( unread_display )
unread_display.textContent = unread_count || '';
}
if ( row )
row.querySelector('span').innerHTML = unread;
if ( tab )
tab.querySelector('span').innerHTML = unread;
}
},

210
src/ember/conversations.js Normal file
View file

@ -0,0 +1,210 @@
var FFZ = window.FrankerFaceZ,
utils = require('../utils'),
constants = require('../constants');
// ---------------
// Settings
// ---------------
FFZ.settings_info.conv_focus_on_click = {
type: "boolean",
value: false,
no_mobile: true,
visible: false,
category: "Conversations",
name: "Focus Input on Click",
help: "Focus on a conversation's input box when you click it."
};
FFZ.settings_info.top_conversations = {
type: "boolean",
value: false,
no_mobile: true,
category: "Conversations",
name: "Position 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);
}
};
// ---------------
// Initialization
// ---------------
FFZ.prototype.setup_conversations = function() {
document.body.classList.toggle('ffz-top-conversations', this.settings.top_conversations);
this.log("Hooking the Ember Conversation Window component.");
var ConvWindow = App.__container__.resolve('component:conversation-window');
if ( ConvWindow )
this._modify_conversation_window(ConvWindow);
this.log("Hooking the Ember Conversation Line component.");
var ConvLine = App.__container__.resolve('component:conversation-line');
if ( ConvLine )
this._modify_conversation_line(ConvLine);
// TODO: Make this better later.
jQuery('.conversations-list').find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
}
FFZ.prototype._modify_conversation_window = function(component) {
var f = this,
Layout = App.__container__.lookup('controller:layout'),
Settings = App.__container__.lookup('controller:settings');
component.reopen({
headerBadges: Ember.computed("thread.participants", "currentUsername", function() {
var e = this.get("thread.participants").rejectBy("username", this.get("currentUsername")).objectAt(0),
badges = {},
ut = e.get("userType");
if ( ut === "staff" )
badges[0] = {classes: 'badge staff', title: 'Staff'};
else if ( ut === 'admin' )
badges[0] = {classes: 'badge admin', title: 'Admin'};
else if ( ut === 'global_mod' )
badges[0] = {classes: 'badge global-moderator', title: 'Global Moderator'};
if ( e.get('hasTurbo') )
badges[15] = {classes: 'badge turbo', title: 'Turbo'}
// FFZ Badges
var data = f.users[e.get('username')];
if ( data && data.badges ) {
for(var slot in data.badges) {
if ( ! data.badges.hasOwnProperty(slot) )
continue;
var badge = data.badges[slot],
full_badge = f.badges[badge.id] || {},
old_badge = badges[slot];
if ( full_badge.visible !== undefined ) {
var visible = full_badge.visible;
if ( typeof visible === "function" )
try {
visible = visible.bind(f)(null, e.get('username'), null, badges);
} catch(err) {
f.error("badge " + badge.id + " visible: " + err);
continue;
}
if ( ! visible )
continue;
}
if ( old_badge ) {
var replaces = badge.hasOwnProperty('replaces') ? badge.replaces : full_badge.replaces;
if ( ! replaces )
continue;
old_badge.klass = 'badge ffz-badge-' + badge.id;
old_badge.title += ', ' + (badge.title || full_badge.title);
continue;
}
badges[slot] = {
classes: 'badge ffz-badge-' + badge.id,
title: badge.title || full_badge.title
}
}
}
var out = [];
for(var slot in badges)
out.push(badges[slot]);
return out;
}),
didInsertElement: function() {
var el = this.get('element'),
header = el && el.querySelector('.conversation-header'),
header_name = header && header.querySelector('.conversation-header-name'),
raw_color = this.get('otherUser.color'),
colors = raw_color && f._handle_color(raw_color),
is_dark = (Layout && Layout.get('isTheatreMode')) || f.settings.dark_twitch;
if ( header_name && raw_color ) {
header_name.style.color = (is_dark ? colors[1] : colors[0]);
header_name.classList.add('has-color');
header_name.setAttribute('data-color', raw_color);
}
jQuery(el).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
}
});
}
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-color="' + 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>');
}
});
}

View file

@ -30,49 +30,52 @@ FFZ.prototype.setup_directory = function() {
if ( 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');
if ( HostView )
this._modify_directory_host(HostView);
var VideoView = App.__container__.resolve('view:video');
if ( VideoView )
this._modify_directory_video(VideoView);
// TODO: Process existing views.
}
FFZ.prototype._modify_directory_video = function(dir) {
var f = this;
dir.reopen({
didInsertElement: function() {
this._super();
// Initialize existing views.
for(var key in Ember.View.views) {
var view = Ember.View.views[key];
try {
if ( (ChannelView && view instanceof ChannelView) || (CreativeChannel && view instanceof CreativeChannel) || (CSGOChannel && view instanceof CSGOChannel) || (HostView && view instanceof HostView) )
view.ffzInit();
} catch(err) {
this.error("Directory Setup: " + err);
}
});
try {
dir.create().destroy();
} catch(err) { }
}
}
FFZ.prototype._modify_directory_live = function(dir) {
FFZ.prototype._modify_directory_live = function(dir, is_csgo) {
var f = this;
dir.reopen({
didInsertElement: function() {
this._super();
this.ffzInit();
},
ffzInit: function() {
var el = this.get('element'),
meta = el && el.querySelector('.meta'),
thumb = el && el.querySelector('.thumb'),
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');
t_el.className = 'overlay_info length live';
jQuery(t_el).tipsy({html: true});
jQuery(t_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 's')});
cap.appendChild(t_el);
this._ffz_uptime_timer = setInterval(this.ffzUpdateUptime.bind(this), 1000);
@ -88,6 +91,8 @@ FFZ.prototype._modify_directory_live = function(dir) {
target = this.get('context.model.channel.name');
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.alt = this.get('context.model.channel.display_name');

318
src/ember/following.js Normal file
View file

@ -0,0 +1,318 @@
var FFZ = window.FrankerFaceZ,
utils = require('../utils'),
constants = require('../constants');
// --------------------
// Settings
// --------------------
FFZ.settings_info.enhance_profile_following = {
type: "boolean",
value: true,
category: "Appearance",
name: "Enhanced Following Control",
help: "Display additional controls on your own profile's Following tab to make management easier."
}
// --------------------
// Initialization
// --------------------
FFZ.prototype.setup_profile_following = function() {
if ( ! window.App )
return;
var f = this;
// Build our is-following cache.
this._following_cache = {};
// First, we need to hook the model. This is what we'll use to grab the following notification state,
// rather than making potentially hundreds of API requests.
var Following = App.__container__.resolve('model:kraken-channel-following');
if ( ! Following )
return;
this._hook_following(Following);
// Also try hooking that other model.
var Notification = App.__container__.resolve('model:notification');
if ( Notification )
this._hook_following(Notification, true);
// Now, we need to edit the profile Following view itself.
var ProfileView = App.__container__.resolve('view:channel/following');
if ( ! ProfileView )
return;
ProfileView.reopen({
didInsertElement: function() {
this._super();
try {
this.ffzInit();
} catch(err) {
f.error("ProfileView ffzInit: " + err);
}
},
willClearRender: function() {
try {
this.ffzTeardown();
} catch(err) {
f.error("ProvileView ffzTeardown: " + err);
}
this._super();
},
ffzInit: function() {
// Only process our own profile following page.
var user = f.get_user();
if ( ! f.settings.enhance_profile_following || ! user || ! user.login === this.get('context.id') )
return;
var el = this.get('element'),
users = el && el.querySelectorAll('.user.item');
el.classList.add('ffz-enhanced-following');
var had_data = true;
if ( users && users.length )
for(var i=0; i < users.length; i++)
had_data = this.ffzProcessUser(users[i]) && had_data;
else
had_data = false;
if ( ! had_data ) {
// Force a refresh.
f.log("Forcing a refresh of user following data.");
var following = this.get('context.following'),
refresher = function() {
if ( following.get('isLoading') )
setTimeout(refresher, 25);
following.clear();
following.load();
}
// We use this weird function to prevent trying to load twice mucking things up.
setTimeout(refresher);
}
// Watch for new ones the bad way.
if ( ! this._ffz_observer ) {
var t = this;
var observer = this._ffz_observer = new MutationObserver(function(mutations) {
for(var i=0; i < mutations.length; i++) {
var mutation = mutations[i];
if ( mutation.type !== "childList" )
continue;
for(var x=0; x < mutation.addedNodes.length; x++) {
var added = mutation.addedNodes[x];
if ( added.nodeType !== added.ELEMENT_NODE || added.tagName !== "DIV" )
continue;
// Is it an ember-view? Check its kids.
if ( added.classList.contains('ember-view') ) {
var users = added.querySelectorAll('.user.item');
if ( users )
for(var y=0; y < users.length; y++)
t.ffzProcessUser(users[y]);
} else if ( added.classList.contains('user') )
t.ffzProcessUser(added);
}
}
});
observer.observe(el, {
childList: true,
subtree: true
});
}
},
ffzTeardown: function() {
if ( this._ffz_observer ) {
this._ffz_observer.disconnect();
this._ffz_observer = null;
}
},
ffzProcessUser: function(user) {
if ( user.classList.contains('ffz-processed') )
return true;
var link = user.querySelector('a'),
link_parts = link && link.href.split("/"),
user_id = link_parts && link_parts[3],
data = f._following_cache[user_id],
t_el = document.createElement('div');
user.classList.add('ffz-processed');
if ( ! data )
return false;
t_el.className = 'overlay_info length';
jQuery(t_el).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 's')});
var age = data[0] ? Math.floor((Date.now() - data[0].getTime()) / 1000) : 0;
if ( age ) {
t_el.innerHTML = constants.CLOCK + ' ' + utils.human_time(age, 10);
t_el.setAttribute('original-title', 'Following Since: <nobr>' + data[0].toLocaleString() + '</nobr>');
} else
t_el.style.display = 'none';
user.appendChild(t_el);
var actions = document.createElement('div'),
follow = document.createElement('button'),
notif = document.createElement('button'),
update_follow = function() {
data = f._following_cache[user_id];
user.classList.toggle('followed', data);
follow.innerHTML = constants.HEART + constants.UNHEART + '<span> Follow</span>';
if ( t_el ) {
var age = data && data[0] ? Math.floor((Date.now() - data[0].getTime()) / 1000) : undefined;
if ( age !== undefined ) {
t_el.innerHTML = constants.CLOCK + ' ' + (age < 60 ? 'now' : utils.human_time(age, 10));
t_el.setAttribute('original-title', 'Following Since: <nobr>' + data[0].toLocaleString() + '</nobr>');
t_el.style.display = '';
} else {
t_el.style.display = 'none';
}
}
},
update_notif = function() {
data = f._following_cache[user_id];
notif.classList.toggle('notifications-on', data && data[1]);
notif.textContent = 'Notification ' + (data && data[1] ? 'On' : 'Off');
};
actions.className = 'actions';
follow.className = 'button follow';
notif.className = 'button notifications';
update_follow();
update_notif();
follow.addEventListener('click', function() {
var was_following = !!data;
follow.disabled = true;
notif.disabled = true;
follow.textContent = 'Updating';
(was_following ?
Twitch.api.del("users/:login/follows/channels/" + user_id) :
Twitch.api.put("users/:login/follows/channels/" + user_id, {notifications: false}))
.done(function() {
data = f._following_cache[user_id] = was_following ? null : [new Date(), false];
})
.always(function() {
update_follow();
update_notif();
follow.disabled = false;
notif.disabled = false;
})
});
notif.addEventListener('click', function() {
var was_following = data[1];
follow.disabled = true;
notif.disabled = true;
notif.textContent = 'Updating';
Twitch.api.put("users/:login/follows/channels/" + user_id, {notifications: !was_following})
.done(function() {
data[1] = ! was_following;
})
.always(function() {
update_notif();
follow.disabled = false;
notif.disabled = false;
});
});
actions.appendChild(follow);
actions.appendChild(notif);
user.appendChild(actions);
return true;
}
});
// Now, rebuild any views.
try {
ProfileView.create().destroy();
} catch(err) { }
for(var key in Ember.View.views) {
var view = Ember.View.views[key];
if ( ! view || !(view instanceof ProfileView) )
continue;
this.log("Manually updating existing Following View.", view);
try {
var following = view.get('context.following');
this._hook_following(following);
} catch(err) {
this.error("setup: view:channel/following: model hook: " + err);
}
try {
view.ffzInit();
} catch(err) {
this.error("setup: view:channel/following: " + err);
}
}
}
FFZ.prototype._hook_following = function(Following) {
var f = this;
Following.reopen({
apiLoad: function(e) {
var user = f.get_user(),
channel_id = this.get('id'),
t = this;
if ( ! user || user.login !== channel_id )
return this._super(e);
return new RSVP.Promise(function(success, fail) {
t._super(e).then(function(data) {
if ( data && data.follows ) {
var now = Date.now();
for(var i=0; i < data.follows.length; i++) {
var follow = data.follows[i];
if ( ! follow || ! follow.channel || ! follow.channel.name ) {
continue;
}
if ( follow.channel.display_name )
FFZ.capitalization[follow.channel.name] = [follow.channel.display_name, now];
f._following_cache[follow.channel.name] = [follow.created_at ? utils.parse_date(follow.created_at) : null, follow.notifications || false];
}
}
success(data);
}, function(err) {
fail(err);
})
});
}
});
}

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 = {
type: "boolean",
value: false,
@ -177,7 +183,7 @@ FFZ.prototype.setup_layout = function() {
height = size[1];
// 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
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>";
}.property("playerSize"),
/*ffzUpdateWidth: _.throttle(function() {
var rc = document.querySelector('#right_close');
if ( ! rc )
ffzPortraitWarning: function() {
if ( ! f.settings.portrait_mode || f._portrait_warning || f.settings.portrait_warning || ! this.get('isTooSmallForRightColumn') )
return;
var left_width = this.get("isLeftColumnClosed") ? 50 : 240,
right_width;
f._portrait_warning = true;
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="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 )
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),*/
}.observes("isTooSmallForRightColumn"),
ffzUpdateCss: function() {
// TODO: Fix this mess of duplicate code.
@ -332,6 +325,6 @@ FFZ.prototype.setup_layout = function() {
Layout.set('rawPortraitMode', this.settings.portrait_mode);
// Force re-calculation of everything.
Ember.propertyDidChange(Layout, 'contentWidth');
Ember.propertyDidChange(Layout, 'windowWidth');
Ember.propertyDidChange(Layout, 'windowHeight');
}

View file

@ -113,7 +113,7 @@ FFZ.settings_info.scrollback_length = {
for(var room_id in this.rooms) {
var room = this.rooms[room_id];
room.room.set('messageBufferSize', new_val + ((this._roomv && !this._roomv.get('stuckToBottom') && current_id === room_id) ? 150 : 0));
room.room && room.room.set('messageBufferSize', new_val + ((this._roomv && !this._roomv.get('stuckToBottom') && current_id === room_id) ? 150 : 0));
}
}
};
@ -149,7 +149,6 @@ FFZ.settings_info.banned_words = {
category: "Chat Filtering",
no_bttv: true,
//visible: function() { return ! this.has_bttv },
name: "Banned Words",
help: "Set a list of words that will be locally removed from chat messages.",
@ -181,7 +180,6 @@ FFZ.settings_info.keywords = {
category: "Chat Filtering",
no_bttv: true,
//visible: function() { return ! this.has_bttv },
name: "Highlight Keywords",
help: "Set additional keywords that will be highlighted in chat.",
@ -246,6 +244,21 @@ FFZ.settings_info.link_image_hover = {
};
FFZ.settings_info.emote_image_hover = {
type: "boolean",
value: false,
category: "Chat Tooltips",
no_mobile: true,
name: "Emote Preview",
help: "Display scaled up high-DPI emoticon images in tooltips to help see details on low-resolution monitors.",
on_update: function(val) {
this._reset_tooltips();
}
};
FFZ.settings_info.image_hover_all_domains = {
type: "boolean",
value: false,
@ -269,7 +282,10 @@ FFZ.settings_info.chat_rows = {
name: "Chat Line Backgrounds",
help: "Display alternating background colors for lines in chat.",
on_update: function(val) { document.body.classList.toggle("ffz-chat-background", !this.has_bttv && val); }
on_update: function(val) {
this.toggle_style('chat-background', !this.has_bttv && val);
this.toggle_style('chat-setup', !this.has_bttv && (val || this.settings.chat_separators));
}
};
@ -301,10 +317,12 @@ FFZ.settings_info.chat_separators = {
help: "Display thin lines between chat messages for further visual separation.",
on_update: function(val) {
document.body.classList.toggle("ffz-chat-separator", !this.has_bttv && val !== 0);
document.body.classList.toggle("ffz-chat-separator-3d", !this.has_bttv && val === 2);
document.body.classList.toggle("ffz-chat-separator-3d-inset", !this.has_bttv && val === 3);
document.body.classList.toggle("ffz-chat-separator-wide", !this.has_bttv && val === 4);
this.toggle_style('chat-setup', !this.has_bttv && (val || this.settings.chat_rows));
this.toggle_style('chat-separator', !this.has_bttv && val);
this.toggle_style('chat-separator-3d', !this.has_bttv && val === 2);
this.toggle_style('chat-separator-3d-inset', !this.has_bttv && val === 3);
this.toggle_style('chat-separator-wide', !this.has_bttv && val === 4);
}
};
@ -319,7 +337,7 @@ FFZ.settings_info.chat_padding = {
name: "Reduced Chat Line Padding",
help: "Reduce the amount of padding around chat messages to fit more on-screen at once.",
on_update: function(val) { document.body.classList.toggle("ffz-chat-padding", !this.has_bttv && val); }
on_update: function(val) { this.toggle_style('chat-padding', !this.has_bttv && val); }
};
@ -352,9 +370,9 @@ FFZ.settings_info.high_contrast_chat = {
},
on_update: function(val) {
document.body.classList.toggle("ffz-high-contrast-chat-text", !this.has_bttv && val[2] === '1');
document.body.classList.toggle("ffz-high-contrast-chat-bold", !this.has_bttv && val[1] === '1');
document.body.classList.toggle("ffz-high-contrast-chat-bg", !this.has_bttv && val[0] === '1');
this.toggle_style('chat-hc-text', !this.has_bttv && val[2] === '1');
this.toggle_style('chat-hc-bold', !this.has_bttv && val[1] === '1');
this.toggle_style('chat-hc-background', !this.has_bttv && val[0] === '1');
}
};
@ -397,7 +415,7 @@ FFZ.settings_info.chat_font_family = {
var span = document.createElement('span');
span.style.fontFamily = val;
css = ".ember-chat .chat-messages {" + span.style.cssText + "}";
css = ".timestamp-line,.conversation-chat-line,.conversation-system-messages,.chat-history,.ember-chat .chat-messages {" + span.style.cssText + "}";
}
utils.update_css(this._chat_style, "chat_font_family", css);
@ -439,7 +457,7 @@ FFZ.settings_info.chat_font_size = {
else {
var lh = Math.max(20, Math.round((20/12)*val)),
pd = Math.floor((lh - 20) / 2);
css = ".ember-chat .chat-messages .chat-line { font-size: " + val + "px !important; line-height: " + lh + "px !important; }";
css = ".timestamp-line,.conversation-chat-line,.conversation-system-messages,.chat-history .chat-line,.ember-chat .chat-messages .chat-line { font-size: " + val + "px !important; line-height: " + lh + "px !important; }";
if ( pd )
css += ".ember-chat .chat-messages .chat-line .mod-icons, .ember-chat .chat-messages .chat-line .badges { padding-top: " + pd + "px; }";
}
@ -487,7 +505,7 @@ FFZ.settings_info.chat_ts_size = {
css = "";
else {
var lh = Math.max(20, Math.round((20/12)*val), Math.round((20/12)*this.settings.chat_font_size));
css = ".ember-chat .chat-messages .timestamp { font-size: " + val + "px !important; line-height: " + lh + "px !important; }";
css = ".chat-history .timestamp,.ember-chat .chat-messages .timestamp { font-size: " + val + "px !important; line-height: " + lh + "px !important; }";
}
utils.update_css(this._chat_style, "chat_ts_font_size", css);
@ -526,19 +544,19 @@ FFZ.prototype.setup_line = function() {
// Chat Enhancements
document.body.classList.toggle("ffz-chat-colors", !this.has_bttv && this.settings.fix_color !== '-1');
document.body.classList.toggle("ffz-chat-colors-gray", !this.has_bttv && this.settings.fix_color === '-1');
this.toggle_style('chat-setup', !this.has_bttv && (this.settings.chat_rows || this.settings.chat_separators));
this.toggle_style('chat-padding', !this.has_bttv && this.settings.chat_padding);
document.body.classList.toggle('ffz-chat-background', !this.has_bttv && this.settings.chat_rows);
document.body.classList.toggle("ffz-chat-separator", !this.has_bttv && this.settings.chat_separators !== 0);
document.body.classList.toggle("ffz-chat-separator-wide", !this.has_bttv && this.settings.chat_separators === 4);
document.body.classList.toggle("ffz-chat-separator-3d", !this.has_bttv && this.settings.chat_separators === 2);
document.body.classList.toggle("ffz-chat-separator-3d-inset", !this.has_bttv && this.settings.chat_separators === 3);
document.body.classList.toggle("ffz-chat-padding", !this.has_bttv && this.settings.chat_padding);
this.toggle_style('chat-background', !this.has_bttv && this.settings.chat_rows);
document.body.classList.toggle("ffz-high-contrast-chat-text", !this.has_bttv && this.settings.high_contrast_chat[2] === '1');
document.body.classList.toggle("ffz-high-contrast-chat-bold", !this.has_bttv && this.settings.high_contrast_chat[1] === '1');
document.body.classList.toggle("ffz-high-contrast-chat-bg", !this.has_bttv && this.settings.high_contrast_chat[0] === '1');
this.toggle_style('chat-separator', !this.has_bttv && this.settings.chat_separators);
this.toggle_style('chat-separator-3d', !this.has_bttv && this.settings.chat_separators === 2);
this.toggle_style('chat-separator-3d-inset', !this.has_bttv && this.settings.chat_separators === 3);
this.toggle_style('chat-separator-wide', !this.has_bttv && this.settings.chat_separators === 4);
this.toggle_style('chat-hc-text', !this.has_bttv && this.settings.high_contrast_chat[2] === '1');
this.toggle_style('chat-hc-bold', !this.has_bttv && this.settings.high_contrast_chat[1] === '1');
this.toggle_style('chat-hc-background', !this.has_bttv && this.settings.high_contrast_chat[0] === '1');
this._last_row = {};
@ -549,7 +567,6 @@ FFZ.prototype.setup_line = function() {
this._modify_line(Whisper);
this.log("Hooking the Ember Message Line component.");
var Line = App.__container__.resolve('component:message-line');
if ( Line )
@ -577,46 +594,13 @@ FFZ.prototype._modify_line = function(component) {
component.reopen({
tokenizedMessage: function() {
// Add our own step to the tokenization procedure.
var tokens = this.get("msgObject.cachedTokens");
if ( tokens )
return tokens;
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;
}
try {
return f.tokenize_chat_line(this.get('msgObject'));
} catch(err) {
f.error("chat-line tokenizedMessage: " + err);
return this._super();
}
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"),
ffzUpdated: Ember.observer("msgObject.ffz_deleted", "msgObject.ffz_old_messages", function() {
@ -633,26 +617,16 @@ FFZ.prototype._modify_line = function(component) {
if ( e.target && e.target.classList.contains('mod-icon') ) {
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') ) {
var room_id = this.get('msgObject.room'),
room = room_id && f.rooms[room_id] && f.rooms[room_id].room,
cmd = e.target.getAttribute('data-cmd');
if ( room ) {
room.send(cmd, true);
var lines = cmd.split("\n");
for(var i=0; i < lines.length; i++)
room.send(lines[i], true);
if ( e.target.classList.contains('is-timeout') )
room.clearMessages(this.get('msgObject.from'));
}
@ -660,30 +634,8 @@ FFZ.prototype._modify_line = function(component) {
}
}
if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons && e.target && e.target.classList.contains('emoticon') ) {
var eid = e.target.getAttribute('data-emote');
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);
}
}
if ( f._click_emote(e.target, e) )
return;
return this._super(e);
},
@ -746,8 +698,8 @@ FFZ.prototype._modify_line = function(component) {
else {
if ( typeof btn === "string" ) {
cmd = btn.replace(/{user}/g, user);
tip = 'Custom Command\n' + cmd;
cmd = btn.replace(/{user}/g, user).replace(/ *<LINE> */, "\n");
tip = 'Custom Command' + (cmd.indexOf('\n') !== -1 ? 's' : '') + '\n' + cmd;
} else {
cmd = "/timeout " + user + " " + btn;
tip = "Timeout User (" + utils.duration_string(btn) + ")";
@ -768,7 +720,7 @@ FFZ.prototype._modify_line = function(component) {
else if ( this.get('isAdmin') )
badges[0] = {klass: 'admin', title: 'Admin'};
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') )
badges[0] = {klass: 'moderator', title: 'Moderator'};
@ -835,7 +787,7 @@ FFZ.prototype._modify_line = function(component) {
if ( deleted )
e.push('<span class="deleted"><a class="undelete" href="#">&lt;message deleted&gt;</a></span>');
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));
var old_messages = this.get('msgObject.ffz_old_messages');
@ -847,7 +799,6 @@ FFZ.prototype._modify_line = function(component) {
},
classNameBindings: [
'msgObject.ffz_alternate:ffz-alternate',
'msgObject.ffz_has_mention:ffz-mentioned',
'ffzWasDeleted:ffz-deleted',
'ffzHasOldMessages:clearfix',

View file

@ -182,7 +182,7 @@ FFZ.settings_info.mod_buttons = {
old_val += ' ' + prefix + cmd;
}
var new_val = prompt("Custom In-Line Moderation Icons\n\nPlease enter a list of commands to be made available as mod icons within chat lines. Commands are separated by spaces. To include spaces in a command, surround the command with double quotes (\"). Use \"{user}\" to insert the user's username into the command, otherwise it will be appended to the end.\n\nExample: !permit \"!reg add {user}\"\n\nNumeric values will become timeout buttons for that number of seconds. The text \"<BAN>\" is a special value that will act like the normal Ban button in chat.\n\nTo assign a specific letter for use as the icon, specify it at the start of the command followed by an equals sign.\n\nExample: A=\"!reg add\"\n\nDefault: <BAN> 600", old_val);
var new_val = prompt("Custom In-Line Moderation Icons\n\nPlease enter a list of commands to be made available as mod icons within chat lines. Commands are separated by spaces. To include spaces in a command, surround the command with double quotes (\"). Use \"{user}\" to insert the user's username into the command, otherwise it will be appended to the end.\n\nExample: !permit \"!reg add {user}\"\n\nTo send multiple commands, separate them with \"<LINE>\".\n\nNumeric values will become timeout buttons for that number of seconds. The text \"<BAN>\" is a special value that will act like the normal Ban button in chat.\n\nTo assign a specific letter for use as the icon, specify it at the start of the command followed by an equals sign.\n\nExample: A=\"!reg add\"\n\nDefault: <BAN> 600", old_val);
if ( new_val === null || new_val === undefined )
return;
@ -253,8 +253,15 @@ FFZ.settings_info.mod_buttons = {
} else
had_prefix = true;
if ( typeof val === "string" && val.indexOf('{user}') === -1 )
val += ' {user}';
if ( typeof val === "string" ) {
// Split it up for this step.
var lines = val.split(/ *<LINE> */);
for(var x=0; x < lines.length; x++) {
if ( lines[x].indexOf('{user}') === -1 )
lines[x] += ' {user}';
}
val = lines.join("<LINE>");
}
final.push([prefix, val, had_prefix]);
}
@ -435,13 +442,20 @@ FFZ.prototype.setup_mod_card = function() {
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() {
this._super();
window._card = this;
try {
if ( f.has_bttv )
return;
f._mod_card = this;
var el = this.get('element'),
controller = this.get('controller'),
line,
@ -450,11 +464,14 @@ FFZ.prototype.setup_mod_card = function() {
chat = window.App && App.__container__.lookup('controller:chat'),
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'),
alias = f.aliases[user_id];
this.ffz_room_id = room_id;
// Alias Display
if ( alias ) {
var name = el.querySelector('h3.name'),
@ -465,7 +482,7 @@ FFZ.prototype.setup_mod_card = function() {
if ( name ) {
name.classList.add('ffz-alias');
name.title = utils.sanitize(controller.get('cardInfo.user.display_name') || user_id.capitalize());
jQuery(name).tipsy();
jQuery(name).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
}
@ -512,7 +529,7 @@ FFZ.prototype.setup_mod_card = function() {
btn.innerHTML = utils.sanitize(title);
btn.title = utils.sanitize(cmd.replace(/{user}/g, controller.get('cardInfo.user.id') || '{user}'));
jQuery(btn).tipsy();
jQuery(btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
btn.addEventListener('click', add_btn_click.bind(this, cmd));
return btn;
};
@ -591,7 +608,7 @@ FFZ.prototype.setup_mod_card = function() {
else if ( f.settings.mod_card_hotkeys && timeout === 1 )
btn.title = "(P)urge - " + btn.title;
jQuery(btn).tipsy();
jQuery(btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
btn.addEventListener('click', btn_click.bind(this, timeout));
return btn;
@ -627,7 +644,7 @@ FFZ.prototype.setup_mod_card = function() {
unban_btn.innerHTML = CHECK;
unban_btn.title = (f.settings.mod_card_hotkeys ? "(U)" : "U") + "nban User";
jQuery(unban_btn).tipsy();
jQuery(unban_btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
unban_btn.addEventListener("click", btn_click.bind(this, -1));
jQuery(ban_btn).after(unban_btn);
@ -651,7 +668,7 @@ FFZ.prototype.setup_mod_card = function() {
msg_btn.classList.add('message');
msg_btn.title = "Whisper User";
jQuery(msg_btn).tipsy();
jQuery(msg_btn).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
var real_msg = document.createElement('button');
@ -714,41 +731,82 @@ FFZ.prototype.setup_mod_card = function() {
if ( f.settings.mod_card_history ) {
var Chat = App.__container__.lookup('controller:chat'),
room = Chat && Chat.get('currentRoom'),
ffz_room = room && f.rooms && f.rooms[room.get('id')],
user_history = ffz_room && ffz_room.user_history && ffz_room.user_history[controller.get('cardInfo.user.id')];
tmiSession = room.tmiSession || (window.TMI && TMI._sessions && TMI._sessions[0]),
room_id = room.get('id'),
user_id = controller.get('cardInfo.user.id'),
ffz_room = room && f.rooms && f.rooms[room_id],
user_history = ffz_room && ffz_room.user_history && ffz_room.user_history[user_id] || [],
if ( user_history && user_history.length ) {
var history = document.createElement('ul'),
alternate = false;
history.className = 'interface clearfix chat-history';
history = document.createElement('ul');
for(var i=0; i < user_history.length; i++) {
var line = user_history[i],
l_el = document.createElement('li');
history.className = 'interface clearfix chat-history';
l_el.className = 'message-line chat-line clearfix';
l_el.classList.toggle('ffz-alternate', alternate);
alternate = !alternate;
if ( user_history.length < 20 ) {
var before = user_history.length > 0 ? user_history[0].date.getTime() : Date.now();
f.ws_send("user_history", [room_id, user_id, 50 - user_history.length], function(success, data) {
if ( ! success )
return;
if ( line.style )
l_el.classList.add(line.style);
var i = data.length,
was_at_top = history && history.scrollTop >= (history.scrollHeight - history.clientHeight),
first = true;
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>';
while(i--) {
var msg = data[i];
if ( ! msg )
continue;
// Banned Links
var bad_links = l_el.querySelectorAll('a.deleted-link');
for(var x=0; x < bad_links.length; x++)
bad_links[x].addEventListener("click", f._deleted_link_click);
if ( typeof msg.date === "string" || typeof msg.date === "number" )
msg.date = utils.parse_date(msg.date);
jQuery('.html-tooltip', l_el).tipsy({html:true});
history.appendChild(l_el);
}
if ( ! msg.date || msg.date.getTime() >= before )
continue;
el.appendChild(history);
if ( first ) {
first = false;
history.insertBefore(f._build_mod_card_history({
date: msg.date,
from: "jtv",
style: "admin",
cachedTokens: ["(Server History Above)"]
}), history.firstElementChild);
}
// Lazy scroll-to-bottom
history.scrollTop = history.scrollHeight;
if ( ! msg.style ) {
if ( msg.from === "jtv" )
msg.style = "admin";
else if ( msg.from === "twitchnotify" )
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 )
f.tokenize_chat_line(msg, true, room.get('roomProperties.hide_chat_links'));
history.insertBefore(f._build_mod_card_history(msg), history.firstElementChild);
if ( history.childElementCount >= 50 )
break;
}
if ( was_at_top )
setTimeout(function() { history.scrollTop = history.scrollHeight; });
});
}
for(var i=0; i < user_history.length; i++)
history.appendChild(f._build_mod_card_history(user_history[i]));
el.appendChild(history);
// Lazy scroll-to-bottom
history.scrollTop = history.scrollHeight;
}
// Reposition the menu if it's off-screen.
@ -778,6 +836,29 @@ FFZ.prototype.setup_mod_card = function() {
}
FFZ.prototype._build_mod_card_history = function(line) {
var l_el = document.createElement('li'),
f = this;
l_el.className = 'message-line chat-line clearfix';
if ( line.ffz_has_mention )
l_el.classList.add('ffz-mentioned');
if ( line.style )
l_el.classList.add(line.style);
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>';
// 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, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')});
return l_el;
}
// ----------------
// Aliases
// ----------------

View file

@ -61,7 +61,7 @@ FFZ.prototype.setup_player = function() {
this.players = {};
var Player2 = App && App.__container__.resolve('component:twitch-player2');
var Player2 = window.App && App.__container__.resolve('component:twitch-player2');
if ( ! Player2 )
return this.log("Unable to find twitch-player2 component.");

View file

@ -5,6 +5,7 @@ var FFZ = window.FrankerFaceZ,
HOSTED_SUB = / subscribed to /,
constants = require('../constants'),
utils = require('../utils'),
helpers,
// StrimBagZ Support
is_android = navigator.userAgent.indexOf('Android') !== -1,
@ -14,7 +15,12 @@ var FFZ = window.FrankerFaceZ,
return "";
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) { }
// --------------------
@ -128,6 +134,15 @@ FFZ.prototype._modify_rview = function(view) {
this._super();
},
ffzAlternate: function() {
/*if ( ! this._ffz_chat_display ) {
var el = this.get('element');
this._ffz_chat_display = el && el.querySelector('ul.chat-lines');
}
this._ffz_chat_display && this._ffz_chat_display.classList.toggle('ffz-should-alternate');*/
},
ffzInit: function() {
f._roomv = this;
@ -173,6 +188,9 @@ FFZ.prototype._modify_rview = function(view) {
if ( f._roomv === this )
f._roomv = undefined;
if ( this._ffz_chat_display )
this._ffz_chat_display = undefined;
this.ffzDisableFreeze();
},
@ -361,12 +379,12 @@ FFZ.prototype._modify_rview = function(view) {
}
},
ffzUnfreeze: function() {
ffzUnfreeze: function(from_stuck) {
this.ffz_frozen = false;
this._ffz_last_move = 0;
this.ffzUnwarnPaused();
if ( this.get('stuckToBottom') )
if ( ! from_stuck && this.get('stuckToBottom') )
this._scrollToBottom();
},
@ -428,7 +446,7 @@ FFZ.prototype._modify_rview = function(view) {
this.set("stuckToBottom", val);
this.get("controller.model") && this.set("controller.model.messageBufferSize", f.settings.scrollback_length + (val ? 0 : 150));
if ( ! val )
this.ffzUnfreeze();
this.ffzUnfreeze(true);
},
// Warnings~!
@ -722,28 +740,29 @@ FFZ.prototype._insert_history = function(room_id, data) {
tmiSession = r.tmiSession || (TMI._sessions && TMI._sessions[0]),
tmiRoom = r.tmiRoom,
removed = 0,
inserted = 0,
purged = {},
last_msg = data[data.length - 1],
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,
is_old = age > 300,
i = data.length,
alternation = r.get('messages.0.ffz_alternate') || false;
if ( is_old )
alternation = ! alternation;
i = data.length;
var i = data.length;
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.ffz_alternate = alternation = ! alternation;
if ( ! msg.room )
msg.room = room_id;
@ -774,6 +793,14 @@ FFZ.prototype._insert_history = function(room_id, data) {
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 )
this.tokenize_chat_line(msg, true, r.get('roomProperties.hide_chat_links'));
@ -791,11 +818,18 @@ FFZ.prototype._insert_history = function(room_id, data) {
} else
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 ) {
var msg = {
ffz_alternate: ! alternation,
color: "#755000",
date: new Date(),
from: "frankerfacez_admin",
@ -807,10 +841,15 @@ FFZ.prototype._insert_history = function(room_id, data) {
this.tokenize_chat_line(msg, true, r.get('roomProperties.hide_chat_links'));
if ( r.shouldShowMessage(msg) ) {
messages.insertAt(inserted, msg);
while( messages.length > r.get('messageBufferSize') )
while( messages.length > r.get('messageBufferSize') ) {
messages.removeAt(0);
removed++;
}
}
}
if ( (removed % 2) && this._roomv && this._roomv.get('context.model.id') === room_id )
this._roomv.ffzAlternate();
}
@ -975,7 +1014,7 @@ FFZ.prototype._modify_room = function(room) {
var msgs = t.get('messages'),
total = msgs.get('length'),
i = total,
alternate;
removed = 0;
// Delete visible messages
while(i--) {
@ -983,9 +1022,8 @@ FFZ.prototype._modify_room = function(room) {
if ( msg.from === user ) {
if ( f.settings.remove_deleted ) {
if ( alternate === undefined )
alternate = ! msg.ffz_alternate;
msgs.removeAt(i);
removed++;
continue;
}
@ -993,15 +1031,11 @@ FFZ.prototype._modify_room = function(room) {
if ( ! f.settings.prevent_clear )
t.set('messages.' + i + '.deleted', true);
}
if ( alternate === undefined )
alternate = msg.ffz_alternate;
else {
alternate = ! alternate;
t.set('messages.' + i + '.ffz_alternate', alternate);
}
}
if ( (removed % 2) && f._roomv && f._roomv.get('context.model.id') === this.get('id') )
f._roomv.ffzAlternate();
// Delete pending messages
if (t.ffzPending) {
msgs = t.ffzPending;
@ -1053,8 +1087,11 @@ FFZ.prototype._modify_room = function(room) {
len = messages.get("length"),
limit = this.get("messageBufferSize");
if ( len > limit )
if ( len > limit ) {
messages.removeAt(0, len - limit);
if ( ((len - limit) % 2) && f._roomv && f._roomv.get('context.model.id') === this.get('id') )
f._roomv.ffzAlternate();
}
},
// Artificial chat delay
@ -1075,13 +1112,6 @@ FFZ.prototype._modify_room = function(room) {
ffzActualPushMessage: function (msg) {
if ( this.shouldShowMessage(msg) && this.ffzShouldShowMessage(msg) ) {
var row_type = msg.ffz_alternate;
if ( row_type === undefined ) {
var room_id = this.get('id');
row_type = f._last_row[room_id] = f._last_row.hasOwnProperty(room_id) ? !f._last_row[room_id] : false;
msg.ffz_alternate = row_type;
}
this.get("messages").pushObject(msg);
this.trimMessages();
@ -1155,10 +1185,10 @@ FFZ.prototype._modify_room = function(room) {
return;
var is_whisper = msg.style === 'whisper';
if ( f.settings.group_tabs && f.settings.whisper_room ) {
if ( ( is_whisper && ! this.ffz_whisper_room ) || ( ! is_whisper && this.ffz_whisper_room ) )
return;
}
// Ignore whispers if conversations are enabled.
if ( is_whisper && App.__container__.lookup('route:application').controller.get('isConversationsEnabled') )
return;
if ( ! is_whisper )
msg.room = this.get('id');
@ -1190,6 +1220,22 @@ FFZ.prototype._modify_room = function(room) {
if ( user_history.length > 20 )
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 ) {
history.appendChild(f._build_mod_card_history(msg));
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

@ -4,46 +4,11 @@ var FFZ = window.FrankerFaceZ,
constants = require('./constants'),
utils = require('./utils'),
/*check_margins = function(margins, height) {
var mlist = margins.split(/ +/);
if ( mlist.length != 2 )
return margins;
mlist[0] = parseFloat(mlist[0]);
mlist[1] = parseFloat(mlist[1]);
if ( mlist[0] == (height - 18) / -2 && mlist[1] == 0 )
return null;
return margins;
},
build_legacy_css = function(emote) {
var margin = emote.margins, srcset = "";
if ( ! margin )
margin = ((emote.height - 18) / -2) + "px 0";
if ( emote.urls[2] || emote.urls[4] ) {
srcset = 'url("' + emote.urls[1] + '") 1x';
if ( emote.urls[2] )
srcset += ', url("' + emote.urls[2] + '") 2x';
if ( emote.urls[4] )
srcset += ', url("' + emote.urls[4] + '") 4x';
srcset = '-webkit-image-set(' + srcset + '); image-set(' + srcset + ');';
}
return ".ffz-emote-" + emote.id + ' { background-image: url("' + emote.urls[1] + '"); height: ' + emote.height + "px; width: " + emote.width + "px; margin: " + margin + (srcset ? '; ' + srcset : '') + (emote.css ? "; " + emote.css : "") + "}\n";
},*/
build_css = function(emote) {
if ( ! emote.margins && ! emote.css )
return ""; //build_legacy_css(emote);
return "";
return /*build_legacy_css(emote) +*/ 'img[src="' + emote.urls[1] + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.css || "") + " }\n";
return 'img[src="' + emote.urls[1] + '"] { ' + (emote.margins ? "margin: " + emote.margins + ";" : "") + (emote.css || "") + " }\n";
},
@ -120,8 +85,15 @@ FFZ.prototype.setup_emoticons = function() {
// Emote Usage
// ------------------------
FFZ.prototype.add_usage = function(room_id, emote_id, count) {
var rooms = this.emote_usage[emote_id] = this.emote_usage[emote_id] || {};
FFZ.prototype.add_usage = function(room_id, emote, count) {
// Only report usage from FFZ emotes. Not extensions to FFZ.
var emote_set = this.emote_sets[emote.set_id];
if ( ! emote_set || emote_set.source_ext )
return;
var emote_id = emote.id,
rooms = this.emote_usage[emote_id] = this.emote_usage[emote_id] || {};
rooms[room_id] = (rooms[room_id] || 0) + (count || 1);
if ( this._emote_report_scheduled )
@ -141,6 +113,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
// ------------------------
@ -225,12 +232,82 @@ FFZ.prototype._emote_tooltip = function(emote) {
var set = this.emote_sets[emote.set_id],
owner = emote.owner,
title = set && set.title || "Global",
source = set && set.source || "FFZ";
source = set && set.source || "FFZ",
emote._tooltip = "Emoticon: " + (emote.hidden ? "???" : emote.name) + "\n" + source + " " + title + (owner ? "\nBy: " + owner.display_name : "");
preview_url = this.settings.emote_image_hover ? (emote.urls[4] || emote.urls[2]) : null,
image = preview_url ? '<img class="emoticon ffz-image-hover" src="' + preview_url + '?_=preview">' : '';
emote._tooltip = image + "Emoticon: " + (emote.hidden ? "???" : emote.name) + "<br>" + source + " " + title + (owner ? "<br>By: " + owner.display_name : "");
return emote._tooltip;
}
FFZ.prototype._reset_tooltips = function(twitch_only) {
for(var emote_id in this._twitch_emotes) {
var data = this._twitch_emotes[emote_id];
if ( data && data.tooltip )
data.tooltip = null;
}
if ( ! twitch_only ) {
for(var set_id in this.emote_sets) {
var emote_set = this.emote_sets[set_id];
for(var emote_id in emote_set.emoticons) {
var emote = emote_set.emoticons[emote_id];
if ( emote._tooltip )
emote._tooltip = null;
}
}
}
var emotes = document.querySelectorAll('img.emoticon');
for(var i=0; i < emotes.length; i++) {
var emote = emotes[i];
if ( emote.classList.contains('ffz-image-hover') )
continue;
var set_id,
emote_id = emote.getAttribute('data-emote');
if ( emote_id ) {
// Twitch Emotes
if ( this.has_bttv )
continue;
emote.setAttribute('original-title', utils.build_tooltip.bind(this)(emote_id, false, emote.alt));
continue;
}
if ( twitch_only )
continue;
// FFZ Emoji
emote_id = emote.getAttribute('data-ffz-emoji');
if ( emote_id ) {
var emoji = this.emoji_data && this.emoji_data[emote_id],
setting = this.settings.parse_emoji,
src = emoji ? (setting === 2 ? emoji.noto_src : emoji.tw_src) : null,
image = '';
if ( src && this.settings.emote_image_hover )
image = '<img class="emoticon ffz-image-hover emoji" src="' + src + '">';
emote.setAttribute('original-title', emoji ? (image + 'Emoji: ' + emote.alt + '<br>Name: ' + emoji.name + (emoji.short_name ? "<br>Short Name: :" + emoji.short_name + ":" : "")) : emote.alt);
continue;
}
// FFZ Emotes
emote_id = emote.getAttribute('data-ffz-emote');
set_id = emote.getAttribute('data-ffz-set');
var emote_set = this.emote_sets[set_id];
if ( ! emote_set || ! emote_set.emoticons || ! emote_set.emoticons[emote_id] )
continue;
emote.setAttribute('original-title', this._emote_tooltip(emote_set.emoticons[emote_id]));
}
}
// ---------------------
// Emoji Loading

View file

@ -25,8 +25,6 @@ FFZ.prototype.setup_bttv = function(delay) {
this.log("BetterTTV was detected after " + delay + "ms. Hooking.");
this.has_bttv = true;
// this.track('setCustomVariable', '3', 'BetterTTV', BetterTTV.info.versionString());
// Disable Dark if it's enabled.
document.body.classList.remove("ffz-dark");
if ( this._dark_style ) {
@ -59,22 +57,28 @@ FFZ.prototype.setup_bttv = function(delay) {
this._roomv.ffzUpdateStatus();
}
// Disable style blocks.
this.toggle_style('chat-setup');
this.toggle_style('chat-padding');
this.toggle_style('chat-background');
this.toggle_style('chat-separator');
this.toggle_style('chat-separator-3d');
this.toggle_style('chat-separator-3d-inset');
this.toggle_style('chat-separator-wide');
this.toggle_style('chat-hc-text');
this.toggle_style('chat-hc-bold');
this.toggle_style('chat-hc-background');
this.toggle_style('chat-colors-gray');
this.toggle_style('badges-transparent');
// Disable other features too.
document.body.classList.remove("ffz-chat-colors");
document.body.classList.remove("ffz-chat-colors-gray");
document.body.classList.remove("ffz-chat-background");
document.body.classList.remove("ffz-chat-padding");
document.body.classList.remove("ffz-chat-separator");
document.body.classList.remove("ffz-chat-separator-3d");
document.body.classList.remove("ffz-chat-separator-wide");
document.body.classList.remove("ffz-chat-separator-3d-inset");
document.body.classList.remove('ffz-transparent-badges');
document.body.classList.remove("ffz-sidebar-swap");
document.body.classList.remove("ffz-portrait");
document.body.classList.remove("ffz-flip-dashboard");
document.body.classList.remove("ffz-transparent-badges");
document.body.classList.remove("ffz-high-contrast-chat-text");
document.body.classList.remove("ffz-high-contrast-chat-bg");
document.body.classList.remove("ffz-high-contrast-chat-bold");
// Remove Following Count
if ( this.settings.following_count ) {
@ -218,14 +222,14 @@ FFZ.prototype.setup_bttv = function(delay) {
// Why is emote parsing so bad? ;_;
_.each(emotes, function(emote) {
var tooltip = f._emote_tooltip(emote),
eo = ['<img class="emoticon" data-ffz-emote="' + emote.id + '" srcset="' + (emote.srcSet || "") + '" src="' + emote.urls[1] + '" data-regex="' + emote.name + '" title="' + tooltip + '" />'],
eo = ['<img class="emoticon html-tooltip" data-ffz-emote="' + emote.id + '" srcset="' + utils.quote_attr(emote.srcSet || "") + '" src="' + utils.quote_attr(emote.urls[1]) + '" data-regex="' + utils.quote_attr(emote.name) + '" title="' + utils.quote_attr(tooltip) + '">'],
old_tokens = tokens;
tokens = [];
for(var i=0; i < old_tokens.length; i++) {
var token = old_tokens[i];
if ( typeof token != "string" ) {
if ( typeof token !== "string" ) {
tokens.push(token);
continue;
}
@ -242,7 +246,7 @@ FFZ.prototype.setup_bttv = function(delay) {
tokens.push(eo);
if ( mine && l_room )
f.add_usage(l_room, emote.id);
f.add_usage(l_room, emote);
} else
tokens.push(bit);
@ -252,9 +256,10 @@ FFZ.prototype.setup_bttv = function(delay) {
}
// Sneak in Emojicon Processing
/*
if ( f.settings.parse_emoji && f.emoji_data ) {
var old_tokens = tokens;
var old_tokens = tokens,
setting = f.settings.parse_emoji;
tokens = [];
for(var i=0; i < old_tokens.length; i++) {
@ -274,20 +279,25 @@ FFZ.prototype.setup_bttv = function(delay) {
variant = tbits.shift();
if ( variant === '\uFE0E' )
bits.push(match);
tokens.push(match);
else {
var eid = utils.emoji_to_codepoint(match, variant),
data = f.emoji_data[eid];
data = f.emoji_data[eid],
src = data && (setting === 2 ? data.noto_src : data.tw_src);
if ( data ) {
tokens.push(['<img class="emoticon" height="18px" srcset="' + (data.srcSet || "") + '" src="' + data.src + '" alt="' + alt + '" title="Emoji: ' + data.raw + '\nName: :' + data.short_name + ':">']);
if ( data && src ) {
var image = src && f.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + src + '">' : '',
tooltip = image + "Emoji: " + data.raw + "<br>Name: " + data.name + (data.short_name ? "<br>Short Name: :" + data.short_name + ":" : ""),
code = utils.quote_attr(data.raw);
tokens.push(['<img class="emoticon emoji html-tooltip" height="18px" data-ffz-emoji="' + eid + '" src="' + utils.quote_attr(src) + '" data-regex="' + code + '" alt="' + code + '" title="' + utils.quote_attr(tooltip) + '">']);
} else
tokens.push(match + (variant || ""));
}
}
}
}
}*/
}
return tokens;
}

277
src/ext/rechat.js Normal file
View file

@ -0,0 +1,277 @@
var FFZ = window.FrankerFaceZ,
constants = require('../constants'),
utils = require('../utils');
// --------------------
// Initialization
// --------------------
FFZ.prototype.setup_rechat = function() {
if ( this.has_bttv || navigator.userAgent.indexOf('Android') !== -1 )
return;
this._rechat_listening = false;
this.log("Installing ReChat mutation observer.");
var f = this;
this._rechat_observer = new MutationObserver(function(mutations) {
for(var i=0; i < mutations.length; i++) {
var mutation = mutations[i];
if ( mutation.type !== "childList" )
continue;
for(var x=0; x < mutation.addedNodes.length; x++) {
var added = mutation.addedNodes[x];
if ( added.nodeType !== added.ELEMENT_NODE || added.tagName !== "DIV" )
continue;
// Is this a ReChat line?
if ( added.classList.contains('rechat-chat-line') && ! added.classList.contains('ffz-processed') )
f.process_rechat_line(added);
}
}
});
this.log("Starting ReChat check loop.");
this._rechat_interval = setInterval(this.find_rechat.bind(this), 1000);
this.find_rechat();
}
// --------------------
// ReChat Detection
// --------------------
FFZ.prototype.find_rechat = function() {
var el = !this.has_bttv ? document.querySelector('.rechat-chat-line') : null;
// If there's no change, don't continue.
if ( !!el === this._rechat_listening )
return;
// If we're no longer listening, stop the observer and quit.
if ( ! el ) {
this._rechat_observer.disconnect();
this._rechat_listening = false;
return;
}
// We're newly listening. Process all existing ReChat chat lines
// and darken the container if required, also enable the observer.
var container = jQuery(el).parents('.chat-container');
if ( ! container.length )
return;
container = container[0];
// Look-up dark mode.
var dark_chat = this.settings.dark_twitch;
if ( ! dark_chat ) {
var model = window.App ? App.__container__.lookup('controller:settings').get('model') : undefined;
dark_chat = model && model.get('darkMode');
}
container.classList.toggle('dark', dark_chat);
jQuery(container).find('.chat-lines').addClass('ffz-scrollbar');
// Tooltips
jQuery(container).find('.tooltip').tipsy({live: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
jQuery(container).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 'n')});
// Load the room data.
var room_id = el.getAttribute('data-room');
if ( room_id && ! this.rooms[room_id] )
this.load_room(room_id, this._reprocess_rechat.bind(this, container));
// Do stuff.
var lines = container.querySelectorAll('.rechat-chat-line');
for(var i=0; i < lines.length; i++) {
var line = lines[i];
if ( line.classList.contains('ffz-processed') )
continue;
this.process_rechat_line(line);
}
// Start observing.
this._rechat_observer.observe(container, {
childList: true,
subtree: true
});
this._rechat_listening = true;
}
// --------------------
// ReChat Lines
// --------------------
FFZ.prototype._reprocess_rechat = function(container) {
var lines = container.querySelectorAll('.rechat-chat-line');
for(var i=0; i < lines.length; i++)
this.process_rechat_line(lines[i], true);
}
FFZ.prototype.process_rechat_line = function(line, reprocess) {
if ( ! reprocess && line.classList.contains('ffz-processed') )
return;
line.classList.add('ffz-processed');
var user_id = line.getAttribute('data-sender'),
room_id = line.getAttribute('data-room'),
Layout = App.__container__.lookup('controller:layout'),
Settings = App.__container__.lookup('controller:settings'),
is_dark = (Layout && Layout.get('isTheatreMode')) || (Settings && Settings.get('model.darkMode')),
badges_el = line.querySelector('.badges'),
from_el = line.querySelector('.from'),
message_el = line.querySelector('.message'),
badges = {},
had_badges = !!badges_el,
raw_color = from_el && FFZ.Color.RGB.fromCSS(from_el.style.color),
colors = raw_color && this._handle_color(raw_color),
alias = this.aliases[user_id];
if ( ! badges_el ) {
badges_el = document.createElement('span');
badges_el.className = 'badges float-left';
line.insertBefore(badges_el, from_el || line.firstElementChild);
}
if ( ! reprocess || ! had_badges ) {
// Read existing known badges.
var existing = badges_el.querySelectorAll('.badge');
for(var i=0; i < existing.length; i++) {
var badge = existing[i];
if ( badge.classList.contains('broadcaster') )
badges[0] = {klass: 'broadcaster', title: 'Broadcaster'};
else if ( badge.classList.contains('staff') )
badges[0] = {klass: 'staff', title: 'Staff'};
else if ( badge.classList.contains('admin') )
badges[0] = {klass: 'admin', title: 'Admin'};
else if ( badge.classList.contains('global-moderator') )
badges[0] = {klass: 'global-moderator', title: 'Global Moderator'};
else if ( badge.classList.contains('moderator') )
badges[0] = {klass: 'moderator', title: 'Moderator'};
else if ( badge.classList.contains('subscriber') )
badges[10] = {klass: 'subscriber', title: 'Subscriber'};
else if ( badge.classList.contains('turbo') )
badges[15] = {klass: 'turbo', title: 'Turbo'};
}
if ( user_id && user_id === room_id )
badges[0] = {klass: 'broadcaster', title: 'Broadcaster'};
if ( user_id )
badges = this._render_badges(user_id, room_id, badges);
var output = '';
for(var key in badges) {
var badge = badges[key],
css = badge.iamge ? 'background-image:url(&quot;' + badge.image + '&quot;);' : '';
if ( badge.color )
css += 'background-color:' + badge.color + ';';
if ( badge.extra_css )
css += badge.extra_css;
output += '<div class="badge float-left tooltip ' + badge.klass + '"' + (css ? ' style="' + css + '"' : '') + ' title="' + badge.title + '"></div>';
}
badges_el.innerHTML = output;
}
if ( ! reprocess && from_el ) {
from_el.style.fontWeight = "";
if ( colors ) {
from_el.classList.add('has_color');
from_el.style.color = is_dark ? colors[1] : colors[0];
}
if ( alias ) {
from_el.classList.add('ffz-alias');
from_el.title = from_el.textContent;
from_el.textContent = alias;
}
}
if ( ! message_el )
return;
if ( ! reprocess && message_el.style.color ) {
message_el.classList.add('has-color');
message_el.style.color = is_dark ? colors[1] : colors[0];
}
var raw_tokens = line.getAttribute('data-tokens'),
tokens = raw_tokens ? JSON.parse(raw_tokens) : [];
if ( ! raw_tokens ) {
for(var i=0; i < message_el.childNodes.length; i++) {
var node = message_el.childNodes[i];
if ( node.nodeType === node.TEXT_NODE )
tokens.push(node.textContent);
else if ( node.nodeType === node.ELEMENT_NODE ) {
if ( node.tagName === 'IMG' )
tokens.push({
altText: node.alt,
emoticonSrc: node.src
});
else if ( node.tagName === 'A' )
tokens.push({
isLink: true,
href: node.textContent
});
else if ( node.tagName === 'SPAN' )
tokens.push({
mentionedUser: node.textContent,
own: node.classList.contains('mentioning')
});
else
this.log("Unknown Tag Type: " + node.tagName);
} else
this.log("Unknown Node Type Tokenizing Message: " + node.nodeType);
}
}
line.setAttribute('data-tokens', JSON.stringify(tokens));
// Further tokenization~!
if ( this.settings.replace_bad_emotes )
tokens = this.tokenize_replace_emotes(tokens);
tokens = this._remove_banned(tokens);
tokens = this.tokenize_emotes(user_id, room_id, tokens, false);
if ( this.settings.parse_emoji )
tokens = this.tokenize_emoji(tokens);
tokens = this.tokenize_mentions(tokens);
// Check for a mention
if ( ! line.classList.contains('ffz-mentioend') )
for(var i=0; i < tokens.length; i++)
if ( tokens[i].mentionedUser ) {
line.classList.add('ffz-mentioned');
break;
}
// Now, put the content back into the element.
message_el.innerHTML = this.render_tokens(tokens);
}

View file

@ -22,7 +22,7 @@ FFZ.get = function() { return FFZ.instance; }
// Version
var VER = FFZ.version_info = {
major: 3, minor: 5, revision: 50,
major: 3, minor: 5, revision: 77,
toString: function() {
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
}
@ -90,12 +90,22 @@ FFZ.prototype._pastebin = function(data, callback) {
// -------------------
FFZ.prototype.get_user = function() {
if ( window.PP && PP.login ) {
return PP;
} else if ( window.App ) {
var nc = App.__container__.lookup("controller:login");
return nc ? nc.get("userData") : undefined;
if ( this.__user )
return this.__user;
var user;
if ( window.App ) {
var nc = App.__container__.lookup('controller:login');
user = nc ? nc.get('userData') : undefined;
}
if ( ! user && window.PP && PP.login )
user = PP;
if ( user )
this.__user = user;
return user;
}
@ -123,14 +133,17 @@ require('./ember/room');
require('./ember/layout');
require('./ember/line');
require('./ember/chatview');
require('./ember/conversations');
require('./ember/viewers');
require('./ember/moderation-card');
require('./ember/chat-input');
//require('./ember/teams');
require('./ember/directory');
require('./ember/following');
require('./debug');
require('./ext/rechat');
require('./ext/betterttv');
require('./ext/emote_menu');
@ -138,6 +151,7 @@ require('./featurefriday');
require('./ui/styles');
require('./ui/dark');
require('./ui/tooltips');
require('./ui/notifications');
require('./ui/viewer_count');
require('./ui/sub_count');
@ -241,6 +255,7 @@ FFZ.prototype.init_normal = function(delay, no_socket) {
// Start this early, for quick loading.
this.setup_dark();
this.setup_css();
if ( ! no_socket )
this.ws_create();
@ -251,9 +266,9 @@ FFZ.prototype.init_normal = function(delay, no_socket) {
this.setup_notifications();
this.setup_following_count(false);
this.setup_css();
this.setup_menu();
this.fix_tooltips();
this.find_bttv(10);
var end = (window.performance && performance.now) ? performance.now() : Date.now(),
@ -278,6 +293,7 @@ FFZ.prototype.init_dashboard = function(delay) {
// Start this early, for quick loading.
this.setup_dark();
this.setup_css();
this.ws_create();
this.setup_colors();
@ -287,7 +303,6 @@ FFZ.prototype.init_dashboard = function(delay) {
this.setup_tokenization();
this.setup_notifications();
this.setup_following_count(false);
this.setup_css();
this.setup_menu();
this._update_subscribers();
@ -295,6 +310,7 @@ FFZ.prototype.init_dashboard = function(delay) {
// Set up the FFZ message passer.
this.setup_message_event();
this.fix_tooltips();
this.find_bttv(10);
var end = (window.performance && performance.now) ? performance.now() : Date.now(),
@ -319,6 +335,7 @@ FFZ.prototype.init_ember = function(delay) {
// Start this early, for quick loading.
this.setup_dark();
this.setup_css();
this.ws_create();
this.setup_emoticons();
@ -335,23 +352,26 @@ FFZ.prototype.init_ember = function(delay) {
this.setup_line();
this.setup_layout();
this.setup_chatview();
this.setup_conversations();
this.setup_viewers();
this.setup_mod_card();
this.setup_chat_input();
this.setup_directory();
this.setup_profile_following();
//this.setup_teams();
this.setup_notifications();
this.setup_css();
this.setup_menu();
this.setup_my_emotes();
this.setup_following();
this.setup_following_count(true);
this.setup_races();
this.fix_tooltips();
this.connect_extra_chat();
this.setup_rechat();
this.find_bttv(10);
this.find_emote_menu(10);

View file

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

View file

@ -0,0 +1,3 @@
.badges .badge:not(.subscriber) {
background-size: 0px;
}

View file

@ -0,0 +1,5 @@
.badges .badge:not(.subscriber) {
height: 10px;
min-width: 10px;
margin: 5px 3px 5px 0;
}

View file

@ -0,0 +1,6 @@
.badges .badge:not(.subscriber) {
border-radius: 9px;
background-size: 16px;
background-repeat: no-repeat;
background-position: center;
}

View file

@ -0,0 +1,4 @@
.badges .moderator {
background-color: #068c10;
background-image: url('//cdn.frankerfacez.com/script/legacy-mod.png');
}

View file

@ -0,0 +1,4 @@
.badges .turbo {
background-color: #6441a3;
background-image: url('//cdn.frankerfacez.com/script/legacy-turbo.png');
}

View file

@ -0,0 +1,14 @@
.badges .staff {
background-color: #6441a5;
background-image: url('//cdn.frankerfacez.com/script/legacy-staff.png');
}
.badges .broadcaster {
background-color: #000;
background-image: url('//cdn.frankerfacez.com/script/legacy-broadcaster.png');
}
.badges .admin {
background-color: #ff0303;
background-image: url('//cdn.frankerfacez.com/script/legacy-admin.png');
}

View file

@ -0,0 +1,4 @@
/* Rounded Badges */
.badges .badge:not(.subscriber) {
border-radius: 2px;
}

View file

@ -0,0 +1,12 @@
.badges .badge {
background-color: transparent !important;
}
/* Invert Some Badges */
body:not(.ffz-dark) .app-main:not(.theatre) .conversation-window .badges .badge:not(.subscriber):not(.ffz-badge-0),
.app-main:not(.theatre) .chat-container:not(.dark):not(.force-dark) .badges .badge:not(.subscriber):not(.ffz-badge-0),
.app-main:not(.theatre) .ember-chat-container:not(.dark):not(.force-dark) .badges .badge:not(.subscriber):not(.ffz-badge-0) {
filter: invert(100%);
-webkit-filter: invert(100%);
}

View file

@ -0,0 +1,101 @@
/* Regular Alternating Background */
.conversation-chat-lines > div:nth-child(2n+0):before,
.chat-history .chat-line:nth-child(2n+0):before,
.ember-chat .chat-lines > div:nth-child(2n+0) .chat-line:before,
/* ReChat */
.ember-chat.chat-messages > .rechat-chat-line:nth-child(2n+0):before {
background-color: rgba(0,0,0, 0.1);
}
/* Dark: Alternating Background */
.ffz-dark .conversation-chat-lines > div:nth-child(2n+0):before,
.ffz-dark .chat-history .chat-line:nth-child(2n+0):before,
.theatre .conversation-chat-lines > div:nth-child(2n+0):before,
.theatre .chat-history .chat-line:nth-child(2n+0):before,
.theatre .ember-chat .chat-lines > div:nth-child(2n+0) .chat-line:before,
.dark .chat-history .chat-line:nth-child(2n+0):before,
.force-dark .chat-history .chat-line:nth-child(2n+0):before,
.dark .chat-lines > div:nth-child(2n+0) .chat-line:before,
.force-dark .chat-lines > div:nth-child(2n+0) .chat-line:before,
/* ReChat */
.theatre .chat-lines > .rechat-chat-line:nth-child(2n+0):before,
.dark .chat-lines > .rechat-chat-line:nth-child(2n+0):before,
.force-dark .chat-lines > .rechat-chat-line:nth-child(2n+0):before {
background-color: rgba(255,255,255, 0.05);
}
/* DEPRECIATED: Mention Backgrounds */
.chat-history .chat-line.ffz-mentioned:before,
.ember-chat .chat-line.ffz-mentioned:before {
background-color: rgba(255,127,127,0.2);
}
.chat-history .chat-line.ffz-mentioned:nth-child(2n+0):before,
.ember-chat .chat-lines > div:nth-child(2n+0) .chat-line.ffz-mentioned:before {
background-color: rgba(255,127,127, 0.4);
}
/* DEPRECIATED: DARK THEME: Mention Backgrounds */
.ffz-dark .chat-history .chat-line.ffz-mentioned:before,
.theatre .chat-line.ffz-mentioned:before,
.dark .chat-line.ffz-mentioned:before,
.force-dark .chat-line.ffz-mentioned:before {
background-color: rgba(255,0,0, 0.2) !important;
}
.theatre .chat-lines > div:nth-child(2n+0) .chat-line.ffz-mentioned:before,
.dark .chat-history .chat-line.ffz-mentioned:nth-child(2n+0):before,
.force-dark .chat-history .chat-line.ffz-mentioned:nth-child(2n+0):before,
.dark .chat-lines > div:nth-child(2n+0) .chat-line.ffz-mentioned:before,
.force-dark .chat-lines > div:nth-child(2n+0) .chat-line.ffz-mentioned:before {
background-color: rgba(255,0,0, 0.3) !important;
}
/* Chat Mentions */
/* TODO: Move this by itself */
.mentioned:empty,
.mentioning:empty {
display: none;
}
.chat-line .mentioned,
.chat-line .mentioning {
border-radius: 10px;
padding: 3px 7px;
font-weight: bold;
color: #323232;
background-color: rgba(255,255,255, 0.75);
}
/* DARK THEME: Chat Mentions */
.ffz-dark .chat-history .mentioned,
.ffz-dark .chat-history .mentioning,
.app-main.theatre .chat-container .chat-line .mentioned,
.app-main.theatre .chat-container .chat-line .mentioning,
.chat-container.dark .chat-line .mentioned,
.chat-container.dark .chat-line .mentioning,
.chat-container.force-dark .chat-line .mentioned,
.chat-container.force-dark .chat-line .mentioning,
.ember-chat-container.dark .chat-line .mentioned,
.ember-chat-container.dark .chat-line .mentioning,
.ember-chat-container.force-dark .chat-line .mentioned,
.ember-chat-container.force-dark .chat-line .mentioning {
color: #8c8c8c;
background-color: rgba(16,16,16, 0.75);
}

View file

@ -0,0 +1,4 @@
.chat-line:not(.admin):not(.notification) span.from,
.chat-line:not(.admin):not(.notification) span.message {
color: inherit !important
}

View file

@ -0,0 +1,16 @@
/* High-Contrast Background */
.chat-container,
.ember-chat-container {
background-color: #fff !important;
}
/* Dark: High-Contrast Background */
.theatre .chat-container,
.theatre .ember-chat-container,
.chat-container.dark,
.chat-container.force-dark,
.ember-chat-container.dark,
.ember-chat-container.force-dark {
background-color: #000 !important;
}

View file

@ -0,0 +1,6 @@
/* High-Contrast Bold */
.chat-line .from,
.chat-line .colon,
.chat-line .message {
font-weight: bold;
}

View file

@ -0,0 +1,19 @@
/* High-Contrast Text */
.chat-container,
.ember-chat-container {
color: #000 !important;
}
/* Dark: High-Contrast Text */
.theatre .chat-container,
.theatre .ember-chat-container,
.chat-container.dark,
.chat-container.force-dark,
.ember-chat-container.dark,
.ember-chat-container.force-dark,
.ffz-dark .ember-chat-container.dark .chat-line,
.ffz-dark .chat-container.dark .chat-line
{
color: #fff !important;
}

View file

@ -0,0 +1,15 @@
/* Chat Line Padding */
.ember-chat .chat-messages .chat-line,
.ember-chat .chat-messages .chat-line.admin,
.conversation-window .conversation-system-messages,
.conversation-window .conversation-chat-line,
.conversation-window .timestamp-line {
padding: 5px;
}
/* Remove Extra Conversation Padding */
.conversation-window .conversation-chat-lines {
padding-top: 0;
}

View file

@ -0,0 +1,17 @@
/* 3D Inset Separators */
.conversation-chat-lines > div:before,
.chat-line:before {
border-top: 1px solid #aaa;
border-bottom-color: rgba(255,255,255, 0.5);
}
/* Dark: 3D Inset Separators */
.ffz-dark .conversation-chat-lines > div:before,
.theatre .conversation-chat-lines > div:before,
.theatre .chat-line:before,
.dark .chat-line:before,
.force-dark .chat-line:before {
border-top-color: #000;
border-bottom-color: rgba(255,255,255, 0.1);
}

View file

@ -0,0 +1,15 @@
/* 3D Separators */
.conversation-chat-lines > div:before,
.chat-line:before {
border-top: 1px solid rgba(255,255,255, 0.5);
}
/* Dark: 3D Separators */
.ffz-dark .conversation-chat-lines > div:before,
.theatre .conversation-chat-lines > div:before,
.theatre .chat-line:before,
.dark .chat-line:before,
.force-dark .chat-line:before {
border-top-color: rgba(255,255,255, 0.1);
}

View file

@ -0,0 +1,15 @@
/* Wide Separators */
.conversation-chat-lines > div:before,
.chat-line:before {
border-top: 1px solid #aaa;
}
/* Dark: Wide Separators */
.ffz-dark .conversation-chat-lines > div:before,
.theatre .conversation-chat-lines > div:before,
.theatre .chat-line:before,
.dark .chat-line:before,
.force-dark .chat-line:before {
border-top-color: #000;
}

View file

@ -0,0 +1,29 @@
/* Simple Separators */
.conversation-chat-lines > div:before,
.chat-line:before {
border-bottom: 1px solid #aaa;
}
/* Dark: Simple Separators */
.ffz-dark .conversation-chat-lines > div:before,
.theatre .conversation-chat-lines > div:before,
.theatre .chat-line:before,
.dark .chat-line:before,
.force-dark .chat-line:before {
border-bottom-color: #000;
}
/* Hide First Line */
.conversation-chat-lines > div:first-child:before,
.chat-lines > div:first-child .chat-line:before {
border-top-color: transparent;
}
/* Hide Last Line */
.conversation-chat-lines > div:last-child:nth-child(odd):before,
.chat-lines > div:last-child:nth-child(odd) .chat-line:before {
border-bottom-color: transparent;
}

18
src/styles/chat-setup.css Normal file
View file

@ -0,0 +1,18 @@
.conversation-chat-lines > div,
.chat-line {
position: relative;
z-index: 1;
}
.conversation-chat-lines > div:before,
.chat-line:before {
content: "";
position: absolute;
z-index: -1;
left: 0; right: 0;
top: 2px; bottom: 1px;
}
.chat-history .chat-line:before {
top: 0; bottom: 0;
}

View file

@ -1,76 +1,11 @@
var FFZ = window.FrankerFaceZ,
utils = require("./utils"),
constants = require("./constants"),
TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/",
helpers,
conv_helpers,
EXPLANATION_TRAIL = '<hr>FFZ is hiding this link because this url shortener is known to be used by Twitch spam bots posting malicious links. Please use caution when visiting shortened links.',
SRCSETS = {};
build_srcset = function(id) {
if ( SRCSETS[id] )
return SRCSETS[id];
var out = SRCSETS[id] = TWITCH_BASE + id + "/1.0 1x, " + TWITCH_BASE + id + "/2.0 2x, " + TWITCH_BASE + id + "/3.0 4x";
return out;
},
data_to_tooltip = function(data) {
var set = data.set,
set_type = data.set_type,
owner = data.owner;
if ( set_type === undefined )
set_type = "Channel";
if ( ! set )
return data.code;
else if ( set === "--global--" ) {
set = "Twitch Global";
set_type = null;
} else if ( set == "--twitch-turbo--" || set == "turbo" || set == "--turbo-faces--" ) {
set = "Twitch Turbo";
set_type = null;
}
return "Emoticon: " + data.code + "\n" + (set_type ? set_type + ": " : "") + set + (owner ? "\nBy: " + owner.display_name : "");
},
build_tooltip = function(id) {
var emote_data = this._twitch_emotes[id],
set = emote_data ? emote_data.set : null;
if ( ! emote_data )
return "???";
if ( typeof emote_data == "string" )
return emote_data;
if ( emote_data.tooltip )
return emote_data.tooltip;
return emote_data.tooltip = data_to_tooltip(emote_data);
},
load_emote_data = function(id, code, success, data) {
if ( ! success )
return code;
if ( code )
data.code = code;
this._twitch_emotes[id] = data;
var tooltip = build_tooltip.bind(this)(id);
var images = document.querySelectorAll('img[data-emote="' + id + '"]');
for(var x=0; x < images.length; x++)
images[x].title = tooltip;
return tooltip;
},
reg_escape = function(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
@ -105,7 +40,7 @@ var FFZ = window.FrankerFaceZ,
return IMGUR_PATH.test(path);
return any_domain ? IMAGE_EXT.test(path) : IMAGE_DOMAINS.indexOf(domain) !== -1;
}
},
image_iframe = function(href, extra_class) {
return '<iframe class="ffz-image-hover' + (extra_class ? ' ' + extra_class : '') + '" allowtransparency="true" src="' + constants.SERVER + 'script/img-proxy.html#' + utils.quote_attr(href) + '"></iframe>';
@ -248,12 +183,12 @@ FFZ._emote_mirror_swap = function(img) {
img.setAttribute('data-alt-attempts', attempts + 1);
var id = img.getAttribute('data-emote');
if ( img.src.substr(0, TWITCH_BASE.length) === TWITCH_BASE ) {
if ( img.src.substr(0, constants.TWITCH_BASE.length) === constants.TWITCH_BASE ) {
img.src = constants.EMOTE_MIRROR_BASE + id + ".png";
img.srcset = "";
} else {
img.src = TWITCH_BASE + id + "/1.0";
img.srcset = build_srcset(id);
img.src = constants.TWITCH_BASE + id + "/1.0";
img.srcset = utils.build_srcset(id);
}
}
@ -317,6 +252,10 @@ FFZ.prototype.setup_tokenization = function() {
if ( ! helpers )
return this.log("Unable to get chat helper functions.");
conv_helpers = window.require && window.require("ember-twitch-conversations/helpers/conversation-line-helpers");
if ( ! conv_helpers )
this.log("Unable to get conversation helper functions.");
this.log("Hooking Ember chat line helpers.");
var f = this;
@ -386,6 +325,8 @@ FFZ.prototype.load_twitch_emote_data = function(tries) {
this._twitch_set_to_channel[33] = "--turbo-faces--";
this._twitch_set_to_channel[42] = "--turbo-faces--";
this._reset_tooltips(true);
}).fail(function(data) {
if ( data.status === 404 )
return;
@ -401,14 +342,61 @@ FFZ.prototype.load_twitch_emote_data = function(tries) {
// 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];
if ( conv_helpers && conv_helpers.checkActionMessage )
tokens = conv_helpers.checkActionMessage(tokens);
// 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) {
if ( msgObject.cachedTokens )
return msgObject.cachedTokens;
var msg = msgObject.message,
var msg = msgObject.message || msgObject.get('body'),
user = this.get_user(),
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,
tokens = [msg];
@ -438,7 +426,7 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
// FrankerFaceZ Extras
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 )
tokens = this.tokenize_emoji(tokens);
@ -446,7 +434,7 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
// Capitalization
var display = msgObject.tags && msgObject.tags['display-name'];
if ( display && display.length )
FFZ.capitalization[msgObject.from] = [display.trim(), Date.now()];
FFZ.capitalization[from_user] = [display.trim(), Date.now()];
// Mentions!
@ -482,7 +470,7 @@ FFZ.prototype.tokenize_chat_line = function(msgObject, prevent_notification, del
else
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' )
msg = '* ' + display + ' ' + msg;
@ -553,39 +541,48 @@ FFZ.prototype.tokenize_line = function(user, room, message, no_emotes, no_emoji)
FFZ.prototype.render_tokens = function(tokens, render_links) {
var f = this;
return _.map(tokens, function(token) {
if ( token.hidden )
return "";
if ( token.emoticonSrc ) {
var tooltip, src = token.emoticonSrc, srcset, extra;
var tooltip, src = token.emoticonSrc, srcset, cls, extra;
if ( token.ffzEmote ) {
var emote_set = f.emote_sets && f.emote_sets[token.ffzEmoteSet],
emote = emote_set && emote_set.emoticons && emote_set.emoticons[token.ffzEmote];
tooltip = emote ? utils.sanitize(f._emote_tooltip(emote)) : token.altText;
tooltip = emote ? f._emote_tooltip(emote) : token.altText;
srcset = emote ? emote.srcSet : token.srcSet;
extra = (emote ? ' data-ffz-emote="' + emote.id + '"' : '') + (emote_set ? ' data-ffz-set="' + emote_set.id + '"' : '');
} else if ( token.ffzEmoji ) {
var eid = token.ffzEmoji,
emoji = f.emoji_data && f.emoji_data[eid],
setting = f.settings.parse_emoji;
setting = f.settings.parse_emoji,
image = '';
if ( setting === 0 || (setting === 1 && ! emoji.tw) || (setting === 2 && ! emoji.noto) )
return token.altText;
tooltip = emoji ? "Emoji: " + token.altText + "\nName: " + emoji.name + (emoji.short_name ? "\nShort Name: :" + emoji.short_name + ":" : "") : token.altText;
extra = ' data-ffz-emoji="' + eid + '" height="18px"';
src = setting === 2 ? token.noto_src : token.tw_src;
if ( emoji && f.settings.emote_image_hover )
image = '<img class="emoticon ffz-image-hover" src="' + src + '">';
tooltip = emoji ? image + "Emoji: " + token.altText + "<br>Name: " + emoji.name + (emoji.short_name ? "<br>Short Name: :" + emoji.short_name + ":" : "") : token.altText;
extra = ' data-ffz-emoji="' + eid + '" height="18px"';
cls = ' emoji';
} else {
var id = token.replacedId || FFZ.src_to_id(token.emoticonSrc),
data = id && f._twitch_emotes && f._twitch_emotes[id];
if ( data )
tooltip = data.tooltip ? data.tooltip : token.altText;
tooltip = data.tooltip ? data.tooltip : utils.build_tooltip.bind(f)(id, false, token.altText);
else {
try {
var set_id = f._twitch_emote_to_set[id];
if ( set_id ) {
tooltip = load_emote_data.bind(f)(id, token.altText, true, {
tooltip = utils.load_emote_data.bind(f)(id, token.altText, true, {
code: token.altText,
id: id,
set: f._twitch_set_to_channel[set_id],
@ -593,22 +590,21 @@ FFZ.prototype.render_tokens = function(tokens, render_links) {
});
} else {
tooltip = f._twitch_emotes[id] = token.altText;
f.ws_send("twitch_emote", id, load_emote_data.bind(f, id, token.altText));
f.ws_send("twitch_emote", id, utils.load_emote_data.bind(f, id, token.altText));
}
} catch(err) {
f.error("Error Generating Emote Tooltip: " + err);
}
}
var mirror_url = utils.quote_attr(constants.EMOTE_MIRROR_BASE + id + '.png');
extra = ' data-emote="' + id + '" onerror="FrankerFaceZ._emote_mirror_swap(this)"';
//var mirror_url = utils.quote_attr(constants.EMOTE_MIRROR_BASE + id + '.png');
extra = ' data-emote="' + id + '"'; // onerror="FrankerFaceZ._emote_mirror_swap(this)"'; // Disable error checking for now.
if ( ! constants.EMOTE_REPLACEMENTS[id] )
srcset = build_srcset(id);
srcset = utils.build_srcset(id);
}
return '<img class="emoticon tooltip' + (cls||"") + '"' + (extra||"") + ' src="' + utils.quote_attr(src) + '" ' + (srcset ? 'srcset="' + utils.quote_attr(srcset) + '" ' : '') + 'alt="' + utils.quote_attr(token.altText) + '" title="' + utils.quote_attr(tooltip) + '">';
return '<img class="emoticon html-tooltip' + (cls||"") + '"' + (extra||"") + ' src="' + utils.quote_attr(src) + '" ' + (srcset ? 'srcset="' + utils.quote_attr(srcset) + '" ' : '') + 'alt="' + utils.quote_attr(token.altText) + '" title="' + utils.quote_attr(tooltip) + '">';
}
if ( token.isLink ) {
@ -753,7 +749,7 @@ FFZ.prototype.tokenize_title_emotes = function(tokens) {
data = data.emoticon_sets[0];
for(var i=0; i < data.length; i++) {
var em = data[i];
emotes.push({regex: em.code, url: TWITCH_BASE + em.id + "/1.0"});
emotes.push({regex: em.code, url: utils.TWITCH_BASE + em.id + "/1.0"});
}
if ( f._cindex )
@ -849,7 +845,7 @@ FFZ.prototype.tokenize_emotes = function(user, room, tokens, do_report) {
bits.push(eo);
if ( do_report && room )
f.add_usage(room, emote.id);
f.add_usage(room, emote);
} else
bits.push(bit);

View file

@ -1,6 +1,6 @@
var FFZ = window.FrankerFaceZ,
constants = require("../constants"),
styles = require("../styles");
constants = require("../constants");
//styles = require("../styles");
// ---------------------
@ -144,6 +144,9 @@ FFZ.settings_info.dark_twitch = {
model && model.set('darkMode', true);
} else
model && model.set('darkMode', this.settings.twitch_chat_dark);
// Try coloring ReChat
jQuery('.rechat-chat-line').parents('.chat-container').toggleClass('dark', val || this.settings.twitch_chat_dark);
}
};
@ -211,7 +214,6 @@ FFZ.prototype._load_dark_css = function() {
s.id = "ffz-dark-css";
s.setAttribute('rel', 'stylesheet');
s.setAttribute('href', constants.SERVER + "script/dark.css?_=" + (constants.DEBUG ? Date.now() : FFZ.version_info));
s.onerror = "this.href = this.href + '_';"
s.setAttribute('href', constants.DIRECT_SERVER + "script/dark.css?_=" + (constants.DEBUG ? Date.now() : FFZ.version_info));
document.head.appendChild(s);
}

View file

@ -52,7 +52,7 @@ FFZ.prototype.setup_following_count = function(has_ember) {
// If we don't have Ember, no point in trying this stuff.
if ( ! has_ember )
return this._update_following_count();
return this._following_get_me();
this.log("Connecting to Live Streams model.");
var Stream = window.App && App.__container__.resolve('model:stream');
@ -80,6 +80,27 @@ FFZ.prototype.setup_following_count = function(has_ember) {
}
FFZ.prototype._following_get_me = function(tries) {
// get_user doesn't properly return an oauth token any longer, so we need to get me manually.
if ( ! window.Twitch )
// Wait around till the API shows up.
return setTimeout(this._following_get_me.bind(this, tries), Math.floor(2000*Math.random()) + 500);
var f = this;
Twitch.api.get("/api/me").done(function(data) {
f.log("Fetched User Data -- " + (data.name || data.login));
f.__user = data;
f._update_following_count();
}).fail(function() {
tries = (tries||0) + 1;
if ( tries < 5 )
return setTimeout(f._following_get_me.bind(f, tries), Math.floor(2000*Math.random()) + 500);
f.log("Failed to get proper user object.");
});
}
FFZ.prototype._schedule_following_count = function() {
if ( ! this.settings.following_count ) {
if ( this._following_count_timer ) {
@ -111,8 +132,13 @@ FFZ.prototype._update_following_count = function() {
if ( Live )
Live.load();
else
Twitch.api && Twitch.api.get("streams/followed", {limit:5, offset:0}, {version:3})
else {
var a = {},
u = this.get_user();
a.Authorization = "OAuth " + u.chat_oauth_token;
Twitch.api && Twitch.api.get("streams/followed", {limit:20, offset:0}, {version:3, headers: a})
.done(function(data) {
f._draw_following_count(data._total);
f._draw_following_channels(data.streams, data._total);
@ -120,6 +146,7 @@ FFZ.prototype._update_following_count = function() {
f._draw_following_count();
f._draw_following_channels();
})
}
}

View file

@ -2,8 +2,6 @@ var FFZ = window.FrankerFaceZ,
constants = require('../constants'),
utils = require('../utils'),
TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/",
fix_menu_position = function(container) {
var swapped = document.body.classList.contains('ffz-sidebar-swap') && ! document.body.classList.contains('ffz-portrait');
@ -63,7 +61,8 @@ FFZ.prototype.setup_menu = function() {
this.log("Hooking the Ember Chat Settings view.");
var Settings = window.App && App.__container__.resolve('view:settings');
var Settings = window.App && App.__container__.resolve('view:settings'),
Layout = App.__container__.lookup('controller:layout');
if ( ! Settings )
return;
@ -155,6 +154,12 @@ FFZ.prototype.setup_menu = function() {
menu.appendChild(header);
menu.appendChild(content);
// Maximum Height
var e = el.querySelector('.chat-settings');
if ( Layout && e )
e.style.maxHeight = (Layout.get('windowHeight') - 90) + 'px';
},
ffzTeardown: function() {
@ -162,6 +167,15 @@ FFZ.prototype.setup_menu = function() {
}
});
// Maximum height~!
if ( Layout )
Layout.addObserver('windowHeight', function() {
var el = document.querySelector('.ember-chat .chat-settings');
if ( el )
el.style.maxHeight = (Layout.get('windowHeight') - 90) + 'px';
});
// For some reason, this doesn't work unless we create an instance of the
// chat settings view and then destroy it immediately.
try {
@ -224,6 +238,9 @@ FFZ.prototype.build_ui_popup = function(view) {
container.classList.toggle('dark', dark);
// Stuff
jQuery(inner).find('.html-tooltip').tipsy({live: true, html: true, gravity: utils.tooltip_placement(2*constants.TOOLTIP_DISTANCE, 's')});
// Menu Container
var sub_container = document.createElement('div');
@ -313,7 +330,7 @@ FFZ.prototype.build_ui_popup = function(view) {
link.title = page.name;
link.innerHTML = page.icon;
jQuery(link).tipsy();
jQuery(link).tipsy({gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
link.addEventListener("click", this._ui_change_page.bind(this, view, inner, menu, sub_container, key));
@ -435,11 +452,11 @@ FFZ.menu_pages.channel = {
var s = document.createElement('span'),
can_use = is_subscribed || !emote.subscriber_only,
img_set = 'image-set(url("' + TWITCH_BASE + emote.id + '/1.0") 1x, url("' + TWITCH_BASE + emote.id + '/2.0") 2x, url("' + TWITCH_BASE + emote.id + '/3.0") 4x)';
img_set = 'image-set(url("' + constants.TWITCH_BASE + emote.id + '/1.0") 1x, url("' + constants.TWITCH_BASE + emote.id + '/2.0") 2x), url("' + constants.TWITCH_BASE + emote.id + '/3.0") 4x)';
s.className = 'emoticon tooltip' + (!can_use ? " locked" : "");
s.className = 'emoticon html-tooltip' + (!can_use ? " locked" : "");
s.style.backgroundImage = 'url("' + TWITCH_BASE + emote.id + '/1.0")';
s.style.backgroundImage = 'url("' + constants.TWITCH_BASE + emote.id + '/1.0")';
s.style.backgroundImage = '-webkit-' + img_set;
s.style.backgroundImage = '-moz-' + img_set;
s.style.backgroundImage = '-ms-' + img_set;
@ -447,7 +464,7 @@ FFZ.menu_pages.channel = {
s.style.width = emote.width + "px";
s.style.height = emote.height + "px";
s.title = emote.regex;
s.title = (this.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + constants.TWITCH_BASE + emote.id + '/3.0?_=preview">' : '') + emote.regex;
s.addEventListener('click', function(can_use, id, code, e) {
if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons )
@ -599,7 +616,7 @@ FFZ.prototype._emotes_for_sets = function(parent, view, sets, header, image, sub
c++;
var s = document.createElement('span');
s.className = 'emoticon tooltip';
s.className = 'emoticon html-tooltip';
s.style.backgroundImage = 'url("' + emote.urls[1] + '")';
if ( srcset ) {

View file

@ -2,7 +2,6 @@ var FFZ = window.FrankerFaceZ,
constants = require("../constants"),
utils = require("../utils"),
TWITCH_BASE = "http://static-cdn.jtvnw.net/emoticons/v1/",
BANNED_SETS = {"00000turbo":true};
@ -166,11 +165,14 @@ FFZ.menu_pages.myemotes = {
if ( (settings === 1 && ! emoji.tw) || (settings === 2 && ! emoji.noto) )
continue;
em.className = 'emoticon tooltip';
em.title = 'Emoji: ' + emoji.raw + '\nName: ' + emoji.name + (emoji.short_name ? '\nShort Name: :' + emoji.short_name + ':' : '');
var src = settings === 2 ? emoji.noto_src : emoji.tw_src,
image = this.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + src + '">' : '';
em.className = 'emoticon emoji html-tooltip';
em.title = image + 'Emoji: ' + emoji.raw + '<br>Name: ' + emoji.name + (emoji.short_name ? '<br>Short Name: :' + emoji.short_name + ':' : '');
em.addEventListener('click', this._add_emote.bind(this, view, emoji.raw));
em.style.backgroundImage = 'url("' + (settings === 2 ? emoji.noto_src : emoji.tw_src) + '")';
em.style.backgroundImage = 'url("' + src + '")';
em.style.backgroundSize = "18px";
menu.appendChild(em);
@ -237,21 +239,21 @@ FFZ.menu_pages.myemotes = {
code = constants.KNOWN_CODES[emote.code] || emote.code,
em = document.createElement('span'),
img_set = 'image-set(url("' + TWITCH_BASE + emote.id + '/1.0") 1x, url("' + TWITCH_BASE + emote.id + '/2.0") 2x, url("' + TWITCH_BASE + emote.id + '/3.0") 4x)';
img_set = 'image-set(url("' + constants.TWITCH_BASE + emote.id + '/1.0") 1x, url("' + constants.TWITCH_BASE + emote.id + '/2.0") 2x, url("' + constants.TWITCH_BASE + emote.id + '/3.0") 4x)';
em.className = 'emoticon tooltip';
em.className = 'emoticon html-tooltip';
if ( this.settings.replace_bad_emotes && constants.EMOTE_REPLACEMENTS[emote.id] ) {
em.style.backgroundImage = 'url("' + constants.EMOTE_REPLACEMENT_BASE + constants.EMOTE_REPLACEMENTS[emote.id] + '")';
} else {
em.style.backgroundImage = 'url("' + TWITCH_BASE + emote.id + '/1.0")';
em.style.backgroundImage = 'url("' + constants.TWITCH_BASE + emote.id + '/1.0")';
em.style.backgroundImage = '-webkit-' + img_set;
em.style.backgroundImage = '-moz-' + img_set;
em.style.backgroundImage = '-ms-' + img_set;
em.style.backgroudnImage = img_set;
}
em.title = code;
em.title = (this.settings.emote_image_hover ? '<img class="emoticon ffz-image-hover" src="' + constants.TWITCH_BASE + emote.id + '/3.0?_=preview">' : '') + code;
em.addEventListener("click", function(id, c, e) {
e.preventDefault();
if ( (e.shiftKey || e.shiftLeft) && f.settings.clickable_emoticons )
@ -316,7 +318,7 @@ FFZ.menu_pages.myemotes = {
img_set += ')';
em.className = 'emoticon tooltip';
em.className = 'emoticon html-tooltip';
em.style.backgroundImage = 'url("' + emote.urls[1] + '")';
em.style.backgroundImage = '-webkit-' + img_set;
em.style.backgroundImage = '-moz-' + img_set;

View file

@ -181,6 +181,11 @@ FFZ.prototype.show_notification = function(message, title, tag, timeout, on_clic
// ---------------------
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({
text: message,
theme: "ffzTheme",

View file

@ -1,18 +1,33 @@
var FFZ = window.FrankerFaceZ,
constants = require('../constants'),
styles = require('../styles');
utils = require('../utils'),
constants = require('../constants');
styles = require('../compiled_styles');
FFZ.prototype.setup_css = function() {
document.body.classList.toggle('ffz-flip-dashboard', this.settings.flip_dashboard);
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);
this.log("Readying toggleable styles.");
this._toggle_style_state = {};
s = this._toggle_style = document.createElement('style');
s.type = "text/css";
s.id = "ffz-toggle-css";
document.head.appendChild(s);
/*var s = this._main_style = document.createElement('style');
s.textContent = styles.style;
s.id = "ffz-ui-css";
s.id = "ffz-main-css";
document.head.appendChild(s);
document.head.appendChild(s);*/
if ( window.jQuery && jQuery.noty )
jQuery.noty.themes.ffzTheme = {
@ -25,4 +40,15 @@ FFZ.prototype.setup_css = function() {
onClose: function() {}
}
};
}
FFZ.prototype.toggle_style = function(key, enabled) {
var state = this._toggle_style_state[key];
if ( (enabled && state) || (!enabled && !state) )
return;
this._toggle_style_state[key] = enabled;
utils.update_css(this._toggle_style, key, enabled ? styles[key] || null : null);
}

View file

@ -84,7 +84,7 @@ FFZ.prototype._update_subscribers = function() {
});
cont.appendChild(stat);
jQuery(stat).tipsy(f.is_dashboard ? {"gravity":"s"} : undefined);
jQuery(stat).tipsy({gravity: f.is_dashboard ? "s" : utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
el.innerHTML = sub_count;

38
src/ui/tooltips.js Normal file
View file

@ -0,0 +1,38 @@
var FFZ = window.FrankerFaceZ,
utils = require('../utils'),
constants = require('../constants');
// ---------------------
// Initialization
// ---------------------
FFZ.prototype.fix_tooltips = function() {
// First, override the tooltip mixin.
var TipsyTooltip = window.App && App.__container__.resolve('component:tipsy-tooltip');
if ( TipsyTooltip ) {
this.log("Modifying Tipsy-Tooltip component to use gravity.");
TipsyTooltip.reopen({
didInsertElement: function() {
var gravity = this.get("gravity");
if ( ! gravity || typeof gravity === "string" )
gravity = utils.tooltip_placement(constants.TOOLTIP_DISTANCE, gravity || 's');
this.$().tipsy({
gravity: gravity
});
}
})
}
// Iterate all existing tipsy stuff~!
this.log('Fixing already existing tooltips.');
if ( ! window.jQuery || ! jQuery.cache )
return;
for(var obj_id in jQuery.cache) {
var obj = jQuery.cache[obj_id];
if ( obj && obj.data && obj.data.tipsy && obj.data.tipsy.options && typeof obj.data.tipsy.options.gravity !== "function" )
obj.data.tipsy.options.gravity = utils.tooltip_placement(constants.TOOLTIP_DISTANCE, obj.data.tipsy.options.gravity || 's');
}
}

View file

@ -66,6 +66,6 @@ FFZ.ws_commands.viewers = function(data) {
view_count.innerHTML = content;
parent.appendChild(view_count);
jQuery(view_count).tipsy(this.is_dashboard ? {"gravity":"s"} : undefined);
jQuery(view_count).tipsy({gravity: this.is_dashboard ? "s" : utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
}
}

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}))?)?)?)?)?$/,
parse_date = function(str) {
if ( typeof str === "number" )
return new Date(str);
var parts = str.match(date_regex);
if ( ! parts )
return null;
@ -179,10 +182,98 @@ var sanitize_el = document.createElement('span'),
out = es[variant] = r.join("-");
return out;
},
// Twitch Emote Tooltips
SRCSETS = {},
build_srcset = function(id) {
if ( SRCSETS[id] )
return SRCSETS[id];
var out = SRCSETS[id] = constants.TWITCH_BASE + id + "/1.0 1x, " + constants.TWITCH_BASE + id + "/2.0 2x, " + constants.TWITCH_BASE + id + "/3.0 4x";
return out;
},
data_to_tooltip = function(data) {
var emote_set = data.set,
set_type = data.set_type,
f = FFZ.get(),
image = '';
if ( data.id && f.settings.emote_image_hover )
image = '<img class="emoticon ffz-image-hover" src="' + constants.TWITCH_BASE + data.id + '/3.0?_=preview">';
if ( set_type === undefined )
set_type = "Channel";
if ( ! emote_set )
return image + data.code;
else if ( emote_set === "--global--" ) {
emote_set = "Twitch Global";
set_type = null;
} else if ( emote_set == "--twitch-turbo--" || emote_set == "turbo" || emote_set == "--turbo-faces--" ) {
emote_set = "Twitch Turbo";
set_type = null;
}
return image + "Emoticon: " + data.code + "<br>" + (set_type ? set_type + ": " : "") + emote_set;
},
build_tooltip = function(id, force_update, code) {
var emote_data = this._twitch_emotes[id];
if ( ! emote_data && code ) {
var set_id = this._twitch_emote_to_set[id];
if ( set_id ) {
emote_data = this._twitch_emotes[id] = {
code: code,
id: id,
set: this._twitch_set_to_channel[set_id],
set_id: set_id
}
}
}
if ( ! emote_data )
return "???";
if ( typeof emote_data == "string" )
return emote_data;
if ( ! force_update && emote_data.tooltip )
return emote_data.tooltip;
return emote_data.tooltip = data_to_tooltip(emote_data);
},
load_emote_data = function(id, code, success, data) {
if ( ! success )
return code;
if ( code )
data.code = code;
this._twitch_emotes[id] = data;
var tooltip = build_tooltip.bind(this)(id);
var images = document.querySelectorAll('img[data-emote="' + id + '"]');
for(var x=0; x < images.length; x++)
images[x].title = tooltip;
return tooltip;
};
module.exports = {
build_srcset: build_srcset,
build_tooltip: build_tooltip,
load_emote_data: load_emote_data,
update_css: function(element, id, css) {
var all = element.innerHTML,
start = "/*BEGIN " + id + "*/",
@ -204,6 +295,25 @@ module.exports = {
},
tooltip_placement: function(margin, prefer) {
return function() {
var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)},
$this = $(this),
half_width = $this.width() / 2,
half_height = $this.height() / 2,
boundTop = $(document).scrollTop() + half_height + (margin*2),
boundLeft = $(document).scrollLeft() + half_width + margin;
if ($this.offset().top < boundTop) dir.ns = 'n';
if ($this.offset().left < boundLeft) dir.ew = 'w';
if ($(window).width() + $(document).scrollLeft() - ($this.offset().left + half_width) < margin) dir.ew = 'e';
if ($(window).height() + $(document).scrollTop() - ($this.offset().top + half_height) < (2*margin)) dir.ns = 's';
return dir.ns + (dir.ew ? dir.ew : '');
}
},
splitIRCMessage: splitIRCMessage,
parseIRCTags: parseIRCTags,

File diff suppressed because it is too large Load diff