/* -*- 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, once) { if (typeof block !== "function") { $.error("need a function"); } var stack = this.events[event] || []; stack.push([block, once]); this.events[event] = stack; }, one: function(event, block) { this.on(event, block, true); }, trigger: function(event, data) { var self = this; var stack = this.events[event] || []; this.events[event] = _.reject(stack, function(pair) { var block = pair[0], once = pair[1]; block.call(self, data); return once; }); }, send: function(json) { this.connection.send(JSON.stringify(json)); }, }); /************************************************************************* * 個別メニュー *************************************************************************/ var ContextMenu = Narou.ContextMenu = function(action, tag) { this.action = action; this.notification = Notification.instance(); this.tag = tag; this.closed = true; this.initializeConsoleDialog(); this.initializeDiffListEvent(); this.initializeMenu(); }; $.extend(ContextMenu.prototype, { open: function(target_id, pos, callback) { var self = this; this.target_id = target_id; if (!this.closed) { // メニューを開いた状態で直接ボタンを押した場合に一旦閉じるイベントを起こさせる this.close(); } this.closed = false; var caller = function() { if (typeof callback === "function") callback(); }; $(document).one("show.bs.dropdown", function() { self.object.hide(); self.closed = true; caller(); }); Narou.popupMenu(this.object, pos, function() { self.object.hide(); self.closed = true; caller(); }); }, close: function() { $(document).trigger("click"); this.closed = true; }, save: function(text) { this.text = text; storage.set("context_menu_text", text); storage.save(); this.object.remove(); this.initializeMenu(); }, 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({ 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(); }); }, initializeMenu: function() { this.initializeMenuObject(); this.initializeMenuEvents(); $("body").append(this.object); }, initializeMenuObject: function(text) { this.text = text || storage.get("context_menu_text") || this.createDefaultMenuText(); this.object = this.createMenuObject(this.text); }, createMenuObject: function(text) { var object = $('