// $Id: imdbweaver.user.js 781 2014-06-26 23:26:56Z Chris $
// -----------------------------------------------------------------------------
// This is a Greasemonkey user script.
// To use it, first install Greasemonkey: http://www.greasespot.net/
// Then restart Firefox and revisit this script
// From the Firefox menu select: Tools -> Install User Script
// Accept the default configuration and install
// Now whenever you visit imdb.com you will see extra functionality
// Documentation here: http://refactoror.net/greasemonkey/imdbWeaver/doc.html
// -----------------------------------------------------------------------------
// ==UserScript==
// @name IMDb Weaver
// @moniker iwvr
// @namespace http://refactoror.net/
// @description Enhances the content of imdb pages by correlating information from related pages.
// @version 0.3.8.2
// @author Chris Noe
// @include imdb.com/*
// @include *.imdb.com/*
//---------
// @exclude *.imdb.com/images/*
// @exclude *doubleclick*
// @exclude *google_afc*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_log
// @grant GM_xmlhttpRequest
// @downloadURL https://update.greasyfork.icu/scripts/5058/IMDb%20Weaver.user.js
// @updateURL https://update.greasyfork.icu/scripts/5058/IMDb%20Weaver.meta.js
// ==/UserScript==
var dm = new DomMonkey({
name : "IMDb Weaver"
,moniker : "iwvr"
,version : "0.3.8.2"
});
// The values listed here are the first-time-use defaults
// They have NO EFFECT once this script is installed.
prefs.config({
"ajaxOperationLimit": 10
,"all-topnav-isExpanded": true
,"highlightTitleTypes": true
,"firstMatchAccessKey": "A"
,"name-headshotMagnifier": true
,"name-ShowAge": true
,"name-ShowAgeAtTitleRelease": true
,"prefsMenuAccessKey": "P"
,"prefsMenuPosition": "BR"
,"prefsMenuVisible": true
,"removeAds": true
,"title-attributes": true
,"title-headshotMagnifier": true
,"title-headshotMagnification": 12
,"title-ShowAges": true
,"title-StartResearch": true
});
var titleHighlighers = {
Khaki: ["(V)"] // direct to video, no theatrical release
,Lavender: ["(TV)", "TV series", "TV mini-series", "TV episode"]
,Pink: ["(VG)"] // video game
};
// --------------- Page handlers ---------------
tryCatch(dm.metadata["moniker"], function () {
if (isStaticPageRequest()) {
// a request from a DocumentContainer object
log.debug("~~~~~ Static page request, no page processing: " + dm.xdoc.location.href);
// TBD: need to add "ajaxstatic" param to all URLs to prevent page graph recursion
dm.xdoc.foreachNode(
"//a[contains(@href, 'imdb.com')]",
function(a) {
a.href = ajaxstaticUrl(a.href);
log.debug("In " + dm.xdoc.location.href + " :: " + a.href);
}
);
return;
}
if (window != window.top) {
log.debug("subordinate window, no page processing: " + document.location.href);
return; // this is a subordinate window (iframe, etc)
}
if (dm.xdoc.location.href.match(/\/title\/tt\d+\/?/)) enhanceTitlePage();
else if (dm.xdoc.location.href.match(/\/name\/nm\d+\/?/)) enhanceNamePage();
else if (dm.xdoc.location.href.match(/\/find\?/)) enhanceFindPage();
else if (dm.xdoc.location.href.match(/\/updates\/history/)) enhanceUpdatePage();
else {
log.info("IMDb generic page");
extendImdbDocument(dm.xdoc);
enhanceImdbPage(dm.xdoc);
}
});
function isStaticPageRequest() {
return dm.xdoc.location.href.match("ajaxstatic");
}
var titleDoc;
var nameDoc;
// --------- Annoying intervening Ad pages ---------
function expediteAdPage()
{
var i = location.href.indexOf("dest=");
var redirUrl = "http:" + location.href.substring(i + 12);
log.info("Ad page, skipping immediately to: " + redirUrl);
// location.href = redirUrl;
}
// --------------- Title Page handler ---------------
function enhanceTitlePage()
{
log.info("IMDb Title page");
titleDoc = extendImdbTitleDocument(dm.xdoc);
enhanceImdbPage(dm.xdoc);
dispatchFeature("removeAds", function()
{
titleDoc.removeAds();
});
// Gallery Magnifier
// titleDoc.foreachNode("//div[@class='media_strip_thumbs']//img[contains(@src, '._V1._CR')]", function(img) {
// // substitute the larger image
// img.src = img.src.replace("_V1._CR75,0,300,300_SS90_.jpg", "_V1._SY400_SX600_.jpg");
// img.style.cssFloat = "none";
// var a = img.selectNode("ancestor::a[1]");
// var wrapper_div = a.wrapIn("div", {className: "iwvr_gallery_pic"} );
// wrapper_div.style.minWidth = img.clientWidth;
// });
// titleDoc.addStyle(
// "div.iwvr_gallery_pic:hover a img {\n"
// + " height: auto;\n"
// + " width: auto;\n"
// + " position: absolute;\n"
// + " margin-top: +100px;\n"
// + " margin-left: -25px;\n"
// + "}\n"
// );
// createImageMagnifiers(titleDoc, "_V1._CR", /_CR[0-9,_]*/, "_SX600_SY400_");
dispatchFeature("title-headshotMagnifier", function()
{
var mag = prefs.get("title-headshotMagnification") || 12;
var pixels = mag * 32;
var fileSfx = "_V1._SX" + pixels + "_SY" + pixels + "_";
createImageMagnifiers(titleDoc, "_V1._SX23_SY30_", /_SX\d+_SY\d+_/, fileSfx);
});
dispatchFeature("title-attributes", function()
{
addTitleAttrs();
});
// Display rating/runtime/language directly below title
function addTitleAttrs()
{
var titleAttrs = new Array();
var cert = titleDoc.getCertification();
if (cert != null) {
titleAttrs.push(cert);
}
var runtime = titleDoc.getRuntime();
if (runtime != null) {
var rt = runtime + " min";
if (runtime > 60) {
rt += " (" + formatHoursMinutes(runtime) + ")";
}
titleAttrs.push(rt);
}
var lang = titleDoc.getLanguage();
if (lang != null) {
titleAttrs.push(lang);
}
var languages = [];
titleDoc.foreachNode("//a[contains(@href,'/language/')]", function(lang_a) {
languages.push(lang_a.textContent);
});
titleAttrs.push(languages.join("/"));
var title_div = titleDoc.selectNodeNullable("//div[@id='tn15title']");
if (titleAttrs.length > 0) {
title_div.appendChildText(titleAttrs.join(", "));
}
}
function formatHoursMinutes(min)
{
var m = "0" + min % 60; // (extra leading zero)
var h = (min - m) / 60;
return h + ":" + m.substring(m.length - 2);
}
addCastToolbar();
function addCastToolbar()
{
var toolbar_span = titleDoc.createXElement("span", {id: "iwvr_casttoolbar"} );
var quotedTitle = '"' + titleDoc.getTitle() + '"';
dispatchFeature("title-ShowAges", function()
{
var showCastAges_button = titleDoc.createXElement("button", {id: "iwvr_showCastAges"} );
with (showCastAges_button) {
className = "iwvr_button";
textContent = "Show Ages";
title = "Show cast ages when " + quotedTitle + " was released in " + titleDoc.getTitleYear();
addEventListener('click', showCastAges, false);
}
with (toolbar_span) {
appendChildTextNbsp(3);
appendChild(showCastAges_button);
}
});
dispatchFeature("title-StartResearch", function()
{
var startResearch_button = titleDoc.createXElement("button", {id: "iwvr_startReasearch"} );
with (startResearch_button) {
className = "iwvr_button";
addEventListener('click', startResearch, false);
textContent = "Start Research...";
title = "Mine additional information about " + quotedTitle;
}
with (toolbar_span) {
appendChildTextNbsp(3);
appendChild(startResearch_button);
}
});
var cast_table = titleDoc.getCast_table();
if (cast_table != null) {
cast_table.prependSibling(toolbar_span);
}
}
function showCastAges(event)
{
// prevent second execution on same page
event.target.removeEventListener('click', showCastAges, false);
// process each cast member name
var nodeCount = 0;
var ajaxOperationLimit = prefs.get("ajaxOperationLimit");
titleDoc.foreachCastMember_tr
(
"//td[@class='nm']"
+ titleDoc.CAST_NAMES_A
+ "[not(following-sibling::span[@id='iwvr_age'])]",
function(a_name)
{
if (nodeCount < ajaxOperationLimit || ajaxOperationLimit == -1)
{
var titleDC = new DocumentContainer();
log.debug("%%%%% loadFromSameOrigin: " + a_name.href);
titleDC.loadFromSameOrigin(
// extract info from the cast member page
a_name.href,
function(doc) {
var nameDoc = extendImdbNameDocument(doc);
var titleYear = titleDoc.getTitleYear();
if (titleYear == null || titleYear == "") {
age = "?";
}
else {
var age = nameDoc.getAge(titleYear);
if (age == null)
age = "?";
}
// var dd_a = nameDoc.selectNodeNullable("//a[contains(@href, 'death_date=')]");
// if (dd_a)
// dd = " " + dd_a.textContent;
var age_span = titleDoc.createXElement("span", {id: "iwvr_age"} );
age_span.appendChildText(" (" + age + ")");
a_name.appendSibling(age_span);
}
);
}
nodeCount++;
}
)
event.target.textContent = "Show More Ages";
event.target.addEventListener('click', showCastAges, false);
}
function startResearch()
{
if (titleDoc.selectNodeNullable("//div[@id='iwvr_researchItems_dialog']")) {
return; // the dialog is already open
}
// make all names on page draggable
titleDoc.foreachNode(
titleDoc.CAST_NAMES_A,
function(name_a) {
name_a.className = "iwvr_researchable_item";
name_a.addEventListener('draggesture', addResearchItem, false);
}
);
function addResearchItem(event)
{
var original_item_a = event.target.parentNode;
var dragged_item_a = original_item_a.cloneNode(true);
var tip = titleDoc.selectNodeNullable("//*[@id='draggingTip']");
if (tip != null)
tip.remove();
var ul = titleDoc.selectNode("//ul[@id='iwvr_research_itemlist']");
var li = titleDoc.createXElement("li");
li.appendChild(dragged_item_a);
ul.appendChild(li);
original_item_a.className = null;
original_item_a.removeEventListener('draggesture', addResearchItem, false);
};
// present the Research dialog
var researchDialog = new DialogBox(titleDoc, "Research");
var researchDialog_div = researchDialog.createDialog(
"iwvr_researchItems",
"z-index: 999; position: fixed; bottom: 5px; right: 10px;",
{ OK: okResearch, Cancel: cancelResearch }
);
researchDialog.main_td.style.padding = "6px";
with (researchDialog_div)
{
style.fontSize = "10pt";
style.fontFamily = "Arial, Helvetica, sans-serif";
researchDialog_div.style.overflow = "auto";
var sel = titleDoc.createSelect(
"Research",
{ name: "researchmode" },
{ tic: "Titles in common" }, "tic");
appendChild(sel);
var scroll_div = titleDoc.createXElement("div");
with (scroll_div) {
with (style) {
border = "1px inset Black"; margin = 4; padding = 5;
width = 200; height = 60; overflow = "auto";
}
var items_ul = titleDoc.createXElement("ul", {id: "iwvr_research_itemlist"} );
with (items_ul.style) {
marginTop = "0px"; paddingTop = "0px";
marginLeft = "0px"; paddingLeft = "1em";
}
appendChild(items_ul);
var tip_div = titleDoc.createXElement("div");
tip_div.id = "draggingTip";
with (tip_div.style) { color = "Gray";
width = "100%"; textAlign = "center";
height = "85%"; verticalAlign = "middle";
}
tip_div.appendChildText("Drag Names Here");
appendChild(tip_div);
}
appendChild(scroll_div);
var includes_div = titleDoc.createTopicDiv("Include");
includes_div.style.borderColor = "DarkGreen";
// includes_div.style.backgroundColor = "#66CC33";
with (includes_div.contentElement)
{
appendChild(titleDoc.createCheckbox(
"Individual Episodes",
{id: "include-episodes"},
false
));
}
appendChild(includes_div);
var excludes_div = titleDoc.createTopicDiv("Exclude Categories");
excludes_div.style.borderColor = "#AA0000";
// excludes_div.style.backgroundColor = "#FF3300";
with (excludes_div.contentElement)
{
appendChild(titleDoc.createCheckbox(
"Self",
{id: "exclude-self", title: "Examples: Oprah, Letterman"},
true
));
appendChildElement("br");
appendChild(titleDoc.createCheckbox(
"Archive Footage",
{id: "exclude-archive-footage"},
true
));
}
appendChild(excludes_div);
}
}
function okResearch(doc)
{
var titleDoc = extendImdbTitleDocument(doc);
// get URLs of selected research items
var peopleUrls = new Array();
titleDoc.foreachNode("//ul[@id='iwvr_research_itemlist']//a", function(name_a) {
peopleUrls.push(name_a.href);
});
var includeEpisodes = titleDoc.selectNode("//input[@id='include-episodes']").checked;
var omitCategories = new Array();
if (titleDoc.selectNode("//input[@id='exclude-self']").checked)
omitCategories.push("self");
if (titleDoc.selectNode("//input[@id='exclude-archive-footage']").checked)
omitCategories.push("archive");
endResearch(titleDoc);
if (peopleUrls.length > 0) {
correlatePeople(peopleUrls, includeEpisodes, omitCategories);
}
}
function cancelResearch(doc)
{
var titleDoc = extendImdbTitleDocument(doc);
endResearch(titleDoc);
}
function endResearch(titleDoc)
{
// remove class="iwvr_researchable_item"
titleDoc.foreachNode(
titleDoc.CAST_NAMES_A,
function(name_a) {
name_a.className = null;
}
);
}
var DEFAULT_JOB_ABBREVS = {
"A": "Actor",
"A'": "Actress",
"D": "Director",
"P": "Producer",
"PD": "Production Designer",
"W": "Writer",
"C": "Composer",
"ST": "Soundtrack",
"AD": "Art Department",
"MC": "Miscellaneous Crew",
"S": "Self",
"Ar": "Archive Footage"
};
// gather credits for specified people and
// determine what titles they have in common
function correlatePeople(urlList, includeEpisodes, omitCatList)
{
// foreach (var User in Users)
// {
//
[ActionSpan(User)]
// if (User.IsAnonymous)
// {
// [RenderGravatar(User)]
// [UserRepSpan(User)]
[UserFlairSpan(User)]
// }
// else
// {
// anonymous
// }
// }
var jobAbbrevs = new AbbreviationMap(DEFAULT_JOB_ABBREVS);
var jointCredits_a = new Array();
var nameSet_a = new Array();
withDocuments(
urlList, // gather credits from each person's page
function(doc) {
var nameDoc = extendImdbNameDocument(doc);
var name_a = nameDoc.getName_a();
nameSet_a.push(name_a);
jointCredits_a = jointCredits_a.concat(
nameDoc.getCredits_a( {imdbName_a: name_a}, includeEpisodes, omitCatList)
);
},
function(docList)
{
// sort combined credits (by url)
sortBy(jointCredits_a, ["href"] );
sortBy(nameSet_a, ["textContent"] );
var creditsInCommon_a = new Array();
var soloCreditCount = 0;
foreachGrouping(jointCredits_a, "href", function(creds_a)
{
if (creds_a.length < 2) {
soloCreditCount++;
return; // exclude where not in common with anybody else
}
var nameJobMap = new Array();
for (var c in creds_a) {
var jobList = jobAbbrevs.registerList(creds_a[c].categoryList);
nameJobMap[creds_a[c].propertySet.imdbName_a] = jobList;
}
var combo_a = creds_a[0].cloneNode(true);
combo_a.nameJobMap = nameJobMap;
creditsInCommon_a.push(combo_a);
});
// sort combined credits (by title)
sortBy(creditsInCommon_a, ["textContent"] );
// create information popup
var resultsWindow = new DialogBox(titleDoc, "Research Results");
var resultsWindow_div = resultsWindow.createDialog(
"iwvr_creditsInCommon",
"z-index: 999; position: fixed; left: 15px; top: 20px;",
{ X: noop }
);
resultsWindow.main_td.style.padding = "6px";
with (resultsWindow_div)
{
style.maxWidth = window.innerWidth - 70;
style.maxHeight = window.innerHeight - 90;
// style.overflow = "auto";
// construct the data table
var i = 1;
var table = titleDoc.createXElement("table", {className: "iwvr_results"} );
var caption = titleDoc.createXElement("caption");
caption.appendChildText("Credits in common", ["b"]);
table.appendChild(caption);
var cellAttrList = [
{className: "iwvr_results bulletcol"},
{className: "iwvr_results titlecol"}
].concat(
dup(nameSet_a.length, {className: "iwvr_results datacol"} )
);
var thead = titleDoc.createXElement("thead");
thead.appendTableRow([null, "Title"].concat(nameSet_a), cellAttrList);
table.appendChild(thead);
var tbody = titleDoc.createXElement("tbody");
for (var c in creditsInCommon_a)
{
var rowSet = initArrayIndices(2 + nameSet_a.length);
rowSet[0] = i + ")";
var credit_span = titleDoc.createXElement("span");
if (creditsInCommon_a[c].seriesTitle != null) {
credit_span.appendChildText(creditsInCommon_a[c].seriesTitle);
}
credit_span.appendChild(creditsInCommon_a[c]);
rowSet[1] = credit_span;
for (var name_a in creditsInCommon_a[c].nameJobMap) {
var jobList = creditsInCommon_a[c].nameJobMap[name_a];
var col = 2 + parseInt(arrayIndexOf(nameSet_a, name_a));
rowSet[col] = jobList.sort().join(", ");
}
tbody.appendTableRow(rowSet, cellAttrList);
i++;
}
var legend_div = jobAbbrevs.toLegend("div",
{ className: "iwvr_results_legend" } );
tbody.appendTableRow(
[ legend_div ],
[ { colSpan: (2 + nameSet_a.length) } ]
);
table.appendChild(tbody);
appendChild(table);
}
}
);
}
}
function dup(count, value) {
var returnValue = new Array();
for (var i = 0; i < count; i++) {
returnValue.push(value);
}
return returnValue;
}
// --------------- Name Page handler ---------------
function enhanceNamePage()
{
log.info("IMDb Name page");
var nameDoc = extendImdbNameDocument(dm.xdoc);
enhanceImdbPage(dm.xdoc);
dispatchFeature("removeAds", function()
{
nameDoc.removeAds();
});
dispatchFeature("name-headshotMagnifier", function()
{
createImageMagnifiers(nameDoc, "_V1._SX32_", /_SX\d+_/, "_SX640_SY720_");
});
var birthInfo_div = nameDoc.selectNodeNullable(
"//a[contains(@href, 'birth_year=')]/ancestor::div[1]");
var age = nameDoc.getAge();
var deathInfo_div = nameDoc.selectNodeNullable(
"//a[contains(@href, 'death_date=')]/ancestor::div[1]");
var ageDeath = nameDoc.getAgeDeath();
dispatchFeature("highlightTitleTypes", function()
{
// first defeat the site's regular even/odd row highlighting
nameDoc.foreachNode("//tbody[contains(@class, 'row-filmo-')]", function(tbody)
{
tbody.style.backgroundColor = "White";
});
// now add custom highlighting
highlightTitleTypes(titleHighlighers);
function highlightTitleTypes(hSpecs)
{
for (var color in hSpecs) {
var matchStrs = hSpecs[color];
for (var i in matchStrs) {
nameDoc.foreachNode([
"//text()[contains(., '" + matchStrs[i] + "')]//ancestor-or-self::tbody[1]",
"//text()[contains(., '" + matchStrs[i] + "')]//ancestor-or-self::li[1]"
],
function(item)
{
item.style.backgroundColor = color;
});
}
}
}
});
dispatchFeature("name-ShowAge", function()
{
if (birthInfo_div == null || age == null) {
log.info("name-ShowAge: no birth year");
}
else {
birthInfo_div.appendChildElement("br");
birthInfo_div.appendChildText(
((nameDoc.getDeathDetails_div() != null) ? "would be " : "is ")
+ age + " years old"
);
}
if (deathInfo_div == null || ageDeath == null) {
log.info("name-ShowAge: no death year");
}
else {
deathInfo_div.appendChildElement("br");
deathInfo_div.appendChildText(
"at age " + ageDeath
);
}
});
dispatchFeature("name-ShowAgeAtTitleRelease", function()
{
// if we are arriving at a person's page from a specific title page
if (dm.xdoc.referrer.match("imdb.com/title"))
{
// trim all but base title URL, (could be arriving from other detail pages)
dm.xdoc.referrer.match(/(.*tt\d*)/);
var referrerUrl = RegExp.$1;
var nameDC = new DocumentContainer();
nameDC.loadFromSameOrigin(
// use info from the referring title page
referrerUrl,
function(doc)
{
var titleDoc = extendImdbTitleDocument(doc);
var titleYear = titleDoc.getTitleYear();
var ageThen;
if (titleYear == null || titleYear == "") {
ageThen = "?";
}
else {
ageThen = nameDoc.getAge(titleYear);
if (ageThen == null)
ageThen = "?";
}
if (birthInfo_div == null || age == null) {
log.info("name-ShowAgeAtTitleRelease: no birth year");
return;
}
birthInfo_div.appendChildElement("br");
var t;
if (titleYear > (new Date()).getFullYear())
t = '(will be ' + ageThen
+ ' when "' + titleDoc.getTitle()
+ '" releases in ' + titleYear + ')'
;
else
t = '(was ' + ageThen
+ ' when "' + titleDoc.getTitle()
+ '" was released in ' + titleYear + ')'
;
birthInfo_div.appendChildText(t);
}
);
}
else {
if (dm.xdoc.referrer == "") {
log.warn("The name-ShowAgeAtTitleRelease option is enable"
+ " , but document.referrer is empty. REFERRER MAY BE DISABLED."
);
}
}
});
}
// --------------- Find Page handler ---------------
function enhanceFindPage()
{
log.info("IMDb Find page");
var findDoc = extendImdbNameDocument(dm.xdoc);
enhanceImdbPage(dm.xdoc);
// assign access key to first matching item
var accessKey = prefs.get("firstMatchAccessKey");
if (accessKey != null)
{
// first text link to "/rg/find-", that is inside a TD
var firstMatch_a = findDoc.selectNodeNullable(
"//td/a[not(img)][contains(@onclick, '/rg/find-')][1]");
if (firstMatch_a != null) {
firstMatch_a.accessKey = accessKey.toUpperCase();
var itemNum_td = firstMatch_a.selectNodeNullable("preceding::td[1]");
if (itemNum_td != null) {
var span = findDoc.createXElement("span");
span.appendChildText("[" + accessKey + "]");
firstMatch_a.href.match(/\/(\w+)\//);
var linkType = RegExp.$1; // "title" or "name"
span.title = "Alt-Shift-" + accessKey + " to go to this " + linkType;
span.style.fontWeight = "bold";
itemNum_td.innerHTML = "";
itemNum_td.appendChild(span);
}
}
}
dispatchFeature("highlightTitleTypes", function()
{
highlightTitleTypes(titleHighlighers);
function highlightTitleTypes(hSpecs)
{
for (var color in hSpecs) {
var matchStrs = hSpecs[color];
for (var i in matchStrs) {
findDoc.foreachNode("//a[contains(@onclick, '/rg/find-title')]/ancestor::table[1]//text()[contains(., '" + matchStrs[i] + "')]"
+ "//ancestor-or-self::td[1]", function(item)
{
item.style.backgroundColor = color;
});
}
}
}
});
// // Poster Magnifier
// findDoc.foreachNode("//a[contains(@href, 'title-tiny')]/img", function(img) {
// // substitute the larger image
// img.src = img.src.replace(/(\d)t\.(jpg|png|gif)$/, "$1m.$2");
// img.className = "iwvr_headshot";
// img.height = null;
// img.width = null;
// });
// titleDoc.addStyle(
// "img.iwvr_headshot { height: 32px; width: 22px; }\n"
// + "td:hover img.iwvr_headshot {\n"
// + " height: auto;\n"
// + " width: auto;\n"
// + " position: absolute;\n"
// + " margin-top: -59px;\n"
// + " margin-left: -125px;\n"
// + "}\n"
// );
}
// --------------- Find Page handler ---------------
function enhanceUpdatePage()
{
log.info("IMDb updates page");
// assign access key to first matching item
var accessKey = prefs.get("firstMatchAccessKey");
if (accessKey)
{
var updateDoc = extendDocument(dm.xdoc);
// first update item
var firstMatch_a = updateDoc.selectNodeNullable("(//a[contains(@href, 'update?load=')])[1]");
if (firstMatch_a) {
firstMatch_a.accessKey = accessKey.toUpperCase();
var item_td = firstMatch_a.selectNodeNullable("ancestor::td[1]");
if (item_td) {
var span = updateDoc.createXElement("span");
span.appendChildText("[" + accessKey + "]");
span.style.fontWeight = "bold";
item_td.prependChild(span);
}
}
}
}
// --------------- Find Page handler ---------------
function enhanceImdbPage(imdbDoc)
{
log.info("enhanceImdbPage");
imdbDoc.removeAds();
var navbar_div = imdbDoc.selectNodeNullable("//div[@id='nb20']");
if (navbar_div != null) {
navbar_div.makeCollapsible("all-topnav-isExpanded", true);
}
}
// --------------- Title Page extensions ---------------
function extendImdbTitleDocument(titleDoc)
{
if (titleDoc == null)
return null;
extendImdbDocument(titleDoc);
titleDoc.removeAds_super = titleDoc.removeAds;
titleDoc.removeAds = function()
{
this.removeAds_super();
titleDoc.foreachNode([
"//div[@id='tn15shopbox']"
,"//div[@id='tn15adrhs']"
,"//div[starts-with(@id, 'banner')]"
,"//div[@id='tn15tc']"
// ,"//a[contains(href, NAVSTRIP)]"
], function(node) {
node.remove();
log.debug("Removing ad element: <" + node.tagName + " id=" + node.id);
}
);
}
titleDoc.getTitle = function()
{
var title;
var poster_a = this.selectNodeNullable("//a[@name='poster']");
if (poster_a != null) {
title = poster_a.title;
}
else {
var title_span = this.selectNode("//title/text()");
title = title_span.textContent.replace(/\(\d\d\d\d\)/, "");
}
return title.stripQuoteMarks().normalizeWhitespace();
};
titleDoc.getTitle_a = function()
{
var a = this.createXElement("a");
a.href = this.location.href;
a.appendChildText(this.getTitle());
return a;
}
titleDoc.getTitleYear = function() {
var year;
var airDate = this.selectTextContent(
"//*[text()='Original Air Date:']/following-sibling::*[1]/text()");
if (airDate != null) {
airDate.match(/\d+ \w+ (\d\d\d\d)/);
year = RegExp.$1;
return year;
}
var year = this.selectTextContent(
"//div[@id='tn15title']//a[contains(@href, '/year/')]/text()");
return year;
};
titleDoc.getUserRating = function() {
var userRating = this.selectTextContent(
"//*[contains(text(), 'User Rating:')]/following-sibling::*[1]");
if (userRating == null)
return null;
userRating = userRating.split("/");
return userRating[0] / userRating[1];
};
titleDoc.getRuntime = function() {
var runtime_text = this.selectNodeNullable(
"//*[text()='Runtime:']/following-sibling::*[1]/text()");
if (runtime_text == null) {
return null;
}
var runtime = runtime_text.textContent.match(/(\d+)/);
return RegExp.$1;
};
titleDoc.getCertification = function(country) {
if (country == null) {
country = "USA";
}
var cert = this.selectTextContent(
"//a[starts-with(@href, concat('/List?certificates=', '" + country + ":'))]");
return cert;
};
titleDoc.getLanguage = function() {
var lang_a = this.selectNodeNullable(
"//a[starts-with(@href, '/Sections/Languages/')]");
if (lang_a == null) {
return null;
}
return lang_a.textContent;
};
titleDoc.CAST_TABLE =
"//table[@class='cast']"
;
titleDoc.CAST_NAMES_A = "//a[starts-with(@href, '/name/')]";
titleDoc.CHARACTER_NAME_A = "//a[@href='quotes']";
titleDoc.foreachCastMember_tr = function(relativeXpath, func)
{
this.foreachNode(
titleDoc.CAST_TABLE
+ "//tr"
+ relativeXpath,
func
);
}
titleDoc.getCast_table = function() {
return this.selectNodeNullable(titleDoc.CAST_TABLE);
};
log.debug(
"extendImdbTitleDocument: "
+ "title='" + titleDoc.getTitle() + "'"
+ ", titleYear=" + titleDoc.getTitleYear()
+ ", userRating=" + titleDoc.getUserRating()
);
return titleDoc;
}
// --------------- Title Page extensions ---------------
function extendImdbNameDocument(nameDoc)
{
if (nameDoc == null)
return null;
extendImdbDocument(nameDoc);
nameDoc.removeAds_super = nameDoc.removeAds;
nameDoc.removeAds = function()
{
this.removeAds_super();
var ad_div = nameDoc.selectNodeNullable("//div[@id='tn15adrhs']");
if (ad_div != null) {
ad_div.remove();
}
}
nameDoc.getName = function() {
return this.selectTextContent("//title");
};
nameDoc.getName_a = function()
{
var a = this.createXElement("a");
a.href = this.location.href;
a.appendChildText(this.getName());
return a;
}
nameDoc.getAge = function(refYear) {
if (refYear == null) {
refYear = (new Date()).getFullYear();
}
var birthYear = this.getBirthYear();
if (birthYear == null) {
return null;
}
var age = refYear - birthYear;
if (isNaN(age)) {
return "??";
}
else {
return age;
}
};
nameDoc.getBirthYear = function() {
var birthYear_a = this.selectNodeNullable(
"//a[contains(@href, 'birth_year=')]"
);
if (birthYear_a == null)
return null;
else
return birthYear_a.textContent;
};
nameDoc.getBirthDetails_end = function() {
var birthDetails_end = this.selectNodeNullable(
"//a[contains(@href, 'birth_year=')]"
+ "/following::br[1]"
);
return birthDetails_end;
}
nameDoc.getAgeDeath = function() {
var deathYear = this.getDeathYear();
if (deathYear == null) {
return null;
}
var ageDeath = deathYear - this.getBirthYear();
if (isNaN(ageDeath)) {
return "??";
}
else {
return ageDeath;
}
};
nameDoc.getDeathYear = function() {
var deathYear_a = this.selectNodeNullable(
"//a[contains(@href, 'death_date=')]"
);
if (deathYear_a == null)
return null;
else
return deathYear_a.textContent;
};
nameDoc.getDeathDetails_div = function() {
return this.selectNodeNullable(
"//*[contains(@href, 'death_date=')]"
+ "//ancestor::div[1]"
);
}
nameDoc.CREDIT_CATEGORIES_A =
"//a[starts-with(@href, '/title/')]"
+ "/ancestor::div[@class='filmo']/descendant::a[@name][1]";
// Get all the credits on this page, grouped by title.
// The list of job categories is attached to each "merged" title reference.
nameDoc.getCredits_a = function(propSet, includeEpisodes, omitCatList)
{
var creditList_a = new Array();
nameDoc.foreachNode( // Director, Writer, Actor, etc
nameDoc.CREDIT_CATEGORIES_A,
function (category_a)
{
var catLabel = category_a.textContent.replace(/:/, "");
if (arrayIndexOf(omitCatList, category_a.name))
return;
var matchCredits = "//a[@name='" + category_a.name + "']"
+ "/following::ol[1]/li/a[1]";
nameDoc.foreachNode( // each credit within a category
matchCredits,
function (credit_a) {
credit_a.category = catLabel;
if (propSet != null) {
credit_a.propertySet = propSet;
}
creditList_a.push(credit_a);
if (includeEpisodes) {
credit_a.foreachNode( // each sub-credit within a credit
"following-sibling::a",
function (subCredit_a) {
subCredit_a.category = catLabel;
if (propSet != null) {
subCredit_a.propertySet = propSet;
}
subCredit_a.seriesTitle = credit_a.textContent;
creditList_a.push(subCredit_a);
}
);
}
});
});
return mergeCreditCategories(creditList_a);
// for each title combine multiple credits into a single object
// with an array property that lists the category names
function mergeCreditCategories(creditList_a)
{
sortBy(creditList_a, ["href"] );
var mergedCredits = new Array();
foreachGrouping(creditList_a, "href", function(creds_a)
{
var catList = new Array();
for (var i in creds_a) {
catList.push(creds_a[i].category);
}
// reuse first element as a protoype
var combined_a = creds_a[0].cloneNode(true);
combined_a.textContent = combined_a.textContent.stripQuoteMarks();
combined_a.category = null;
combined_a.categoryList = catList;
combined_a.propertySet = creds_a[0].propertySet;
mergedCredits.push(combined_a);
});
return mergedCredits;
}
}
log.debug(
"extendImdbNameDocument: "
+ "name='" + nameDoc.getName() + "'"
+ ", birthYear='" + nameDoc.getBirthYear() + "'"
+ ", age=" + nameDoc.getAge()
);
return nameDoc;
}
// --------------- IMDb Page extensions ---------------
function extendImdbDocument(imdbDoc)
{
extendDocument(imdbDoc);
addPrefsButton();
imdbDoc.removeAds = function()
{
log.debug("imdbDoc.removeAds");
this.foreachNode("//div[starts-with(@id, 'swf_')]", function(node) {
node.remove();
log.debug("Removing ad element: <" + node.tagName + " id=" + node.id);
}
);
// log.debug("sweeping for DOUBLECLICK:");
// this.foreachNode("//img[contains(@src, 'doubleclick.net')]/ancestor::d[1]", function(node) {
// node.remove();
// log.debug("Removing DOUBLECLICK ad div");
// }
// );
this.removeFlashAds();
// experimental
// this.foreachNode("//area[@alt='Learn more']", function(div) {
// div.remove();
// log.debug("REMOVING NAVSTRIPE AD");
// }
// );
// this.foreachNode("//a[contains(@href, '_NAVSTRIPE')]//ancestor::div[1]", function(div) {
// div.remove();
// log.debug("REMOVING NAVSTRIPE AD");
// }
// );
this.foreachNode(
"//div[starts-with(@id, 'swf_')]",
function(ad_div) {
log.debug("REMOVING FLOATER AD");
ad_div.hideNode();
}
);
// this.removeFloaterAds();
// imdbDoc.onAppears("//div[starts-with(@id, 'swf_')]", 500, function(ad_div)
// {
// log.debug("Removing floater ad");
// alert("Removing floater ad");
// ad_div.hide();
// });
}
imdbDoc.removeFlashAds = function()
{
this.foreachNode(
"//comment()[contains(., 'FLASH AD BEGINS')]",
function(adbegin_cmt) {
// log.info("COMMENT< '" + adbegin_cmt.textContent + "'");
// var adend_cmt = adbegin_cmt.selectNodeNullable(
// "//following-sibling::comment()[contains(., 'FLASH AD ENDS')][1]");
// if (adend_cmt != null) {
// log.info("COMMENT> '" + adend_cmt.textContent + "'");
// }
var ad_div = adbegin_cmt.nextSibling.nextSibling;
if (ad_div != null) {
log.debug("Removing ad element: <" + ad_div.tagName + " id=" + ad_div.id);
ad_div.parentNode.removeChild(ad_div);
}
}
);
}
// buttons
imdbDoc.addStyle(
".iwvr_button {\n"
+ " font-size: 8pt;\n"
+ " font-family: Helvetica Narrow, sans-serif;\n"
+ "}\n"
);
// research items
imdbDoc.addStyle(
"a.iwvr_researchable_item { background-color: Gold; }\n"
+ "a.iwvr_researchable_item:hover { cursor: crosshair; }\n"
);
// research result grid styles
imdbDoc.addStyle(
".iwvr_results {\n"
+ " padding: 3;\n"
+ " font-family: Arial Narrow, Helvetica Narrow, sans-serif;\n"
+ " font-size: small;\n"
+ "}\n"
+ "table.iwvr_results {\n"
+ " border-collapse: collapse;\n"
+ " font-family: Arial, Helvetica, sans-serif;\n"
+ "}\n"
+ "td.iwvr_results {\n"
+ " border: 1px solid Gray;\n"
+ " padding: 3;\n"
+ "}\n"
+ "div.iwvr_results_legend {\n"
+ " max-width: 100%;\n"
+ " text-align: center;\n"
+ "}\n"
+ "td.bulletcol { text-align: right; }\n"
+ "td.titlecol { text-align: left; }\n"
+ "td.datacol { text-align: center; }\n"
);
return imdbDoc;
}
// --------------- helper functions ---------------
function createImageMagnifiers(theDoc, imgUrlContains, imgUrlRegex, imgUrlReplacement)
{
// Headshot Magnifier
theDoc.foreachNode("//img[contains(@src, '" + imgUrlContains + "')]", function(img) {
if (img.src.indexOf("addtiny.gif") != -1) {
return; // skip place-holders
}
// substitute the larger image
img.src = img.src.replace(imgUrlRegex, imgUrlReplacement);
img.className = "iwvr_headshot";
img.height = null;
img.width = null;
});
theDoc.addStyle(
"img.iwvr_headshot { height: 30px; width: 23px; }\n"
+ "td:hover img.iwvr_headshot {\n"
+ " height: auto;\n"
+ " width: auto;\n"
+ " z-index: 999;\n"
+ " position: fixed;\n"
+ " top: 5%;\n"
+ " right: 5%;\n"
+ "}\n"
);
// zoom visualization graphic
// var zoom_img = dm.xdoc.createXElement("img");
// with (zoom_img.style) {
// position = "absolute";
// top = "614px";
// left = "145px";
// }
// var zoom_img_src = 'data:image/gif;base64,' +
// 'R0lGODlhGACCAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD/////' +
// '/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
// 'AAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBmAABmMwBmZgBm' +
// 'mQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/MwD/ZgD/mQD/zAD/' +
// '/zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNmZjNmmTNmzDNm/zOZADOZ' +
// 'MzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYA' +
// 'mWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZmzGZm/2aZAGaZM2aZZmaZmWaZzGaZ' +
// '/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5kzAJkz' +
// 'M5kzZpkzmZkzzJkz/5lmAJlmM5lmZplmmZlmzJlm/5mZAJmZM5mZZpmZmZmZzJmZ/5nMAJnMM5nMZpnM' +
// 'mZnMzJnM/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wzAMwzM8wzZswzmcwzzMwz' +
// '/8xmAMxmM8xmZsxmmcxmzMxm/8yZAMyZM8yZZsyZmcyZzMyZ/8zMAMzMM8zMZszMmczMzMzM/8z/AMz/' +
// 'M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8zAP8zM/8zZv8zmf8zzP8z//9mAP9mM/9mZv9m' +
// 'mf9mzP9m//+ZAP+ZM/+ZZv+Zmf+ZzP+Z///MAP/MM//MZv/Mmf/MzP/M////AP//M///Zv//mf//zP//' +
// '/ywAAAAAGACCAAAI/wAfCBxIsKBBgggOKlSIIOHChwIbQoTY0OHEgxUvMsyosWBFix0jcgwpcmTIjyA7' +
// 'okx5cSXJBy5JrmRJMabKmSdx3tTZcibNjTwn+pTY06fGoURrDi26VGnTh0iTLowqFSNVp0ihUv1Z8ipQ' +
// 'r1a3Tt1adSDZsl3FGjyLli1NtyzhrpWLkK5ZuCDx5sVbl29atnf1/gUMU69Dw4cNDz67mGxjtYgfe41c' +
// 'WHBlv5QzK75MV7Nlz5g3g+4suvRn06FPq07N2m1gu5YlP+XM+HVt24773sYNlnfWubt9zxYeVHdv47/D' +
// 'HkdudOxy5sU9PoeOEmt04MOVX8e+XXpzodm/2i60bpI8WvHlzeesvv6jzPFMz2t1/z59/JeX8efHT/+l' +
// '/aPygaefSAPCVOCBDwQEADs=';
// zoom_img.src = zoom_img_src;
// titleDoc.body.appendChild(zoom_img);
// zoom_img.hide();
}
// ==================== AbbreviationMap object ====================
function AbbreviationMap(initMap)
{
this.map = initMap;
if (this.map == null) {
this.map = new Array();
}
this.register = function(value)
{
var abbrev = arrayIndexOf(this.map, value);
if (abbrev != null)
return abbrev;
for (var len = 1; len < value.length; len++)
{
var abbrev = value.substring(0, len);
if (this.map[abbrev] == null) {
this.map[abbrev] = value;
return abbrev;
}
}
throw "Can't abbreviate: '" + value + "'";
}
this.registerList = function(theList)
{
var abbrevList = new Array();
for (var i in theList) {
abbrevList.push(this.register(theList[i]));
}
return abbrevList;
}
this.toLegend = function(elemType, attrMap)
{
// var dm.xdoc = extendDocument(document);
var node = dm.xdoc.createXElement(elemType, attrMap);
with (node) {
for (var i in this.map) {
appendChildText(i);
appendChildText(':\u00A0"');
appendChildText(this.map[i]);
appendChildText('"');
appendChildText(' - ');
}
}
return node;
}
}
// ==================== Preferences Dialog ====================
function addPrefsButton()
{
configurePrefsButton(function(prefsMgr, prefsDialog_div)
{
var table = dm.xdoc.createXElement("table");
prefsDialog_div.appendChild(table);
var tr = dm.xdoc.createXElement("tr");
table.appendChild(tr);
var td = dm.xdoc.createXElement("td");
td.style.verticalAlign = "top";
tr.appendChild(td);
with (td)
{
var features_div = dm.xdoc.createTopicDiv("Enabled Features", td);
appendChild(features_div);
with (features_div.contentElement)
{
var genFeatures_div = dm.xdoc.createTopicDiv("All Pages", features_div);
appendChild(genFeatures_div);
with (genFeatures_div.contentElement)
{
appendChild(prefsMgr.createPreferenceInput(
"highlightTitleTypes",
"Highlight titles by type",
"white: theatrical release / gold: direct to video / blue: TV / pink: video game"
));
appendChildElement("br");
appendChild(prefsMgr.createPreferenceInput(
"removeAds",
"Remove advertising",
"Remove advertising"
));
}
var titleFeatures_div = dm.xdoc.createTopicDiv("On Title Pages", features_div);
appendChild(titleFeatures_div);
with (titleFeatures_div.contentElement)
{
appendChild(prefsMgr.createPreferenceInput(
"title-attributes",
"Display title attributes",
"Display rating/runtime/language directly below title"
));
appendChildElement("br");
appendChild(prefsMgr.createPreferenceInput(
"title-ShowAges",
"[Show Ages] button",
"Compute the ages of cast members"
));
appendChildElement("br");
appendChild(prefsMgr.createPreferenceInput(
"title-StartResearch",
"[Start Research] button",
"Open the Research dialog"
));
appendChildElement("br");
appendChild(prefsMgr.createPreferenceInput(
"title-headshotMagnifier",
"Headshot Magnifier",
"Hover mouse to magnify cast pictures"
));
appendChildElement("br");
appendChild(prefsMgr.createPreferenceInput(
"title-headshotMagnification",
"Mag level",
"Magnification factor",
{ size:2, maxLength: 2 }
)).style.marginLeft = "28px";
}
var nameFeatures_div = dm.xdoc.createTopicDiv("On Name Pages", features_div);
appendChild(nameFeatures_div);
with (nameFeatures_div.contentElement)
{
appendChild(prefsMgr.createPreferenceInput(
"name-ShowAge",
"Display age",
"Display current age of the person"
));
appendChildElement("br");
appendChild(prefsMgr.createPreferenceInput(
"name-ShowAgeAtTitleRelease",
"Display age at release",
"Display age at time title was released"
));
}
var findFeatures_div = dm.xdoc.createTopicDiv("On Search Results", features_div);
appendChild(findFeatures_div);
with (findFeatures_div.contentElement)
{
appendChild(prefsMgr.createPreferenceInput(
"firstMatchAccessKey",
"Select first match",
"Alt-Shift keyboard to navigate to first title matched",
{ size:1, maxLength: 1 }
));
}
}
}
var td = dm.xdoc.createXElement("td");
td.style.verticalAlign = "top";
tr.appendChild(td);
with (td) {
appendChild(prefsMgr.constructDockPrefsMenuSection(td));
appendChild(prefsMgr.constructAdvancedControlsSection(td));
var controls_div = dm.xdoc.createTopicDiv("Performance Controls", td);
with (controls_div.contentElement)
{
appendChild(prefsMgr.createPreferenceInput(
"ajaxOperationLimit",
"Background threads",
"Control how many simultaneous background operations are allowed"
+ ", (primarily affects Show Ages)",
{ size:1, maxLength: 2 }
));
}
appendChild(controls_div);
}
// Help link
var docs_div = dm.xdoc.createXElement("div");
prefsDialog_div.appendChild(docs_div);
with (docs_div) {
appendChild(dm.xdoc.createHtmlLink(
"http://refactoror.com/greasemonkey/imdbWeaver/doc.html#prefs",
"Help"
));
align = "center";
style.padding = "3px";
}
});
}
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-= refactoror lib -=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// common logic for the way I like to setup Preferences in my apps
// Requires preferences: prefsMenuAccessKey, prefsMenuPosition, prefsMenuVisible, loggerLevel
function configurePrefsButton(dialogConstructor)
{
// Preferences dialog
GM_registerMenuCommand(dm.metadata["name"] + " Preferences...", openPrefsDialog);
createPrefsButton();
// Prefs dialog
function createPrefsButton()
{
var menuButton = dm.xdoc.createXElement("button", { textContent: "Prefs" });
setScreenPosition(menuButton, prefs.get("prefsMenuPosition"));
if (prefs.get("prefsMenuVisible") == false) {
menuButton.style.opacity = 0; // active but not visibile
menuButton.style.zIndex = -1; // don't block other content
}
with (menuButton) {
id = dm.metadata["moniker"] + "_prefs_menu_button";
title = dm.metadata["name"] + " Preferences";
style.fontSize = "9pt";
addEventListener('click', openPrefsDialog, false);
// accessKey = getDeconflicted("prefsMenuAccessKey", "accessKey");
accessKey = prefs.get("prefsMenuAccessKey");
}
if (dm.xdoc.body != null) {
dm.xdoc.body.appendChild(menuButton);
}
}
function getDeconflicted(prefsName, attrName)
{
var prefValue = prefs.get(prefsName);
var node = xdoc.selectNodeNullable("//*[@" + attrName + "='" + prefValue + "']");
if (node != null) {
log.warn("Conflict: <" + node.nodeName + "> element on this page is already using "
+ attrName + "=" + prefValue);
prefValue = null;
}
return prefValue;
}
// Prefs dialog
function openPrefsDialog(event)
{
var prefsMgr = new PreferencesManager(
dm.xdoc,
dm.metadata["moniker"] + "_prefs",
dm.metadata["name"] + " Preferences",
{ OK: function okPrefs(doc) { prefsMgr.storePrefs(); },
Cancel: noop
}
);
var prefsDialog_div = prefsMgr.open();
if (prefsDialog_div == null)
return; // the dialog is already open
prefsMgr.constructDockPrefsMenuSection = function(contextNode)
{
var prefsDock_div = dm.xdoc.createTopicDiv("Dock [Prefs] Menu", contextNode);
contextNode.style.verticalAlign = "top";
with (prefsDock_div.contentElement)
{
appendChild(prefsMgr.createPreferenceInput(
"prefsMenuVisible",
"Visible",
"Prefs menu button visible on the screen"
));
with (appendChild(prefsMgr.createScreenCornerPreference("prefsMenuPosition"))) {
title = "Screen corner for [Prefs] menu button";
style.margin = "1px 0px 3px 20px";
}
appendChild(prefsMgr.createPreferenceInput(
"prefsMenuAccessKey",
"Access Key",
"Alt-Shift keyboard shortcut",
{ size:1, maxLength: 1 }
));
}
return prefsDock_div;
}
prefsMgr.constructAdvancedControlsSection = function(contextNode)
{
var controls_div = dm.xdoc.createTopicDiv("Advanced Controls", contextNode);
with (controls_div.contentElement)
{
appendChild(prefsMgr.createPreferenceInput(
"loggerLevel",
"Logging Level",
"Control level of information that appears in the Error Console",
null,
log.getLogLevelMap()
));
}
return controls_div;
}
dialogConstructor(prefsMgr, prefsDialog_div);
}
dispatchFeature("sendAnonymousStatistics", function() {
if (getElapsed("sendAnonymousStatistics") < 2000) {
log.debug("--------- SKIPPING COUNTER on rapid fire: " + dm.xdoc.location.href);
return;
}
log.debug("--------- EMBEDDING COUNTER: " + dm.xdoc.location.href);
// var counter_img = document.createElement("img");
// counter_img.id = "refactoror.net_counter";
// counter_img.src = "http://refactoror.net/spacer.gif?"
// + dm.metadata["moniker"] + "ver=" + dm.metadata["version"]
// + "&od=" + GM_getValue("odometer")
// ;
// log.debug(counter_img.src + " :: location=" + document.location.href);
// dm.xdoc.body.appendChild(counter_img);
});
function getElapsed(name) {
var prev_ms = parseInt(GM_getValue(name + "_ms", "0"));
var now_ms = Number(new Date());
GM_setValue(name + "_ms", now_ms.toString());
return (now_ms - prev_ms);
}
}
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-= DOM Monkey -=-=-=-=-=-=-=-=-=-=-=-=
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
/* Parses the script headers into the metadata object.
* Adds constants & utility methods to various javascript objects.
* Initializes the Preferences object.
* Initializes the logger object.
*/
function DomMonkey(metadata)
{
extendJavascriptObjects();
// DM objects provided on the context
this.xdoc = extendDocument(document);
this.metadata = metadata;
// The values listed here are the first-time-use defaults
// They have no effect once they are stored as mozilla preferences.
prefs = new Preferences({
"loggerLevel": "WARN"
,"sendAnonymousStatistics": true
});
log = new Logger(this.metadata["version"]);
GM_setValue("odometer", GM_getValue("odometer", 0) + 1);
}
// ==================== DOM object extensions ====================
/** Extend the given document with methods
* for querying and modifying the document object.
*/
function extendDocument(doc)
{
if (doc == null)
return null;
/** Determine if the current document is empty.
*/
doc.isEmpty = function() {
return (this.body == null || this.body.childNodes.length == 0);
};
/** Report number of nodes that matach the given xpath expression.
*/
doc.countNodes = function(xpath) {
var n = 0;
this.foreachNode(xpath, function(node) {
n++;
});
return n;
};
/** Remove nodes that match the given xpath expression.
*/
doc.removeNodes = function(xpath) {
this.foreachNode(xpath, function(node) {
node.remove();
});
};
/** Hide nodes that match the given xpath expression.
*/
doc.hideNodes = function(xpath)
{
if (xpath instanceof Array) {
for (var xp in xpath) {
this.foreachNode(xp, function(node) {
node.hide();
});
}
}
else {
this.foreachNode(xpath, function(node) {
node.hide();
});
}
};
/** Make visible the nodes that match the given xpath expression.
*/
doc.showNodes = function(xpath) {
this.foreachNode(xpath, function(node) {
node.show();
});
};
/** Retrieve the value of the node that matches the given xpath expression.
*/
doc.selectValue = function(xpath, contextNode)
{
if (contextNode == null)
contextNode = this;
var result = this.evaluate(xpath, contextNode, null, XPathResult.ANY_TYPE, null);
var resultVal;
switch (result.resultType) {
case result.STRING_TYPE: resultVal = result.stringValue; break;
case result.NUMBER_TYPE: resultVal = result.numberValue; break;
case result.BOOLEAN_TYPE: resultVal = result.booleanValue; break;
default:
log.error("Unhandled value type: " + result.resultType);
}
return resultVal;
}
/** Select the first node that matches the given xpath expression.
* If none found, log warning and return null.
*/
doc.selectNode = function(xpath, contextNode)
{
var node = this.selectNodeNullable(xpath, contextNode);
if (node == null) {
// is it possible that the structure of this web page has changed?
log.warn("XPath returned no elements: " + xpath
+ "\n" + genStackTrace(arguments.callee)
);
}
return node;
}
/** Select the first node that matches the given xpath expression.
* If none found, return null.
*/
doc.selectNodeNullable = function(xpath, contextNode)
{
if (contextNode == null)
contextNode = this;
var resultNode = this.evaluate(
xpath, contextNode, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null);
if (resultNode.singleNodeValue == null)
log.debug("null result for: " + xpath);
return extendNode(resultNode.singleNodeValue);
}
/** Select all first nodes that match the given xpath expression.
* If none found, return an empty Array.
*/
doc.selectNodes = function(xpath, contextNode)
{
var nodeList = new Array();
this.foreachNode(xpath, function(n) { nodeList.push(n); }, contextNode);
return nodeList;
}
/** Select all nodes that match the given xpath expression.
* If none found, return null.
*/
doc.selectNodeSet = function(xpath, contextNode)
{
if (contextNode == null)
contextNode = this;
var nodeSet = this.evaluate(
xpath, contextNode, null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
return nodeSet;
}
/** Iteratively execute the given func for each node that matches the given xpath expression.
*/
doc.foreachNode = function(xpath, func, contextNode)
{
if (contextNode == null)
contextNode = this;
// if array of xpath strings, call recursively
if (xpath instanceof Array) {
for (var i=0; i < xpath.length; i++)
this.foreachNode(xpath[i], func, contextNode);
return;
}
var nodeSet = contextNode.selectNodeSet(xpath, contextNode);
var i = 0;
var n = nodeSet.snapshotItem(i);
while (n != null) {
var result = func(extendNode(n));
if (result == false) {
// dispatching func can abort the loop by returning false
return;
}
n = nodeSet.snapshotItem(++i);
}
}
/** Retrieve the text content of the node that matches the given xpath expression.
*/
doc.selectTextContent = function(xpath) {
var node = this.selectNodeNullable(xpath, this);
if (node == null)
return null;
return node.textContent.normalizeWhitespace();
};
/** Retrieve the text content of the node that matches the given xpath expression,
* and apply the given regular expression to it, returning the portion that matches.
*/
doc.selectMatchTextContent = function(xpath, regex) {
var text = this.selectTextContent(xpath);
if (text == null)
return null;
return text.match(regex);
};
/** Replace contents of contextNode (default: body), with specified node.
* (The specified node is removed, then re-added to the emptied contextNode.)
* The specified node is expected to be a descendent of the context node.
* Otherwise the result is probably an error.
* DOC-DEFAULT
*/
doc.isolateNode = function(xpath, contextNode)
{
if (contextNode == null)
contextNode = this.body;
extendNode(contextNode);
var subjectNode = this.selectNode(xpath);
if (subjectNode == null || subjectNode.parentNode == null)
return;
// gut the parent node (leave script elements alone)
contextNode.foreachNode("child::*", function(node) {
if (node.tagName != "SCRIPT" && node.tagName != "NOSCRIPT") {
node.remove();
}
});
// re-add the subject node
var replacement_div = this.createElement("div");
replacement_div.id = "isolateNode:" + xpath;
replacement_div.appendChild(subjectNode);
contextNode.appendChild(replacement_div);
return replacement_div;
};
/** Add a