/* -*- coding: utf-8 -*-
*
* Copyright 2013 whiteleaf. All rights reserved.
*/
var Narou = (function() {
"use strict";
var Narou = {};
var storage_cache = null;
var storage = null;
$.ajaxSetup({
cache: false // IE でキャッシュさせないため
});
// jQuery はデフォルトだと dataTransfer オブジェクトをコピーしないので
$.event.props.push("dataTransfer");
/*************************************************************************
* ローカルストレージ
*************************************************************************/
var Storage = Narou.Storage = function() {
this.initialize();
};
$.extend(Storage.prototype, {
storage_name: "Narou.rb_WEB_UI_saved",
initialize: function() {
if (!storage_cache) {
storage_cache = this.load();
}
this.objects = storage_cache;
},
load: function() {
var objects = localStorage.getItem(this.storage_name);
return objects ? JSON.parse(objects) : {};
},
save: function() {
localStorage.setItem(this.storage_name, JSON.stringify(this.objects));
},
get: function(key) {
return this.objects[key];
},
set: function(key, value) {
this.objects[key] = value;
return this;
},
});
storage = new Storage();
/*************************************************************************
* ユーティリティ
*************************************************************************/
$.extend(Narou, {
registerCloseHandler: function(callback) {
// Chrome, IEですぐにclickイベントをバインドすると、メニュー表示時の
// クリックに反応してしまう(表示上のズレによって、クリック時のマウス
// 座標上に対象オブジェクトが存在しないため)ので、イベント作成をほんの
// 少し遅らせる
setTimeout(function() {
// 関係ないところをクリックした時に閉じる
$(document).one("click", callback);
}, 100);
},
popupMenu: function(menu_id, pos, close_menu_handler) {
var $menu = $(menu_id);
var left = $(window).width() < pos.x - $(document).scrollLeft() + $menu.outerWidth() ?
pos.x - $menu.outerWidth() : pos.x;
var top = $(window).height() < pos.y - $(document).scrollTop() + $menu.outerHeight() ?
pos.y - $menu.outerHeight() : pos.y;
$menu.show().offset({
left: left, top: top
});
Narou.registerCloseHandler(close_menu_handler);
},
// http://qiita.com/osakanafish/items/c64fe8a34e7221e811d0
formatDate: function(date, format) {
if (!format) format = 'YYYY-MM-DD hh:mm:ss.SSS';
format = format.replace(/YYYY/g, date.getFullYear());
format = format.replace(/MM/g, ('0' + (date.getMonth() + 1)).slice(-2));
format = format.replace(/DD/g, ('0' + date.getDate()).slice(-2));
format = format.replace(/hh/g, ('0' + date.getHours()).slice(-2));
format = format.replace(/mm/g, ('0' + date.getMinutes()).slice(-2));
format = format.replace(/ss/g, ('0' + date.getSeconds()).slice(-2));
if (format.match(/S/g)) {
var milliSeconds = ('00' + date.getMilliseconds()).slice(-3);
var length = format.match(/S/g).length;
for (var i = 0; i < length; i++) format = format.replace(/S/, milliSeconds.substring(i, i + 1));
}
return format;
},
get_event_position: function(e) {
if (e.type !== "touchstart") {
return { x: e.pageX, y: e.pageY };
}
else {
return { x: e.originalEvent.touches[0].pageX,
y: e.originalEvent.touches[0].pageY };
}
},
noScroll: function() {
$("html, body").css("overflow", "hidden");
},
allowScroll: function() {
$("html, body").css("overflow", "");
},
});
/*************************************************************************
* Push 通知管理
*************************************************************************/
var Notification = Narou.Notification = function() {
this.initialize();
};
Notification.instance = function() {
if (!this.__instance) {
this.__instance = new this;
}
return this.__instance;
};
$.extend(Notification.prototype, {
RETRY_LIMIT: 5,
RETRY_WAIT: 2000,
initialize: function() {
this.events = {};
this.retry_count = this.RETRY_LIMIT;
this.connect();
},
connect: function() {
if (this.connection) return;
var self = this;
this.connected = false;
var connection = window.c = this.connection = new WebSocket(this.create_ws_uri());
connection.onopen = function() {
self.connected = true;
self.trigger("console.clear");
self.retry_count = self.RETRY_LIMIT; // 接続出来たらリトライカウント回復
};
connection.onclose = function() {
self.connection = null;
// PCのスリープ等でコネクションが切れた場合に再接続する
if (self.retry_count-- > 0) {
setTimeout(function() {
self.connected = false;
self.connect();
}, self.RETRY_WAIT);
}
};
connection.onmessage = function(e) {
if (e && e.data) {
self.onmessage(JSON.parse(e.data));
}
};
},
create_ws_uri: function() {
var host = location.hostname,
port = location.port;
return "ws://" + host + ":" + (parseInt(port) + 1) + "/";
},
onmessage: function(data) {
var self = this;
$.each(data, function(event, value) {
self.trigger(event, value);
});
},
on: function(event, block) {
if (typeof block !== "function") {
$.error("need a function");
}
var stack = this.events[event] || [];
stack.push(block);
this.events[event] = stack;
},
trigger: function(event, data) {
var self = this;
var stack = this.events[event] || [];
$.each(stack, function() {
this.call(self, data);
});
},
send: function(json) {
this.connection.send(JSON.stringify(json));
},
});
/*************************************************************************
* コンテキストメニュー
*************************************************************************/
var ContextMenu = Narou.ContextMenu = function(action, notification, tag) {
this.action = action;
this.notification = notification;
this.tag = tag;
this.closed = true;
this.initializeConsoleDialog();
this.initializeMenuEvent();
this.initializeDiffListEvent();
};
$.extend(ContextMenu.prototype, {
open: function(target_id, pos, callback) {
this.target_id = target_id;
if (!this.closed) {
// メニューを開いた状態で直接ボタンを押した場合に一旦閉じるイベントを起こさせる
$(document).trigger("click");
}
this.closed = false;
var caller = function() {
if (typeof callback === "function")
callback();
};
$(document).one("show.bs.dropdown", function() {
$("#context-menu").hide();
caller();
});
Narou.popupMenu("#context-menu", pos, function() {
$("#context-menu").hide();
caller();
});
},
openConsoleDialog: function(callback) {
if (typeof callback !== "function") return;
var $console_dialog = $("#console-dialog");
$console_dialog.one("shown.bs.modal", callback);
$(document).one("cancel.narou.remove", function() {
$console_dialog.modal("hide");
});
this.console.clear();
$console_dialog.modal();
},
initializeConsoleDialog: function() {
this.console = new Narou.Console(this.notification, {
restore: false, buttons: false,
id: "#each-console"
});
},
openSelectDiffListDialog: function(target_id) {
$.get("/api/diff_list", { target: target_id }, function(html) {
var diff_modal = bootbox.dialog({
title: "表示したい差分を選択して下さい",
message: html,
backdrop: true,
className: "diff-list-modal",
buttons: {
clear: {
label: "差分を消去",
className: "btn-danger",
callback: function() {
bootbox.confirm("本当に消去してよろしいですか?", function(result) {
if (result) {
$.post("/api/diff_clean", { target: target_id });
}
diff_modal.modal("hide");
});
return false; // 親モーダルはこの時点では閉じさせない
}
},
main: {
label: "閉じる",
className: "btn-default"
},
}
});
});
},
initializeDiffListEvent: function() {
$(document).on("click", ".diff-list-container .list .item", function() {
var target = $(this).parent().data("diffTarget");
var number = $(this).data("diffItemNumber");
$.post("/api/diff", { ids: [ target ], number: number });
bootbox.hideAll();
});
},
initializeMenuEvent: function() {
var $context_menu = $("#context-menu");
var self = this;
$("#context-menu-setting").on("click", function(e) {
e.preventDefault();
location.href = "/novels/" + self.target_id + "/setting";
});
$("#context-menu-update").on("click", function(e) {
e.preventDefault();
self.openConsoleDialog(function() {
self.action.update(self.target_id);
});
});
$("#context-menu-send").on("click", function(e) {
e.preventDefault();
self.openConsoleDialog(function() {
self.action.send(self.target_id);
});
});
$("#context-menu-freeze-toggle").on("click", function(e) {
e.preventDefault();
self.action.freeze(self.target_id);
});
$("#context-menu-remove").on("click", function(e) {
e.preventDefault();
self.openConsoleDialog(function() {
self.action.remove(self.target_id);
});
});
$("#context-menu-edit-tag").on("click", function(e) {
e.preventDefault();
self.tag.openEditor(self.target_id);
});
$("#context-menu-convert").on("click", function(e) {
e.preventDefault();
self.openConsoleDialog(function() {
self.action.convert(self.target_id);
});
});
$("#context-menu-diff").on("click", function(e) {
e.preventDefault();
self.openSelectDiffListDialog(self.target_id);
});
$("#context-menu-inspect").on("click", function(e) {
e.preventDefault();
self.openConsoleDialog(function() {
self.action.inspect(self.target_id);
});
});
$("#context-menu-folder").on("click", function(e) {
e.preventDefault();
self.action.folder(self.target_id);
});
$("#context-menu-backup").on("click", function(e) {
e.preventDefault();
self.openConsoleDialog(function() {
self.action.backup(self.target_id);
});
});
}
});
/*************************************************************************
* アクション
*************************************************************************/
var Action = Narou.Action = function(table) {
this.table = table;
};
$.extend(Action.prototype, {
_getSelectedIds: function(args) {
if (typeof args !== "undefined" && args.length > 0) {
return Array.prototype.slice.call(args);
}
var ids = [];
$.each(this.table.rows(".selected").data(), function(i, val) {
ids.push(val.id);
});
return ids;
},
selectAll: function() {
this.table.$("tr").addClass("selected");
this.table.fireChangeSelect();
},
selectView: function() {
$("#novel-list tbody tr").addClass("selected");
this.table.fireChangeSelect();
},
selectClear: function() {
this.table.$("tr.selected").removeClass("selected");
this.table.fireChangeSelect();
},
download: function() {
var urls = [];
if (typeof arguments !== "undefined" && arguments.length > 0) {
urls = Array.prototype.slice.call(arguments);
}
if (urls.length == 0) {
$.get("/partial/download_form")
.done(function(html) {
var download_modal = bootbox.dialog({
title: "ダウンロードする小説のURL、もしくはNコードを入力(複数可)",
message: html,
backdrop: true,
buttons: {
cancel: {
label: "キャンセル",
className: "btn-default"
},
main: {
label: "ダウンロード",
className: "btn-primary",
callback: function() {
$("#download-link-submit").click();
}
}
}
});
download_modal.one("shown.bs.modal", function () {
$("#download-input").focus();
});
});
}
else {
$.post("/api/download", { targets: urls });
}
},
downloadForce: function() {
var ids = this._getSelectedIds(arguments);
if (ids.length === 0) return;
$.post("/api/download_force", { "ids": ids });
},
update: function() {
var ids = this._getSelectedIds(arguments);
$.post("/api/update", { "ids": ids });
},
updateView: function() {
var self = this;
var ids = [];
$("#novel-list tbody tr").each(function(i, tr) {
var data = self.table.row(tr).data();
if (data) {
ids.push(data.id);
}
});
if (ids.length > 0) {
$.post("/api/update", { "ids": ids });
}
},
updateGeneralLastup: function() {
bootbox.dialog({
title: '最新話掲載日の更新',
message: "凍結済みを除く各小説の最新話掲載日を更新します。
" +
"最新話掲載日は通常時のUPDATEでも更新されるので、手動で更新する必要は基本的にはありません。
" +
"掲載日だけを調べて、選択的にUPDATEをかけるなど、用途を理解した上で小説サイトのサーバーに負荷をかけない範囲でご利用下さい。",
backdrop: true,
buttons: {
cancel: {
label: "キャンセル",
className: "btn-default",
},
main: {
label: "更新する",
className: "btn-primary",
callback: function() {
$.post("/api/update_general_lastup");
}
},
}
});
},
updateForce: function() {
var ids = this._getSelectedIds(arguments);
$.post("/api/update", { ids: ids, force: true });
},
send: function() {
var ids = this._getSelectedIds(arguments);
$.post("/api/send", { "ids": ids });
},
freeze: function() {
var ids = this._getSelectedIds(arguments);
if (ids.length === 0) return;
$.post("/api/freeze", { "ids": ids });
},
freezeOn: function() {
var ids = this._getSelectedIds(arguments);
if (ids.length === 0) return;
$.post("/api/freeze_on", { "ids": ids });
},
freezeOff: function() {
var ids = this._getSelectedIds(arguments);
if (ids.length === 0) return;
$.post("/api/freeze_off", { "ids": ids });
},
_removeConfirmDialog: function(title, ids, callback) {
var message = "";
this.table.rows().data().each(function(data, idx) {
if (ids.indexOf(data.id + "") !== -1) {
message += "