// ==UserScript==
// @name MyAnimeList (MAL) Track Missing Relations
// @namespace https://greasyfork.org/users/7517
// @description Allows to find missing relations and entries with wrong chapter/episode count
// @icon http://i.imgur.com/b7Fw8oH.png
// @version 7.0.1
// @author akarin
// @include http://myanimelist.net/profile/*
// @grant none
// @noframes
// @include-jquery true
// ==/UserScript==
/*jslint fudge, maxerr: 10, browser, devel, this, white, for */
/*global jQuery, window */
(function($) {
if ($('#malLogin').length !== 0) {
return;
}
// Status codes used by MAL API
var USER_STATUS = {
IN_PROCESS: 1, COMPLETED: 2, ON_HOLD: 3, DROPPED: 4, PLAN_TO: 6
};
var SERIES_STATUS = {
IN_PROCESS: 1, COMPLETED: 2, NOT_YET: 3
};
// Main object
var mal = {
version: '6.5', // cache
name: '', // username
type: '' // anime or manga
};
mal.opt = {
done: 0, fail: 0, skip: 0, total: 0
};
mal.settings = {
ajax: { delay: 300, timeout: 10000 },
width: 670, height: 765,
// Predefined data lists:
availableRelations: [
'Alternative Setting', 'Alternative Version', 'Character', 'Full Story', 'Other',
'Parent Story', 'Prequel', 'Sequel', 'Side Story', 'Spin-off', 'Summary'
],
availableStatus: {
anime: ['Watching', 'Completed', 'On-Hold', 'Dropped', 'Plan to Watch'],
manga: ['Reading', 'Completed', 'On-Hold', 'Dropped', 'Plan to Read']
},
// User settings:
excludedRelations: { anime: [], manga: [] },
excludedStatus: { anime: [], manga: [] },
load: function() {
['anime', 'manga'].forEach(function(status) {
mal.settings.excludedRelations[status] = ['Adaptation'];
mal.settings.availableRelations.forEach(function(val) {
var id = 'mr_xr' + status[0] + '_' + val.toHtmlId();
if (mal.loadSetting(id, 'false') !== 'false') {
mal.settings.excludedRelations[status].push(val);
}
});
mal.settings.excludedStatus[status] = ['Empty Status'];
mal.settings.availableStatus[status].forEach(function(val) {
var id = 'mr_xs' + status[0] + '_' + val.toHtmlId();
if (mal.loadSetting(id, 'false') !== 'false') {
mal.settings.excludedStatus[status].push(val);
}
});
});
},
reset: function() {
['anime', 'manga'].forEach(function(status) {
mal.settings.availableRelations.forEach(function(val) {
mal.saveSetting('mr_xr' + status[0] + '_' + val.toHtmlId(), 'false');
});
mal.settings.availableStatus[status].forEach(function(val) {
mal.saveSetting('mr_xs' + status[0] + '_' + val.toHtmlId(), 'false');
});
});
}
};
$.fn.myfancybox = function(onstart) {
return $(this).click(function() {
mal.fancybox.start(onstart);
});
};
mal.fancybox = {
body: $('<div id="mr_fancybox_inner">'),
outer: $('<div id="mr_fancybox_outer">'),
wrapper: $('<div id="mr_fancybox_wrapper">'),
init: function(el) {
mal.fancybox.outer.hide()
.append(mal.fancybox.body)
.insertAfter(el);
mal.fancybox.wrapper.hide()
.insertAfter(el);
mal.fancybox.wrapper.click(function() {
mal.fancybox.close();
});
},
start: function(onstart) {
mal.fancybox.body.children().hide();
if (onstart()) {
mal.fancybox.wrapper.show();
mal.fancybox.outer.show();
} else {
mal.fancybox.close();
}
},
close: function() {
mal.fancybox.outer.hide();
mal.fancybox.wrapper.hide();
}
};
function main() {
mal.name = $('.icon-rss + div > a:first').prop('href').match(/&u=(.+)$/)[1].trim();
if (mal.name.length === 0) {
return;
}
var panel = $('<div id="mr_panel">')
.append('<span id="mr_profile"><a href="/profile/' + mal.name + '"><b>' + mal.name + '</b></a></span>')
.append(mal.content.status)
.prependTo(mal.content.body);
var span = $('<span id="mr_links_settings">')
.append($('<a href="javascript:void(0);" title="Switch lists" id="mr_link_switch">Manga</a>')
.click(function() {
if (mal.entries.isUpdating()) {
alert('Updating in process!');
} else {
var type = mal.type === 'anime' ? 'manga' : 'anime';
mal.fancybox.close();
$('div.floatRightHeader > a[id^="mr_link_' + type + '"]').trigger('click');
}
})
)
.append(' - ')
.append($('<a href="javascript:void(0);" title="Change calculation settings">Settings</a>')
.myfancybox(function() {
mal.content.updateSettings();
return true;
})
)
.append(' - ')
.append($('<a href="javascript:void(0);" title="Recalculate missing relations">Rescan</a>')
.click(function() {
if (mal.entries.isUpdating()) {
alert('Updating in process!');
} else if (confirm('Recalculate missing relations?')) {
mal.entries.update(false);
}
})
)
.append(' - ')
.append($('<a href="javascript:void(0);" title="Update current missing relations">Update</a>')
.click(function() {
if (mal.entries.isUpdating()) {
alert('Updating in process!');
} else if (confirm('Update current missing relations?')) {
mal.entries.update(true);
}
})
);
$('<div id="mr_links">')
.append(span)
.prependTo(panel);
var header = $('<h2 id="mr_body_title">Missing Relations</h2>')
.prependTo(mal.content.body);
mal.fancybox.init('#contentWrapper');
mal.content.body
.append(mal.content.list)
.appendTo(mal.fancybox.body);
mal.content.settings
.prepend(header.clone())
.appendTo(mal.fancybox.body);
var links = $('<div class="floatRightHeader">')
.append('<a href="javascript:void(0);" id="mr_link_anime">Missing Anime Relations</a>')
.append(' - ')
.append('<a href="javascript:void(0);" id="mr_link_manga">Missing Manga Relations</a>')
.prependTo('.container-right > #statistics > h2');
$('a[id^="mr_link_"]', links).each(function() {
var type = this.id.match(/^mr_link_(\w+)$/)[1];
$(this).myfancybox(function() {
if (type !== mal.type) {
if (!mal.entries.isUpdating()) {
mal.content.status.empty();
} else {
alert('Updating in process!');
return false;
}
}
var listType = $('#mr_list_type', mal.content.body);
mal.type = type;
$('#mr_links_settings > #mr_link_switch', mal.content.body)
.text(mal.type === 'anime' ? 'Manga' : 'Anime');
if (listType.length === 0 || listType.val() !== mal.type) {
mal.settings.load();
mal.entries.load();
mal.content.updateList(false);
}
mal.content.body.show();
return true;
});
});
}
mal.entries = {
// Cache data:
left: {}, graph: {}, title: {}, wrong: {}, hidden: {}, ignored: {},
// Progress data:
total: 0, done: 0, fail: 0, skip: 0,
load: function() {
mal.entries.clear();
mal.entries.left = mal.loadValue('mal.entries.left', mal.entries.left);
mal.entries.graph = mal.loadValue('mal.entries.graph', mal.entries.graph);
mal.entries.title = mal.loadValue('mal.entries.title', mal.entries.title);
mal.entries.wrong = mal.loadValue('mal.entries.wrong', mal.entries.wrong);
mal.entries.ignored = mal.loadValue('mal.entries.ignored', mal.entries.ignored);
mal.entries.hidden = mal.loadValue('mal.entries.hidden', mal.entries.hidden);
},
save: function() {
mal.saveValue('mal.entries.left', mal.entries.left);
mal.saveValue('mal.entries.graph', mal.entries.graph);
mal.saveValue('mal.entries.title', mal.entries.title);
mal.saveValue('mal.entries.wrong', mal.entries.wrong);
mal.saveValue('mal.entries.ignored', mal.entries.ignored);
mal.entries.saveHidden();
},
saveHidden: function() {
mal.saveValue('mal.entries.hidden', mal.entries.hidden);
},
clear: function() {
mal.entries.total = 0;
mal.entries.done = 0;
mal.entries.fail = 0;
mal.entries.skip = 0;
mal.entries.left = {};
mal.entries.graph = {};
mal.entries.title = {};
mal.entries.wrong = {};
mal.entries.ignored = {};
},
clearHidden: function() {
mal.entries.hidden = {};
},
isUpdating: function() {
return (mal.entries.done + mal.entries.fail + mal.entries.skip) < mal.entries.total;
},
update: function(onlyNew) {
var userlist = [];
var left = mal.entries.left;
var right = {};
var relationRe = new RegExp('^(' + mal.settings.excludedRelations[mal.type].join('|') + ')$', 'i');
function setRelations(left_id, right_id) {
[ { x: left_id, y: right_id }, { x: right_id, y: left_id } ].forEach(function(rel) {
if (!mal.entries.graph.hasOwnProperty(rel.x)) {
mal.entries.graph[rel.x] = [];
}
if (mal.entries.graph[rel.x].indexOf(rel.y) < 0) {
mal.entries.graph[rel.x].push(rel.y);
}
});
}
function getRelations(left_id, data) {
$('#dialog .normal_header + form > br ~ div > small', data).each(function() {
var val = $('input[id^="relationGen"]', this).val();
if (!val.match(/^\d+\ \(/) || val.match(/^\d+\ \(\)$/)) {
console.log('Empty Relation: /' + mal.type + '/' + left_id);
return;
}
var right_id = val.match(/\d+/)[0];
var relation = $('select[name^="relationTypeId"] option:selected', this).text();
if (!relation.match(relationRe) && !mal.entries.ignored.hasOwnProperty(right_id)) {
setRelations(left_id, right_id);
right[right_id] = true;
}
});
}
function loadRight() {
if (mal.entries.isUpdating()) {
return;
}
var count = 0;
$.each(right, function(id) {
if (left.hasOwnProperty(id)) {
delete right[id];
} else {
count += 1;
}
});
mal.opt.done += mal.entries.done;
mal.opt.fail += mal.entries.fail;
mal.opt.skip += mal.entries.skip;
mal.opt.total += mal.entries.total;
mal.entries.done = 0;
mal.entries.fail = 0;
mal.entries.skip = 0;
mal.entries.total = count;
mal.content.updateStatus(true);
if (mal.entries.total === 0) {
mal.content.updateList(true);
return;
}
var delay = 0;
$.each(right, function(id) {
setTimeout(function() {
$.ajax('/dbchanges.php?' + mal.type[0] + 'id=' + id + '&t=relations')
.done(function(data) {
mal.entries.title[id] = $('#dialog .normal_header > a', data).text().toHtmlStr();
left[id] = true;
getRelations(id, data);
mal.entries.done += 1;
mal.content.updateStatus(true);
loadRight();
})
.fail(function() {
mal.entries.fail += 1;
mal.content.updateStatus(true);
console.log('Failed: /' + mal.type + '/' + id);
loadRight();
});
}, delay);
delay += mal.settings.ajax.delay;
});
}
function loadLeft() {
if (mal.entries.isUpdating()) {
return;
}
mal.entries.done = 0;
mal.entries.fail = 0;
mal.entries.skip = 0;
mal.entries.total = userlist.length;
mal.content.updateStatus(false);
if (mal.entries.total === 0) {
mal.content.updateList(true);
return;
}
var delay = 0;
var statusRe = new RegExp('^(' + mal.settings.excludedStatus[mal.type].join('|') + ')$', 'i');
userlist.forEach(function(entry) {
mal.entries.title[entry.id] = entry.title;
if (mal.entries.left.hasOwnProperty(entry.id) || entry.status.match(statusRe)) {
mal.entries.skip += 1;
mal.content.updateStatus(false);
mal.content.updateList(true);
return;
}
setTimeout(function() {
$.ajax('/dbchanges.php?' + mal.type[0] + 'id=' + entry.id + '&t=relations')
.done(function(data) {
mal.entries.left[entry.id] = true;
left[entry.id] = true;
getRelations(entry.id, data);
mal.entries.done += 1;
mal.content.updateStatus(false);
loadRight();
})
.fail(function() {
mal.entries.fail += 1;
mal.content.updateStatus(false);
console.log('Failed: /' + mal.type + '/' + entry.id);
loadRight();
});
}, delay);
delay += mal.settings.ajax.delay;
});
}
function loadUserList() {
var statusRe = new RegExp('^(' + mal.settings.excludedStatus[mal.type].join('|') + ')$', 'i');
$.get('/malappinfo.php?u=' + mal.name + '&status=all&type=' + mal.type, function(data) {
$(mal.type, data).each(function() {
var id = $('series_' + mal.type + 'db_id', this).text().trim();
var title = $('series_title', this).text();
var status = parseInt($('series_status', this).text().trim() || '0');
var episodes = parseInt($('series_episodes', this).text().trim() || '0');
var chapters = parseInt($('series_chapters', this).text().trim() || '0');
var volumes = parseInt($('series_volumes', this).text().trim() || '0');
var userStatus = parseInt($('my_status', this).text().trim() || '0');
var userEpisodes = parseInt($('my_watched_episodes', this).text().trim() || '0');
var userChapters = parseInt($('my_read_chapters', this).text().trim() || '0');
var userVolumes = parseInt($('my_read_volumes', this).text().trim() || '0');
//my_rereadingg is not an error
var rewatching = parseInt($(mal.type === 'anime' ? 'my_rewatching' : 'my_rereadingg', this).text().trim() || '0');
var entry = {
id: parseInt(id),
title: title.toHtmlStr(),
status: mal.settings.availableStatus[mal.type][
// [ 1, 2, 3, 4, 6 ] --> [ 0, 1, 2, 3, 4 ]
userStatus - (userStatus === USER_STATUS.PLAN_TO ? 2 : 1)
],
isCorrect: !(
(userStatus !== USER_STATUS.PLAN_TO && status === SERIES_STATUS.NOT_YET) ||
(userStatus === USER_STATUS.COMPLETED && rewatching === 0 &&
(status !== SERIES_STATUS.COMPLETED || episodes !== userEpisodes ||
chapters !== userChapters || volumes !== userVolumes)) ||
(userStatus !== USER_STATUS.COMPLETED && status === SERIES_STATUS.COMPLETED &&
((episodes > 0 && userEpisodes >= episodes) ||
(volumes > 0 && userVolumes >= volumes) ||
(chapters > 0 && userChapters >= chapters)))
)
};
if (!entry.isCorrect) {
mal.entries.wrong[entry.id] = true;
}
if (entry.status.match(statusRe)) {
mal.entries.ignored[entry.id] = true;
}
userlist.push(entry);
});
loadLeft();
});
}
if (!mal.entries.isUpdating()) {
mal.content.status.text(' - Loading...');
mal.opt = { done: 0, fail: 0, skip: 0, total: 0 };
if (!onlyNew) {
mal.entries.clear();
left = {};
} else {
mal.entries.wrong = {};
mal.entries.ignored = {};
}
mal.settings.load();
loadUserList();
}
},
getTitle: function(id) {
var result = mal.entries.title.hasOwnProperty(id) ? mal.entries.title[id] : '';
return result.length === 0 ? '?' : result;
},
getComps: function() {
var result = {};
var used = {};
var comp = [];
function dfs(v) {
used[v] = true;
comp.push(v);
if (!mal.entries.graph.hasOwnProperty(v)) {
return;
}
mal.entries.graph[v].forEach(function(to) {
if (!used.hasOwnProperty(to)) {
dfs(to);
}
});
}
$.each(mal.entries.graph, function(v) {
if (!used.hasOwnProperty(v)) {
comp = [];
dfs(v);
if (comp.length > 1) {
result[v] = comp;
}
}
});
return result;
},
comparator: function(a, b) {
var aTitle = mal.entries.getTitle(a).toLowerCase();
var bTitle = mal.entries.getTitle(b).toLowerCase();
return aTitle.localeCompare(bTitle);
}
};
mal.content = {
body: $('<div id="mr_body" class="mr_body">'),
settings: $('<div id="mr_settings" class="mr_body"><div class="mr_list" /></div>'),
status: $('<span id="mr_status_msg">'),
list: $('<div class="mr_list">'),
updateStatus: function(extra) {
var msg = {
done: '', fail: '', skip: '', total: ''
};
if (extra && mal.entries.total === 0) {
msg.done = 'Done: <b><span style="color: green;">' + (mal.opt.done + mal.opt.fail + mal.opt.skip) + '</span></b>';
msg.fail = 'Failed: <b><span style="color: #c32;">' + mal.opt.fail + '</span></b>';
msg.skip = 'Skipped: <b><span style="color: gray;">' + mal.opt.skip + '</span></b>';
msg.total = 'Total: <b>' + mal.opt.total + '</b>';
} else {
msg.done = 'Done: <b><span style="color: green;">' + (extra ? ((mal.opt.done + mal.opt.fail + mal.opt.skip) + '+') : '') + (mal.entries.done + mal.entries.fail + mal.entries.skip) + '</span></b>';
msg.fail = 'Failed: <b><span style="color: #c32;">' + (extra ? (mal.opt.fail + '+') : '') + mal.entries.fail + '</span></b>';
msg.skip = 'Skipped: <b><span style="color: gray;">' + (extra ? (mal.opt.skip + '+') : '') + mal.entries.skip + '</span></b>';
msg.total = 'Total: <b>' + (extra ? (mal.opt.total + '+') : '') + mal.entries.total + '</b>';
}
mal.content.status.html(' - ' + msg.done + ' <small>(' + msg.fail + ', ' + msg.skip + ')</small> / ' + msg.total);
},
updateList: function(save) {
if (mal.entries.isUpdating()) {
return;
}
if (save) {
mal.entries.save();
}
$('#mr_body_title', mal.content.body)
.text('Missing Relations – ' + (mal.type === 'anime' ? 'Anime' : 'Manga'));
var listType = $('<input type="hidden" id="mr_list_type" value="' + mal.type + '">');
var comps = mal.entries.getComps();
var wrong = Object.keys(mal.entries.wrong);
if (!Object.keys(comps).length && wrong.length === 0) {
mal.content.list.empty().append('<p id="mr_notfound">No missing relations found.</p>').append(listType);
return;
}
var table = $('<table class="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><thead><tr><th>You\'ve ' + (mal.type === 'anime' ? 'watched' : 'read') + ' this…</th><th>…so you might want to check this:</th></tr></table>');
var undel = $('<div id="mr_undelete"><p id="mr_undelete_msg" style="display: none;">There are <span id="mr_undelete_num" style="font-weight: bold;" /> hidden relations. <a href="javascript:void(0);" title="Show hidden relations" onclick="window.showHiddenRelations();">Show them</a></p></div>');
var tfoot = $('<tfoot><tr><td class="mr_td_left"><div class="mr_count"><span /></div></td><td class="mr_td_right"><div class="mr_count"><span /></div></td></tr></tfoot>');
// red relations
if (wrong.length > 0) {
var ul = $('<ul>');
var size = 0;
wrong.sort(mal.entries.comparator).forEach(function(id) {
mal.content.getLiLeft(id).prop('id', 'mr_li_red_' + id).appendTo(ul);
size += 1;
});
var tbody_red = $('<tbody>');
var tr_red = $('<tr class="mr_tr_data">')
.append($('<td class="mr_td_left">').append(ul))
.append($('<td class="mr_td_right">').append(mal.content.getEntryWarning()))
.appendTo(tbody_red);
if (size > 5) {
tr_red.addClass('mr_tr_collapsed');
$('<tr class="mr_tr_more">').append(mal.content.getMoreLink()).insertAfter(tr_red);
}
tbody_red.appendTo(table);
}
// normal relations
Object.keys(comps).sort(mal.entries.comparator).forEach(function(key) {
var ulLeft = $('<ul>');
var ulRight = $('<ul>');
var lSize = 0;
var rSize = 0;
comps[key].sort(mal.entries.comparator).forEach(function(id) {
if (mal.entries.ignored.hasOwnProperty(id)) {
return;
}
if (mal.entries.left.hasOwnProperty(id)) {
mal.content.getLiLeft(id).prop('id', 'mr_li_' + id).appendTo(ulLeft);
lSize += 1;
} else {
mal.content.getLiRight(id).prop('id', 'mr_li_' + id).appendTo(ulRight);
rSize += 1;
}
});
if (lSize > 0 && rSize > 0) {
var tbody = $('<tbody>');
var tr = $('<tr class="mr_tr_data">')
.append($('<td class="mr_td_left">').append(ulLeft))
.append($('<td class="mr_td_right">').append(ulRight))
.appendTo(tbody);
if (lSize > 5 || rSize > 5) {
tr.addClass('mr_tr_collapsed');
$('<tr class="mr_tr_more">').append(mal.content.getMoreLink()).insertAfter(tr);
}
tbody.appendTo(table);
}
});
mal.content.list.empty()
.append(undel)
.append(table.append(tfoot))
.append(listType);
mal.hideHiddenRelations(true);
mal.content.updateLineCount();
},
updateSettings: function() {
$('#mr_body_title', mal.content.settings)
.text('Missing Relations – Settings');
var tableExclude = $('<table class="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><thead><tr><th>Exclude Anime Relations:</th><th>Exclude Manga Relations:</th></tr><tbody /></table>');
var tableIgnore = $('<table class="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><thead><tr><th>Ignore Anime Entries:</th><th>Ignore Manga Entries:</th></tr><tbody /></table>');
var buttons = $('<div class="mr_buttons">')
.append($('<input class="inputButton" value="Save" type="button">').click(function() {
$('input[type="checkbox"]', mal.content.settings).each(function() {
mal.saveSetting(this.id, $(this).prop('checked').toString());
});
mal.settings.load();
mal.fancybox.start(function() {
mal.content.body.show();
return true;
});
}))
.append($('<input class="inputButton" value="Cancel" type="button">').click(function() {
mal.fancybox.start(function() {
mal.content.body.show();
return true;
});
}))
.append($('<input class="inputButton" value="Reset" type="button">').click(function() {
if (!confirm('Reset all settings?')) {
return;
}
mal.settings.reset();
mal.settings.load();
mal.fancybox.start(function() {
mal.content.body.show();
return true;
});
}));
// Add exclude relations
var tbody = $('tbody', tableExclude);
mal.settings.availableRelations.forEach(function(rel) {
$('<tr class="mr_tr_data">')
.append($('<td class="mr_td_left">')
.append(mal.content.getCbSetting(rel, 'mr_xra_' + rel, false)))
.append($('<td class="mr_td_right">')
.append(mal.content.getCbSetting(rel, 'mr_xrm_' + rel, false)))
.appendTo(tbody);
});
// Add ignore status
tbody = $('tbody', tableIgnore);
$.each(mal.settings.availableStatus.anime, function(i, statusA) {
var statusM = mal.settings.availableStatus.manga[i];
$('<tr class="mr_tr_data">')
.append($('<td class="mr_td_left">')
.append(mal.content.getCbSetting(statusA, 'mr_xsa_' + statusA, false)))
.append($('<td class="mr_td_right">')
.append(mal.content.getCbSetting(statusM, 'mr_xsm_' + statusM, false)))
.appendTo(tbody);
});
var list = $('.mr_list', mal.content.settings).empty()
.append(tableExclude)
.append(tableIgnore);
if (!mal.entries.isUpdating()) {
list.append(buttons);
} else {
list.append(buttons.empty()
.append('<div id="mr_warning">The settings can\'t be changed during relations calculation!</div>')
.append($('<input class="inputButton" value="OK" type="button">').click(function() {
mal.fancybox.start(function() {
mal.content.body.show();
return true;
});
}))
);
}
mal.content.settings.show();
},
getLiLeft: function(id) {
return $('<li>')
.append(mal.content.getEntryLink(id, mal.entries.getTitle(id)));
},
getLiRight: function(id) {
return $('<li>')
.append(mal.content.getHideButton('window.hideRelation(' + id + ');', 'Hide this relation'))
.append(mal.content.getEntryLink(id, mal.entries.getTitle(id)));
},
getCbSetting: function(str, id, state) {
id = id.toHtmlId();
state = state.toString();
return $('<div class="mr_checkbox">')
.append($('<input name="' + id + '" id="' + id + '" type="checkbox">')
.prop('checked', mal.loadSetting(id, state) !== 'false'))
.append('<label for="' + id + '">' + str + '</label>');
},
getEntryLink: function(id, title) {
return $('<a title="' + title + '"href="' +
'/' + mal.type + '/' + id + '" target="_blank">' + title + '</a>');
},
getEntryWarning: function() {
return $('<div class="mr_warning"><span>Wrong status or ' +
(mal.type === 'anime' ? 'episode' : 'vol./chap.') + ' count</span></div>');
},
getHideButton: function(func, title) {
return $('<div class="mr_hide"><span><a href="javascript:void(0);" title="' +
title + '" onclick="' + func + '">x</a></span></div>');
},
getMoreLink: function() {
var result = $('<td colspan="2">');
$('<a class="mr_more" href="javascript:void(0);">show more</a>').click(function() {
var tr = $(this).closest('tr');
tr.prev('.mr_tr_data').removeClass('mr_tr_collapsed');
tr.remove();
}).appendTo(result);
return result;
},
updateLineCount: function() {
$('tfoot td.mr_td_left .mr_count span', mal.content.list).text(
'Total: ' + $('.relTable td.mr_td_left li', mal.content.list).length + ', ' +
'Visible: ' + $('.relTable tbody:not([style*="display: none"]) td.mr_td_left li', mal.content.list).length
);
$('tfoot td.mr_td_right .mr_count span', mal.content.list).text(
'Total: ' + $('.relTable td.mr_td_right li', mal.content.list).length + ', ' +
'Visible: ' + $('.relTable td.mr_td_right li:not([style*="display: none"])', mal.content.list).length
);
}
};
mal.hideRelation = function(id, save) {
var li = $('td.mr_td_right li[id="mr_li_' + id + '"]', mal.content.list);
if (li.length === 0) {
if (mal.entries.hidden.hasOwnProperty(id)) {
delete mal.entries.hidden[id];
}
if (save) {
mal.entries.saveHidden();
}
return;
}
var row = li.hide().closest('tbody');
var lSize = $('td.mr_td_left li', row).length;
var rSize = $('td.mr_td_right li:not([style*="display: none;"])', row).length;
row.toggle(rSize > 0);
if (lSize <= 5 && rSize <= 5) {
$('a.mr_more', row).trigger('click');
}
mal.entries.hidden[id] = true;
if (save) {
mal.entries.saveHidden();
var count = Object.keys(mal.entries.hidden).length;
$('#mr_undelete_num', mal.content.list).text(count);
$('#mr_undelete_msg', mal.content.list).toggle(count > 0);
}
};
mal.hideHiddenRelations = function(save) {
mal.showHiddenRelations(false);
$.each(mal.entries.hidden, function(id) {
mal.hideRelation(id, false);
});
var count = Object.keys(mal.entries.hidden).length;
$('#mr_undelete_num', mal.content.list).text(count);
$('#mr_undelete_msg', mal.content.list).toggle(count > 0);
if (save) {
mal.entries.saveHidden();
}
};
mal.showHiddenRelations = function(save) {
$('#mr_undelete_msg', mal.content.list).hide();
$('li[id^="mr_li_"]', mal.content.list).show();
$('tbody', mal.content.list).show();
if (save) {
mal.entries.clearHidden();
mal.entries.saveHidden();
}
};
function encodeKey(key) {
return (mal.version + '#' + mal.name + '#' + mal.type + '#' + key);
}
mal.loadValue = function(key, value) {
try {
value = JSON.parse(localStorage.getItem(encodeKey(key))) || value;
}
finally {
return value;
}
};
mal.saveValue = function(key, value) {
localStorage.setItem(encodeKey(key), JSON.stringify(value));
};
mal.loadSetting = function(key, value) {
try {
value = JSON.parse(localStorage.getItem('global#' + key)) || value;
}
finally {
return value;
}
};
mal.saveSetting = function(key, value) {
localStorage.setItem('global#' + key, JSON.stringify(value));
};
window.hideRelation = function(id) {
mal.hideRelation(id, true);
mal.content.updateLineCount();
};
window.showHiddenRelations = function() {
mal.showHiddenRelations(true);
mal.content.updateLineCount();
};
String.prototype.toHtmlId = function() {
return this.trim().toLowerCase().replace(/\s/g, '_').replace(/[^\w]/g, '_');
};
String.prototype.toHtmlStr = function() {
return this.trim().replace(/"/g, '"');
};
$('<style type="text/css">').html(
'div#mr_fancybox_wrapper { position: fixed; width: 100%; height: 100%; top: 0; left: 0; background: rgba(102, 102, 102, 0.3); z-index: 99990; }' +
'div#mr_fancybox_inner { width: ' + mal.settings.width + 'px !important; height: ' + mal.settings.height + 'px !important; overflow: hidden; }' +
'div#mr_fancybox_outer { position: absolute; display: block; width: auto; height: auto; padding: 10px; border-radius: 8px; top: 80px; left: 50%; margin-top: 0 !important; margin-left: ' + (-mal.settings.width/2) + 'px !important; background: #fff; box-shadow: 0 0 15px rgba(32, 32, 32, 0.4); z-index: 99991; }' +
'div.mr_body { text-align: center; width: 100%; height: 100%; padding: 42px 0 0; box-sizing: border-box; }' +
'div#mr_body { padding-top: 65px; }' +
'div.mr_body a, div.mr_body a:visited { color: #1969cb; text-decoration: none; }' +
'div.mr_body a:hover { color: #2d7de0; text-decoration: underline; }' +
'div.mr_body #mr_body_title { position: absolute; top: 10px; left: 10px; width: ' + mal.settings.width + 'px; font-size: 16px; font-weight: normal; text-align: center; margin: 0; border: 0; }' +
'div.mr_body #mr_body_title:after { content: ""; position: absolute; left: 0; bottom: -14px; width: 100%; height: 8px; border-top: 1px solid #eee; background: center bottom no-repeat radial-gradient(#f6f6f6, #fff 70%); background-size: 100% 16px; }' +
'div.mr_body #mr_panel { position: absolute; top: 50px; left: 10px; text-align: left; width: ' + mal.settings.width + 'px; height: 2em; margin: 0 0 1em; }' +
'div.mr_body #mr_links { float: right; }' +
'div.mr_body p#mr_notfound { margin: 10px 0; }' +
'div.mr_body #mr_undelete { background-color: #fff; padding: 0; margin: 0; }' +
'div.mr_body #mr_undelete_msg { margin: 10px 0; font-weight: normal; text-align: center; line-height: 20px; font-size: 13px; }' +
'div.mr_body .mr_list { width: 100%; height: 100%; overflow-x: hidden; overflow-y: auto; margin: 0 auto; border: 1px solid #eee; box-sizing: border-box; }' +
'div.mr_body .relTable { border: none; }' +
'div.mr_body .relTable thead { background-color: #f5f5f5; }' +
'div.mr_body .relTable th { background-color: transparent; width: 50%; padding: 5px 0 5px 6px; font-size: 12px; font-weight: bold; text-align: left; line-height: 20px !important; box-shadow: none; }' +
'div.mr_body .relTable tbody { background-color: #fff; }' +
'div#mr_body .relTable tbody:hover { background-color: #f5f5f5; }' +
'div#mr_body .relTable tbody tr:first-of-type td { box-shadow: 0px 1em 1em -1em #ddd inset; }' +
'div.mr_body .relTable td { background-color: transparent; width: 50%; padding: 5px 0 5px 6px; font-size: 13px; font-weight: normal; text-align: left; line-height: 20px !important; vertical-align: top; }' +
'div.mr_body .relTable td div span { line-height: 20px !important; }' +
'div.mr_body .relTable td ul { list-style-type: none; margin: 0; padding: 0; }' +
'div.mr_body .relTable tr.mr_tr_collapsed td ul { height: 100px; overflow-y: hidden; }' +
'div.mr_body .relTable td ul li { width: 100%; padding: 0; margin: 0; }' +
'div.mr_body .relTable td ul li > a { display: block; width: 295px !important; line-height: 20px !important; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }' +
'div.mr_body .relTable td.mr_td_left ul li > a { width: 310px !important; }' +
'div.mr_body .relTable tfoot td { border-top: 1px solid #f5f5f5; }' +
'div.mr_body .relTable td .mr_count { color: #666; font-size: 11px; font-weight: normal; text-align: left; }' +
'div.mr_body .relTable td .mr_warning { width: 260px; color: #e43; font-size: 12px; font-weight: bold; text-align: left; }' +
'div.mr_body .relTable td .mr_hide { display: inline-block !important; width: 15px; float: right; text-align: left; font-size: 11px; }' +
'div.mr_body .relTable td .mr_hide a { color: #888 !important; line-height: 20px !important; font-style: normal !important; text-decoration: none !important; }' +
'div.mr_body .relTable tr.mr_tr_more td { padding: 0 0 2px 0; }' +
'div.mr_body .relTable td .mr_more { display: block !important; text-align: center; color: #c0c0c0 !important; font-style: normal !important; font-size: 0.9em; text-decoration: none !important; }' +
'div.mr_body .relTable .mr_checkbox > * { vertical-align: middle; }' +
'div.mr_body .relTable .mr_comment { background-color: #f6f6f6; border: 1px solid #ebebeb; font-size: 11px; line-height: 16px; padding: 1px 4px; }' +
'div.mr_body .mr_buttons { position: absolute; bottom: 13px; width: 100%; text-align: center; padding: 5px 10px; box-sizing: border-box; }' +
'div.mr_body .mr_buttons > .inputButton { margin: 2px 5px !important; font-size: 12px; }' +
'div#mr_settings .relTable { margin-bottom: 10px; }' +
'div#mr_settings div#mr_warning { font-size: 12px; font-weight: bold; color: #d33; margin-bottom: 2px; }'
).appendTo('head');
main();
}(jQuery));