narou 1.7.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of narou might be problematic. Click here for more details.
- checksums.yaml +5 -13
- data/.gitignore +1 -0
- data/ChangeLog.md +35 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +100 -0
- data/README.md +28 -39
- data/lib/color.rb +0 -2
- data/lib/command.rb +1 -0
- data/lib/command/convert.rb +33 -4
- data/lib/command/diff.rb +5 -4
- data/lib/command/download.rb +9 -1
- data/lib/command/flag.rb +2 -2
- data/lib/command/list.rb +1 -1
- data/lib/command/mail.rb +3 -3
- data/lib/command/remove.rb +2 -1
- data/lib/command/send.rb +7 -6
- data/lib/command/setting.rb +39 -95
- data/lib/command/tag.rb +25 -13
- data/lib/command/update.rb +6 -1
- data/lib/command/version.rb +5 -1
- data/lib/command/web.rb +111 -0
- data/lib/commandbase.rb +5 -2
- data/lib/commandline.rb +16 -0
- data/lib/converterbase.rb +20 -14
- data/lib/device.rb +5 -4
- data/lib/downloader.rb +68 -39
- data/lib/eventable.rb +72 -0
- data/lib/helper.rb +105 -37
- data/lib/ini.rb +2 -1
- data/lib/input.rb +68 -0
- data/lib/inventory.rb +4 -0
- data/lib/kindlestrip.rb +2 -2
- data/lib/logger.rb +41 -19
- data/lib/narou.rb +10 -0
- data/lib/narou/api.rb +1 -1
- data/lib/novelconverter.rb +8 -21
- data/lib/novelsetting.rb +79 -4
- data/lib/version.rb +1 -1
- data/lib/web/all.rb +12 -0
- data/lib/web/appserver.rb +612 -0
- data/lib/web/helper4web.rb +15 -0
- data/lib/web/progressbar4web.rb +32 -0
- data/lib/web/public/favicon.ico +0 -0
- data/lib/web/public/resources/bootbox.min.js +6 -0
- data/lib/web/public/resources/common.ui.js +143 -0
- data/lib/web/public/resources/dataTables.colVis.js +1113 -0
- data/lib/web/public/resources/help/rect_select.png +0 -0
- data/lib/web/public/resources/help/ssmain.png +0 -0
- data/lib/web/public/resources/help/tag.png +0 -0
- data/lib/web/public/resources/jquery.moveto.js +44 -0
- data/lib/web/public/resources/jquery.outerclick.js +60 -0
- data/lib/web/public/resources/jquery.slidenavbar.js +89 -0
- data/lib/web/public/resources/narou.library.js +815 -0
- data/lib/web/public/resources/narou.ui.js +993 -0
- data/lib/web/public/resources/perfect-scrollbar.min.css +5 -0
- data/lib/web/public/resources/perfect-scrollbar.min.js +4 -0
- data/lib/web/public/resources/shortcut.js +223 -0
- data/lib/web/public/resources/sort_asc.png +0 -0
- data/lib/web/public/resources/sort_desc.png +0 -0
- data/lib/web/public/resources/toggle-switch.css +322 -0
- data/lib/web/public/robots.txt +3 -0
- data/lib/web/public/test/jquery.outerclick.html +72 -0
- data/lib/web/pushserver.rb +110 -0
- data/lib/web/settingmessages.rb +14 -0
- data/lib/web/streaminginput.rb +103 -0
- data/lib/web/streaminglogger.rb +52 -0
- data/lib/web/views/about.haml +11 -0
- data/lib/web/views/help.haml +105 -0
- data/lib/web/views/index.haml +245 -0
- data/lib/web/views/js/widget.erb +74 -0
- data/lib/web/views/layout.haml +49 -0
- data/lib/web/views/novels/setting.haml +177 -0
- data/lib/web/views/settings.haml +115 -0
- data/lib/web/views/style.scss +737 -0
- data/lib/web/views/widget.haml +39 -0
- data/lib/web/web-socket-ruby/.gitignore +1 -0
- data/lib/web/web-socket-ruby/README.txt +75 -0
- data/lib/web/web-socket-ruby/lib/web_socket.rb +601 -0
- data/lib/web/web-socket-ruby/samples/chat_server.rb +58 -0
- data/lib/web/web-socket-ruby/samples/echo_server.rb +33 -0
- data/lib/web/web-socket-ruby/samples/stdio_client.rb +25 -0
- data/lib/web/worker.rb +87 -0
- data/narou.gemspec +36 -3
- data/narou.rb +8 -6
- data/preset/ncode.syosetu.com/n8725k/converter.rb +2 -1
- data/spec/data/convert_test/replace/correct_test_replace.txt +1 -1
- data/spec/data/convert_test/replace/test_replace.txt +1 -1
- data/spec/data/convert_test/ruby/correct_test_ruby.txt +18 -1
- data/spec/data/convert_test/ruby/test_ruby.txt +18 -0
- data/spec/downloader_spec.rb +37 -0
- data/spec/eventable_spec.rb +172 -0
- data/spec/exit_code_spec.rb +67 -0
- data/spec/helper_spec.rb +72 -0
- data/spec/input_spec.rb +76 -0
- data/spec/logger_spec.rb +53 -0
- data/spec/novelsetting_spec.rb +35 -0
- data/spec/worker_spec.rb +56 -0
- data/template/ibunko_novel.txt.erb +2 -2
- data/template/novel.txt.erb +2 -2
- metadata +213 -29
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,44 @@
|
|
1
|
+
/* -*- coding: utf-8 -*-
|
2
|
+
*
|
3
|
+
* jquery.moveto.js
|
4
|
+
* Copyright 2014 whiteleaf. All rights reserved.
|
5
|
+
*
|
6
|
+
* Usage:
|
7
|
+
* $.moveTo({
|
8
|
+
* duration: 500
|
9
|
+
* });
|
10
|
+
*
|
11
|
+
* in your html:
|
12
|
+
* <a href="#section2" data-move-to="#section2">Jump</a>
|
13
|
+
* ==/snip/==
|
14
|
+
* <h2 id="section2">Section #2</h2>
|
15
|
+
*/
|
16
|
+
|
17
|
+
(function($) {
|
18
|
+
"use strict";
|
19
|
+
// default options
|
20
|
+
var options = {
|
21
|
+
duration: "fast"
|
22
|
+
};
|
23
|
+
var touchable_device = "ontouchstart" in window;
|
24
|
+
var toggle_event_name = (touchable_device ? "touchstart" : "click");
|
25
|
+
var moveTo = {
|
26
|
+
initialize: function() {
|
27
|
+
var body_padding_top = parseInt($("body").css("padding-top"));
|
28
|
+
return $("[data-move-to]").on(toggle_event_name, function(e) {
|
29
|
+
e.preventDefault();
|
30
|
+
var target = $(this).data("moveTo");
|
31
|
+
var target_y = 0;
|
32
|
+
if (target !== "top") {
|
33
|
+
target_y = $(target).offset().top - body_padding_top;
|
34
|
+
}
|
35
|
+
$("html,body").animate({ scrollTop: target_y }, options.duration);
|
36
|
+
});
|
37
|
+
},
|
38
|
+
};
|
39
|
+
$.moveTo = function(opts) {
|
40
|
+
$.extend(options, opts);
|
41
|
+
return moveTo.initialize();
|
42
|
+
};
|
43
|
+
}(jQuery));
|
44
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
/* -*- coding: utf-8 -*-
|
2
|
+
*
|
3
|
+
* jquery.outerclick.js
|
4
|
+
* Copyright 2014 whiteleaf. All rights reserved.
|
5
|
+
*
|
6
|
+
* Usage:
|
7
|
+
* $("#menu").outerClick(function() {
|
8
|
+
* $(this).hide(); // do something
|
9
|
+
* });
|
10
|
+
* $("#menu").outerClickOne(function() {
|
11
|
+
* // once process
|
12
|
+
* });
|
13
|
+
* either
|
14
|
+
* $("#menu").outerClick(function(){}, "one");
|
15
|
+
*/
|
16
|
+
|
17
|
+
(function($) {
|
18
|
+
"use strict";
|
19
|
+
var outerClick = function(obj, callback, bind_func) {
|
20
|
+
var self = this;
|
21
|
+
$(document)[bind_func]("click", function(e) {
|
22
|
+
var pos = self.getEventPosition(e);
|
23
|
+
var element = self.elementFromPoint(pos);
|
24
|
+
if (obj !== element) {
|
25
|
+
if (!self.searchTargetUpstream(element, obj)) {
|
26
|
+
callback.apply(obj);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
});
|
30
|
+
};
|
31
|
+
$.extend(outerClick.prototype, {
|
32
|
+
elementFromPoint : function(pos) {
|
33
|
+
var doc = $(document);
|
34
|
+
var element = document.elementFromPoint(pos.x - doc.scrollLeft(),
|
35
|
+
pos.y - doc.scrollTop());
|
36
|
+
return element;
|
37
|
+
},
|
38
|
+
getEventPosition : function(event) {
|
39
|
+
return { x: event.pageX, y: event.pageY };
|
40
|
+
},
|
41
|
+
searchTargetUpstream : function(element, target) {
|
42
|
+
var parent = $(element).parent();
|
43
|
+
if (parent.length === 0) return null;
|
44
|
+
if (parent[0] === target) return parent;
|
45
|
+
return this.searchTargetUpstream(parent, target);
|
46
|
+
},
|
47
|
+
});
|
48
|
+
$.fn.outerClick = function(callback, bind_func) {
|
49
|
+
if (typeof bind_func === "undefined") bind_func = "on";
|
50
|
+
return this.each(function() {
|
51
|
+
new outerClick(this, callback, bind_func);
|
52
|
+
});
|
53
|
+
};
|
54
|
+
$.fn.outerClickOne = function(callback) {
|
55
|
+
return this.each(function() {
|
56
|
+
new outerClick(this, callback, "one");
|
57
|
+
});
|
58
|
+
}
|
59
|
+
}(jQuery));
|
60
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
/* -*- coding: utf-8 -*-
|
2
|
+
*
|
3
|
+
* jquery.slideNavbar.js
|
4
|
+
* Copyright 2014 whiteleaf. All rights reserved.
|
5
|
+
*
|
6
|
+
* Usage:
|
7
|
+
* var slideNavbar = $(".navbar-collapse").slideNavbar();
|
8
|
+
* slideNavbar.slide(); // slide nav manually
|
9
|
+
* slideNavbar.slide(true); // no animation
|
10
|
+
*
|
11
|
+
* in your css:
|
12
|
+
* @media (max-width:767px) {
|
13
|
+
* .navbar-collapse {
|
14
|
+
* position: absolute;
|
15
|
+
* right: -270px;
|
16
|
+
* width: 270px;
|
17
|
+
* }
|
18
|
+
* }
|
19
|
+
*
|
20
|
+
* Require:
|
21
|
+
* Bootstrap v3.2.0 or higher
|
22
|
+
*/
|
23
|
+
|
24
|
+
(function($) {
|
25
|
+
"use strict";
|
26
|
+
// default options
|
27
|
+
var options = {
|
28
|
+
hiding_class: "slide-hiding-collapse",
|
29
|
+
width: 270,
|
30
|
+
duration: 250
|
31
|
+
};
|
32
|
+
var touchable_device = "ontouchstart" in window;
|
33
|
+
var toggle_event_name = (touchable_device ? "touchstart" : "click");
|
34
|
+
var slideNavbar = {
|
35
|
+
initialize: function(i, obj) {
|
36
|
+
var collapse = $(obj);
|
37
|
+
var self = this;
|
38
|
+
this.hiding_class = options.hiding_class;
|
39
|
+
this.collapse = collapse;
|
40
|
+
collapse.css({
|
41
|
+
"max-height": $(window).height() * 0.9,
|
42
|
+
});
|
43
|
+
$(".navbar-toggle").off("click");
|
44
|
+
$(".navbar-toggle").on(toggle_event_name, function(e) {
|
45
|
+
e.preventDefault();
|
46
|
+
e.stopPropagation();
|
47
|
+
self.slide();
|
48
|
+
});
|
49
|
+
collapse.addClass(self.hiding_class);
|
50
|
+
collapse.addClass("in"); // enable visible collpase nav
|
51
|
+
},
|
52
|
+
slide: function(force_close) {
|
53
|
+
var duration_org = options.duration;
|
54
|
+
if (this.collapse.hasClass(this.hiding_class) && !force_close) {
|
55
|
+
this.slideOpen();
|
56
|
+
this.collapse.removeClass(this.hiding_class);
|
57
|
+
}
|
58
|
+
else {
|
59
|
+
this.slideClose();
|
60
|
+
this.collapse.addClass(this.hiding_class);
|
61
|
+
}
|
62
|
+
},
|
63
|
+
slideOpen: function() {
|
64
|
+
this.collapse.stop().animate({right: "0"}, options.duration);
|
65
|
+
},
|
66
|
+
slideClose: function() {
|
67
|
+
this.collapse.stop().animate({
|
68
|
+
right: "-" + options.width + "px"
|
69
|
+
}, options.duration);
|
70
|
+
},
|
71
|
+
};
|
72
|
+
var Api = function() {};
|
73
|
+
Api.prototype.slide = function(force_close) {
|
74
|
+
if ($(".navbar-toggle").is(":visible")) {
|
75
|
+
slideNavbar.slide(force_close);
|
76
|
+
}
|
77
|
+
};
|
78
|
+
Api.prototype.close = function() {
|
79
|
+
slideNavbar.slide(true);
|
80
|
+
};
|
81
|
+
$.fn.slideNavbar = function(opts) {
|
82
|
+
$.extend(options, opts);
|
83
|
+
this.each(function(i, obj) {
|
84
|
+
slideNavbar.initialize(i, obj);
|
85
|
+
});
|
86
|
+
return new Api();
|
87
|
+
};
|
88
|
+
}(jQuery));
|
89
|
+
|
@@ -0,0 +1,815 @@
|
|
1
|
+
/* -*- coding: utf-8 -*-
|
2
|
+
*
|
3
|
+
* Copyright 2013 whiteleaf. All rights reserved.
|
4
|
+
*/
|
5
|
+
|
6
|
+
var Narou = (function() {
|
7
|
+
"use strict";
|
8
|
+
|
9
|
+
var Narou = {};
|
10
|
+
var storage_cache = null;
|
11
|
+
var storage = null;
|
12
|
+
|
13
|
+
/*************************************************************************
|
14
|
+
* ローカルストレージ
|
15
|
+
*************************************************************************/
|
16
|
+
var Storage = Narou.Storage = function() {
|
17
|
+
this.initialize();
|
18
|
+
};
|
19
|
+
|
20
|
+
$.extend(Storage.prototype, {
|
21
|
+
storage_name: "Narou.rb_WEB_UI_saved",
|
22
|
+
initialize: function() {
|
23
|
+
this.objects = storage_cache ? storage_cache : this.load();
|
24
|
+
},
|
25
|
+
|
26
|
+
load: function() {
|
27
|
+
var objects = localStorage.getItem(this.storage_name);
|
28
|
+
return objects ? JSON.parse(objects) : {};
|
29
|
+
},
|
30
|
+
|
31
|
+
save: function() {
|
32
|
+
localStorage.setItem(this.storage_name, JSON.stringify(this.objects));
|
33
|
+
},
|
34
|
+
|
35
|
+
get: function(key) {
|
36
|
+
return this.objects[key];
|
37
|
+
},
|
38
|
+
|
39
|
+
set: function(key, value) {
|
40
|
+
this.objects[key] = value;
|
41
|
+
return this;
|
42
|
+
},
|
43
|
+
});
|
44
|
+
|
45
|
+
storage = new Storage();
|
46
|
+
|
47
|
+
/*************************************************************************
|
48
|
+
* ユーティリティ
|
49
|
+
*************************************************************************/
|
50
|
+
$.extend(Narou, {
|
51
|
+
registerCloseHandler: function(callback) {
|
52
|
+
// Chrome, IEですぐにclickイベントをバインドすると、メニュー表示時の
|
53
|
+
// クリックに反応してしまう(表示上のズレによって、クリック時のマウス
|
54
|
+
// 座標上に対象オブジェクトが存在しないため)ので、イベント作成をほんの
|
55
|
+
// 少し遅らせる
|
56
|
+
setTimeout(function() {
|
57
|
+
// 関係ないところをクリックした時に閉じる
|
58
|
+
$(document).one("click", callback);
|
59
|
+
}, 100);
|
60
|
+
},
|
61
|
+
|
62
|
+
popupMenu: function(menu_id, pos, close_menu_handler) {
|
63
|
+
var $menu = $(menu_id);
|
64
|
+
var left = $(window).width() < pos.x - $(document).scrollLeft() + $menu.outerWidth() ?
|
65
|
+
pos.x - $menu.outerWidth() : pos.x;
|
66
|
+
var top = $(window).height() < pos.y - $(document).scrollTop() + $menu.outerHeight() ?
|
67
|
+
pos.y - $menu.outerHeight() : pos.y;
|
68
|
+
$menu.show().offset({
|
69
|
+
left: left, top: top
|
70
|
+
});
|
71
|
+
Narou.registerCloseHandler(close_menu_handler);
|
72
|
+
},
|
73
|
+
|
74
|
+
// http://qiita.com/osakanafish/items/c64fe8a34e7221e811d0
|
75
|
+
formatDate: function(date, format) {
|
76
|
+
if (!format) format = 'YYYY-MM-DD hh:mm:ss.SSS';
|
77
|
+
format = format.replace(/YYYY/g, date.getFullYear());
|
78
|
+
format = format.replace(/MM/g, ('0' + (date.getMonth() + 1)).slice(-2));
|
79
|
+
format = format.replace(/DD/g, ('0' + date.getDate()).slice(-2));
|
80
|
+
format = format.replace(/hh/g, ('0' + date.getHours()).slice(-2));
|
81
|
+
format = format.replace(/mm/g, ('0' + date.getMinutes()).slice(-2));
|
82
|
+
format = format.replace(/ss/g, ('0' + date.getSeconds()).slice(-2));
|
83
|
+
if (format.match(/S/g)) {
|
84
|
+
var milliSeconds = ('00' + date.getMilliseconds()).slice(-3);
|
85
|
+
var length = format.match(/S/g).length;
|
86
|
+
for (var i = 0; i < length; i++) format = format.replace(/S/, milliSeconds.substring(i, i + 1));
|
87
|
+
}
|
88
|
+
return format;
|
89
|
+
},
|
90
|
+
});
|
91
|
+
|
92
|
+
/*************************************************************************
|
93
|
+
* Push 通知管理
|
94
|
+
*************************************************************************/
|
95
|
+
var Notification = Narou.Notification = function() {
|
96
|
+
this.initialize();
|
97
|
+
};
|
98
|
+
|
99
|
+
Notification.instance = function() {
|
100
|
+
if (!this.__instance) {
|
101
|
+
this.__instance = new this;
|
102
|
+
}
|
103
|
+
return this.__instance;
|
104
|
+
};
|
105
|
+
|
106
|
+
$.extend(Notification.prototype, {
|
107
|
+
RETRY_LIMIT: 5,
|
108
|
+
RETRY_WAIT: 2000,
|
109
|
+
|
110
|
+
initialize: function() {
|
111
|
+
this.events = {};
|
112
|
+
this.retry_count = this.RETRY_LIMIT;
|
113
|
+
this.connect();
|
114
|
+
},
|
115
|
+
|
116
|
+
connect: function() {
|
117
|
+
if (this.connection) return;
|
118
|
+
var self = this;
|
119
|
+
this.connected = false;
|
120
|
+
var connection = window.c = this.connection = new WebSocket(this.create_ws_uri());
|
121
|
+
|
122
|
+
connection.onopen = function() {
|
123
|
+
self.connected = true;
|
124
|
+
self.trigger("console.clear");
|
125
|
+
self.retry_count = self.RETRY_LIMIT; // 接続出来たらリトライカウント回復
|
126
|
+
};
|
127
|
+
|
128
|
+
connection.onclose = function() {
|
129
|
+
self.connection = null;
|
130
|
+
// PCのスリープ等でコネクションが切れた場合に再接続する
|
131
|
+
if (self.retry_count-- > 0) {
|
132
|
+
setTimeout(function() {
|
133
|
+
self.connected = false;
|
134
|
+
self.connect();
|
135
|
+
}, self.RETRY_WAIT);
|
136
|
+
}
|
137
|
+
};
|
138
|
+
|
139
|
+
connection.onmessage = function(e) {
|
140
|
+
if (e && e.data) {
|
141
|
+
self.onmessage(JSON.parse(e.data));
|
142
|
+
}
|
143
|
+
};
|
144
|
+
},
|
145
|
+
|
146
|
+
create_ws_uri: function() {
|
147
|
+
var host = location.hostname,
|
148
|
+
port = location.port;
|
149
|
+
return "ws://" + host + ":" + (parseInt(port) + 1) + "/";
|
150
|
+
},
|
151
|
+
|
152
|
+
onmessage: function(data) {
|
153
|
+
var self = this;
|
154
|
+
$.each(data, function(event, value) {
|
155
|
+
self.trigger(event, value);
|
156
|
+
});
|
157
|
+
},
|
158
|
+
|
159
|
+
on: function(event, block) {
|
160
|
+
if (typeof block !== "function") {
|
161
|
+
$.error("need a function");
|
162
|
+
}
|
163
|
+
var stack = this.events[event] || [];
|
164
|
+
stack.push(block);
|
165
|
+
this.events[event] = stack;
|
166
|
+
},
|
167
|
+
|
168
|
+
trigger: function(event, data) {
|
169
|
+
var self = this;
|
170
|
+
var stack = this.events[event] || [];
|
171
|
+
$.each(stack, function() {
|
172
|
+
this.call(self, data);
|
173
|
+
});
|
174
|
+
},
|
175
|
+
|
176
|
+
send: function(json) {
|
177
|
+
this.connection.send(JSON.stringify(json));
|
178
|
+
},
|
179
|
+
});
|
180
|
+
|
181
|
+
/*************************************************************************
|
182
|
+
* コンテキストメニュー
|
183
|
+
*************************************************************************/
|
184
|
+
var ContextMenu = Narou.ContextMenu = function(action, notification) {
|
185
|
+
this.action = action;
|
186
|
+
this.notification = notification;
|
187
|
+
this.closed = true;
|
188
|
+
this.initializeConsoleDialog();
|
189
|
+
this.initializeMenuEvent();
|
190
|
+
};
|
191
|
+
|
192
|
+
$.extend(ContextMenu.prototype, {
|
193
|
+
open: function(target_id, pos, callback) {
|
194
|
+
this.target_id = target_id;
|
195
|
+
if (!this.closed) {
|
196
|
+
// メニューを開いた状態で直接ボタンを押した場合に一旦閉じるイベントを起こさせる
|
197
|
+
$(document).trigger("click");
|
198
|
+
}
|
199
|
+
this.closed = false;
|
200
|
+
Narou.popupMenu("#context-menu", pos, function() {
|
201
|
+
$("#context-menu").hide();
|
202
|
+
if (typeof callback === "function") {
|
203
|
+
callback();
|
204
|
+
}
|
205
|
+
});
|
206
|
+
},
|
207
|
+
|
208
|
+
openConsoleDialog: function(callback) {
|
209
|
+
if (typeof callback !== "function") return;
|
210
|
+
var $console_dialog = $("#console-dialog");
|
211
|
+
$console_dialog.one("show.bs.modal", callback);
|
212
|
+
$(document).one("cancel.narou.remove", function() {
|
213
|
+
$console_dialog.modal("hide");
|
214
|
+
});
|
215
|
+
this.console.clear();
|
216
|
+
$console_dialog.modal();
|
217
|
+
},
|
218
|
+
|
219
|
+
initializeConsoleDialog: function() {
|
220
|
+
this.console = new Narou.Console(this.notification, {
|
221
|
+
restore: false, buttons: false,
|
222
|
+
id: "#each-console"
|
223
|
+
});
|
224
|
+
},
|
225
|
+
|
226
|
+
initializeMenuEvent: function() {
|
227
|
+
var $context_menu = $("#context-menu");
|
228
|
+
var self = this;
|
229
|
+
$("#context-menu-setting").on("click", function(e) {
|
230
|
+
e.preventDefault();
|
231
|
+
location.href = "/novels/" + self.target_id + "/setting";
|
232
|
+
});
|
233
|
+
$("#context-menu-update").on("click", function(e) {
|
234
|
+
e.preventDefault();
|
235
|
+
self.openConsoleDialog(function() {
|
236
|
+
self.action.update(self.target_id);
|
237
|
+
});
|
238
|
+
});
|
239
|
+
$("#context-menu-send").on("click", function(e) {
|
240
|
+
e.preventDefault();
|
241
|
+
self.openConsoleDialog(function() {
|
242
|
+
self.action.send(self.target_id);
|
243
|
+
});
|
244
|
+
});
|
245
|
+
$("#context-menu-freeze-toggle").on("click", function(e) {
|
246
|
+
e.preventDefault();
|
247
|
+
self.action.freeze(self.target_id);
|
248
|
+
});
|
249
|
+
$("#context-menu-remove").on("click", function(e) {
|
250
|
+
e.preventDefault();
|
251
|
+
self.openConsoleDialog(function() {
|
252
|
+
self.action.remove(self.target_id);
|
253
|
+
});
|
254
|
+
});
|
255
|
+
$("#context-menu-remove-with-file").on("click", function(e) {
|
256
|
+
e.preventDefault();
|
257
|
+
self.openConsoleDialog(function() {
|
258
|
+
self.action.removeWithFile(self.target_id);
|
259
|
+
});
|
260
|
+
});
|
261
|
+
$("#context-menu-convert").on("click", function(e) {
|
262
|
+
e.preventDefault();
|
263
|
+
self.openConsoleDialog(function() {
|
264
|
+
self.action.convert(self.target_id);
|
265
|
+
});
|
266
|
+
});
|
267
|
+
$("#context-menu-diff").on("click", function(e) {
|
268
|
+
e.preventDefault();
|
269
|
+
self.action.diff(self.target_id);
|
270
|
+
});
|
271
|
+
$("#context-menu-inspect").on("click", function(e) {
|
272
|
+
e.preventDefault();
|
273
|
+
self.openConsoleDialog(function() {
|
274
|
+
self.action.inspect(self.target_id);
|
275
|
+
});
|
276
|
+
});
|
277
|
+
$("#context-menu-folder").on("click", function(e) {
|
278
|
+
e.preventDefault();
|
279
|
+
self.action.folder(self.target_id);
|
280
|
+
});
|
281
|
+
$("#context-menu-backup").on("click", function(e) {
|
282
|
+
e.preventDefault();
|
283
|
+
self.openConsoleDialog(function() {
|
284
|
+
self.action.backup(self.target_id);
|
285
|
+
});
|
286
|
+
});
|
287
|
+
}
|
288
|
+
});
|
289
|
+
|
290
|
+
/*************************************************************************
|
291
|
+
* アクション
|
292
|
+
*************************************************************************/
|
293
|
+
var Action = Narou.Action = function(table) {
|
294
|
+
this.table = table;
|
295
|
+
};
|
296
|
+
|
297
|
+
$.extend(Action.prototype, {
|
298
|
+
_getSelectedIds: function(args) {
|
299
|
+
if (typeof args !== "undefined" && args.length > 0) {
|
300
|
+
return Array.prototype.slice.call(args);
|
301
|
+
}
|
302
|
+
var ids = [];
|
303
|
+
$.each(this.table.rows(".selected").data(), function(i, val) {
|
304
|
+
ids.push(val.id);
|
305
|
+
});
|
306
|
+
return ids;
|
307
|
+
},
|
308
|
+
|
309
|
+
selectAll: function() {
|
310
|
+
this.table.$("tr").addClass("selected");
|
311
|
+
this.table.fireChangeSelect();
|
312
|
+
},
|
313
|
+
|
314
|
+
selectView: function() {
|
315
|
+
$("#novel-list tbody tr").addClass("selected");
|
316
|
+
this.table.fireChangeSelect();
|
317
|
+
},
|
318
|
+
|
319
|
+
selectClear: function() {
|
320
|
+
this.table.$("tr.selected").removeClass("selected");
|
321
|
+
this.table.fireChangeSelect();
|
322
|
+
},
|
323
|
+
|
324
|
+
download: function() {
|
325
|
+
bootbox.prompt("ダウンロードする小説のURL、もしくはNコードを入力", function(target) {
|
326
|
+
if (!target) return;
|
327
|
+
$.post("/api/download", { "target": target });
|
328
|
+
console.log("new downloading %o", target);
|
329
|
+
});
|
330
|
+
},
|
331
|
+
|
332
|
+
downloadForce: function() {
|
333
|
+
var ids = this._getSelectedIds(arguments);
|
334
|
+
if (ids.length === 0) return;
|
335
|
+
$.post("/api/download_force", { "ids": ids });
|
336
|
+
console.log("force downloading " + ids.join(", "));
|
337
|
+
},
|
338
|
+
|
339
|
+
update: function() {
|
340
|
+
var ids = this._getSelectedIds(arguments);
|
341
|
+
if (ids.length === 0) {
|
342
|
+
$.post("/api/update");
|
343
|
+
console.log("updating all");
|
344
|
+
}
|
345
|
+
else {
|
346
|
+
$.post("/api/update_select", { "ids": ids });
|
347
|
+
console.log("updating " + ids.join(", "));
|
348
|
+
}
|
349
|
+
},
|
350
|
+
|
351
|
+
send: function() {
|
352
|
+
var ids = this._getSelectedIds(arguments);
|
353
|
+
if (ids.length === 0) {
|
354
|
+
$.post("/api/send");
|
355
|
+
console.log("sending all");
|
356
|
+
}
|
357
|
+
else {
|
358
|
+
$.post("/api/send_select", { "ids": ids });
|
359
|
+
console.log("sending " + ids.join(", "));
|
360
|
+
}
|
361
|
+
},
|
362
|
+
|
363
|
+
freeze: function() {
|
364
|
+
var ids = this._getSelectedIds(arguments);
|
365
|
+
if (ids.length === 0) return;
|
366
|
+
$.post("/api/freeze", { "ids": ids });
|
367
|
+
console.log("freezing(toggle) " + ids.join(", "));
|
368
|
+
},
|
369
|
+
|
370
|
+
freezeOn: function() {
|
371
|
+
var ids = this._getSelectedIds(arguments);
|
372
|
+
if (ids.length === 0) return;
|
373
|
+
$.post("/api/freeze_on", { "ids": ids });
|
374
|
+
console.log("freezing " + ids.join(", "));
|
375
|
+
},
|
376
|
+
|
377
|
+
freezeOff: function() {
|
378
|
+
var ids = this._getSelectedIds(arguments);
|
379
|
+
if (ids.length === 0) return;
|
380
|
+
$.post("/api/freeze_off", { "ids": ids });
|
381
|
+
console.log("thawing " + ids.join(", "));
|
382
|
+
},
|
383
|
+
|
384
|
+
_removeConfirmDialog: function(title, ids, callback) {
|
385
|
+
var message = "";
|
386
|
+
this.table.rows().data().each(function(data, idx) {
|
387
|
+
if (ids.indexOf(data.id + "") !== -1) {
|
388
|
+
message += "<li>" + data.title + "</li>";
|
389
|
+
}
|
390
|
+
});
|
391
|
+
message = '<div style="max-height:300px;overflow:auto"><ul>' + message + '</ul></div>';
|
392
|
+
bootbox.dialog({
|
393
|
+
title: title,
|
394
|
+
message: message,
|
395
|
+
buttons: {
|
396
|
+
danger: {
|
397
|
+
label: "削除する",
|
398
|
+
className: "btn-danger",
|
399
|
+
callback: function() {
|
400
|
+
callback(true);
|
401
|
+
$(document).trigger("ok.narou.remove");
|
402
|
+
}
|
403
|
+
},
|
404
|
+
main: {
|
405
|
+
label: "キャンセル",
|
406
|
+
className: "btn-default",
|
407
|
+
callback: function() {
|
408
|
+
callback(false);
|
409
|
+
$(document).trigger("cancel.narou.remove");
|
410
|
+
}
|
411
|
+
}
|
412
|
+
}
|
413
|
+
});
|
414
|
+
},
|
415
|
+
|
416
|
+
remove: function() {
|
417
|
+
var ids = this._getSelectedIds(arguments);
|
418
|
+
if (ids.length === 0) return;
|
419
|
+
this._removeConfirmDialog("選択した小説を削除しますか?", ids, function(result) {
|
420
|
+
if (!result) return;
|
421
|
+
$.post("/api/remove", { "ids": ids });
|
422
|
+
console.log("removing " + ids.join(", "));
|
423
|
+
});
|
424
|
+
},
|
425
|
+
|
426
|
+
removeWithFile: function() {
|
427
|
+
var ids = this._getSelectedIds(arguments);
|
428
|
+
if (ids.length === 0) return;
|
429
|
+
this._removeConfirmDialog("選択した小説を“完全に”削除しますか?", ids, function(result) {
|
430
|
+
if (!result) return;
|
431
|
+
$.post("/api/remove_with_file", { "ids": ids });
|
432
|
+
console.log("removing with file " + ids.join(", "));
|
433
|
+
});
|
434
|
+
},
|
435
|
+
|
436
|
+
convert: function() {
|
437
|
+
var ids = this._getSelectedIds(arguments);
|
438
|
+
if (ids.length === 0) return;
|
439
|
+
$.post("/api/convert", { "ids": ids });
|
440
|
+
console.log("converting " + ids.join(", "));
|
441
|
+
},
|
442
|
+
|
443
|
+
diff: function() {
|
444
|
+
var ids = this._getSelectedIds(arguments);
|
445
|
+
if (ids.length === 0) return;
|
446
|
+
$.post("/api/diff", { "ids": ids });
|
447
|
+
console.log("diffing " + ids.join(", "));
|
448
|
+
},
|
449
|
+
|
450
|
+
inspect: function() {
|
451
|
+
var ids = this._getSelectedIds(arguments);
|
452
|
+
if (ids.length === 0) return;
|
453
|
+
$.post("/api/inspect", { "ids": ids });
|
454
|
+
console.log("inspecting " + ids.join(", "));
|
455
|
+
},
|
456
|
+
|
457
|
+
folder: function() {
|
458
|
+
var ids = this._getSelectedIds(arguments);
|
459
|
+
if (ids.length === 0) return;
|
460
|
+
$.post("/api/folder", { "ids": ids });
|
461
|
+
console.log("opening folder " + ids.join(", "));
|
462
|
+
},
|
463
|
+
|
464
|
+
backup: function() {
|
465
|
+
var ids = this._getSelectedIds(arguments);
|
466
|
+
if (ids.length === 0) return;
|
467
|
+
$.post("/api/backup", { "ids": ids });
|
468
|
+
console.log("backup " + ids.join(", "));
|
469
|
+
},
|
470
|
+
});
|
471
|
+
|
472
|
+
/*************************************************************************
|
473
|
+
* コンソール
|
474
|
+
*************************************************************************/
|
475
|
+
var Console = Narou.Console = function(notification, options) {
|
476
|
+
this.options = $.extend({
|
477
|
+
restore: true, // コンソールの大きさを復元・保存するか
|
478
|
+
buttons: true, // 拡大縮小等のコントロールボタンを使用するか
|
479
|
+
id: "#console", // コンソールのID名
|
480
|
+
buttons_id: "#console-buttons" // コントロールボタンを格納している要素のID名
|
481
|
+
}, options);
|
482
|
+
this.initialize(notification);
|
483
|
+
};
|
484
|
+
|
485
|
+
$.extend(Console.prototype, {
|
486
|
+
animate_duration: 200,
|
487
|
+
|
488
|
+
initialize: function(notification) {
|
489
|
+
this.notification = notification;
|
490
|
+
this.last_char_was_return = true;
|
491
|
+
this.console = $(this.options.id);
|
492
|
+
this.init_scrollbar();
|
493
|
+
if (this.options.buttons) this.init_buttons();
|
494
|
+
this.init_events();
|
495
|
+
this.init_notification();
|
496
|
+
if (this.options.restore) this.restore_console_shape();
|
497
|
+
},
|
498
|
+
|
499
|
+
init_scrollbar: function() {
|
500
|
+
this.console.perfectScrollbar({
|
501
|
+
wheelspeed: 80,
|
502
|
+
suppressScrollX: true,
|
503
|
+
minScrollbarLength: 20,
|
504
|
+
});
|
505
|
+
this.original_height = this.console.height();
|
506
|
+
},
|
507
|
+
|
508
|
+
init_buttons: function() {
|
509
|
+
var self = this;
|
510
|
+
this.console.css("min-height", this.original_height);
|
511
|
+
$(this.options.buttons_id + " .console-expand").on("click", function(e) {
|
512
|
+
self.expand_console();
|
513
|
+
});
|
514
|
+
$(this.options.buttons_id + " .console-trash").on("click", function(e) {
|
515
|
+
self.trash_console();
|
516
|
+
});
|
517
|
+
},
|
518
|
+
|
519
|
+
init_events: function() {
|
520
|
+
this.manage_resize_event();
|
521
|
+
var self = this;
|
522
|
+
this.console.on("resize", function() {
|
523
|
+
if (!self.options.restore) return;
|
524
|
+
var data = {
|
525
|
+
height: self.console.height(),
|
526
|
+
expanded: self.console.hasClass("expanded")
|
527
|
+
};
|
528
|
+
storage.set("console", data);
|
529
|
+
storage.save();
|
530
|
+
});
|
531
|
+
this.init_events_progressbar();
|
532
|
+
this.notification.on("console.clear", function() {
|
533
|
+
self.clear();
|
534
|
+
});
|
535
|
+
},
|
536
|
+
|
537
|
+
init_events_progressbar: function() {
|
538
|
+
var self = this;
|
539
|
+
var create_progress_html = function() {
|
540
|
+
return '<div class="progress"><div class="progress-bar progress-bar-success progress-bar-striped active" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%"></div></div>';
|
541
|
+
};
|
542
|
+
var $progress = null;
|
543
|
+
var setProgressValue = function(step) {
|
544
|
+
$progress.attr("aria-valuenow", step)
|
545
|
+
.width(step + "%");
|
546
|
+
};
|
547
|
+
this.notification.on("progressbar.init", function() {
|
548
|
+
self.puts(create_progress_html());
|
549
|
+
$progress = self.console.find(".progress > div");
|
550
|
+
});
|
551
|
+
this.notification.on("progressbar.step", function(value) {
|
552
|
+
if (!$progress) return;
|
553
|
+
setProgressValue(value);
|
554
|
+
});
|
555
|
+
this.notification.on("progressbar.clear", function() {
|
556
|
+
if (!$progress) return;
|
557
|
+
// 表示のタイムラグで100%付近のが表示されないまま消えてしまうので、
|
558
|
+
// 演出的にプログレスバーの消去を遅らせて、100%付近まで表示する
|
559
|
+
setProgressValue(100);
|
560
|
+
$progress = null;
|
561
|
+
setTimeout(function() {
|
562
|
+
$(".progress").parent("div").remove();
|
563
|
+
}, 500);
|
564
|
+
});
|
565
|
+
},
|
566
|
+
|
567
|
+
init_notification: function() {
|
568
|
+
var self = this;
|
569
|
+
this.notification.on("echo", function(message) {
|
570
|
+
self.print(message);
|
571
|
+
});
|
572
|
+
},
|
573
|
+
|
574
|
+
manage_resize_event: function() {
|
575
|
+
/*
|
576
|
+
var con = this.console;
|
577
|
+
con.data('x', con.outerWidth());
|
578
|
+
con.data('y', con.outerHeight());
|
579
|
+
$(document).on("mouseup", function () {
|
580
|
+
if (con.outerWidth() != con.data('x') || con.outerHeight() != con.data('y')) {
|
581
|
+
con.trigger("resize");
|
582
|
+
}
|
583
|
+
con.data('x', con.outerWidth());
|
584
|
+
con.data('y', con.outerHeight());
|
585
|
+
});
|
586
|
+
*/
|
587
|
+
},
|
588
|
+
|
589
|
+
restore_console_shape: function() {
|
590
|
+
var data = storage.get("console");
|
591
|
+
if (data) {
|
592
|
+
this.console.height(data.height);
|
593
|
+
if (data.expanded) this.toggle_expanded();
|
594
|
+
}
|
595
|
+
},
|
596
|
+
|
597
|
+
scroll_to_bottom: function() {
|
598
|
+
var con = this.console;
|
599
|
+
con.scrollTop(con.prop("scrollHeight"));
|
600
|
+
},
|
601
|
+
|
602
|
+
position_is_bottom: function() {
|
603
|
+
var con = this.console;
|
604
|
+
return (con.scrollTop() === con.prop("scrollHeight") - con.outerHeight());
|
605
|
+
},
|
606
|
+
|
607
|
+
print: function(str) {
|
608
|
+
var self = this;
|
609
|
+
var con = this.console;
|
610
|
+
var last_char = str[str.length - 1];
|
611
|
+
var lines = str.split("\n");
|
612
|
+
var was_bottom = this.position_is_bottom();
|
613
|
+
var last_char_is_return = last_char === "\n";
|
614
|
+
if (last_char_is_return) {
|
615
|
+
lines = lines.slice(0, -1);
|
616
|
+
}
|
617
|
+
if (!this.last_char_was_return) {
|
618
|
+
var last = con.find("div.console-line:last-child");
|
619
|
+
var char = lines.pop();
|
620
|
+
if (char) last.append(char);
|
621
|
+
}
|
622
|
+
$.each(lines, function() {
|
623
|
+
con.append("<div class=console-line>" + this);
|
624
|
+
});
|
625
|
+
con.perfectScrollbar("update");
|
626
|
+
// 表示する段階で最下部までスクロールしてあった場合はスクロールする
|
627
|
+
if (was_bottom) {
|
628
|
+
this.scroll_to_bottom();
|
629
|
+
}
|
630
|
+
this.last_char_was_return = last_char_is_return;
|
631
|
+
},
|
632
|
+
|
633
|
+
puts: function(str) {
|
634
|
+
if (str[str.length - 1] !== "\n") {
|
635
|
+
this.print(str.concat("\n"));
|
636
|
+
}
|
637
|
+
else {
|
638
|
+
this.print(str);
|
639
|
+
}
|
640
|
+
},
|
641
|
+
|
642
|
+
expand_console: function() {
|
643
|
+
var self = this;
|
644
|
+
var calced_height;
|
645
|
+
if (this.console.hasClass("expanded")) {
|
646
|
+
calced_height = this.original_height;
|
647
|
+
}
|
648
|
+
else {
|
649
|
+
var top = this.console.offset().top;
|
650
|
+
calced_height = ($(window).height() - top) * 0.8;
|
651
|
+
}
|
652
|
+
var was_bottom = this.position_is_bottom();
|
653
|
+
this.console.stop().animate({ height: calced_height }, this.animate_duration,
|
654
|
+
// after do
|
655
|
+
function() {
|
656
|
+
if (was_bottom) self.scroll_to_bottom();
|
657
|
+
self.console.trigger("resize");
|
658
|
+
self.console.perfectScrollbar("update");
|
659
|
+
});
|
660
|
+
this.toggle_expanded();
|
661
|
+
},
|
662
|
+
|
663
|
+
toggle_expanded: function() {
|
664
|
+
this.console.toggleClass("expanded");
|
665
|
+
$(".console-expand > span").toggleClass("hide");
|
666
|
+
},
|
667
|
+
|
668
|
+
// コンソールのログを削除すると同時に、サーバの履歴も削除する
|
669
|
+
trash_console: function() {
|
670
|
+
this.clear();
|
671
|
+
$.post("/api/clear_history");
|
672
|
+
},
|
673
|
+
|
674
|
+
clear: function() {
|
675
|
+
this.console.find("div.console-line").remove();
|
676
|
+
}
|
677
|
+
});
|
678
|
+
|
679
|
+
/*************************************************************************
|
680
|
+
* タグ機能
|
681
|
+
*************************************************************************/
|
682
|
+
var Tag = Narou.Tag = (function(table) {
|
683
|
+
this.table = table;
|
684
|
+
this.updateCanvas();
|
685
|
+
});
|
686
|
+
|
687
|
+
$.extend(Tag.prototype, {
|
688
|
+
updateCanvas: function() {
|
689
|
+
var $canvas = $("#tag-list-canvas");
|
690
|
+
this.registerEvents($canvas);
|
691
|
+
$.get("/api/tag_list", function(source) {
|
692
|
+
$canvas.html(source);
|
693
|
+
});
|
694
|
+
},
|
695
|
+
|
696
|
+
registerEvents: function($target, stop_bubbling) {
|
697
|
+
var self = this;
|
698
|
+
if (typeof stop_bubbling === "undefined") stop_bubbling = true;
|
699
|
+
var args = { stop_bubbling: stop_bubbling };
|
700
|
+
$target.on("click", ".tag", args, function(e) {
|
701
|
+
if (e.data.stop_bubbling) e.stopPropagation();
|
702
|
+
var tag_name = $(this).data("tag");
|
703
|
+
self.table.column("tags:name").search(tag_name).draw();
|
704
|
+
}).on("mousedown", ".tag", args, function(e) {
|
705
|
+
// 範囲選択モードでもクリック出来るように
|
706
|
+
if (e.data.stop_bubbling) e.stopPropagation();
|
707
|
+
});
|
708
|
+
},
|
709
|
+
|
710
|
+
openEditor: function() {
|
711
|
+
var ids = Action.prototype._getSelectedIds.call(this);
|
712
|
+
if (ids.length === 0) return;
|
713
|
+
this._createEditorField(ids, function(field) {
|
714
|
+
bootbox.dialog({
|
715
|
+
title: "タグの編集",
|
716
|
+
message: field,
|
717
|
+
buttons: {
|
718
|
+
cancel: {
|
719
|
+
label: "キャンセル",
|
720
|
+
className: "btn-default",
|
721
|
+
callback: function() {
|
722
|
+
}
|
723
|
+
},
|
724
|
+
main: {
|
725
|
+
label: "適用",
|
726
|
+
className: "btn-primary",
|
727
|
+
callback: function() {
|
728
|
+
var states = {};
|
729
|
+
var new_tag = $("#new-tag").val();
|
730
|
+
$("#tag-editor-field input[type=checkbox]").each(function(i, v) {
|
731
|
+
states[$(v).data("tagname")] = $(v).data("checkState");
|
732
|
+
});
|
733
|
+
if (new_tag) {
|
734
|
+
states[new_tag] = 2;
|
735
|
+
}
|
736
|
+
$.post("/api/edit_tag", {
|
737
|
+
ids: ids,
|
738
|
+
states: states
|
739
|
+
});
|
740
|
+
}
|
741
|
+
}
|
742
|
+
}
|
743
|
+
});
|
744
|
+
});
|
745
|
+
},
|
746
|
+
|
747
|
+
_createEditorField: function(ids, callback) {
|
748
|
+
var field = $("<div id=tag-editor-field class=form-group>");
|
749
|
+
var self = this;
|
750
|
+
var ids_count = ids.length;
|
751
|
+
|
752
|
+
function calcState(count) {
|
753
|
+
if (count === 0) {
|
754
|
+
return 0;
|
755
|
+
}
|
756
|
+
else if (count < ids_count) {
|
757
|
+
return 1;
|
758
|
+
}
|
759
|
+
else {
|
760
|
+
return 2;
|
761
|
+
}
|
762
|
+
}
|
763
|
+
|
764
|
+
$.post("/api/taginfo.json", { ids: ids }, function(taginfo) {
|
765
|
+
$.each(taginfo, function(tagname, info) {
|
766
|
+
var label = $('<label><input type="checkbox" data-tagname="' + tagname.replace(/"/g, """) +
|
767
|
+
'" data-default-checkstate=' + calcState(info.count) + '> ' + info.html + ' </label>');
|
768
|
+
field.append(label);
|
769
|
+
});
|
770
|
+
var input = $('<div><input type="text" id="new-tag" placeholder="新規タグ" class="form-control"></div>' +
|
771
|
+
'<div><small>(複数追加する場合は半角スペースで区切る)</small></div>');
|
772
|
+
field.append(input);
|
773
|
+
self._registerEventsForEditorField(field);
|
774
|
+
callback(field);
|
775
|
+
});
|
776
|
+
},
|
777
|
+
|
778
|
+
_registerEventsForEditorField: function(field) {
|
779
|
+
function setCheckState(element, state) {
|
780
|
+
switch (state) {
|
781
|
+
case 0:
|
782
|
+
element.prop("indeterminate", false);
|
783
|
+
element.prop("checked", false);
|
784
|
+
break;
|
785
|
+
case 1:
|
786
|
+
if (element.data("defaultCheckstate") == 1) {
|
787
|
+
element.prop("indeterminate", true);
|
788
|
+
element.prop("checked", false);
|
789
|
+
break;
|
790
|
+
}
|
791
|
+
state++;
|
792
|
+
case 2:
|
793
|
+
element.prop("indeterminate", false);
|
794
|
+
element.prop("checked", true);
|
795
|
+
break;
|
796
|
+
}
|
797
|
+
element.data("checkState", state);
|
798
|
+
}
|
799
|
+
|
800
|
+
field.find("input[type=checkbox]")
|
801
|
+
.on("click", function(e) {
|
802
|
+
var elm = $(e.target);
|
803
|
+
var next_state = (elm.data("checkState") + 1) % 3;
|
804
|
+
setCheckState(elm, next_state);
|
805
|
+
})
|
806
|
+
.each(function(i, v) {
|
807
|
+
var elm = $(v);
|
808
|
+
setCheckState(elm, elm.data("defaultCheckstate"));
|
809
|
+
});
|
810
|
+
},
|
811
|
+
});
|
812
|
+
|
813
|
+
return Narou;
|
814
|
+
})();
|
815
|
+
|