1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-19 12:30:54 +00:00

Basic settings. Pull in FileSaver as a requirement. Menus can have sub-pages now, so that's cool.

This commit is contained in:
SirStendec 2015-08-04 01:43:08 -04:00
parent 9ece18ec0f
commit 771e290197
15 changed files with 2590 additions and 694 deletions

256
src/FileSaver.js Normal file
View file

@ -0,0 +1,256 @@
/* FileSaver.js
* A saveAs() FileSaver implementation.
* 1.1.20150716
*
* By Eli Grey, http://eligrey.com
* License: X11/MIT
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
*/
/*global self */
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var saveAs = saveAs || (function(view) {
"use strict";
// IE <10 is explicitly unsupported
if (typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
return;
}
var
doc = view.document
// only get URL when necessary in case Blob.js hasn't overridden it yet
, get_URL = function() {
return view.URL || view.webkitURL || view;
}
, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
, can_use_save_link = "download" in save_link
, click = function(node) {
var event = new MouseEvent("click");
node.dispatchEvent(event);
}
, webkit_req_fs = view.webkitRequestFileSystem
, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
, throw_outside = function(ex) {
(view.setImmediate || view.setTimeout)(function() {
throw ex;
}, 0);
}
, force_saveable_type = "application/octet-stream"
, fs_min_size = 0
// See https://code.google.com/p/chromium/issues/detail?id=375297#c7 and
// https://github.com/eligrey/FileSaver.js/commit/485930a#commitcomment-8768047
// for the reasoning behind the timeout and revocation flow
, arbitrary_revoke_timeout = 500 // in ms
, revoke = function(file) {
var revoker = function() {
if (typeof file === "string") { // file is an object URL
get_URL().revokeObjectURL(file);
} else { // file is a File
file.remove();
}
};
if (view.chrome) {
revoker();
} else {
setTimeout(revoker, arbitrary_revoke_timeout);
}
}
, dispatch = function(filesaver, event_types, event) {
event_types = [].concat(event_types);
var i = event_types.length;
while (i--) {
var listener = filesaver["on" + event_types[i]];
if (typeof listener === "function") {
try {
listener.call(filesaver, event || filesaver);
} catch (ex) {
throw_outside(ex);
}
}
}
}
, auto_bom = function(blob) {
// prepend BOM for UTF-8 XML and text/* types (including HTML)
if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
return new Blob(["\ufeff", blob], {type: blob.type});
}
return blob;
}
, FileSaver = function(blob, name, no_auto_bom) {
if (!no_auto_bom) {
blob = auto_bom(blob);
}
// First try a.download, then web filesystem, then object URLs
var
filesaver = this
, type = blob.type
, blob_changed = false
, object_url
, target_view
, dispatch_all = function() {
dispatch(filesaver, "writestart progress write writeend".split(" "));
}
// on any filesys errors revert to saving with object URLs
, fs_error = function() {
// don't create more object URLs than needed
if (blob_changed || !object_url) {
object_url = get_URL().createObjectURL(blob);
}
if (target_view) {
target_view.location.href = object_url;
} else {
var new_tab = view.open(object_url, "_blank");
if (new_tab == undefined && typeof safari !== "undefined") {
//Apple do not allow window.open, see http://bit.ly/1kZffRI
view.location.href = object_url
}
}
filesaver.readyState = filesaver.DONE;
dispatch_all();
revoke(object_url);
}
, abortable = function(func) {
return function() {
if (filesaver.readyState !== filesaver.DONE) {
return func.apply(this, arguments);
}
};
}
, create_if_not_found = {create: true, exclusive: false}
, slice
;
filesaver.readyState = filesaver.INIT;
if (!name) {
name = "download";
}
if (can_use_save_link) {
object_url = get_URL().createObjectURL(blob);
save_link.href = object_url;
save_link.download = name;
setTimeout(function() {
click(save_link);
dispatch_all();
revoke(object_url);
filesaver.readyState = filesaver.DONE;
});
return;
}
// Object and web filesystem URLs have a problem saving in Google Chrome when
// viewed in a tab, so I force save with application/octet-stream
// http://code.google.com/p/chromium/issues/detail?id=91158
// Update: Google errantly closed 91158, I submitted it again:
// https://code.google.com/p/chromium/issues/detail?id=389642
if (view.chrome && type && type !== force_saveable_type) {
slice = blob.slice || blob.webkitSlice;
blob = slice.call(blob, 0, blob.size, force_saveable_type);
blob_changed = true;
}
// Since I can't be sure that the guessed media type will trigger a download
// in WebKit, I append .download to the filename.
// https://bugs.webkit.org/show_bug.cgi?id=65440
if (webkit_req_fs && name !== "download") {
name += ".download";
}
if (type === force_saveable_type || webkit_req_fs) {
target_view = view;
}
if (!req_fs) {
fs_error();
return;
}
fs_min_size += blob.size;
req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
var save = function() {
dir.getFile(name, create_if_not_found, abortable(function(file) {
file.createWriter(abortable(function(writer) {
writer.onwriteend = function(event) {
target_view.location.href = file.toURL();
filesaver.readyState = filesaver.DONE;
dispatch(filesaver, "writeend", event);
revoke(file);
};
writer.onerror = function() {
var error = writer.error;
if (error.code !== error.ABORT_ERR) {
fs_error();
}
};
"writestart progress write abort".split(" ").forEach(function(event) {
writer["on" + event] = filesaver["on" + event];
});
writer.write(blob);
filesaver.abort = function() {
writer.abort();
filesaver.readyState = filesaver.DONE;
};
filesaver.readyState = filesaver.WRITING;
}), fs_error);
}), fs_error);
};
dir.getFile(name, {create: false}, abortable(function(file) {
// delete file if it already exists
file.remove();
save();
}), abortable(function(ex) {
if (ex.code === ex.NOT_FOUND_ERR) {
save();
} else {
fs_error();
}
}));
}), fs_error);
}), fs_error);
}
, FS_proto = FileSaver.prototype
, saveAs = function(blob, name, no_auto_bom) {
return new FileSaver(blob, name, no_auto_bom);
}
;
// IE 10+ (native saveAs)
if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
return function(blob, name, no_auto_bom) {
if (!no_auto_bom) {
blob = auto_bom(blob);
}
return navigator.msSaveOrOpenBlob(blob, name || "download");
};
}
FS_proto.abort = function() {
var filesaver = this;
filesaver.readyState = filesaver.DONE;
dispatch(filesaver, "abort");
};
FS_proto.readyState = FS_proto.INIT = 0;
FS_proto.WRITING = 1;
FS_proto.DONE = 2;
FS_proto.error =
FS_proto.onwritestart =
FS_proto.onprogress =
FS_proto.onwrite =
FS_proto.onabort =
FS_proto.onerror =
FS_proto.onwriteend =
null;
return saveAs;
}(
typeof self !== "undefined" && self
|| typeof window !== "undefined" && window
|| this.content
));
// `self` is undefined in Firefox for Android content script context
// while `this` is nsIContentFrameMessageManager
// with an attribute `content` that corresponds to the window
if (typeof module !== "undefined" && module.exports) {
module.exports.saveAs = saveAs;
} else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
define([], function() {
return saveAs;
});
}

View file

@ -50,7 +50,7 @@ FFZ.prototype.setup_channel = function() {
view.ffzInit();
};
this.log("Hooking the Ember Channel model.");
Channel = App.__container__.resolve('model:channel');
if ( ! Channel )
@ -58,7 +58,7 @@ FFZ.prototype.setup_channel = function() {
Channel.reopen({
ffz_host_target: undefined,
setHostMode: function(e) {
if ( f.settings.hosted_channels ) {
this.set('ffz_host_target', e.target);
@ -87,13 +87,13 @@ FFZ.prototype.setup_channel = function() {
ffzUpdateInfo: function() {
if ( this._ffz_update_timer )
clearTimeout(this._ffz_update_timer);
if ( ! this.get('content.id') )
return;
this._ffz_update_timer = setTimeout(this.ffzCheckUpdate.bind(this), 60000);
}.observes("content.id"),
ffzCheckUpdate: function() {
var t = this,
id = t.get('content.id');
@ -115,11 +115,11 @@ FFZ.prototype.setup_channel = function() {
t.set('game', game);
t.set('rollbackData.game', game);
}
if ( data.stream.channel ) {
if ( data.stream.channel.status )
t.set('status', data.stream.channel.status);
if ( data.stream.channel.views )
t.set('views', data.stream.channel.views);
@ -176,7 +176,7 @@ FFZ.prototype.setup_channel = function() {
}.observes("content.hostModeTarget")
});
Channel.ffzUpdateInfo();
}
@ -265,8 +265,8 @@ FFZ.prototype._modify_cindex = function(view) {
user = f.get_user(),
room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room,
now_hosting = room && room.ffz_host_target,
hosts_left = room && room.ffz_hosts_left,
hosts_left = room && room.ffz_hosts_left,
el = this.get('element');
this.set('ffz_host_updating', false);
@ -283,13 +283,13 @@ FFZ.prototype._modify_cindex = function(view) {
btn = document.createElement('span');
btn.id = 'ffz-ui-host-button';
btn.className = 'button action tooltip';
btn.addEventListener('click', this.ffzClickHost.bind(btn, this, false));
var before;
try { before = container.querySelector(':scope > .theatre-button'); }
catch(err) { before = undefined; }
if ( before )
container.insertBefore(btn, before);
else
@ -307,8 +307,8 @@ FFZ.prototype._modify_cindex = function(view) {
btn.title += ' You have ' + hosts_left + ' host command' + utils.pluralize(hosts_left) + ' remaining this half hour.';
}
}
if ( hosted_id ) {
var container = el && el.querySelector('#hostmode .channel-actions'),
btn = container && container.querySelector('#ffz-ui-host-button');
@ -321,9 +321,9 @@ FFZ.prototype._modify_cindex = function(view) {
btn = document.createElement('span');
btn.id = 'ffz-ui-host-button';
btn.className = 'button action tooltip';
btn.addEventListener('click', this.ffzClickHost.bind(btn, this, true));
var before;
try { before = container.querySelector(':scope > .theatre-button'); }
catch(err) { before = undefined; }
@ -344,9 +344,9 @@ FFZ.prototype._modify_cindex = function(view) {
if ( typeof hosts_left === "number" )
btn.title += ' You have ' + hosts_left + ' host command' + utils.pluralize(hosts_left) + ' remaining this half hour.';
}
}
}
},
ffzClickHost: function(controller, is_host) {
var target = controller.get(is_host ? 'controller.hostModeTarget.id' : 'controller.id'),
user = f.get_user(),
@ -454,7 +454,7 @@ FFZ.prototype._modify_cindex = function(view) {
var container = el && el.querySelector('.stats-and-actions .channel-stats'),
stat_el = container && container.querySelector('#ffz-ui-player-stats'),
el = stat_el && stat_el.querySelector('span'),
player_cont = f.players && f.players[channel_id],
player = player_cont && player_cont.player,
stats = player && player.stats;
@ -468,29 +468,29 @@ FFZ.prototype._modify_cindex = function(view) {
stat_el = document.createElement('span');
stat_el.id = 'ffz-ui-player-stats';
stat_el.className = 'ffz stat tooltip';
stat_el.innerHTML = constants.GRAPH + " ";
el = document.createElement('span');
stat_el.appendChild(el);
var other = container.querySelector('#ffz-uptime-display');
if ( other )
container.insertBefore(stat_el, other.nextSibling);
else
container.appendChild(stat_el);
}
stat_el.title = 'Stream Latency\nFPS: ' + stats.fps + '\nPlayback Rate: ' + stats.playbackRate + ' Kbps';
el.textContent = stats.hlsLatencyBroadcaster + 's';
}
}
if ( hosted_id ) {
var container = el && el.querySelector('#hostmode .channel-stats'),
stat_el = container && container.querySelector('#ffz-ui-player-stats'),
el = stat_el && stat_el.querySelector('span'),
player_cont = f.players && f.players[hosted_id],
player = player_cont && player_cont.player,
stats = player && player.stats;
@ -504,22 +504,22 @@ FFZ.prototype._modify_cindex = function(view) {
stat_el = document.createElement('span');
stat_el.id = 'ffz-ui-player-stats';
stat_el.className = 'ffz stat tooltip';
stat_el.innerHTML = constants.GRAPH + " ";
el = document.createElement('span');
stat_el.appendChild(el);
var other = container.querySelector('#ffz-uptime-display');
if ( other )
container.insertBefore(stat_el, other.nextSibling);
else
container.appendChild(stat_el);
}
stat_el.title = 'Stream Latency\nFPS: ' + stats.fps + '\nPlayback Rate: ' + stats.playbackRate + ' Kbps';
el.textContent = stats.hlsLatencyBroadcaster + 's';
}
}
}
},
@ -667,10 +667,10 @@ FFZ.settings_info.hosted_channels = {
var cb = document.querySelector('input.ffz-setting-hosted-channels');
if ( cb )
cb.checked = val;
if ( ! this._cindex )
return;
var chan = this._cindex.get('controller.model'),
room = chan && this.rooms && this.rooms[chan.get('id')],
target = room && room.room && room.room.get('ffz_host_target');

View file

@ -12,7 +12,7 @@ FFZ.settings_info.minimal_chat = {
value: false,
category: "Chat Appearance",
name: "Minimalistic Chat",
help: "Hide all of the chat user interface, only showing messages and an input box.",
@ -25,22 +25,22 @@ FFZ.settings_info.minimal_chat = {
f._roomv && f._roomv.get('stuckToBottom') && f._roomv._scrollToBottom();
},0);
}
if ( this._chatv && this._chatv.get('controller.showList') )
this._chatv.set('controller.showList', false);
// Remove the style if we have it.
if ( ! val && this._chat_style ) {
if ( this._inputv ) {
if ( this._inputv._ffz_minimal_style )
this._inputv._ffz_minimal_style.innerHTML = '';
this._inputv._ffz_last_height = undefined;
}
utils.update_css(this._chat_style, "input_height", '');
this._roomv && this._roomv.get('stuckToBottom') && this._roomv._scrollToBottom();
} else if ( this._inputv )
this._inputv.ffzResizeInput();
}
@ -71,7 +71,7 @@ FFZ.settings_info.remove_deleted = {
total = msgs.get('length'),
i = total,
alternate;
while(i--) {
var msg = msgs.get(i);
@ -81,7 +81,7 @@ FFZ.settings_info.remove_deleted = {
msgs.removeAt(i);
continue;
}
if ( alternate === undefined )
alternate = msg.ffz_alternate;
else {
@ -186,11 +186,11 @@ FFZ.prototype.setup_chatview = function() {
ffzUpdateChannels: function() {
if ( ! f._chatv )
return;
f._chatv.ffzRebuildMenu();
if ( f.settings.group_tabs )
f._chatv.ffzRebuildTabs();
}.observes("currentChannelRoom", "connectedPrivateGroupRooms"),
removeCurrentChannelRoom: function() {
@ -204,7 +204,7 @@ FFZ.prototype.setup_chatview = function() {
if ( ! f.settings.pinned_rooms || f.settings.pinned_rooms.indexOf(room_id) === -1 ) {
if ( room === this.get("currentRoom") )
this.blurRoom();
// Don't destroy it if it's the user's room.
if ( room && user && user.login === room_id )
room.destroy();
@ -292,7 +292,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});
this.$('.chat-messages').find('.html-tooltip').tipsy({live: true, html: true, gravity: jQuery.fn.tipsy.autoNS});
if ( !f.has_bttv && f.settings.group_tabs )
this.ffzEnableTabs();
@ -330,7 +330,7 @@ FFZ.prototype._modify_cview = function(view) {
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');
@ -361,23 +361,23 @@ FFZ.prototype._modify_cview = function(view) {
}),
// Better Menu
ffzRebuildMenu: function() {
return;
var el = this.get('element'),
room_list = el && el.querySelector('.chat-rooms .tse-content');
if ( ! room_list )
return;
if ( ! room_list.classList.contains('ffz-room-list') ) {
room_list.classList.add('ffz-room-list');
// Find the Pending Invitations
var headers = room_list.querySelectorAll('.list-header'),
hdr = headers.length ? headers[headers.length-1] : undefined;
if ( hdr ) {
hdr.classList.add('ffz');
if ( hdr.nextSibling && hdr.nextSibling.classList )
@ -389,7 +389,7 @@ FFZ.prototype._modify_cview = function(view) {
// Channel Table
var t = this,
chan_table = this._ffz_chan_table || room_list.querySelector('#ffz-channel-table tbody');
if ( ! chan_table ) {
var tbl = document.createElement('table');
tbl.setAttribute('cellspacing', 0);
@ -409,13 +409,13 @@ FFZ.prototype._modify_cview = function(view) {
row = this.ffzBuildRow(this, room, true);
row && chan_table.appendChild(row);
}
// Host Target
if ( this._ffz_host_room ) {
row = this.ffzBuildRow(this, this._ffz_host_room, false, true);
row && chan_table.appendChild(row);
}
// Pinned Rooms
for(var i=0; i < f.settings.pinned_rooms.length; i++) {
var room_id = f.settings.pinned_rooms[i];
@ -424,8 +424,8 @@ FFZ.prototype._modify_cview = function(view) {
row && chan_table.appendChild(row);
}
}
// Group Chat Table
var group_table = this._ffz_group_table || room_list.querySelector('#ffz-group-table tbody');
if ( ! group_table ) {
@ -434,49 +434,49 @@ FFZ.prototype._modify_cview = function(view) {
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>';
var before = room_list.querySelector('#ffz-channel-table');
room_list.insertBefore(tbl, before.nextSibling);
group_table = this._ffz_group_table = tbl.querySelector('tbody');
}
group_table.innerHTML = '';
_.each(this.get('controller.connectedPrivateGroupRooms'), function(room) {
var row = t.ffzBuildRow(t, room);
row && group_table && group_table.appendChild(row);
});
// Change Create Tooltip
var create_btn = el.querySelector('.button.create');
if ( create_btn )
create_btn.title = 'Create a Group Room';
},
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')),
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);
}));
name_el.className = 'ffz-room';
name_el.innerHTML = utils.sanitize(name);
if ( current_channel ) {
icon.innerHTML = constants.CAMERA;
icon.title = name_el.title = "Current Channel";
@ -486,12 +486,12 @@ FFZ.prototype._modify_cview = function(view) {
icon.title = name_el.title = "Hosted Channel";
icon.className = name_el.className = 'tooltip';
}
toggle_pinned.className = toggle_visible.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'));
row.className = 'ffz-room-row';
@ -499,20 +499,20 @@ FFZ.prototype._modify_cview = function(view) {
row.classList.toggle('host-channel', host_channel);
row.classList.toggle('group-chat', group);
row.classList.toggle('active', current);
row.appendChild(icon);
row.appendChild(name_el);
if ( ! group ) {
row.appendChild(toggle_pinned);
btn = toggle_pinned.querySelector('a.switch');
btn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation && e.stopPropagation();
var room_id = room.get('id'),
is_pinned = f.settings.pinned_rooms.indexOf(room_id) !== -1;
if ( is_pinned )
f._leave_room(room_id);
else
@ -527,14 +527,14 @@ FFZ.prototype._modify_cview = function(view) {
btn.title = 'Leave Group';
name_el.appendChild(btn);
btn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation && e.stopPropagation();
if ( ! confirm('Are you sure you want to leave the group room "' + name + '"?') )
return;
room.get('isGroupRoom') && room.del();
});
}
@ -544,12 +544,12 @@ FFZ.prototype._modify_cview = function(view) {
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 )
if ( is_visible )
visible_rooms.removeObject(room_id);
else
visible_rooms.push(room_id);
@ -558,13 +558,13 @@ FFZ.prototype._modify_cview = function(view) {
this.classList.toggle('active', !is_visible);
view.ffzRebuildTabs();
});
row.addEventListener('click', function() {
var controller = view.get('controller');
controller.focusRoom(room);
controller.set('showList', false);
});
return row;
},
@ -684,7 +684,7 @@ FFZ.prototype._modify_cview = function(view) {
ffzTabUnread: function(room_id) {
// TODO: Update menu.
if ( f.has_bttv || ! f.settings.group_tabs )
return;
@ -803,7 +803,7 @@ FFZ.prototype.connect_extra_chat = function() {
r = Room && Room.findOne(user.login);
}
}
if ( this.has_bttv )
return;

View file

@ -55,7 +55,8 @@ FFZ.settings_info.replace_bad_emotes = {
name: "Fix Low Quality Twitch Global Emoticons",
help: "Replace emoticons such as DansGame and RedCoat with cleaned up versions that don't have pixels around the edges or white backgrounds for nicer display on dark chat."
}
};
FFZ.settings_info.parse_emoji = {
type: "boolean",
@ -211,7 +212,6 @@ FFZ.settings_info.clickable_emoticons = {
};
FFZ.settings_info.link_info = {
type: "boolean",
value: true,
@ -333,7 +333,7 @@ FFZ.settings_info.high_contrast_chat = {
'112': "Background + Bold",
'111': 'All'
},
value: '000',
value: '222',
category: "Chat Appearance",
no_bttv: true,
@ -343,7 +343,7 @@ FFZ.settings_info.high_contrast_chat = {
process_value: function(val) {
if ( val === false )
return '000';
return '222';
else if ( val === true )
return '111';
return val;

View file

@ -50,6 +50,43 @@ try {
// Settings
// ----------------
FFZ.basic_settings.enhanced_moderation_cards = {
type: "boolean",
no_bttv: true,
category: "Chat",
name: "Enhanced Moderation Cards",
help: "Improve moderation cards with hotkeys, additional buttons, chat history, and other information to make moderating easier.",
get: function() {
return this.settings.mod_card_hotkeys &&
this.settings.mod_card_info &&
this.settings.mod_card_history;
},
set: function(val) {
this.settings.set('mod_card_hotkeys', val);
this.settings.set('mod_card_info', val);
this.settings.set('mod_card_history', val);
}
};
FFZ.basic_settings.chat_hover_pause = {
type: "boolean",
no_bttv: true,
category: "Chat",
name: "Pause Chat Scrolling on Mouse Hover",
help: "Automatically prevent the chat from scrolling when moving the mouse over it to prevent moderation mistakes and link misclicks.",
get: 'chat_hover_pause',
set: 'chat_hover_pause'
};
FFZ.settings_info.chat_hover_pause = {
type: "boolean",
value: false,
@ -58,7 +95,7 @@ FFZ.settings_info.chat_hover_pause = {
category: "Chat Moderation",
name: "Pause Chat Scrolling on Mouse Hover",
help: "Automatically prevent the chat from scrolling when moving the mouse over it to prevent moderation mistakes and link mis-clicks.",
help: "Automatically prevent the chat from scrolling when moving the mouse over it to prevent moderation mistakes and link misclicks.",
on_update: function(val) {
if ( ! this._roomv )
@ -151,7 +188,7 @@ FFZ.settings_info.mod_card_buttons = {
else
old_val += ' ' + cmd;
}
var new_val = prompt("Moderation Card Additional Buttons\n\nPlease enter a list of additional commands to display buttons for on moderation cards. 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}\"", old_val);
if ( new_val === null || new_val === undefined )
@ -159,7 +196,7 @@ FFZ.settings_info.mod_card_buttons = {
var vals = [];
new_val = new_val.trim();
while(new_val) {
if ( new_val.charAt(0) === '"' ) {
var end = new_val.indexOf('"', 1);
@ -170,8 +207,8 @@ FFZ.settings_info.mod_card_buttons = {
if ( segment )
vals.push(segment);
new_val = new_val.substr(end + 1);
new_val = new_val.substr(end + 1);
} else {
var ind = new_val.indexOf(' ');
if ( ind === -1 ) {
@ -274,7 +311,7 @@ FFZ.prototype.setup_mod_card = function() {
if ( typeof followers === "number" ) {
out += '<span class="stat tooltip" title="Followers">' + constants.HEART + ' ' + utils.number_commas(followers || 0) + '</span>';
} else if ( followers === undefined ) {
var t = this;
this.set('cardInfo.user.ffz_followers', false);
@ -299,7 +336,7 @@ FFZ.prototype.setup_mod_card = function() {
userName: Ember.computed("cardInfo.user.id", "cardInfo.user.display_name", function() {
var user_id = this.get("cardInfo.user.id"),
alias = f.aliases[user_id];
return alias || this.get("cardInfo.user.display_name") || user_id.capitalize();
}),
@ -313,7 +350,7 @@ FFZ.prototype.setup_mod_card = function() {
var el = this.get('element'),
controller = this.get('controller'),
line,
user_id = controller.get('cardInfo.user.id'),
alias = f.aliases[user_id];
@ -340,7 +377,7 @@ FFZ.prototype.setup_mod_card = function() {
after = el.querySelector('h3.name');
if ( after ) {
el.classList.add('ffz-has-info');
info.className = 'info channel-stats';
info.className = 'info channel-stats';
after.parentElement.insertBefore(info, after.nextSibling);
this.ffzRebuildInfo();
}
@ -350,16 +387,16 @@ FFZ.prototype.setup_mod_card = function() {
if ( f.settings.mod_card_buttons && f.settings.mod_card_buttons.length ) {
line = document.createElement('div');
line.className = 'extra-interface interface clearfix';
var cmds = {},
add_btn_click = function(cmd) {
var user_id = controller.get('cardInfo.user.id'),
cont = App.__container__.lookup('controller:chat'),
room = cont && cont.get('currentRoom');
room && room.send(cmd.replace(/{user}/g, user_id));
},
add_btn_make = function(cmd) {
var btn = document.createElement('button'),
segment = cmd.split(' ', 1)[0],
@ -373,12 +410,12 @@ FFZ.prototype.setup_mod_card = function() {
btn.className = 'button';
btn.innerHTML = utils.sanitize(title);
btn.title = utils.sanitize(cmd.replace(/{user}/g, controller.get('cardInfo.user.id') || '{user}'));
jQuery(btn).tipsy();
btn.addEventListener('click', add_btn_click.bind(this, cmd));
return btn;
};
var cmds = {};
for(var i=0; i < f.settings.mod_card_buttons.length; i++)
cmds[f.settings.mod_card_buttons[i].split(' ',1)[0]] = (cmds[f.settings.mod_card_buttons[i].split(' ',1)[0]] || 0) + 1;
@ -392,7 +429,7 @@ FFZ.prototype.setup_mod_card = function() {
line.appendChild(add_btn_make(cmd))
}
el.appendChild(line);
}
@ -431,7 +468,7 @@ FFZ.prototype.setup_mod_card = function() {
// Only do the big stuff if we're mod.
if ( controller.get('cardInfo.isModeratorOrHigher') ) {
el.classList.add('ffz-is-mod');
// Key Handling
if ( f.settings.mod_card_hotkeys ) {
el.classList.add('no-mousetrap');
@ -544,31 +581,31 @@ FFZ.prototype.setup_mod_card = function() {
msg_btn.title = "Whisper User";
jQuery(msg_btn).tipsy();
var real_msg = document.createElement('button');
real_msg.className = 'message-button button glyph-only message tooltip';
real_msg.innerHTML = MESSAGE;
real_msg.title = "Message User";
real_msg.addEventListener('click', function() {
window.open('http://www.twitch.tv/message/compose?to=' + controller.get('cardInfo.user.id'));
})
msg_btn.parentElement.insertBefore(real_msg, msg_btn.nextSibling);
}
// Alias Button
var alias_btn = document.createElement('button');
alias_btn.className = 'alias button glyph-only tooltip';
alias_btn.innerHTML = constants.EDIT;
alias_btn.title = "Set Alias";
alias_btn.addEventListener('click', function() {
var user = controller.get('cardInfo.user.id'),
alias = f.aliases[user];
var new_val = prompt("Alias for User: " + user + "\n\nPlease enter an alias for the user. Leave it blank to remove the alias.", alias);
if ( new_val === null || new_val === undefined )
return;
@ -579,20 +616,20 @@ FFZ.prototype.setup_mod_card = function() {
f.aliases[user] = new_val;
f.save_aliases();
// Update UI
f._update_alias(user);
Ember.propertyDidChange(controller, 'userName');
var name = el.querySelector('h3.name'),
link = name && name.querySelector('a');
if ( link )
name = link;
if ( name )
name.classList.toggle('ffz-alias', new_val);
name.classList.toggle('ffz-alias', new_val);
});
if ( msg_btn )
msg_btn.parentElement.insertBefore(alias_btn, msg_btn);
else {
@ -676,22 +713,21 @@ FFZ.prototype.setup_mod_card = function() {
FFZ.prototype._update_alias = function(user) {
var alias = this.aliases && this.aliases[user],
display_name = alias,
cap_name = FFZ.get_capitalization(user),
display_name = alias || cap_name,
el = this._roomv && this._roomv.get('element'),
lines = el && el.querySelectorAll('.chat-line[data-sender="' + user + '"]');
if ( ! lines )
return;
if ( ! display_name )
display_name = FFZ.get_capitalization(user);
for(var i=0, l = lines.length; i < l; i++) {
var line = lines[i],
el_from = line.querySelector('.from');
el_from.classList.toggle('ffz-alias', alias);
el_from.textContent = display_name;
el_from.title = alias ? cap_name : '';
}
}

View file

@ -33,12 +33,12 @@ FFZ.prototype.setup_bttv = function(delay) {
this._dark_style.parentElement.removeChild(this._dark_style);
this._dark_style = undefined;
}
if ( this._layout_style ) {
this._layout_style.parentElement.removeChild(this._layout_style);
this._layout_style = undefined;
}
if ( this._chat_style ) {
utils.update_css(this._chat_style, 'chat_font_size', '');
utils.update_css(this._chat_style, 'chat_ts_font_size', '');
@ -76,6 +76,7 @@ FFZ.prototype.setup_bttv = function(delay) {
if ( this.settings.following_count ) {
this._schedule_following_count();
this._draw_following_count();
this._draw_following_channels();
}
// Remove Sub Count
@ -186,7 +187,7 @@ FFZ.prototype.setup_bttv = function(delay) {
var original_emoticonize = BetterTTV.chat.templates.emoticonize;
BetterTTV.chat.templates.emoticonize = function(message, emotes) {
var tokens = original_emoticonize(message, emotes),
room = (received_room || BetterTTV.getChannel()),
l_room = room && room.toLowerCase(),
l_sender = received_sender && received_sender.toLowerCase(),

View file

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

View file

@ -1,5 +1,6 @@
var FFZ = window.FrankerFaceZ,
constants = require("./constants");
constants = require("./constants"),
FileSaver = require("./FileSaver");
make_ls = function(key) {
@ -11,9 +12,28 @@ var FFZ = window.FrankerFaceZ,
this.settings.set(key, val);
swit.classList.toggle('active', val);
},
option_setting = function(select, key) {
this.settings.set(key, JSON.parse(select.options[select.selectedIndex].value));
},
toggle_basic_setting = function(swit, key) {
var getter = FFZ.basic_settings[key].get,
val = !(typeof getter === 'function' ? getter.bind(this)() : this.settings.get(getter)),
setter = FFZ.basic_settings[key].set;
if ( typeof setter === 'function' )
setter.bind(this)(val);
else
this.settings.set(setter, val);
swit.classList.toggle('active', val);
},
option_basic_setting = function(select, key) {
FFZ.basic_settings[key].set.bind(this)(JSON.parse(select.options[select.selectedIndex].value));
};
@ -21,7 +41,11 @@ var FFZ = window.FrankerFaceZ,
// Initializer
// --------------------
FFZ.settings_info = {};
FFZ.settings_info = {
advanced_settings: { value: false, visible: false }
};
FFZ.basic_settings = {};
FFZ.prototype.load_settings = function() {
this.log("Loading settings.");
@ -61,200 +85,610 @@ FFZ.prototype.load_settings = function() {
}
// --------------------
// Backup and Restore
// --------------------
FFZ.prototype.save_settings_file = function() {
var data = {
version: 1,
script_version: FFZ.version_info + '',
aliases: this.aliases,
settings: {}
};
for(var key in FFZ.settings_info) {
if ( ! FFZ.settings_info.hasOwnProperty(key) )
continue;
var info = FFZ.settings_info[key],
ls_key = info.storage_key || make_ls(key);
if ( localStorage.hasOwnProperty(ls_key) )
data.settings[key] = this.settings[key];
}
var blob = new Blob([JSON.stringify(data, null, 4)], {type: "application/json;charset=utf-8"});
FileSaver.saveAs(blob, "ffz-settings.json");
}
FFZ.prototype.load_settings_file = function(file) {
if ( typeof file === "string" )
this._load_settings_file(file);
else {
var reader = new FileReader(),
f = this;
reader.onload = function(e) { f._load_settings_file(e.target.result); }
reader.readAsText(file);
}
}
FFZ.prototype._load_settings_file = function(data) {
try {
data = JSON.parse(data);
} catch(err) {
this.error("Error Loading Settings: " + err);
return alert("There was an error attempting to read the provided settings data.");
}
this.log("Loading Settings Data", data);
var skipped = [],
applied = [];
if ( data.settings ) {
for(var key in data.settings) {
if ( ! FFZ.settings_info.hasOwnProperty(key) ) {
skipped.push(key);
continue;
}
var info = FFZ.settings_info[key],
val = data.settings[key];
if ( info.process_value )
val = info.process_value.bind(this)(val);
if ( val !== this.settings.get(key) )
this.settings.set(key, val);
applied.push(key);
}
}
// Do this in a timeout so that any styles have a moment to update.
setTimeout(function(){
alert('Successfully loaded ' + applied.length + ' settings and skipped ' + skipped.length + ' settings.');
});
}
// --------------------
// Menu Page
// --------------------
FFZ.menu_pages.settings = {
render: function(view, container) {
var settings = {},
categories = [],
is_android = navigator.userAgent.indexOf('Android') !== -1;
// Bottom Bar
var menu = document.createElement('ul'),
page = document.createElement('div'),
for(var key in FFZ.settings_info) {
if ( ! FFZ.settings_info.hasOwnProperty(key) )
tab_basic = document.createElement('li'),
link_basic = document.createElement('a'),
tab_adv = document.createElement('li'),
link_adv = document.createElement('a'),
tab_save = document.createElement('li'),
link_save = document.createElement('a'),
height = parseInt(container.style.maxHeight || '0');
// Height Calculation
if ( ! height )
height = Math.max(200, view.$().height() - 172);
if ( height && height !== NaN ) {
height -= 37;
page.style.maxHeight = height + 'px';
}
// Menu Building
page.className = 'ffz-ui-sub-menu-page';
menu.className = 'menu sub-menu clearfix';
tab_basic.className = 'item';
tab_basic.id = 'ffz-settings-page-basic';
link_basic.innerHTML = 'Basic';
tab_basic.appendChild(link_basic);
tab_adv.className = 'item';
tab_adv.id = 'ffz-settings-page-advanced';
link_adv.innerHTML = 'Advanced';
tab_adv.appendChild(link_adv);
tab_save.className = 'item';
tab_save.id = 'ffz-settings-page-save';
link_save.textContent = 'Backup & Restore';
tab_save.appendChild(link_save);
menu.appendChild(tab_basic);
menu.appendChild(tab_adv);
menu.appendChild(tab_save);
var cp = FFZ.menu_pages.settings.change_page;
link_basic.addEventListener('click', cp.bind(this, view, container, menu, page, 'basic'));
link_adv.addEventListener('click', cp.bind(this, view, container, menu, page, 'advanced'));
link_save.addEventListener('click', cp.bind(this, view, container, menu, page, 'save'));
if ( this.settings.advanced_settings )
link_adv.click();
else
link_basic.click();
container.appendChild(page);
container.appendChild(menu);
},
change_page: function(view, container, menu, page, key) {
page.innerHTML = '';
page.setAttribute('data-page', key);
var els = menu.querySelectorAll('li.active');
for(var i=0, l = els.length; i < l; i++)
els[i].classList.remove('active');
var el = menu.querySelector('#ffz-settings-page-' + key);
if ( el )
el.classList.add('active');
FFZ.menu_pages.settings['render_' + key].bind(this)(view, page);
if ( key === 'advanced' )
this.settings.set('advanced_settings', true);
else if ( key === 'basic' )
this.settings.set('advanced_settings', false);
},
render_save: function(view, container) {
var backup_head = document.createElement('div'),
restore_head = document.createElement('div'),
backup_cont = document.createElement('div'),
restore_cont = document.createElement('div'),
backup_para = document.createElement('p'),
backup_link = document.createElement('a'),
backup_help = document.createElement('span'),
restore_para = document.createElement('p'),
restore_input = document.createElement('input'),
restore_link = document.createElement('a'),
restore_help = document.createElement('span'),
f = this;
backup_cont.className = 'chat-menu-content';
backup_head.className = 'heading';
backup_head.innerHTML = 'Backup Settings';
backup_cont.appendChild(backup_head);
backup_para.className = 'clearfix option';
backup_link.href = '#';
backup_link.innerHTML = 'Save to File';
backup_link.addEventListener('click', this.save_settings_file.bind(this));
backup_help.className = 'help';
backup_help.innerHTML = 'This generates a JSON file containing all of your settings and prompts you to save it.';
backup_para.appendChild(backup_link);
backup_para.appendChild(backup_help);
backup_cont.appendChild(backup_para);
restore_cont.className = 'chat-menu-content';
restore_head.className = 'heading';
restore_head.innerHTML = 'Restore Settings';
restore_cont.appendChild(restore_head);
restore_para.className = 'clearfix option';
restore_input.type = 'file';
restore_input.addEventListener('change', function() { f.load_settings_file(this.files[0]); })
restore_link.href = '#';
restore_link.innerHTML = 'Restore from File';
restore_link.addEventListener('click', function(e) { e.preventDefault(); restore_input.click(); });
restore_help.className = 'help';
restore_help.innerHTML = 'This loads settings from a previously generated JSON file.';
restore_para.appendChild(restore_link);
restore_para.appendChild(restore_help);
restore_cont.appendChild(restore_para);
container.appendChild(backup_cont);
container.appendChild(restore_cont);
},
render_basic: function(view, container) {
var settings = {},
categories = [],
is_android = navigator.userAgent.indexOf('Android') !== -1;
for(var key in FFZ.basic_settings) {
if ( ! FFZ.basic_settings.hasOwnProperty(key) )
continue;
var info = FFZ.basic_settings[key],
cat = info.category || "Miscellaneous",
cs = settings[cat];
if ( info.visible !== undefined && info.visible !== null ) {
var visible = info.visible;
if ( typeof info.visible == "function" )
visible = info.visible.bind(this)();
if ( ! visible )
continue;
var info = FFZ.settings_info[key],
cat = info.category || "Miscellaneous",
cs = settings[cat];
if ( info.visible !== undefined && info.visible !== null ) {
var visible = info.visible;
if ( typeof info.visible == "function" )
visible = info.visible.bind(this)();
if ( ! visible )
continue;
}
if ( is_android && info.no_mobile )
continue;
if ( ! cs ) {
categories.push(cat);
cs = settings[cat] = [];
}
cs.push([key, info]);
}
categories.sort(function(a,b) {
var a = a.toLowerCase(),
b = b.toLowerCase();
if ( is_android && info.no_mobile )
continue;
if ( a === "debugging" )
a = "zzz" + a;
if ( ! cs ) {
categories.push(cat);
cs = settings[cat] = [];
}
if ( b === "debugging" )
b = "zzz" + b;
cs.push([key, info]);
}
categories.sort(function(a,b) {
var a = a.toLowerCase(),
b = b.toLowerCase();
if ( a === "debugging" )
a = "zzz" + a;
if ( b === "debugging" )
b = "zzz" + b;
if ( a < b ) return -1;
else if ( a > b ) return 1;
return 0;
});
var f = this,
current_page = this._ffz_basic_settings_page || categories[0];
for(var ci=0; ci < categories.length; ci++) {
var category = categories[ci],
cset = settings[category],
menu = document.createElement('div'),
heading = document.createElement('div');
heading.className = 'heading';
menu.className = 'chat-menu-content'; // collapsable';
menu.setAttribute('data-category', category);
//menu.classList.toggle('collapsed', current_page !== category);
heading.innerHTML = category;
menu.appendChild(heading);
/*menu.addEventListener('click', function() {
if ( ! this.classList.contains('collapsed') )
return;
var t = this,
old_selection = container.querySelectorAll('.chat-menu-content:not(.collapsed)');
for(var i=0; i < old_selection.length; i++)
old_selection[i].classList.add('collapsed');
f._ffz_basic_settings_page = t.getAttribute('data-category');
t.classList.remove('collapsed');
setTimeout(function(){t.scrollIntoViewIfNeeded()});
});*/
cset.sort(function(a,b) {
var a = a[1],
b = b[1],
at = a.type === "boolean" ? 1 : 2,
bt = b.type === "boolean" ? 1 : 2,
an = a.name.toLowerCase(),
bn = b.name.toLowerCase();
if ( at < bt ) return -1;
else if ( at > bt ) return 1;
else if ( an < bn ) return -1;
else if ( an > bn ) return 1;
if ( a < b ) return -1;
else if ( a > b ) return 1;
return 0;
});
var f = this,
current_page = this._ffz_settings_page || categories[0];
for(var i=0; i < cset.length; i++) {
var key = cset[i][0],
info = cset[i][1],
el = document.createElement('p'),
val = info.type !== "button" && typeof info.get === 'function' ? info.get.bind(this)() : this.settings.get(info.get);
for(var ci=0; ci < categories.length; ci++) {
var category = categories[ci],
cset = settings[category],
el.className = 'clearfix';
menu = document.createElement('div'),
heading = document.createElement('div');
if ( this.has_bttv && info.no_bttv ) {
var label = document.createElement('span'),
help = document.createElement('span');
label.className = 'switch-label';
label.innerHTML = info.name;
heading.className = 'heading';
menu.className = 'chat-menu-content collapsable';
menu.setAttribute('data-category', category);
menu.classList.toggle('collapsed', current_page !== category);
help = document.createElement('span');
help.className = 'help';
help.innerHTML = 'Disabled due to incompatibility with BetterTTV.';
heading.innerHTML = category;
menu.appendChild(heading);
el.classList.add('disabled');
el.appendChild(label);
el.appendChild(help);
menu.addEventListener('click', function() {
if ( ! this.classList.contains('collapsed') )
return;
} else {
if ( info.type == "boolean" ) {
var swit = document.createElement('a'),
label = document.createElement('span');
var t = this,
old_selection = container.querySelectorAll('.chat-menu-content:not(.collapsed)');
for(var i=0; i < old_selection.length; i++)
old_selection[i].classList.add('collapsed');
f._ffz_settings_page = t.getAttribute('data-category');
t.classList.remove('collapsed');
setTimeout(function(){t.scrollIntoViewIfNeeded()});
});
swit.className = 'switch';
swit.classList.toggle('active', val);
swit.innerHTML = "<span></span>";
cset.sort(function(a,b) {
var a = a[1],
b = b[1],
at = a.type === "boolean" ? 1 : 2,
bt = b.type === "boolean" ? 1 : 2,
an = a.name.toLowerCase(),
bn = b.name.toLowerCase();
if ( at < bt ) return -1;
else if ( at > bt ) return 1;
else if ( an < bn ) return -1;
else if ( an > bn ) return 1;
return 0;
});
for(var i=0; i < cset.length; i++) {
var key = cset[i][0],
info = cset[i][1],
el = document.createElement('p'),
val = this.settings.get(key);
el.className = 'clearfix';
if ( this.has_bttv && info.no_bttv ) {
var label = document.createElement('span'),
help = document.createElement('span');
label.className = 'switch-label';
label.innerHTML = info.name;
help = document.createElement('span');
help.className = 'help';
help.innerHTML = 'Disabled due to incompatibility with BetterTTV.';
el.classList.add('disabled');
el.appendChild(swit);
el.appendChild(label);
el.appendChild(help);
swit.addEventListener("click", toggle_basic_setting.bind(this, swit, key));
} else if ( info.type === "select" ) {
var select = document.createElement('select'),
label = document.createElement('span');
label.className = 'option-label';
label.innerHTML = info.name;
for(var ok in info.options) {
var op = document.createElement('option');
op.value = JSON.stringify(ok);
if ( val === ok )
op.setAttribute('selected', true);
op.innerHTML = info.options[ok];
select.appendChild(op);
}
select.addEventListener('change', option_basic_setting.bind(this, select, key));
el.appendChild(label);
el.appendChild(select);
} else {
if ( info.type == "boolean" ) {
var swit = document.createElement('a'),
label = document.createElement('span');
el.classList.add("option");
var link = document.createElement('a');
link.innerHTML = info.name;
link.href = "#";
el.appendChild(link);
swit.className = 'switch';
swit.classList.toggle('active', val);
swit.innerHTML = "<span></span>";
label.className = 'switch-label';
label.innerHTML = info.name;
el.appendChild(swit);
el.appendChild(label);
swit.addEventListener("click", toggle_setting.bind(this, swit, key));
} else if ( info.type === "select" ) {
var select = document.createElement('select'),
label = document.createElement('span');
label.className = 'option-label';
label.innerHTML = info.name;
for(var ok in info.options) {
var op = document.createElement('option');
op.value = JSON.stringify(ok);
if ( val === ok )
op.setAttribute('selected', true);
op.innerHTML = info.options[ok];
select.appendChild(op);
}
select.addEventListener('change', option_setting.bind(this, select, key));
el.appendChild(label);
el.appendChild(select);
} else {
el.classList.add("option");
var link = document.createElement('a');
link.innerHTML = info.name;
link.href = "#";
el.appendChild(link);
link.addEventListener("click", info.method.bind(this));
}
if ( info.help ) {
var help = document.createElement('span');
help.className = 'help';
help.innerHTML = info.help;
el.appendChild(help);
}
link.addEventListener("click", info.method.bind(this));
}
menu.appendChild(el);
if ( info.help ) {
var help = document.createElement('span');
help.className = 'help';
help.innerHTML = info.help;
el.appendChild(help);
}
}
container.appendChild(menu);
menu.appendChild(el);
}
},
container.appendChild(menu);
}
},
render_advanced: function(view, container) {
var settings = {},
categories = [],
is_android = navigator.userAgent.indexOf('Android') !== -1;
for(var key in FFZ.settings_info) {
if ( ! FFZ.settings_info.hasOwnProperty(key) )
continue;
var info = FFZ.settings_info[key],
cat = info.category || "Miscellaneous",
cs = settings[cat];
if ( info.visible !== undefined && info.visible !== null ) {
var visible = info.visible;
if ( typeof info.visible == "function" )
visible = info.visible.bind(this)();
if ( ! visible )
continue;
}
if ( is_android && info.no_mobile )
continue;
if ( ! cs ) {
categories.push(cat);
cs = settings[cat] = [];
}
cs.push([key, info]);
}
categories.sort(function(a,b) {
var a = a.toLowerCase(),
b = b.toLowerCase();
if ( a === "debugging" )
a = "zzz" + a;
if ( b === "debugging" )
b = "zzz" + b;
if ( a < b ) return -1;
else if ( a > b ) return 1;
return 0;
});
var f = this,
current_page = this._ffz_settings_page || categories[0];
for(var ci=0; ci < categories.length; ci++) {
var category = categories[ci],
cset = settings[category],
menu = document.createElement('div'),
heading = document.createElement('div');
heading.className = 'heading';
menu.className = 'chat-menu-content collapsable';
menu.setAttribute('data-category', category);
menu.classList.toggle('collapsed', current_page !== category);
heading.innerHTML = category;
menu.appendChild(heading);
menu.addEventListener('click', function() {
if ( ! this.classList.contains('collapsed') )
return;
var t = this,
old_selection = container.querySelectorAll('.chat-menu-content:not(.collapsed)');
for(var i=0; i < old_selection.length; i++)
old_selection[i].classList.add('collapsed');
f._ffz_settings_page = t.getAttribute('data-category');
t.classList.remove('collapsed');
setTimeout(function(){t.scrollIntoViewIfNeeded()});
});
cset.sort(function(a,b) {
var a = a[1],
b = b[1],
at = a.type === "boolean" ? 1 : 2,
bt = b.type === "boolean" ? 1 : 2,
an = a.name.toLowerCase(),
bn = b.name.toLowerCase();
if ( at < bt ) return -1;
else if ( at > bt ) return 1;
else if ( an < bn ) return -1;
else if ( an > bn ) return 1;
return 0;
});
for(var i=0; i < cset.length; i++) {
var key = cset[i][0],
info = cset[i][1],
el = document.createElement('p'),
val = this.settings.get(key);
el.className = 'clearfix';
if ( this.has_bttv && info.no_bttv ) {
var label = document.createElement('span'),
help = document.createElement('span');
label.className = 'switch-label';
label.innerHTML = info.name;
help = document.createElement('span');
help.className = 'help';
help.innerHTML = 'Disabled due to incompatibility with BetterTTV.';
el.classList.add('disabled');
el.appendChild(label);
el.appendChild(help);
} else {
if ( info.type == "boolean" ) {
var swit = document.createElement('a'),
label = document.createElement('span');
swit.className = 'switch';
swit.classList.toggle('active', val);
swit.innerHTML = "<span></span>";
label.className = 'switch-label';
label.innerHTML = info.name;
el.appendChild(swit);
el.appendChild(label);
swit.addEventListener("click", toggle_setting.bind(this, swit, key));
} else if ( info.type === "select" ) {
var select = document.createElement('select'),
label = document.createElement('span');
label.className = 'option-label';
label.innerHTML = info.name;
for(var ok in info.options) {
var op = document.createElement('option');
op.value = JSON.stringify(ok);
if ( val === ok )
op.setAttribute('selected', true);
op.innerHTML = info.options[ok];
select.appendChild(op);
}
select.addEventListener('change', option_setting.bind(this, select, key));
el.appendChild(label);
el.appendChild(select);
} else {
el.classList.add("option");
var link = document.createElement('a');
link.innerHTML = info.name;
link.href = "#";
el.appendChild(link);
link.addEventListener("click", info.method.bind(this));
}
if ( info.help ) {
var help = document.createElement('span');
help.className = 'help';
help.innerHTML = info.help;
el.appendChild(help);
}
}
menu.appendChild(el);
}
container.appendChild(menu);
}
},
name: "Settings",
icon: constants.GEAR,
sort_order: 99999,
wide: true
wide: true,
sub_menu: true
};

View file

@ -6,6 +6,107 @@ var FFZ = window.FrankerFaceZ,
// Settings
// ---------------------
FFZ.basic_settings.dark_twitch = {
type: "boolean",
no_bttv: true,
category: "General",
name: "Dark Twitch",
help: "Apply a dark background to channels and other related pages for easier viewing.",
get: function() {
return this.settings.dark_twitch;
},
set: function(val) {
this.settings.set('dark_twitch', val);
this.settings.set('dark_no_blue', val);
}
};
FFZ.basic_settings.separated_chat = {
type: "boolean",
no_bttv: true,
category: "Chat",
name: "Separated Lines",
help: "Use alternating rows and thin lines to visually separate chat messages for easier reading.",
get: function() {
return this.settings.chat_rows && this.settings.chat_separators !== '0';
},
set: function(val) {
this.settings.set('chat_rows', val);
this.settings.set('chat_separators', val ? '2' : '0');
}
};
FFZ.basic_settings.minimalistic_chat = {
type: "boolean",
category: "Chat",
name: "Minimalistic UI",
help: "Hide all of chat except messages and the input box and reduce chat margins.",
get: function() {
return this.settings.minimal_chat && this.settings.chat_padding;
},
set: function(val) {
this.settings.set('minimal_chat', val);
this.settings.set('chat_padding', val);
}
};
FFZ.basic_settings.high_contrast = {
type: "boolean",
category: "Chat",
no_bttv: true,
name: "High Contrast",
help: "Display chat using white and black for maximum contrast. This is suitable for capturing and chroma keying chat to display on stream.",
get: function() {
return this.settings.high_contrast_chat !== '222';
},
set: function(val) {
this.settings.set('high_contrast_chat', val ? '111': '222');
}
};
FFZ.basic_settings.keywords = {
type: "button",
category: "Chat",
no_bttv: true,
name: "Highlight Keywords",
help: "Set additional keywords that will be highlighted in chat.",
method: function() {
FFZ.settings_info.keywords.method.bind(this)();
}
};
FFZ.basic_settings.banned_words = {
type: "button",
category: "Chat",
no_bttv: true,
name: "Banned Keywords",
help: "Set a list of words that will be removed from chat messages, locally.",
method: function() {
FFZ.settings_info.banned_words.method.bind(this)();
}
};
FFZ.settings_info.twitch_chat_dark = {
type: "boolean",
value: false,
@ -28,7 +129,7 @@ FFZ.settings_info.dark_twitch = {
var cb = document.querySelector('input.ffz-setting-dark-twitch');
if ( cb )
cb.checked = val;
if ( this.has_bttv )
return;

View file

@ -1,19 +1,25 @@
var FFZ = window.FrankerFaceZ,
utils = require('../utils'),
constants = require('../constants');
constants = require('../constants'),
FOLLOW_GRAVITY = function(f, el) {
return (f.settings.following_count && el.parentElement.getAttribute('data-name') === 'following' ? 'n' : '') + (f.settings.swap_sidebars ? 'e' : 'w');
},
WIDE_TIP = function(f, el) {
return ( ! f.settings.following_count || (el.id !== 'header_following' && el.parentElement.getAttribute('data-name') !== 'following') ) ? '' : 'ffz-wide-tip';
};
FFZ.settings_info.following_count = {
type: "boolean",
value: true,
no_bttv: true,
no_mobile: true,
category: "Appearance",
name: "Sidebar Following Count",
help: "Display the number of live channels you're following on the sidebar.",
name: "Sidebar Following Data",
help: "Display the number of live channels you're following on the sidebar, and list the channels in a tooltip.",
on_update: function(val) {
this._schedule_following_count();
@ -21,10 +27,14 @@ FFZ.settings_info.following_count = {
var Stream = window.App && App.__container__.resolve('model:stream'),
Live = Stream && Stream.find("live");
if ( Live )
this._draw_following_count(Live.get('total') || 0);
else
if ( Live ) {
var total = Live.get('total') || 0;
this._draw_following_count(total);
this._draw_following_channels(Live.get('content'), total);;
} else {
this._update_following_count();
this._draw_following_channels();
}
}
};
@ -37,6 +47,9 @@ FFZ.prototype.setup_following_count = function(has_ember) {
if ( this.settings.following_count )
this._schedule_following_count();
// Tooltips~!
this._install_following_tooltips();
// If we don't have Ember, no point in trying this stuff.
if ( ! has_ember )
return this._update_following_count();
@ -68,7 +81,7 @@ FFZ.prototype.setup_following_count = function(has_ember) {
FFZ.prototype._schedule_following_count = function() {
if ( this.has_bttv || ! this.settings.following_count ) {
if ( ! this.settings.following_count ) {
if ( this._following_count_timer ) {
clearTimeout(this._following_count_timer);
this._following_count_timer = undefined;
@ -110,9 +123,21 @@ FFZ.prototype._update_following_count = function() {
}
FFZ.prototype._draw_following_channels = function(streams, total) {
// First, build the data.
var tooltip = 'Following';
FFZ.prototype._build_following_tooltip = function(el) {
if ( el.id !== 'header_following' && el.parentElement.getAttribute('data-name') !== 'following' )
return el.getAttribute('original-title');
if ( ! this.settings.following_count )
return 'Following';
var tooltip = (this.has_bttv ? '<span class="stat playing">FrankerFaceZ</span>' : '') + 'Following',
bb = el.getBoundingClientRect(),
height = document.body.clientHeight - (bb.bottom + 54),
max_lines = Math.max(Math.floor(height / 36) - 1, 2),
streams = this._tooltip_streams,
total = this._tooltip_total || (streams && streams.length) || 0;
if ( streams && streams.length ) {
var c = 0;
@ -122,61 +147,93 @@ FFZ.prototype._draw_following_channels = function(streams, total) {
continue;
c += 1;
if ( c > 5 ) {
var ttl = total || streams.length;
tooltip += '<hr><span>And ' + utils.number_commas(ttl - 5) + ' more...</span>';
if ( c > max_lines ) {
tooltip += '<hr><span>And ' + utils.number_commas(total - max_lines) + ' more...</span>';
break;
}
tooltip += (i > 0 ? '<br>' : '<hr>') + '<span class="viewers">' + constants.LIVE + ' ' + utils.number_commas(stream.viewers) + '</span><b>' + utils.sanitize(stream.channel.display_name || stream.channel.name) + '</b><br><span class="playing">' + (stream.channel.game ? 'Playing ' + utils.sanitize(stream.channel.game) : 'Not Playing') + '</span>';
}
}
var up_since = this.settings.stream_uptime && stream.created_at && utils.parse_date(stream.created_at),
uptime = up_since && Math.floor((Date.now() - up_since.getTime()) / 1000) || 0,
minutes = Math.floor(uptime / 60) % 60,
hours = Math.floor(uptime / 3600);
tooltip += (i === 0 ? '<hr>' : '') +
(uptime > 0 ? '<span class="stat">' + constants.CLOCK + ' ' + (hours > 0 ? hours + 'h' : '') + minutes + 'm</span>' : '') +
'<span class="stat">' + constants.LIVE + ' ' + utils.number_commas(stream.viewers) + '</span>' +
'<b>' + utils.sanitize(stream.channel.display_name || stream.channel.name) + '</b><br>' +
'<span class="playing">' + (stream.channel.game ? 'Playing ' + utils.sanitize(stream.channel.game) : 'Not Playing') + '</span>';
}
} else
tooltip += "<hr>No one you're following is online.";
// Reposition the tooltip.
setTimeout(function() {
var tip = document.querySelector('.tipsy'),
bb = tip.getBoundingClientRect(),
left = parseInt(tip.style.left || '0'),
right = bb.left + tip.scrollWidth;
if ( bb.left < 5 )
tip.style.left = (left - bb.left) + 5 + 'px';
else if ( right > document.body.clientWidth - 5 )
tip.style.left = (left - (5 + right - document.body.clientWidth)) + 'px';
});
return tooltip;
}
FFZ.prototype._install_following_tooltips = function() {
var f = this,
data = {
html: true,
className: function() { return WIDE_TIP(f, this); },
title: function() { return f._build_following_tooltip(this); }
};
// Small
var small_following = jQuery('#small_nav ul.game_filters li[data-name="following"] a');
if ( small_following && small_following.length ) {
var data = small_following.data('tipsy');
if ( data && data.options ) {
data.options.gravity = function() { return this.parentElement.getAttribute('data-name') === 'following' ? 'nw': 'w'; };
data.options.html = true;
data.options.className = 'ffz-wide-tip';
var td = small_following.data('tipsy');
if ( td && td.options ) {
td.options = _.extend(td.options, data);
td.options.gravity = function() { return FOLLOW_GRAVITY(f, this); };
} else
small_following.tipsy({html: true, className: 'ffz-wide-tip', gravity: 'nw'});
small_following.attr('title', tooltip);
small_following.tipsy(_.extend({gravity: function() { return FOLLOW_GRAVITY(f, this); }}, data));
}
// Large
var large_following = jQuery('#large_nav #nav_personal li[data-name="following"] a');
if ( large_following && large_following.length ) {
var data = large_following.data('tipsy');
if ( data && data.options ) {
data.options.html = true;
data.options.className = 'ffz-wide-tip';
} else
large_following.tipsy({html:true, className: 'ffz-wide-tip'});
large_following.attr('title', tooltip);
var td = large_following.data('tipsy');
if ( td && td.options )
td.options = _.extend(td.options, data);
else
large_following.tipsy(data);
}
// Heading
var head_following = jQuery('#header_actions #header_following');
if ( head_following && head_following.length ) {
var data = head_following.data('tipsy');
if ( data && data.options ) {
data.options.html = true;
data.options.className = 'ffz-wide-tip';
} else
head_following.tipsy({html: true, className: 'ffz-wide-tip'});
head_following.attr('title', tooltip);
var td = head_following.data('tipsy');
if ( td && td.options )
td.options = _.extend(td.options, data);
else
head_following.tipsy(data);
}
}
FFZ.prototype._draw_following_channels = function(streams, total) {
this._tooltip_streams = streams;
this._tooltip_total = total;
}
FFZ.prototype._draw_following_count = function(count) {
// Small
var small_following = document.querySelector('#small_nav ul.game_filters li[data-name="following"] a');

View file

@ -308,7 +308,7 @@ FFZ.prototype.build_ui_popup = function(view) {
el = document.createElement('li'),
link = document.createElement('a');
el.className = 'item';
el.className = 'item' + (page.sub_menu ? ' has-sub-menu' : '');
el.id = "ffz-menu-page-" + key;
link.title = page.name;
link.innerHTML = page.icon;

View file

@ -10,6 +10,25 @@ var FFZ = window.FrankerFaceZ,
// Initialization
// -------------------
FFZ.basic_settings.replace_twitch_menu = {
type: "boolean",
category: "Chat",
name: "Unified Emoticons Menu",
help: "Completely replace the default Twitch emoticon menu and display global emoticons in the My Emoticons menu.",
get: function() {
return this.settings.replace_twitch_menu && this.settings.global_emotes_in_menu && this.settings.emoji_in_menu;
},
set: function(val) {
this.settings.set('replace_twitch_menu', val);
this.settings.set('global_emotes_in_menu', val);
this.settings.set('emoji_in_menu', val);
}
};
FFZ.settings_info.replace_twitch_menu = {
type: "boolean",
value: false,