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.

Files changed (100) hide show
  1. checksums.yaml +5 -13
  2. data/.gitignore +1 -0
  3. data/ChangeLog.md +35 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE.txt +100 -0
  6. data/README.md +28 -39
  7. data/lib/color.rb +0 -2
  8. data/lib/command.rb +1 -0
  9. data/lib/command/convert.rb +33 -4
  10. data/lib/command/diff.rb +5 -4
  11. data/lib/command/download.rb +9 -1
  12. data/lib/command/flag.rb +2 -2
  13. data/lib/command/list.rb +1 -1
  14. data/lib/command/mail.rb +3 -3
  15. data/lib/command/remove.rb +2 -1
  16. data/lib/command/send.rb +7 -6
  17. data/lib/command/setting.rb +39 -95
  18. data/lib/command/tag.rb +25 -13
  19. data/lib/command/update.rb +6 -1
  20. data/lib/command/version.rb +5 -1
  21. data/lib/command/web.rb +111 -0
  22. data/lib/commandbase.rb +5 -2
  23. data/lib/commandline.rb +16 -0
  24. data/lib/converterbase.rb +20 -14
  25. data/lib/device.rb +5 -4
  26. data/lib/downloader.rb +68 -39
  27. data/lib/eventable.rb +72 -0
  28. data/lib/helper.rb +105 -37
  29. data/lib/ini.rb +2 -1
  30. data/lib/input.rb +68 -0
  31. data/lib/inventory.rb +4 -0
  32. data/lib/kindlestrip.rb +2 -2
  33. data/lib/logger.rb +41 -19
  34. data/lib/narou.rb +10 -0
  35. data/lib/narou/api.rb +1 -1
  36. data/lib/novelconverter.rb +8 -21
  37. data/lib/novelsetting.rb +79 -4
  38. data/lib/version.rb +1 -1
  39. data/lib/web/all.rb +12 -0
  40. data/lib/web/appserver.rb +612 -0
  41. data/lib/web/helper4web.rb +15 -0
  42. data/lib/web/progressbar4web.rb +32 -0
  43. data/lib/web/public/favicon.ico +0 -0
  44. data/lib/web/public/resources/bootbox.min.js +6 -0
  45. data/lib/web/public/resources/common.ui.js +143 -0
  46. data/lib/web/public/resources/dataTables.colVis.js +1113 -0
  47. data/lib/web/public/resources/help/rect_select.png +0 -0
  48. data/lib/web/public/resources/help/ssmain.png +0 -0
  49. data/lib/web/public/resources/help/tag.png +0 -0
  50. data/lib/web/public/resources/jquery.moveto.js +44 -0
  51. data/lib/web/public/resources/jquery.outerclick.js +60 -0
  52. data/lib/web/public/resources/jquery.slidenavbar.js +89 -0
  53. data/lib/web/public/resources/narou.library.js +815 -0
  54. data/lib/web/public/resources/narou.ui.js +993 -0
  55. data/lib/web/public/resources/perfect-scrollbar.min.css +5 -0
  56. data/lib/web/public/resources/perfect-scrollbar.min.js +4 -0
  57. data/lib/web/public/resources/shortcut.js +223 -0
  58. data/lib/web/public/resources/sort_asc.png +0 -0
  59. data/lib/web/public/resources/sort_desc.png +0 -0
  60. data/lib/web/public/resources/toggle-switch.css +322 -0
  61. data/lib/web/public/robots.txt +3 -0
  62. data/lib/web/public/test/jquery.outerclick.html +72 -0
  63. data/lib/web/pushserver.rb +110 -0
  64. data/lib/web/settingmessages.rb +14 -0
  65. data/lib/web/streaminginput.rb +103 -0
  66. data/lib/web/streaminglogger.rb +52 -0
  67. data/lib/web/views/about.haml +11 -0
  68. data/lib/web/views/help.haml +105 -0
  69. data/lib/web/views/index.haml +245 -0
  70. data/lib/web/views/js/widget.erb +74 -0
  71. data/lib/web/views/layout.haml +49 -0
  72. data/lib/web/views/novels/setting.haml +177 -0
  73. data/lib/web/views/settings.haml +115 -0
  74. data/lib/web/views/style.scss +737 -0
  75. data/lib/web/views/widget.haml +39 -0
  76. data/lib/web/web-socket-ruby/.gitignore +1 -0
  77. data/lib/web/web-socket-ruby/README.txt +75 -0
  78. data/lib/web/web-socket-ruby/lib/web_socket.rb +601 -0
  79. data/lib/web/web-socket-ruby/samples/chat_server.rb +58 -0
  80. data/lib/web/web-socket-ruby/samples/echo_server.rb +33 -0
  81. data/lib/web/web-socket-ruby/samples/stdio_client.rb +25 -0
  82. data/lib/web/worker.rb +87 -0
  83. data/narou.gemspec +36 -3
  84. data/narou.rb +8 -6
  85. data/preset/ncode.syosetu.com/n8725k/converter.rb +2 -1
  86. data/spec/data/convert_test/replace/correct_test_replace.txt +1 -1
  87. data/spec/data/convert_test/replace/test_replace.txt +1 -1
  88. data/spec/data/convert_test/ruby/correct_test_ruby.txt +18 -1
  89. data/spec/data/convert_test/ruby/test_ruby.txt +18 -0
  90. data/spec/downloader_spec.rb +37 -0
  91. data/spec/eventable_spec.rb +172 -0
  92. data/spec/exit_code_spec.rb +67 -0
  93. data/spec/helper_spec.rb +72 -0
  94. data/spec/input_spec.rb +76 -0
  95. data/spec/logger_spec.rb +53 -0
  96. data/spec/novelsetting_spec.rb +35 -0
  97. data/spec/worker_spec.rb +56 -0
  98. data/template/ibunko_novel.txt.erb +2 -2
  99. data/template/novel.txt.erb +2 -2
  100. metadata +213 -29
@@ -0,0 +1,993 @@
1
+ /* -*- coding: utf-8 -*-
2
+ *
3
+ * Copyright 2013 whiteleaf. All rights reserved.
4
+ */
5
+ var t; // for debugging
6
+ $(document).ready(function() {
7
+ "use strict";
8
+
9
+ var touchable_device = "ontouchstart" in window;
10
+ var click_event_name = (touchable_device ? "touchstart" : "click");
11
+
12
+ // MEMO: table 全てに対して操作
13
+ // table.$("tr.selected").removeClass("selected");
14
+ // MEMO: .selected が付いている行を全て取得
15
+ // table.rows('.selected').data().length
16
+
17
+ // 端末ごとの表示項目初期設定
18
+ var cell_visible = (touchable_device ?
19
+ // スマフォ系端末
20
+ {
21
+ id: false, last_update: false, title: true, author: false, sitename: false,
22
+ toc_url: false, novel_type: false, tags: false, status: false, menu: true,
23
+ download: false,
24
+ }
25
+ // PC
26
+ : {
27
+ id: true, last_update: true, title: true, author: true, sitename: true,
28
+ toc_url: true, novel_type: true, tags: true, status: true, menu: true,
29
+ download: true,
30
+ });
31
+
32
+ $.fn.dataTable.Api.register("fireChangeSelect()", function() {
33
+ $(this.table().node()).trigger("changeselect");
34
+ });
35
+
36
+ $.fn.dataTable.Api.register("eachRow()", function(callback) {
37
+ this.table().rows().eq(0).each(function(idx) {
38
+ var row = table.row(idx);
39
+ var data = row.data();
40
+ callback(row, data);
41
+ });
42
+ });
43
+
44
+ var now = new Date;
45
+ var today = Narou.formatDate(now, "YYYYMMDD");
46
+ var NEW_ARRIVALS_LIMIT = 6 * 60 * 60; // 6時間(秒)
47
+
48
+ function isToday(date) {
49
+ var dateobj = (typeof date === "number" ? new Date(date * 1000) : date);
50
+ return Narou.formatDate(dateobj, "YYYYMMDD") === today;
51
+ }
52
+
53
+ var table = t = $("#novel-list").DataTable({
54
+ ajax: "/api/list",
55
+ dom: (touchable_device ? 'lprtpi' : 'Rlprtpi'),
56
+ stateSave: true,
57
+ stateDuration: 0, // tableの状態保存を永続化
58
+ paginationType: "full_numbers",
59
+ lengthMenu: [[20, 50, 100, -1], [20, 50, 100, "全"]],
60
+ order: [[ 1, "desc" ]], // 最初は新着順でソートしておく
61
+ columns: [
62
+ // ID
63
+ { title: "ID",
64
+ data: "id", className: "column-id text-center", visible: cell_visible.id },
65
+ // 最終更新日
66
+ { title: "更新日",
67
+ data: "last_update", className: "text-center",
68
+ orderSequence: [ "desc", "asc" ], visible: cell_visible.last_update,
69
+ render: function(data, type, row) {
70
+ if (type === "display" || type === "filter") {
71
+ var date = new Date(data * 1000);
72
+ var display = Narou.formatDate(date, "YYYY/MM/DD<br>hh:mm");
73
+ if (type === "display") {
74
+ var klass = null;
75
+ if (row.new_arrivals_date) {
76
+ if ((row.new_arrivals_date + NEW_ARRIVALS_LIMIT) * 1000 >= now) {
77
+ klass = "new-arrivals";
78
+ }
79
+ }
80
+ if (!klass && isToday(date)) {
81
+ klass = "today-update";
82
+ }
83
+ if (klass) {
84
+ display = "<span class=" + klass + ">" + display + "</span>";
85
+ }
86
+ }
87
+ return display;
88
+ }
89
+ return data;
90
+ }
91
+ },
92
+ // タイトル
93
+ { title: "タイトル",
94
+ data: "title", visible: cell_visible.title },
95
+ // 作者名
96
+ { title: "作者名",
97
+ data: "author", visible: cell_visible.author },
98
+ // 掲載サイト
99
+ { title: "掲載",
100
+ data: "sitename", visible: cell_visible.sitename },
101
+ // 掲載URL
102
+ {
103
+ title: "リンク",
104
+ data: "toc_url", className: "text-center", orderable: false,
105
+ searchable: false, visible: cell_visible.toc_url },
106
+ // 小説種別
107
+ { title: "種別",
108
+ data: "novel_type", className: "text-center", width: "25px",
109
+ orderSequence: [ "desc", "asc" ], visible: cell_visible.novel_type },
110
+ // タグ
111
+ { title: "タグ",
112
+ data: "tags", orderSequence: [ "desc", "asc" ], width: "70px",
113
+ visible: cell_visible.tags },
114
+ // 状態
115
+ { title: "状態",
116
+ data: "status", visible: cell_visible.status },
117
+ // 書籍データDLリンク
118
+ {
119
+ title: "DL",
120
+ data: "download", className: "text-center", orderable: false,
121
+ searchable: false, visible: cell_visible.download },
122
+ {
123
+ title: "個別",
124
+ data: "menu", className: "text-center",
125
+ defaultContent: '<button class="btn btn-default btn-xs">' +
126
+ '<span class="glyphicon glyphicon-cog"></span></button>',
127
+ visible: cell_visible.menu,
128
+ createdCell: function(td, cellData, rowData, rowIndex, colIndex) {
129
+ $(td).children("button")
130
+ .data("targetId", rowData.id)
131
+ .on("click", function(e) {
132
+ e.stopPropagation();
133
+ var $this = $(this);
134
+ var $tr = $(e.target).closest("tr");
135
+ var target_id = $this.data("targetId");
136
+ var $cover = $("<div class=context-menu-target-cover>");
137
+ // MEMO: IEだと要素を追加してから座標を指定しないと反映されない
138
+ $("body").append($cover);
139
+ $cover.offset($tr.offset()).width($tr.width()).height($tr.height());
140
+ context_menu.open(target_id, get_event_position(e), function() {
141
+ $cover.remove();
142
+ });
143
+ });
144
+ },
145
+ render: function() {},
146
+ searchable: false, orderable: false,
147
+ },
148
+ // 凍結状態(内部データ用)
149
+ { title: "凍結", data: "frozen", visible: false, system_column: true },
150
+ // 新着時間(内部データ用)
151
+ { title: "新着時間", data: "new_arrivals_date", visible: false, system_column: true },
152
+ ].map(function(hash) {
153
+ // 検索用に name 要素を追加
154
+ hash.name = hash.data;
155
+ // table-cell 内で position: abosolute を使うために div をかます
156
+ // 参考: http://no1026.com/archives/406
157
+ if (!hash.render) {
158
+ hash.render = function(data, type, row) {
159
+ if (type === "display" || type === "filter") {
160
+ return "<div>" + data + "</div>";
161
+ }
162
+ return data;
163
+ }
164
+ }
165
+ return hash;
166
+ }),
167
+ stateLoadParams: function (_settings, data) {
168
+ // 独自フィルタボックスへセーブデータを反映
169
+ if (data.search.search != "") {
170
+ $("#myFilter").val(data.search.search);
171
+ $("#myFilter-clear").show();
172
+ }
173
+ else {
174
+ $("#myFilter-clear").hide();
175
+ }
176
+ },
177
+ createdRow: function(row, data, data_index) {
178
+ if (data.frozen) {
179
+ $(row).addClass("frozen");
180
+ }
181
+ },
182
+ initComplete: function(_settings, _json) {
183
+ table.$("[data-toggle=tooltip]").tooltip({
184
+ animation: false,
185
+ container: "body",
186
+ });
187
+ // バッジを更新しておく
188
+ table.fireChangeSelect();
189
+ // 範囲選択モードでもリンクとボタンが使えるように
190
+ table.$("a, button").on("mousedown", function(e) {
191
+ e.stopPropagation();
192
+ });
193
+ // タグのイベントを反映
194
+ tag.registerEvents(table);
195
+ },
196
+ language: {
197
+ lengthMenu: "_MENU_ 件分表示",
198
+ zeroRecords: "データがひとつもないか、処理中です",
199
+ info: "Page _PAGE_ of _PAGES_",
200
+ infoEmpty: "該当なし",
201
+ infoFiltered: "(全_MAX_件から検索しました)",
202
+ paginate: {
203
+ first: "&laquo;",
204
+ previous: "前へ",
205
+ next: "次へ",
206
+ last: "&raquo;"
207
+ },
208
+ },
209
+ /*
210
+ * Extensions section
211
+ */
212
+ colReorder: {
213
+ "realtime": false
214
+ },
215
+ });
216
+
217
+ $.fn.dataTableExt.afnFiltering.push(function(oSettings, aData, iDataIndex) {
218
+ var row = table.row(iDataIndex);
219
+ return !$(row.node()).hasClass("exclude");
220
+ });
221
+
222
+ var storage = new Narou.Storage();
223
+ var action = new Narou.Action(table);
224
+ var notification = Narou.Notification.instance();
225
+ var stream_console = new Narou.Console(notification);
226
+ var context_menu = new Narou.ContextMenu(action, notification);
227
+ var tag = new Narou.Tag(table);
228
+
229
+ notification.on("table.reload", function() {
230
+ // リロードする前に現在の状態を保存
231
+ var selected_ids = [], excluded_ids = [];
232
+ table.eachRow(function(row, data) {
233
+ var $node = $(row.node());
234
+ if ($node.hasClass("selected")) {
235
+ selected_ids.push(data.id);
236
+ }
237
+ if ($node.hasClass("exclude")) {
238
+ excluded_ids.push(data.id);
239
+ }
240
+ });
241
+ table.ajax.reload(function() {
242
+ // リロードしたら状態は解除されているので復元
243
+ table.$("[data-toggle=tooltip]").tooltip({
244
+ animation: false,
245
+ container: "body",
246
+ });
247
+ table.eachRow(function(row, data) {
248
+ if ($.inArray(data.id, selected_ids) !== -1) {
249
+ $(row.node()).addClass("selected");
250
+ }
251
+ if ($.inArray(data.id, excluded_ids) !== -1) {
252
+ $(row.node()).addClass("exclude");
253
+ }
254
+ });
255
+ table.fireChangeSelect();
256
+ table.draw(false);
257
+ }, false);
258
+ });
259
+
260
+ notification.on("tag.updateCanvas", function() {
261
+ tag.updateCanvas();
262
+ });
263
+
264
+ tag.registerEvents($(".tag-field"));
265
+
266
+ var action_select_view = $("#action-select-view");
267
+ var myfilter_clear = $("#myFilter-clear");
268
+
269
+ /*
270
+ * カスタムフィルタボックスで入力値をフィルタする
271
+ */
272
+ $("#myFilter").on("keyup", function() {
273
+ table.search($(this).val()).draw();
274
+ if ($(this).val() === "") {
275
+ myfilter_clear.hide();
276
+ }
277
+ else {
278
+ myfilter_clear.show();
279
+ }
280
+ })
281
+ /*
282
+ * リセットボタン(X)
283
+ */
284
+ $("#myFilter-clear").on(click_event_name, function() {
285
+ table.search("").draw();
286
+ $("#myFilter").val("");
287
+ myfilter_clear.hide();
288
+ });
289
+
290
+ /*************************************************************
291
+ * 小説の選択関係
292
+ *************************************************************/
293
+
294
+ // 指定地点に対象要素があるかどうか背面の要素までさかのぼって探す
295
+ $.searchElementFromPoint = function(pos, target) {
296
+ var doc = $(document);
297
+ var html_dom = $("html")[0];
298
+ var display_x = pos.x - doc.scrollLeft();
299
+ var display_y = pos.y - doc.scrollTop();
300
+ var get_element_from_pos = function(x, y, t) {
301
+ var elm = document.elementFromPoint(x, y);
302
+ if (!elm || elm === html_dom) return null;
303
+ var elm_jq = $(elm);
304
+ if (!elm_jq.is(t)) {
305
+ elm_jq.hide();
306
+ elm = get_element_from_pos(x, y, t);
307
+ elm_jq.show();
308
+ }
309
+ return elm;
310
+ };
311
+ var element = get_element_from_pos(display_x, display_y, target);
312
+ return element ? $(element) : null;
313
+ };
314
+
315
+ $.elementFromPoint = function(pos) {
316
+ var doc = $(document);
317
+ var rect = $("#rect-select-area");
318
+ var rect_visibility = rect.is(":visible");
319
+ if (rect_visibility) rect.hide();
320
+ var elm = document.elementFromPoint(pos.x - doc.scrollLeft(),
321
+ pos.y - doc.scrollTop());
322
+ if (rect_visibility) rect.show();
323
+ return elm ? $(elm) : null;
324
+ };
325
+
326
+ function get_event_position(e) {
327
+ if (e.type !== "touchstart") {
328
+ return { x: e.pageX, y: e.pageY };
329
+ }
330
+ else {
331
+ return { x: e.originalEvent.touches[0].pageX,
332
+ y: e.originalEvent.touches[0].pageY };
333
+ }
334
+ }
335
+
336
+ var select_mode = storage.get("select_mode") || "single";
337
+ var enable_rect_select_mode = function() {};
338
+ var disable_rect_select_mode = function() {};
339
+
340
+ $("#action-select-mode-single").on(click_event_name, function(e) {
341
+ if (select_mode != "single") {
342
+ $(this).addClass("active");
343
+ $("#action-select-mode-rect").removeClass("active");
344
+ select_mode = "single";
345
+ storage.set("select_mode", select_mode).save();
346
+ disable_rect_select_mode();
347
+ enable_single_select_mode();
348
+ }
349
+ slideNavbar.close();
350
+ e.preventDefault();
351
+ });
352
+
353
+ function disable_single_select_mode() {
354
+ table.off("click", "tr")
355
+ table.off("click", "a")
356
+ $("#novel-list tbody").css("cursor", "auto");
357
+ }
358
+
359
+ function enable_single_select_mode() {
360
+ var dont_select = false;
361
+ // ここだけはスマフォ系でも touchstart ではなく click で。
362
+ // touchstart だと画面スクロールのためのスワイプでも反応してしまう
363
+ table
364
+ .on("click", "tr", function () {
365
+ $(this).toggleClass("selected");
366
+ table.fireChangeSelect();
367
+ })
368
+ .on("click", "a", function(e) {
369
+ // リンクをクリックした時は選択処理は行わない
370
+ e.stopPropagation();
371
+ })
372
+ $("#novel-list tbody").css("cursor", "pointer");
373
+ }
374
+
375
+ if (touchable_device) {
376
+ // スマフォ系は範囲選択モードは封印しておく
377
+ $("#action-select-mode-rect").addClass("disabled");
378
+ }
379
+ else {
380
+ $("#action-select-mode-rect").on(click_event_name, function(e) {
381
+ if (select_mode != "rect") {
382
+ $(this).addClass("active");
383
+ $("#action-select-mode-single").removeClass("active");
384
+ select_mode = "rect";
385
+ storage.set("select_mode", select_mode).save();
386
+ disable_single_select_mode();
387
+ enable_rect_select_mode();
388
+ }
389
+ slideNavbar.close();
390
+ e.preventDefault();
391
+ });
392
+
393
+ var selected_rect_element = $("<div id=rect-select-area>").hide();
394
+ $("body").append(selected_rect_element);
395
+
396
+ disable_rect_select_mode = function() {
397
+ $("body").off("mousedown mousemove mouseup");
398
+ $("#novel-list tbody, #rect-select-area").css("cursor", "auto");
399
+ close_rect_select_menu_handler();
400
+ };
401
+
402
+ var close_rect_select_menu_handler = function() {
403
+ $("#rect-select-menu").hide();
404
+ selected_rect_element.hide();
405
+ };
406
+
407
+ var unregister_close_handler = function() {
408
+ $(document).off("click", close_rect_select_menu_handler);
409
+ };
410
+
411
+ var start_element, end_element;
412
+
413
+ var get_rows_rect_selected = function() {
414
+ var target = "td";
415
+ var start = start_element;
416
+ var end = end_element;
417
+ var rows = [];
418
+ if (!start || !end) {
419
+ return rows;
420
+ }
421
+ var from, to;
422
+ if (start.offset().top < end.offset().top) {
423
+ from = start.parents("tr");
424
+ to = end.parents("tr");
425
+ }
426
+ else {
427
+ from = end.parents("tr");
428
+ to = start.parents("tr");
429
+ }
430
+ var current = from;
431
+ while (current.length !== 0) {
432
+ rows.push(current);
433
+ if (current[0] === to[0]) break;
434
+ current = current.next();
435
+ }
436
+ return rows;
437
+ };
438
+
439
+ // 範囲選択後に表示される選択肢
440
+ var rect_select_menu_initialize = function() {
441
+ var menu = $("#rect-select-menu");
442
+ // 選択
443
+ $("#rect-select-menu-select").on("click", function(e) {
444
+ e.preventDefault();
445
+ var rows = get_rows_rect_selected();
446
+ $.each(rows, function(_i, row) {
447
+ row.addClass("selected");
448
+ });
449
+ table.fireChangeSelect();
450
+ menu.hide();
451
+ selected_rect_element.hide();
452
+ });
453
+ // 解除
454
+ $("#rect-select-menu-clear").on("click", function(e) {
455
+ e.preventDefault();
456
+ var rows = get_rows_rect_selected();
457
+ $.each(rows, function(_i, row) {
458
+ row.removeClass("selected");
459
+ });
460
+ table.fireChangeSelect();
461
+ menu.hide();
462
+ selected_rect_element.hide();
463
+ });
464
+ // 反転
465
+ $("#rect-select-menu-reverse").on("click", function(e) {
466
+ e.preventDefault();
467
+ var rows = get_rows_rect_selected();
468
+ $.each(rows, function(_i, row) {
469
+ row.toggleClass("selected");
470
+ });
471
+ table.fireChangeSelect();
472
+ menu.hide();
473
+ selected_rect_element.hide();
474
+ });
475
+ // キャンセル
476
+ $("#rect-select-menu-cancel").on("click", function(e) {
477
+ e.preventDefault();
478
+ menu.hide();
479
+ selected_rect_element.hide();
480
+ });
481
+ };
482
+ rect_select_menu_initialize();
483
+
484
+ enable_rect_select_mode = function() {
485
+ var not_clicked_yet = true;
486
+ var is_moved = false;
487
+ var start_position = null;
488
+ var end_position = null;
489
+ $("#novel-list tbody, #rect-select-area").css("cursor", "crosshair");
490
+ $("body").on("mousedown", function(e) {
491
+ var _pos = get_event_position(e);
492
+ var mousedowned_element = $.elementFromPoint(_pos);
493
+ if (!mousedowned_element || mousedowned_element.closest("#novel-list tbody").length === 0)
494
+ return;
495
+ e.preventDefault();
496
+ e.stopPropagation();
497
+ var self = this;
498
+ var finish_selecting_proc = function() {
499
+ $(self).off("mousemove mouseup");
500
+ not_clicked_yet = true;
501
+ is_moved = false;
502
+ Narou.popupMenu("#rect-select-menu", end_position, close_rect_select_menu_handler);
503
+ };
504
+ if (not_clicked_yet) {
505
+ // 範囲選択開始
506
+ $("#rect-select-menu").hide();
507
+ unregister_close_handler();
508
+ not_clicked_yet = false;
509
+ start_position = _pos;
510
+ start_element = mousedowned_element;
511
+ $(self).on("mousemove", function(e) {
512
+ is_moved = true;
513
+ var pos = get_event_position(e);
514
+ if ($.elementFromPoint(pos).closest("#novel-list tbody").length === 0)
515
+ return;
516
+ end_position = pos;
517
+ end_element = $.searchElementFromPoint(end_position, "td");
518
+ var rect = {};
519
+ rect.x = (pos.x < start_position.x ? pos.x : start_position.x);
520
+ rect.y = (pos.y < start_position.y ? pos.y : start_position.y);
521
+ rect.w = Math.abs(pos.x - start_position.x);
522
+ rect.h = Math.abs(pos.y - start_position.y);
523
+ selected_rect_element.css({
524
+ left: rect.x, top: rect.y,
525
+ width: rect.w, height: rect.h
526
+ });
527
+ });
528
+ // ボタンを離さないでそのまま移動させた場合は、ドラッグによる範囲選択とみなす
529
+ $(self).on("mouseup", function(e) {
530
+ if (is_moved) {
531
+ finish_selecting_proc();
532
+ }
533
+ });
534
+ selected_rect_element.css({
535
+ left: start_position.x,
536
+ top: start_position.y,
537
+ width: 1, height: 1
538
+ }).show();
539
+ }
540
+ else {
541
+ finish_selecting_proc();
542
+ }
543
+ });
544
+ }
545
+ }
546
+
547
+ function restore_select_mode() {
548
+ if (select_mode === "single") {
549
+ enable_single_select_mode();
550
+ $("#action-select-mode-single").addClass("active");
551
+ }
552
+ else {
553
+ enable_rect_select_mode();
554
+ $("#action-select-mode-rect").addClass("active");
555
+ }
556
+ }
557
+ restore_select_mode();
558
+
559
+ /*
560
+ * メニュー
561
+ * 表示>全ての項目を表示
562
+ */
563
+ $("#action-view-all").on(click_event_name, function(e) {
564
+ e.preventDefault();
565
+ slideNavbar.slide();
566
+ var aoColumns = table.settings()[0].aoColumns;
567
+ table.columns().eq(0).each(function(index) {
568
+ if (!aoColumns[index].system_column) {
569
+ table.column(index).visible(true);
570
+ }
571
+ });
572
+ table.draw(false);
573
+ });
574
+
575
+ /*
576
+ * メニュー
577
+ * 表示>表示する項目を設定
578
+ */
579
+ var colvis = new $.fn.dataTable.ColVis(table, {
580
+ restore: "元に戻す",
581
+ showAll: "全ての項目を表示",
582
+ showNone: "全て隠す",
583
+ bCssPosition: true,
584
+ overlayFade: 300,
585
+ exclude: [ "title", "frozen", "new_arrivals_date" ],
586
+ });
587
+ $("#action-view-setting").on(click_event_name, function(e) {
588
+ e.preventDefault();
589
+ var pos = {};
590
+ if (touchable_device) {
591
+ var target = $(this);
592
+ pos.x = target.offset().left;
593
+ pos.y = target.offset().top + target.outerHeight();
594
+ }
595
+ else {
596
+ pos = get_event_position(e);
597
+ }
598
+ $(colvis.dom.collection).css({
599
+ position: "absolute",
600
+ left: pos.x, top: pos.y
601
+ });
602
+ slideNavbar.slide();
603
+ colvis._fnCollectionShow();
604
+ });
605
+
606
+ /*
607
+ * メニュー
608
+ * 表示>凍結中以外を表示
609
+ */
610
+ var nonfrozen_rows = [];
611
+ $("#action-view-without-frozen").on(click_event_name, function(e) {
612
+ e.preventDefault();
613
+ slideNavbar.slide();
614
+ $(this).toggleClass("active");
615
+ table.rows().eq(0).each(function(idx) {
616
+ var row = table.row(idx);
617
+ var data = row.data();
618
+ if (!data.frozen) {
619
+ $(row.node()).toggleClass("exclude");
620
+ }
621
+ });
622
+ table.draw();
623
+ });
624
+
625
+ /*
626
+ * メニュー
627
+ * 表示>凍結中を表示
628
+ */
629
+ var frozen_list = [];
630
+ $("#action-view-frozen").on(click_event_name, function(e) {
631
+ e.preventDefault();
632
+ slideNavbar.slide();
633
+ $(this).toggleClass("active");
634
+ table.rows().eq(0).each(function(idx) {
635
+ var row = table.row(idx);
636
+ var data = row.data();
637
+ if (data.frozen) {
638
+ $(row.node()).toggleClass("exclude");
639
+ }
640
+ });
641
+ table.draw();
642
+ });
643
+
644
+ /*
645
+ * メニュー
646
+ * 表示>表示設定を全てリセット
647
+ */
648
+ $("#action-view-reset").on(click_event_name, function(e) {
649
+ // localStorageを全て消してリロードする
650
+ localStorage.clear();
651
+ });
652
+
653
+ /*
654
+ * メニュー
655
+ * 選択>全ての小説を選択
656
+ */
657
+ $("#action-select-all").on(click_event_name, function(e) {
658
+ e.preventDefault();
659
+ slideNavbar.slide();
660
+ action.selectAll();
661
+ });
662
+
663
+ /*
664
+ * メニュー
665
+ * 選択>表示されている小説を選択
666
+ */
667
+ $("#action-select-view").on(click_event_name, function(e) {
668
+ e.preventDefault();
669
+ slideNavbar.slide();
670
+ action.selectView();
671
+ });
672
+
673
+ /*
674
+ * メニュー
675
+ * 選択>選択を全て解除
676
+ */
677
+ $("#action-select-clear").on(click_event_name, function(e) {
678
+ e.preventDefault();
679
+ slideNavbar.slide();
680
+ action.selectClear();
681
+ });
682
+
683
+ /*
684
+ * メニュー
685
+ * タグ > 選択した小説のタグを編集
686
+ */
687
+ $("#action-tag-edit").on(click_event_name, function(e) {
688
+ e.preventDefault();
689
+ slideNavbar.slide();
690
+ tag.openEditor();
691
+ });
692
+
693
+ /*
694
+ * メニュー
695
+ * オプション>環境設定
696
+ */
697
+ $("#action-option-settings").on(click_event_name, function(e) {
698
+ // ページ遷移するだけ
699
+ });
700
+
701
+ /*
702
+ * メニュー
703
+ * オプション>ヘルプ
704
+ */
705
+ $("#action-option-help").on(click_event_name, function(e) {
706
+ // ページ遷移するだけ
707
+ });
708
+
709
+ /*
710
+ * メニュー
711
+ * オプション>Narou.rbについて
712
+ */
713
+ $("#action-option-about").on(click_event_name, function(e) {
714
+ e.preventDefault();
715
+ slideNavbar.slide();
716
+ $.get("/about", function(data) {
717
+ bootbox.dialog({
718
+ title: '<span class="glyphicon glyphicon-info-sign"></span> Narou.rb について',
719
+ message: data,
720
+ backdrop: true
721
+ });
722
+ });
723
+ });
724
+
725
+ /*
726
+ * メニュー
727
+ * オプション>サーバをシャットダウン
728
+ */
729
+ $("#action-option-server-shutdown").on(click_event_name, function(e) {
730
+ e.preventDefault();
731
+ slideNavbar.slide();
732
+ bootbox.dialog({
733
+ title: '<span class="glyphicon glyphicon-off"></span> シャットダウン',
734
+ message: "Narou.rb WEB UI サーバをシャットダウンします。<br>" +
735
+ "シャットダウンすると再起動するまでアクセスは出来なくなります。<br>" +
736
+ "再起動するにはコンソールでもう一度 <kbd>narou web</kbd> を実行" +
737
+ "して下さい。",
738
+ backdrop: true,
739
+ buttons: {
740
+ danger: {
741
+ label: "シャットダウン",
742
+ className: "btn-danger",
743
+ callback: function() {
744
+ $.post("/shutdown", function(data) {
745
+ bootbox.alert(data);
746
+ });
747
+ }
748
+ },
749
+ main: {
750
+ label: "キャンセル",
751
+ className: "btn-default",
752
+ }
753
+ }
754
+ });
755
+ });
756
+
757
+ /*
758
+ * 「選択」メニューの横に現在選択中の小説数をバッジで表示
759
+ */
760
+ var register_badge_number_of_selecting = function(table) {
761
+ table.on("changeselect", function() {
762
+ var selected_count = table.rows(".selected").data().length;
763
+ $("#badge-number-of-selecting").text(selected_count);
764
+ var tooltip_opt = {
765
+ animation: false,
766
+ container: "body",
767
+ placement: "bottom"
768
+ }
769
+ if (selected_count > 0) {
770
+ tooltip_opt.title = "選択した小説を更新";
771
+ $("#button-update").tooltip("destroy").tooltip(tooltip_opt);
772
+ tooltip_opt.title = "選択した小説を送信";
773
+ $("#button-send").tooltip("destroy").tooltip(tooltip_opt);
774
+ enable_menu_item(".enable-selected");
775
+ }
776
+ else {
777
+ tooltip_opt.title = "すべての小説を更新";
778
+ $("#button-update").tooltip("destroy").tooltip(tooltip_opt);
779
+ tooltip_opt.title = "更新された小説を送信";
780
+ $("#button-send").tooltip("destroy").tooltip(tooltip_opt);
781
+ disable_menu_item(".enable-selected");
782
+ }
783
+ });
784
+ };
785
+ register_badge_number_of_selecting(table);
786
+
787
+ /*
788
+ * Tooltip 化
789
+ */
790
+ $("[data-toggle=tooltip]").tooltip({
791
+ animation: false,
792
+ container: "body",
793
+ placement: "bottom",
794
+ });
795
+
796
+ /*
797
+ * ボタンを一時的に非アクティブ化
798
+ */
799
+ function disableButtonMoment($button) {
800
+ $button.addClass("disabled");
801
+ // 少したったらアクティブに戻す
802
+ setTimeout(function() {
803
+ $button.removeClass("disabled");
804
+ }, 1000);
805
+ }
806
+
807
+ /*
808
+ * ボタン
809
+ * Download > 新規ダウンロード
810
+ */
811
+ $("#button-download").on("click", function(e) {
812
+ e.preventDefault();
813
+ action.download();
814
+ });
815
+
816
+ /*
817
+ * ボタン
818
+ * Download > 選択した小説を強制再ダウンロード
819
+ */
820
+ $("#button-download-force").on("click", function(e) {
821
+ e.preventDefault();
822
+ action.downloadForce();
823
+ });
824
+
825
+ /*
826
+ * ボタン
827
+ * Update
828
+ */
829
+ $("#button-update").on("click", function(e) {
830
+ e.preventDefault();
831
+ disableButtonMoment($(this));
832
+ action.update();
833
+ });
834
+
835
+ /*
836
+ * ボタン
837
+ * Send
838
+ */
839
+ $("#button-send").on("click", function(e) {
840
+ e.preventDefault();
841
+ disableButtonMoment($(this));
842
+ action.send();
843
+ });
844
+
845
+ /*
846
+ * ボタン
847
+ * Freeze > 選択した小説を凍結
848
+ */
849
+ $("#button-freeze-on").on("click", function(e) {
850
+ e.preventDefault();
851
+ action.freezeOn();
852
+ });
853
+
854
+ /*
855
+ * ボタン
856
+ * Freeze > 選択した小説の凍結を解除
857
+ */
858
+ $("#button-freeze-off").on("click", function(e) {
859
+ e.preventDefault();
860
+ action.freezeOff();
861
+ });
862
+
863
+ /*
864
+ * ボタン
865
+ * Remove > 選択した小説を削除
866
+ */
867
+ $("#button-remove").on("click", function(e) {
868
+ e.preventDefault();
869
+ action.remove();
870
+ });
871
+
872
+ /*
873
+ * ボタン
874
+ * Remove > 選択した小説をファイルも含めて削除
875
+ */
876
+ $("#button-remove-with-file").on("click", function(e) {
877
+ e.preventDefault();
878
+ action.removeWithFile();
879
+ });
880
+
881
+ /*
882
+ * ボタン
883
+ * Convert
884
+ */
885
+ $("#button-convert").on("click", function(e) {
886
+ e.preventDefault();
887
+ disableButtonMoment($(this));
888
+ action.convert();
889
+ });
890
+
891
+ /*
892
+ * ボタン
893
+ * Other > 選択した小説の差分を表示
894
+ */
895
+ $("#button-diff").on("click", function(e) {
896
+ e.preventDefault();
897
+ action.diff();
898
+ });
899
+
900
+ /*
901
+ * ボタン
902
+ * Other > 選択した小説の調査状況ログを表示
903
+ */
904
+ $("#button-inspect").on("click", function(e) {
905
+ e.preventDefault();
906
+ action.inspect();
907
+ });
908
+
909
+ /*
910
+ * ボタン
911
+ * Other > 選択した小説の保存フォルダを開く
912
+ */
913
+ $("#button-folder").on("click", function(e) {
914
+ e.preventDefault();
915
+ action.folder();
916
+ });
917
+
918
+ /*
919
+ * ボタン
920
+ * Other > 選択した小説のバックアップを作成
921
+ */
922
+ $("#button-backup").on("click", function(e) {
923
+ e.preventDefault();
924
+ action.backup();
925
+ });
926
+
927
+ /*
928
+ * ショートカット設定
929
+ * http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php
930
+ */
931
+ var initialize_shortcut = function() {
932
+ var options = {
933
+ disable_in_input: true,
934
+ };
935
+ var add = function() {
936
+ var keys = [];
937
+ var callback = null;
938
+ for (var i = 0; i < arguments.length; i++) {
939
+ var value = arguments[i];
940
+ switch (typeof value) {
941
+ case "string":
942
+ keys.push(value);
943
+ break;
944
+ case "function":
945
+ callback = value;
946
+ break;
947
+ default:
948
+ $.error("invalid arguments: unknow type");
949
+ break;
950
+ }
951
+ }
952
+ if (!callback) $.error("shortcut error: need callback");
953
+ for (var i = 0; i < keys.length; i++) {
954
+ shortcut.add(keys[i], callback, options);
955
+ }
956
+ };
957
+ add("Ctrl+A", "Meta+A", function() { action.selectView(); });
958
+ add("Shift+A", function() { action.selectAll(); });
959
+ add("Ctrl+Shift+A", "Meta+Shift+A", function() { action.selectClear(); });
960
+ add("ESC", function() {
961
+ if ($("#rect-select-menu").is(":visible")) {
962
+ close_rect_select_menu_handler();
963
+ }
964
+ else {
965
+ action.selectClear();
966
+ }
967
+ });
968
+ add("S", function() {
969
+ $("#action-select-mode-single").trigger("click");
970
+ });
971
+ add("R", function() {
972
+ $("#action-select-mode-rect").trigger("click");
973
+ });
974
+ };
975
+ if (!touchable_device) {
976
+ initialize_shortcut();
977
+ }
978
+
979
+ /*
980
+ * disabled なメニューは何もしないように
981
+ */
982
+ var nothing_to_do = function() { return false };
983
+ function disable_menu_item(id) {
984
+ $(id).addClass("disabled").on(click_event_name, nothing_to_do);
985
+ }
986
+
987
+ function enable_menu_item(id) {
988
+ $(id).removeClass("disabled").off(click_event_name, nothing_to_do);
989
+ }
990
+
991
+ disable_menu_item(".navbar li.disabled");
992
+ });
993
+