There are 17 previous versions of this script.
Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)
// ==UserScript==
// @name Nico Nico Ranking NG
// @namespace http://userscripts.org/users/121129
// @description ニコニコ動画のランキングにNG機能を追加
// @include http://www.nicovideo.jp/ranking/*
// @version 1.3
// ==/UserScript==
Util = {};
Util.throwErrorIfHasNotPropertyChangedFunc = function(listener) {
if (typeof(listener.propertyChanged) !== "function") {
throw new Error("the listener must have a propertyChanged function");
}
};
Util.emptyPropertyChangeListener = {
propertyChanged: function(source, changedPropertyName) {}
};
Util.getEmptyPropertyChangeListener = function() {
return Util.emptyPropertyChangeListener;
};
Util.isEmptyString = function(value) {
return typeof(value) === "string" && value.length === 0;
};
Util.throwErrorIfEmptyString = function(value, name) {
if (Util.isEmptyString(value)) {
throw new Error("the " + name + " must not be an empty string");
}
};
Util.removeAllChildren = function(parent) {
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
};
Util.contains = function(start, end, value) {
return start <= value && value <= end;
};
function View() {
this.reduceRadio = View.newVisitedMovieViewModeRadio(View.Mode.REDUCE.name);
this.hideRadio = View.newVisitedMovieViewModeRadio(View.Mode.HIDE.name);
this.doNothingRadio = View.newVisitedMovieViewModeRadio(View.Mode.DO_NOTHING.name);
this.ngMovieVisibilityCheckBox = View.newInput("checkbox");
this.configButton = View.newLinkSpan(View.CONFIG_TEXT);
this.movieIdToData = {};
this.toolboxAdded = false;
}
View.PREPEND_TEXT = "閲覧済みの動画を";
View.REDUCE_TEXT = " 縮小";
View.HIDE_TEXT = " 非表示";
View.DO_NOTHING_TEXT = " 通常表示";
View.NG_MOVIES_SHOW_TEXT = " NG動画を表示";
View.CONFIG_TEXT = "設定";
View.PROMPT_MESSAGE = "NGタイトルを入力";
View.VISITED_MOVIE_VIEW_MODE = "visitedMovieViewMode";
View.ADDITION = {
ngMovieIdButtonType: "NG登録",
ngMovieTitleButtonType: "NGタイトル追加",
visitButtonText: "閲覧済み",
visitButtonClicked: function(model, movieId) {
model.addVisitedMovieId(movieId);
},
ngMovieIdButtonClicked: function(model, movieId) {
model.addNgMovieId(movieId);
},
ngMovieTitleButtonClicked: function(model, movieId) {
var promptResult = View.promptNgMovieTitle(
model.getMovieById(movieId).getTitle());
if (promptResult) {
model.addNgMovieTitle(promptResult);
}
}
};
View.REMOVAL = {
ngMovieIdButtonType: "NG解除",
ngMovieTitleButtonType: "NGタイトル削除",
visitButtonText: "未閲覧",
visitButtonClicked: function(model, movieId) {
model.removeVisitedMovieId(movieId);
},
ngMovieIdButtonClicked: function(model, movieId) {
model.removeNgMovieId(movieId);
},
ngMovieTitleButtonClicked: function(model, movieId) {
model.removeNgMovieTitle(
model.getMovieById(movieId).getMatchedNgTitle());
}
};
View.Mode = {};
View.Mode.DO_NOTHING = {
name: "doNothing",
restoreViewState: function(movieId) {
},
setViewState: function(movieId) {
},
viewModeChanged: function(view) {
view.getDoNothingRadio().checked = true;
}
};
View.Mode.HIDE = {
name: "hide",
restoreViewState: function(movieId) {
View.Mode.setMovieRootVisible(movieId, true);
},
setViewState: function(movieId) {
View.Mode.setMovieRootVisible(movieId, false);
},
viewModeChanged: function(view) {
view.getHideRadio().checked = true;
}
};
View.Mode.REDUCE = {
name: "reduce",
restoreViewState: function(movieId) {
new View.Unreduction().perform(movieId);
},
setViewState: function(movieId) {
new View.Reduction().perform(movieId);
},
viewModeChanged: function(view) {
view.getReduceRadio().checked = true;
}
};
View.Mode.setMovieRootVisible = function(movieId, visible) {
var r = View.getMovieRoot(View.getWatchAnchor(movieId));
if (!r) {
throw new Error("View.getMovieRoot(child) return null");
}
if (visible) {
r.style.removeProperty("display");
} else {
r.style.display = "none";
}
};
View.Mode.getMode = function(name) {
switch (name) {
case View.Mode.DO_NOTHING.name:
return View.Mode.DO_NOTHING;
case View.Mode.HIDE.name:
return View.Mode.HIDE;
case View.Mode.REDUCE.name:
return View.Mode.REDUCE;
default:
throw new Error(name);
}
};
View.newElement = function(tagName, attrMap, styleMap, children) {
var result = document.createElement(tagName);
for (var attrName in attrMap) {
result.setAttribute(attrName, attrMap[attrName]);
}
for (var styleName in styleMap) {
result.style.setProperty(styleName, styleMap[styleName], null);
}
for (var i = 3; i < arguments.length; i++) {
if (typeof(arguments[i]) === "string") {
result.appendChild(document.createTextNode(arguments[i]));
} else {
result.appendChild(arguments[i]);
}
}
return result;
};
View.newAnchorButton = function(text) {
return View.newElement("a",
{ "href": "javascript:void(0)", "style": "color:#FFF;" }, null, text);
};
View.prototype.getDataByMovieId = function(movieId) {
var data = this.movieIdToData[String(movieId)];
if (data)
return data;
return this.movieIdToData[String(movieId)] = {
viewState: View.Mode.DO_NOTHING,
ngMovieId: false,
ngMovieTitle: "",
ngMovieIdType: View.ADDITION,
ngMovieTitleType: View.ADDITION,
visitType: View.ADDITION,
ngMovieIdButton: View.newAnchorButton(View.ADDITION.ngMovieIdButtonType),
ngMovieTitleButton: View.newAnchorButton(View.ADDITION.ngMovieTitleButtonType),
visitButton: View.newAnchorButton(View.ADDITION.visitButtonText),
movieButtonsAppended: false
};
};
View.prototype.getReduceRadio = function() {
return this.reduceRadio;
};
View.prototype.getHideRadio = function() {
return this.hideRadio;
};
View.prototype.getDoNothingRadio = function() {
return this.doNothingRadio;
};
View.prototype.getNgMovieVisibilityCheckBox = function() {
return this.ngMovieVisibilityCheckBox;
};
View.prototype.getConfigButton = function() {
return this.configButton;
};
View.getMovieIdInHRef = function(href) {
var execResult = /^watch\/([^?]+)/.exec(href);
return execResult ? execResult[1] : null;
};
View.getWatchAnchors = function() {
var a = document.querySelectorAll("#PAGEBODY div.content_672 a.watch") || [];
var result = [];
Array.forEach(a, function(e) { result.push(e); });
return result;
};
View.getMovies = function() {
var result = [];
View.getWatchAnchors().forEach(function(a) {
var movieId = View.getMovieIdInHRef(a.getAttribute("href"));
if (movieId) {
result.push(new Movie(movieId, a.firstChild.nodeValue));
}
});
return result;
};
View.newVisitedMovieViewModeRadio = function(value) {
return View.newElement(
"input",
{ "type": "radio",
"name": View.VISITED_MOVIE_VIEW_MODE,
"value": value });
};
View.newLabel = function(element, text) {
return View.newElement("label", null, null, element, text);
};
View.appendTextSeparator = function(element, separator) {
element.appendChild(document.createTextNode(separator || " | "));
};
View.newInput = function(type) {
return View.newElement("input", { "type": type });
};
View.newLinkSpan = function(text) {
return View.newElement("span", null,
{ "text-decoration": "underline", "cursor": "pointer"}, text);
};
View.prototype.newVisitedMovieViewModeFragment = function() {
var result = document.createDocumentFragment();
result.appendChild(document.createTextNode(View.PREPEND_TEXT));
result.appendChild(View.newLabel(this.reduceRadio, View.REDUCE_TEXT));
result.appendChild(View.newLabel(this.hideRadio, View.HIDE_TEXT));
result.appendChild(View.newLabel(this.doNothingRadio, View.DO_NOTHING_TEXT));
return result;
};
View.prototype.newToolsDiv = function() {
var result = document.createElement("div");
result.appendChild(this.newVisitedMovieViewModeFragment());
View.appendTextSeparator(result);
result.appendChild(View.newLabel(
this.ngMovieVisibilityCheckBox, View.NG_MOVIES_SHOW_TEXT));
View.appendTextSeparator(result);
result.appendChild(this.configButton);
return result;
};
View.prototype.addToolbox = function() {
if (this.toolboxAdded) {
throw new Error("the toolbox has already been added");
}
this.toolboxAdded = true;
var c672 = document.querySelector("#PAGEBODY div.content_672");
c672.insertBefore(this.newToolsDiv(), c672.firstChild);
};
View.prototype.getViewState = function(movieId) {
Util.throwErrorIfEmptyString(movieId, "movieId");
return this.getDataByMovieId(movieId).viewState;
};
View.prototype.setViewState = function(movieId, viewState) {
Util.throwErrorIfEmptyString(movieId, "movieId");
Store.throwErrorIfNotVisitedMovidViewMode(viewState, "viewState");
var currentViewState = this.getDataByMovieId(movieId).viewState;
if (currentViewState === viewState) {
return;
}
currentViewState.restoreViewState(movieId);
this.getDataByMovieId(movieId).viewState = viewState;
viewState.setViewState(movieId);
};
View.getMovieRoot = function(child) {
for (var parent = child; parent; parent = parent.parentNode) {
if (parent.id && parent.id.indexOf("item") === 0) {
return parent.parentNode;
}
}
return null;
};
View.getMovieIdOfMovieRoot = function(movieRoot) {
var a = movieRoot.querySelector("a.watch");
if (!a) {
throw new Error("the movieRoot is not a movie root");
}
return View.getMovieIdInHRef(a.getAttribute("href"));
};
View.watchAnchorsCache = null;
View.newWatchAnchorsCache = function() {
var result = {};
var anchors = View.getWatchAnchors();
for (var i = 0; i < anchors.length; i++) {
var a = anchors[i];
var movieId = View.getMovieIdInHRef(a.getAttribute("href"));
result[movieId] = a;
}
return result;
};
View.getWatchAnchor = function(movieId) {
if (!View.watchAnchorsCache)
View.watchAnchorsCache = View.newWatchAnchorsCache();
var a = View.watchAnchorsCache[movieId];
if (a)
return a;
throw new Error(movieId + " not found");
};
View.prototype.isNgMovieId = function(movieId) {
return this.getDataByMovieId(movieId).ngMovieId;
};
View.prototype.setNgMovieId = function(movieId, ngMovieId) {
this.getDataByMovieId(movieId).ngMovieId = Boolean(ngMovieId);
if (ngMovieId) {
View.getWatchAnchor(movieId).style.textDecoration = "line-through";
} else {
View.getWatchAnchor(movieId).style.removeProperty("text-decoration");
}
};
View.prototype.getNgMovieTitle = function(movieId) {
return this.getDataByMovieId(movieId).ngMovieTitle;
};
View.prototype.setNgMovieTitle = function(movieId, ngMovieTitle) {
this.getDataByMovieId(movieId).ngMovieTitle = String(ngMovieTitle);
var a = View.getWatchAnchor(movieId);
var t = a.textContent;
var i = t.indexOf(ngMovieTitle);
if (i === -1) {
throw new Error("'" + ngMovieTitle + "' not found");
}
Util.removeAllChildren(a);
if (ngMovieTitle.length === 0) {
a.textContent = t;
return;
}
if (i !== 0) {
a.appendChild(document.createTextNode(t.substring(0, i)));
}
a.appendChild(View.newElement("span", null,
{ "color": "white", "background-color": "fuchsia" },
t.substring(i, i + ngMovieTitle.length)));
if (i + ngMovieTitle.length !== t.length) {
a.appendChild(document.createTextNode(
t.substring(i + ngMovieTitle.length)));
}
};
View.prototype.appendMovieButtons = function(movieId) {
if (this.getDataByMovieId(movieId).movieButtonsAppended) {
throw new Error("movie buttons have already been appended");
}
this.getDataByMovieId(movieId).movieButtonsAppended = true;
var selector = "#MENU_" + movieId + " p.menu_palet";
var menu = document.querySelector(selector);
if (!menu) {
throw new Error("'" + selector + "' not found");
}
View.appendTextSeparator(menu, "| ");
menu.appendChild(this.getVisitButton(movieId));
View.appendTextSeparator(menu);
menu.appendChild(this.getNgMovieIdButton(movieId));
View.appendTextSeparator(menu);
menu.appendChild(this.getNgMovieTitleButton(movieId));
};
View.prototype.appendAllMovieButtons = function() {
View.getMovies().forEach(function(movie) {
this.appendMovieButtons(movie.getId());
}, this);
};
View.prototype.getNgMovieIdButton = function(movieId) {
return this.getDataByMovieId(movieId).ngMovieIdButton;
};
View.prototype.getNgMovieTitleButton = function(movieId) {
return this.getDataByMovieId(movieId).ngMovieTitleButton;
};
View.prototype.getVisitButton = function(movieId) {
return this.getDataByMovieId(movieId).visitButton;
};
View.prototype.getNgMovieIdType = function(movieId) {
return this.getDataByMovieId(movieId).ngMovieIdType;
};
View.throwErrorIfNotType = function(type) {
if (!(type === View.ADDITION || type === View.REMOVAL)) {
throw new Error("the type must be ADDITION or REMOVAL");
}
};
View.prototype.setNgMovieIdType = function(movieId, type) {
View.throwErrorIfNotType(type);
var d = this.getDataByMovieId(movieId);
d.ngMovieIdType = type;
d.ngMovieIdButton.textContent = type.ngMovieIdButtonType;
};
View.prototype.getNgMovieTitleType = function(movieId) {
return this.getDataByMovieId(movieId).ngMovieTitleType;
};
View.prototype.setNgMovieTitleType = function(movieId, type) {
View.throwErrorIfNotType(type);
var d = this.getDataByMovieId(movieId);
d.ngMovieTitleType = type;
d.ngMovieTitleButton.textContent = type.ngMovieTitleButtonType;
};
View.prototype.getVisitType = function(movieId) {
return this.getDataByMovieId(movieId).visitType;
};
View.prototype.setVisitType = function(movieId, type) {
View.throwErrorIfNotType(type);
var d = this.getDataByMovieId(movieId);
d.visitType = type;
d.visitButton.textContent = type.visitButtonText;
};
View.getMovieImages = function() {
var result = [];
var imgs = document.querySelectorAll("#PAGEBODY div.content_672 a > img");
Array.forEach(imgs || [], function(img) {
if (img.id && img.id.indexOf("video_img_") === 0) {
result.push(img);
}
});
return result;
};
View.getMovieAnchors = function() {
var result = [];
Array.forEach(View.getMovieImages(), function(img) {
result.push(img.parentNode);
});
return result.concat(View.getWatchAnchors());
};
View.setTargetAttrsOfMovieAnchors = function(blank) {
View.getMovieAnchors().forEach(function(movieAnchor) {
if (blank) {
movieAnchor.setAttribute("target", "_blank");
} else {
movieAnchor.removeAttribute("target");
}
});
};
View.newButton = function(text) {
return View.newElement("button", null, null, text);
};
View.promptNgMovieTitle = function(initValue) {
return window.prompt(View.PROMPT_MESSAGE, initValue);
};
View.getAbsoluteTop = function(target) {
var result = target.offsetTop;
var parent = target.offsetParent;
while (parent && parent !== document.body) {
result += parent.offsetTop;
parent = parent.offsetParent;
}
return result;
};
View.moveTitleToSrc = function(img) {
img.src = img.title;
img.title = "";
};
View.loadLazyImageIfRequired = function(pageYOffset, innerHeight, img) {
if (img.title &&
Util.contains(pageYOffset,
pageYOffset + innerHeight,
View.getAbsoluteTop(img))) {
View.moveTitleToSrc(img);
}
};
View.loadLazyImagesIfRequired = function() {
View.getMovieImages().forEach(function(img) {
View.loadLazyImageIfRequired(
window.pageYOffset, window.innerHeight, img);
});
};
View.ConfigDialog = function() {
this.ngMovieTitlesSelect = View.newElement(
"select",
{ "size": "10", "multiple": "multiple" },
{ "width": "250px", "float": "left", "margin-bottom": "10px" });
this.ngMovieTitlesRemovalButton = View.newElement(
"button",
null,
{ "width": "70px" },
View.ConfigDialog.NG_TITLES_REMOVAL_BUTTON_TEXT);
this.ngTitleAllRemovalButton = View.newButton(
View.ConfigDialog.NG_TITLE_ALL_REMOVAL_BUTTON_TEXT);
this.ngMovieIdAllRemovalButton = View.newButton(
View.ConfigDialog.NG_MOVIE_ID_ALL_REMOVAL_BUTTON_TEXT);
this.visitedMovieAllRemovalButton = View.newButton(
View.ConfigDialog.VISITED_MOVIE_ALL_REMOVAL_BUTTON_TEXT);
this.newWindowOpenCheckBox = View.newInput("checkbox");
this.closeButton = View.newButton(
View.ConfigDialog.CLOSE_BUTTON_TEXT);
this.rootElement = View.newElement("div", null,
{ "background-color": "white", "z-index": View.ConfigDialog.Z_INDEX,
"position": "fixed", "padding": "15px",
"border": "medium solid black", "overflow": "auto",
"width": "320px" },
View.newElement("div", null, null, View.ConfigDialog.NG_TITLE_TEXT),
this.ngMovieTitlesSelect,
this.ngMovieTitlesRemovalButton,
View.newElement("div", null, { "clear": "left" },
this.ngTitleAllRemovalButton),
View.newElement("div", null, { "margin-top": "10px" },
this.ngMovieIdAllRemovalButton),
View.newElement("div", null, { "margin-top": "10px" },
this.visitedMovieAllRemovalButton),
View.newElement("div", null, { "margin-top": "10px" },
View.newLabel(
this.newWindowOpenCheckBox,
View.ConfigDialog.NEW_WINDOW_OPEN_CHECK_BOX_TEXT)),
View.newElement("div", null,
{ "text-align": "center", "margin-top": "20px" },
this.closeButton));
};
View.ConfigDialog.NG_TITLE_TEXT = "NGタイトル";
View.ConfigDialog.NG_TITLES_REMOVAL_BUTTON_TEXT = "削除";
View.ConfigDialog.NG_TITLE_ALL_REMOVAL_BUTTON_TEXT = "NGタイトルを全て削除";
View.ConfigDialog.NG_MOVIE_ID_ALL_REMOVAL_BUTTON_TEXT = "NG登録を全て削除";
View.ConfigDialog.VISITED_MOVIE_ALL_REMOVAL_BUTTON_TEXT = "訪問履歴を全て削除";
View.ConfigDialog.NEW_WINDOW_OPEN_CHECK_BOX_TEXT = " 動画を別窓で開く";
View.ConfigDialog.CLOSE_BUTTON_TEXT = "閉じる";
View.ConfigDialog.Z_INDEX = "10000";
View.ConfigDialog.prototype.show = function(owner) {
var e = this.rootElement;
owner.appendChild(e);
e.style.left = ((window.innerWidth - e.offsetWidth) / 2) + "px";
e.style.top = ((window.innerHeight - e.offsetHeight) / 2) + "px";
};
View.ConfigDialog.prototype.hide = function() {
this.rootElement.parentNode.removeChild(this.rootElement);
};
View.ConfigDialog.prototype.getRootElement = function() {
return this.rootElement;
};
View.ConfigDialog.prototype.getNgMovieTitlesSelect = function() {
return this.ngMovieTitlesSelect;
};
View.ConfigDialog.prototype.getNgMovieTitlesRemovalButton = function() {
return this.ngMovieTitlesRemovalButton;
};
View.ConfigDialog.prototype.getNgTitleAllRemovalButton = function() {
return this.ngTitleAllRemovalButton;
};
View.ConfigDialog.prototype.getNgMovieIdAllRemovalButton = function() {
return this.ngMovieIdAllRemovalButton;
};
View.ConfigDialog.prototype.getVisitedMovieAllRemovalButton = function() {
return this.visitedMovieAllRemovalButton;
};
View.ConfigDialog.prototype.getNewWindowOpenCheckBox = function() {
return this.newWindowOpenCheckBox;
};
View.ConfigDialog.prototype.getCloseButton = function() {
return this.closeButton;
};
View.DialogBackground = function() {
var e = document.createElement("div");
e.style.backgroundColor = "black";
e.style.opacity = "0.5";
e.style.zIndex = String(View.ConfigDialog.Z_INDEX - 1);
e.style.position = "fixed";
e.style.left = "0px";
e.style.top = "0px";
e.style.width = "100%";
e.style.height = "100%";
this.rootElement = e;
};
View.DialogBackground.prototype.getRootElement = function() {
return this.rootElement;
};
View.DialogBackground.prototype.show = function(owner) {
owner.appendChild(this.rootElement);
};
View.DialogBackground.prototype.hide = function() {
this.rootElement.parentNode.removeChild(this.rootElement);
};
View.AbstractReduction = function() {
};
View.AbstractReduction.prototype.perform = function(movieId) {
var p = View.getWatchAnchor(movieId).parentNode;
this.setVisibility(p.previousSibling.previousSibling);
for (var n = p.nextSibling; n; n = n.nextSibling) {
if (n.tagName === "P") {
this.setDisplay(n);
}
}
var img = document.getElementById("video_img_" + movieId);
var style = window.getComputedStyle(img, null);
img.style.width = this.computeLength(parseInt(style.width)) + "px";
img.style.height = this.computeLength(parseInt(style.height)) + "px";
};
View.AbstractReduction.prototype.setVisibility = function(target) {
throw new Error("not implemented yet");
};
View.AbstractReduction.prototype.setDisplay = function(target) {
throw new Error("not implemented yet");
};
View.AbstractReduction.prototype.computeLength = function(srcLength) {
throw new Error("not implemented yet");
};
View.Reduction = function() {
};
View.Reduction.prototype = new View.AbstractReduction();
View.Reduction.prototype.setVisibility = function(target) {
target.style.visibility = "hidden";
};
View.Reduction.prototype.setDisplay = function(target) {
target.style.display = "none";
};
View.Reduction.prototype.computeLength = function(srcLength) {
return srcLength / 2;
};
View.Unreduction = function() {
};
View.Unreduction.prototype = new View.AbstractReduction();
View.Unreduction.prototype.setVisibility = function(target) {
target.style.removeProperty("visibility");
};
View.Unreduction.prototype.setDisplay = function(target) {
target.style.removeProperty("display");
};
View.Unreduction.prototype.computeLength = function(srcLength) {
return srcLength * 2;
};
function Store() {
var data = {};
var checkName = function(name) {
if (typeof(name) !== "string") {
throw new Error("the name must be the string type");
}
Util.throwErrorIfEmptyString(name, "name");
};
this.getter = function(name, def) {
checkName(name);
return data.hasOwnProperty(name) ? data[name] : def;
};
this.setter = function(name, value) {
checkName(name);
if (typeof(value) === "boolean" || typeof(value) === "string") {
data[name] = value;
} else if (value === parseInt(value)) {
data[name] = parseInt(value);
} else {
throw new Error("the value must be the type of Boolean, Integer or String");
}
};
this.remover = function(name) {
checkName(name);
delete data[name];
};
}
Store.WAY_OF_PROCESSING_VISITED_ITEMS = "wayOfProcessingVisitedItems";
Store.OPEN_NEW_WINDOW = "openNewWindow";
Store.VISITED_MOVIES = "visitedMovies";
Store.NG_MOVIES = "ngMovies";
Store.NG_TITLES = "ngTitles";
Store.throwErrorIfNotFunction = function(value, name) {
if (typeof(value) !== "function") {
throw new Error("the " + name + " must be a function");
}
};
Store.prototype.getGetter = function() {
return this.getter;
};
Store.prototype.setGetter = function(getter) {
Store.throwErrorIfNotFunction(getter, "getter");
this.getter = getter;
};
Store.prototype.getSetter = function() {
return this.setter;
};
Store.prototype.setSetter = function(setter) {
Store.throwErrorIfNotFunction(setter, "setter");
this.setter = setter;
};
Store.prototype.getRemover = function() {
return this.remover;
};
Store.prototype.setRemover = function(remover) {
Store.throwErrorIfNotFunction(remover, "remover");
this.remover = remover;
};
Store.prototype.getValues = function(name) {
return JSON.parse(this.getter(name, "[]"));
};
Store.containsValue = function(values, value) {
return values.some(function(element) {
return element === value;
});
};
Store.prototype.addValue = function(name, value, valueName) {
Util.throwErrorIfEmptyString(value, valueName);
var values = this.getValues(name);
if (Store.containsValue(values, value)) {
return false;
}
values.push(String(value));
this.setter(name, JSON.stringify(values));
return true;
};
Store.prototype.removeValue = function(name, value) {
var newValue = this.getValues(name).filter(function(element) {
return element !== value;
});
this.setter(name, JSON.stringify(newValue));
};
Store.prototype.getVisitedMovieIds = function() {
return this.getValues(Store.VISITED_MOVIES);
};
Store.prototype.containsVisitedMovieId = function(movieId) {
return Store.containsValue(this.getVisitedMovieIds(), movieId);
};
Store.prototype.addVisitedMovieId = function(movieId) {
return this.addValue(Store.VISITED_MOVIES, movieId, "movieId");
};
Store.prototype.removeVisitedMovieId = function(movieId) {
this.removeValue(Store.VISITED_MOVIES, movieId);
};
Store.prototype.clearVisitedMovieIds = function() {
this.remover(Store.VISITED_MOVIES);
};
Store.prototype.getNgMovieIds = function() {
return this.getValues(Store.NG_MOVIES);
};
Store.prototype.containsNgMovieId = function(movieId) {
return Store.containsValue(this.getNgMovieIds(), movieId);
};
Store.prototype.addNgMovieId = function(movieId) {
return this.addValue(Store.NG_MOVIES, movieId, "movieId");
};
Store.prototype.removeNgMovieId = function(movieId) {
this.removeValue(Store.NG_MOVIES, movieId);
};
Store.prototype.clearNgMovieIds = function() {
this.remover(Store.NG_MOVIES);
};
Store.prototype.getNgMovieTitles = function() {
return this.getValues(Store.NG_TITLES);
};
Store.prototype.containsNgMovieTitle = function(movieTitle) {
return Store.containsValue(this.getNgMovieTitles(), movieTitle);
};
Store.prototype.addNgMovieTitle = function(movieTitle) {
return this.addValue(Store.NG_TITLES, movieTitle, "movieTitle");
};
Store.prototype.removeNgMovieTitle = function(movieTitle) {
this.removeValue(Store.NG_TITLES, movieTitle);
};
Store.prototype.removeNgMovieTitles = function(movieTitles) {
var newValue = this.getValues(Store.NG_TITLES).filter(function(element) {
return movieTitles.indexOf(element) === -1;
});
this.setter(Store.NG_TITLES, JSON.stringify(newValue));
};
Store.prototype.clearNgMovieTitles = function() {
this.remover(Store.NG_TITLES);
};
Store.prototype.getVisitedMovieViewMode = function() {
return View.Mode.getMode(this.getter(
Store.WAY_OF_PROCESSING_VISITED_ITEMS, View.Mode.REDUCE.name));
};
Store.throwErrorIfNotVisitedMovidViewMode = function(visitedMovieViewMode, name) {
try {
View.Mode.getMode(visitedMovieViewMode.name);
} catch (e) {
throw new Error("the " + name + " must be" +
" one of HIDE, DO_NOTHING and REDUCE");
}
};
Store.prototype.setVisitedMovieViewMode = function(visitedMovieViewMode) {
Store.throwErrorIfNotVisitedMovidViewMode(
visitedMovieViewMode, "visitedMovieViewMode");
this.setter(Store.WAY_OF_PROCESSING_VISITED_ITEMS, visitedMovieViewMode.name);
};
Store.prototype.isNewWindowOpen = function() {
return this.getter(Store.OPEN_NEW_WINDOW, true);
};
Store.prototype.setNewWindowOpen = function(newWindowOpen) {
this.setter(Store.OPEN_NEW_WINDOW, Boolean(newWindowOpen));
};
function Movie(id, title) {
Util.throwErrorIfEmptyString(title, "title");
Util.throwErrorIfEmptyString(id, "id");
this.title = String(title);
this.id = String(id);
this.visited = false;
this.ngId = false;
this.ngTitle = false;
this.listener = Util.getEmptyPropertyChangeListener();
this.matchedNgTitle = "";
}
Movie.prototype.getTitle = function() {
return this.title;
};
Movie.prototype.getId = function() {
return this.id;
};
Movie.prototype.isVisited = function() {
return this.visited;
};
Movie.prototype.setVisited = function(visited) {
if (this.visited !== Boolean(visited)) {
this.visited = Boolean(visited);
this.listener.propertyChanged(this, "visited");
}
};
Movie.prototype.setVisitedByMatch = function(regExp) {
this.setVisited(regExp.test(this.id));
};
Movie.prototype.getPropertyChangeListener = function() {
return this.listener;
};
Movie.prototype.setPropertyChangeListener = function(listener) {
Util.throwErrorIfHasNotPropertyChangedFunc(listener);
this.listener = listener;
};
Movie.prototype.isNgId = function() {
return this.ngId;
};
Movie.prototype.setNgId = function(ngId) {
if (this.ngId !== Boolean(ngId)) {
this.ngId = Boolean(ngId);
this.listener.propertyChanged(this, "ngId");
}
};
Movie.prototype.setNgIdByMatch = function(regExp) {
this.setNgId(regExp.test(this.id));
};
Movie.prototype.isNgTitle = function() {
return this.ngTitle;
};
Movie.prototype.setNgTitle = function(ngTitle) {
if (this.ngTitle !== Boolean(ngTitle)) {
this.ngTitle = Boolean(ngTitle);
this.listener.propertyChanged(this, "ngTitle");
}
};
Movie.prototype.setNgTitleByMatch = function(regExp) {
var execResult = regExp.exec(this.title);
var oldMatchedNgTitle = this.matchedNgTitle;
this.matchedNgTitle = Boolean(execResult) ? execResult[0] : "";
var previousNgTitle = this.ngTitle;
this.setNgTitle(Boolean(execResult));
if (previousNgTitle && this.ngTitle &&
oldMatchedNgTitle !== this.matchedNgTitle) {
this.listener.propertyChanged(this, "matchedNgTitle");
}
};
Movie.prototype.isNg = function() {
return this.ngId || this.ngTitle;
};
Movie.prototype.getMatchedNgTitle = function() {
if (this.ngTitle) {
return this.matchedNgTitle;
}
throw new Error("#getMatchedNgTitle() must be called when the ngTitle is true");
};
function Model(movies, store) {
if (!Model.isArray(movies)) {
throw new Error("the movies must be an array");
}
if (!(store instanceof Store)) {
throw new Error("the store must be an instance of Store");
}
this.movies = movies;
this.store = store;
this.idToMovie = {};
movies.forEach(function(movie) {
this.idToMovie[movie.getId()] = movie;
}, this);
this.listener = Util.getEmptyPropertyChangeListener();
this.ngMovieVisible = false;
}
Model.isArray = function(value) {
return Object.prototype.toString.apply(value) === "[object Array]";
};
Model.escapeNoWordCharacters = function(s) {
return s.replace(/\W/g, "\\$&");
};
Model.newTitlePattern = function(rawComponents) {
if (rawComponents.length === 0) {
throw new Error("the rawComponents must not be empty");
}
var s = "(";
rawComponents.forEach(function(c) {
if (Util.isEmptyString(c)) {
throw new Error("the rawComponents must not contain an empty string");
}
s += Model.escapeNoWordCharacters(String(c)) + "|";
});
return s.slice(0, -1) + ")";
};
Model.newIdPattern = function(rawComponents) {
return "^" + Model.newTitlePattern(rawComponents) + "$";
};
Model.prototype.getMovies = function() {
return this.movies;
};
Model.prototype.getMovieById = function(movieId) {
var movie = this.idToMovie[movieId];
return movie ? movie : null;
};
Model.prototype.getStore = function() {
return this.store;
};
Model.prototype.setAllMoviesPropertyChangeListener = function(listener) {
this.movies.forEach(function(movie) {
movie.setPropertyChangeListener(listener);
});
};
Model.prototype.getPropertyChangeListener = function() {
return this.listener;
};
Model.prototype.setPropertyChangeListener = function(listener) {
Util.throwErrorIfHasNotPropertyChangedFunc(listener);
this.listener = listener;
};
Model.prototype.getVisitedMovieViewMode = function() {
return this.store.getVisitedMovieViewMode();
};
Model.prototype.setVisitedMovieViewMode = function(visitedMovieViewMode) {
if (this.store.getVisitedMovieViewMode() === visitedMovieViewMode) {
return;
}
this.store.setVisitedMovieViewMode(visitedMovieViewMode);
this.listener.propertyChanged(this, "visitedMovieViewMode");
};
Model.prototype.isNgMovieVisible = function() {
return this.ngMovieVisible;
};
Model.prototype.setNgMovieVisible = function(ngMovieVisible) {
if (this.ngMovieVisible === Boolean(ngMovieVisible)) {
return;
}
this.ngMovieVisible = Boolean(ngMovieVisible);
this.listener.propertyChanged(this, "ngMovieVisible");
};
Model.prototype.isNewWindowOpen = function() {
return this.store.isNewWindowOpen();
};
Model.prototype.setNewWindowOpen = function(newWindowOpen) {
if (this.store.isNewWindowOpen() === newWindowOpen) {
return;
}
this.store.setNewWindowOpen(newWindowOpen);
this.listener.propertyChanged(this, "newWindowOpen");
};
Model.prototype.addVisitedMovieId = function(movieId) {
this.store.addVisitedMovieId(movieId);
if (this.getMovieById(movieId)) {
this.getMovieById(movieId).setVisited(true);
}
};
Model.prototype.removeVisitedMovieId = function(movieId) {
this.store.removeVisitedMovieId(movieId);
if (this.getMovieById(movieId)) {
this.getMovieById(movieId).setVisited(false);
}
};
Model.prototype.clearVisitedMovieIds = function() {
this.store.clearVisitedMovieIds();
this.movies.forEach(function(movie) {
movie.setVisited(false);
});
};
Model.prototype.addNgMovieId = function(movieId) {
this.store.addNgMovieId(movieId);
if (this.getMovieById(movieId)) {
this.getMovieById(movieId).setNgId(true);
}
};
Model.prototype.removeNgMovieId = function(movieId) {
this.store.removeNgMovieId(movieId);
if (this.getMovieById(movieId)) {
this.getMovieById(movieId).setNgId(false);
}
};
Model.prototype.clearNgMovieIds = function() {
this.store.clearNgMovieIds();
this.movies.forEach(function(movie) {
movie.setNgId(false);
});
};
Model.prototype.addNgMovieTitle = function(movieTitle) {
this.store.addNgMovieTitle(movieTitle);
var regExp = new RegExp(Model.escapeNoWordCharacters(movieTitle));
this.movies.forEach(function(movie) {
movie.isNgTitle() || movie.setNgTitleByMatch(regExp);
});
};
Model.prototype.removeNgMovieTitle = function(movieTitle) {
this.removeNgMovieTitles([movieTitle]);
};
Model.prototype.getAndSetValues = function(
values, setPropertyToFalse, newCallbackWithRegExp, newPattern) {
if (values.length === 0) {
this.movies.forEach(setPropertyToFalse);
} else {
var regExp = new RegExp(newPattern(values));
this.movies.forEach(newCallbackWithRegExp(regExp));
}
};
Model.prototype.removeNgMovieTitles = function(movieTitles) {
if (movieTitles.length !== 0) {
this.store.removeNgMovieTitles(movieTitles);
this.getAndSetAllNgMovieTitles();
}
};
Model.prototype.clearNgMovieTitles = function() {
this.store.clearNgMovieTitles();
this.movies.forEach(function(movie) {
movie.setNgTitle(false);
});
};
Model.prototype.getAndSetAllVisitedMovies = function() {
this.getAndSetValues(
this.store.getVisitedMovieIds(),
function(movie) { movie.setVisited(false); },
function(regExp) {
return function(movie) { movie.setVisitedByMatch(regExp); };
},
Model.newIdPattern
);
};
Model.prototype.getAndSetAllNgMovieIds = function() {
this.getAndSetValues(
this.store.getNgMovieIds(),
function(movie) { movie.setNgId(false); },
function(regExp) {
return function(movie) { movie.setNgIdByMatch(regExp); };
},
Model.newIdPattern
);
};
Model.prototype.getAndSetAllNgMovieTitles = function() {
this.getAndSetValues(
this.store.getNgMovieTitles(),
function(movie) { movie.setNgTitle(false); },
function(regExp) {
return function(movie) { movie.setNgTitleByMatch(regExp); };
},
Model.newTitlePattern
);
};
function Controller(model, view) {
if (!(model instanceof Model)) {
throw new Error("the model must be an instance of Model");
}
if (!(view instanceof View)) {
throw new Error("the view must be an instance of View");
}
this.model = model;
this.view = view;
this.configDialog = new View.ConfigDialog();
this.dialogBackground = new View.DialogBackground();
this.configDialogOwner = document.body;
}
Controller.prototype.getModel = function() {
return this.model;
};
Controller.prototype.getView = function() {
return this.view;
};
Controller.prototype.getConfigDialog = function() {
return this.configDialog;
};
Controller.prototype.getDialogBackground = function() {
return this.dialogBackground;
};
Controller.prototype.isVisible = function(movie) {
return movie.isNg() && !this.model.isNgMovieVisible();
};
Controller.prototype.isHideRequired = function(movie) {
return this.isVisible(movie) || (movie.isVisited() &&
this.model.getVisitedMovieViewMode() === View.Mode.HIDE);
};
Controller.prototype.isReduceRequired = function(movie) {
return !this.isVisible(movie) && movie.isVisited() &&
this.model.getVisitedMovieViewMode() === View.Mode.REDUCE;
};
Controller.prototype.setListenersToAllMovies = function() {
var self = this;
var listener = {
propertyChanged: function(source) { self.synchMovie(source); }
};
this.model.setAllMoviesPropertyChangeListener(listener);
};
Controller.prototype.modelPropertyChanged = function(changedPropertyName) {
switch (changedPropertyName) {
case "newWindowOpen":
this.synchNewWindowOpen();
break;
case "visitedMovieViewMode":
this.synchVisitedMovieViewMode();
this.synchAllMovies();
View.loadLazyImagesIfRequired();
break;
case "ngMovieVisible":
this.view.getNgMovieVisibilityCheckBox().checked =
this.model.isNgMovieVisible();
this.synchAllMovies();
View.loadLazyImagesIfRequired();
break;
}
};
Controller.prototype.setListenerToModel = function() {
var self = this;
this.model.setPropertyChangeListener({
propertyChanged: function(source, changedPropertyName) {
self.modelPropertyChanged(changedPropertyName);
}
});
};
Controller.prototype.viewModeRadioClicked = function() {
if (this.view.getReduceRadio().checked) {
this.model.setVisitedMovieViewMode(View.Mode.REDUCE);
} else if (this.view.getHideRadio().checked) {
this.model.setVisitedMovieViewMode(View.Mode.HIDE);
} else {
this.model.setVisitedMovieViewMode(View.Mode.DO_NOTHING);
}
};
Controller.prototype.ngMovieVisibilityCheckBoxClicked = function() {
this.model.setNgMovieVisible(
this.view.getNgMovieVisibilityCheckBox().checked);
};
Controller.prototype.configButtonClicked = function() {
this.dialogBackground.show(this.configDialogOwner);
this.configDialog.show(this.configDialogOwner);
var select = this.configDialog.getNgMovieTitlesSelect();
Util.removeAllChildren(select);
this.model.getStore().getNgMovieTitles().forEach(function(ngMovieTitle) {
var option = document.createElement("option");
option.textContent = ngMovieTitle;
select.appendChild(option);
});
};
Controller.prototype.movieAnchorClicked = function(movieAnchor) {
var movieId = View.getMovieIdOfMovieRoot(View.getMovieRoot(movieAnchor));
View.ADDITION.visitButtonClicked(this.model, movieId);
if (this.model.getVisitedMovieViewMode() !== View.Mode.DO_NOTHING) {
View.loadLazyImagesIfRequired();
}
};
Controller.prototype.addListenersToView = function() {
this.addListenersToViewModeRadios();
this.addListenersToConfigDialog();
this.addListenersToMovieButtons();
var self = this;
this.view.getNgMovieVisibilityCheckBox().addEventListener("click", function() {
self.ngMovieVisibilityCheckBoxClicked();
}, false);
this.view.getConfigButton().addEventListener("click", function() {
self.configButtonClicked();
}, false);
View.getMovieAnchors().forEach(function(movieAnchor) {
movieAnchor.addEventListener("click", function(event) {
self.movieAnchorClicked(event.target);
}, false);
});
};
Controller.prototype.addListenersToViewModeRadios = function() {
var self = this;
var l = function() {
self.viewModeRadioClicked();
};
this.view.getReduceRadio().addEventListener("click", l, false);
this.view.getHideRadio().addEventListener("click", l, false);
this.view.getDoNothingRadio().addEventListener("click", l, false);
};
Controller.prototype.closeButtonClicked = function() {
this.configDialog.hide();
this.dialogBackground.hide();
};
Controller.prototype.ngMovieTitlesRemovalButtonClicked = function() {
var ngTitles = [];
var selectedOptions = [];
Array.forEach(this.configDialog.getNgMovieTitlesSelect().options, function(e) {
if (e.selected) {
ngTitles.push(e.textContent);
selectedOptions.push(e);
}
});
this.model.removeNgMovieTitles(ngTitles);
selectedOptions.forEach(function(option) {
option.parentNode.removeChild(option);
});
if (ngTitles.length !== 0) {
View.loadLazyImagesIfRequired();
}
};
Controller.prototype.ngMovieTitleAllRemovalButtonClicked = function() {
this.model.clearNgMovieTitles();
Util.removeAllChildren(this.configDialog.getNgMovieTitlesSelect());
View.loadLazyImagesIfRequired();
};
Controller.prototype.ngMovieIdAllRemovalButtonClicked = function() {
this.model.clearNgMovieIds();
View.loadLazyImagesIfRequired();
};
Controller.prototype.visitedMovieAllRemovalButtonClicked = function() {
this.model.clearVisitedMovieIds();
View.loadLazyImagesIfRequired();
};
Controller.prototype.newWindowOpenCheckBoxClicked = function() {
this.model.setNewWindowOpen(
this.configDialog.getNewWindowOpenCheckBox().checked);
};
Controller.prototype.addListenersToConfigDialog = function() {
var self = this;
this.configDialog.getCloseButton().addEventListener("click", function() {
self.closeButtonClicked();
}, false);
this.configDialog.getNgMovieTitlesRemovalButton().addEventListener("click", function() {
self.ngMovieTitlesRemovalButtonClicked();
}, false);
this.configDialog.getNgTitleAllRemovalButton().addEventListener("click", function() {
self.ngMovieTitleAllRemovalButtonClicked();
}, false);
this.configDialog.getNgMovieIdAllRemovalButton().addEventListener("click", function() {
self.ngMovieIdAllRemovalButtonClicked();
}, false);
this.configDialog.getVisitedMovieAllRemovalButton().addEventListener("click", function() {
self.visitedMovieAllRemovalButtonClicked();
}, false);
this.configDialog.getNewWindowOpenCheckBox().addEventListener("click", function() {
self.newWindowOpenCheckBoxClicked();
}, false);
};
Controller.prototype.visitButtonClicked = function(visitButton) {
var movieId = View.getMovieIdOfMovieRoot(View.getMovieRoot(visitButton));
this.view.getVisitType(movieId).visitButtonClicked(this.model, movieId);
if (this.model.getVisitedMovieViewMode() !== View.Mode.DO_NOTHING) {
View.loadLazyImagesIfRequired();
}
};
Controller.prototype.ngMovieIdButtonClicked = function(ngMovieButton) {
var movieId = View.getMovieIdOfMovieRoot(View.getMovieRoot(ngMovieButton));
this.view.getNgMovieIdType(movieId).ngMovieIdButtonClicked(this.model, movieId);
if (!this.model.isNgMovieVisible()) {
View.loadLazyImagesIfRequired();
}
};
Controller.prototype.ngMovieTitleButtonClicked = function(ngTitleButton) {
var movieId = View.getMovieIdOfMovieRoot(View.getMovieRoot(ngTitleButton));
this.view.getNgMovieTitleType(movieId)
.ngMovieTitleButtonClicked(this.model, movieId);
if (!this.model.isNgMovieVisible()) {
View.loadLazyImagesIfRequired();
}
};
Controller.prototype.addListenersToMovieButtons = function() {
var self = this;
var ngMovieIdButtonListener = function(event) {
self.ngMovieIdButtonClicked(event.target);
};
var ngMovieTitleButtonListener = function(event) {
self.ngMovieTitleButtonClicked(event.target);
};
var visitButtonListener = function(event) {
self.visitButtonClicked(event.target);
};
View.getMovies().forEach(function(movie) {
this.view.getNgMovieIdButton(movie.getId())
.addEventListener("click", ngMovieIdButtonListener, false);
this.view.getNgMovieTitleButton(movie.getId())
.addEventListener("click", ngMovieTitleButtonListener, false);
this.view.getVisitButton(movie.getId())
.addEventListener("click", visitButtonListener, false);
}, this);
};
Controller.prototype.synchVisitedMovieViewMode = function() {
this.model.getVisitedMovieViewMode().viewModeChanged(this.view);
};
Controller.prototype.synchNewWindowOpen = function() {
View.setTargetAttrsOfMovieAnchors(this.model.isNewWindowOpen());
this.configDialog.getNewWindowOpenCheckBox().checked =
this.model.isNewWindowOpen();
};
Controller.prototype.synchMovie = function(movie) {
if (this.isHideRequired(movie)) {
this.view.setViewState(movie.getId(), View.Mode.HIDE);
} else if (this.isReduceRequired(movie)) {
this.view.setViewState(movie.getId(), View.Mode.REDUCE);
} else {
this.view.setViewState(movie.getId(), View.Mode.DO_NOTHING);
}
this.view.setNgMovieTitle(movie.getId(),
movie.isNgTitle() ? movie.getMatchedNgTitle() : "");
this.view.setNgMovieId(movie.getId(), movie.isNgId());
this.view.setVisitType(movie.getId(),
movie.isVisited() ? View.REMOVAL : View.ADDITION);
this.view.setNgMovieIdType(movie.getId(),
movie.isNgId() ? View.REMOVAL : View.ADDITION);
this.view.setNgMovieTitleType(movie.getId(),
movie.isNgTitle() ? View.REMOVAL : View.ADDITION);
};
Controller.prototype.synchAllMovies = function() {
this.model.getMovies().forEach(function(movie) {
this.synchMovie(movie);
}, this);
};
function main() {
var view = new View();
view.addToolbox();
view.appendAllMovieButtons();
var store = new Store();
store.setGetter(GM_getValue);
store.setSetter(GM_setValue);
store.setRemover(GM_deleteValue);
var model = new Model(View.getMovies(), store);
model.getAndSetAllNgMovieIds();
model.getAndSetAllNgMovieTitles();
model.getAndSetAllVisitedMovies();
var ctrl = new Controller(model, view);
ctrl.synchVisitedMovieViewMode();
ctrl.synchNewWindowOpen();
ctrl.synchAllMovies();
ctrl.setListenersToAllMovies();
ctrl.setListenerToModel();
ctrl.addListenersToView();
View.loadLazyImagesIfRequired();
window.addEventListener("scroll", function() {
View.loadLazyImagesIfRequired();
}, false);
}
main();