narou 2.8.3.1 → 2.9.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +77 -0
  3. data/Gemfile +2 -1
  4. data/README.md +69 -86
  5. data/lib/command/convert.rb +10 -11
  6. data/lib/command/init.rb +2 -2
  7. data/lib/command/list.rb +12 -3
  8. data/lib/command/setting.rb +20 -5
  9. data/lib/command/update.rb +0 -3
  10. data/lib/command/version.rb +2 -3
  11. data/lib/command/web.rb +58 -7
  12. data/lib/converterbase.rb +7 -24
  13. data/lib/database.rb +7 -6
  14. data/lib/device.rb +15 -1
  15. data/lib/device/ibunko.rb +1 -2
  16. data/lib/device/library/mac.rb +7 -0
  17. data/lib/downloader.rb +44 -21
  18. data/lib/extension.rb +25 -0
  19. data/lib/helper.rb +4 -3
  20. data/lib/html.rb +18 -2
  21. data/lib/narou.rb +20 -0
  22. data/lib/novelconverter.rb +11 -35
  23. data/lib/novelinfo.rb +19 -8
  24. data/lib/novelsetting.rb +0 -6
  25. data/lib/version.rb +1 -1
  26. data/lib/web/appserver.rb +134 -15
  27. data/lib/web/public/favicon.ico +0 -0
  28. data/lib/web/public/resources/common.ui.js +2 -2
  29. data/lib/web/public/resources/narou.library.js +657 -91
  30. data/lib/web/public/resources/narou.queue.js +1 -1
  31. data/lib/web/public/resources/narou.ui.js +253 -102
  32. data/lib/web/public/resources/sprintf.js +245 -0
  33. data/lib/web/pushserver.rb +14 -3
  34. data/lib/web/views/_about.haml +188 -0
  35. data/lib/web/views/{diff_list.haml → _diff_list.haml} +0 -0
  36. data/lib/web/views/{edit_replace_txt.haml → _edit_replace_txt.haml} +4 -4
  37. data/lib/web/views/_rebooting.haml +18 -0
  38. data/lib/web/views/bookmarklet/download.js.erb +2 -2
  39. data/lib/web/views/bookmarklet/insert_button.js.erb +1 -1
  40. data/lib/web/views/edit_menu.haml +223 -0
  41. data/lib/web/views/help.haml +29 -12
  42. data/lib/web/views/index.haml +99 -88
  43. data/lib/web/views/layout.haml +5 -3
  44. data/lib/web/views/notepad.haml +39 -0
  45. data/lib/web/views/novels/setting.haml +15 -5
  46. data/lib/web/views/settings.haml +2 -2
  47. data/lib/web/views/style.scss +72 -21
  48. data/lib/web/views/widget/download.haml +3 -2
  49. data/lib/web/views/widget/drag_and_drop.haml +3 -2
  50. data/lib/web/views/widget/notepad.haml +44 -0
  51. data/narou.gemspec +75 -6
  52. data/narou.rb +8 -14
  53. data/preset/ncode.syosetu.com/n5115cq/converter.rb +30 -0
  54. data/preset/ncode.syosetu.com/n7594ct/converter.rb +37 -0
  55. data/preset/ncode.syosetu.com/n8725k/converter.rb +1 -1
  56. data/preset/vertical_font.css +0 -10
  57. data/spec/generator/convert_spec_gen.rb +2 -0
  58. data/template/novel.txt.erb +3 -0
  59. data/webnovel/https.syosetu.org.yaml +1 -1
  60. data/webnovel/kakuyomu.jp.yaml +82 -0
  61. data/webnovel/ncode.syosetu.com.yaml +1 -0
  62. data/webnovel/syosetu.org.yaml +1 -1
  63. metadata +89 -12
  64. data/lib/web/views/about.haml +0 -85
  65. data/preset/DMincho.ttf +0 -0
@@ -35,7 +35,7 @@ $(document).ready(function() {
35
35
  left: $queue.offset().left + $queue.outerWidth() + 1 - $(document).scrollLeft(),
36
36
  top: $queue.offset().top - 3 - $(document).scrollTop(),
37
37
  });
38
- $(".navbar").append($plus);
38
+ $("#header-navbar").append($plus);
39
39
  $plus.delay(100).animate({
40
40
  top: - $plus.outerHeight(),
41
41
  opacity: 0,
@@ -9,6 +9,8 @@ $(document).ready(function() {
9
9
  var touchable_device = "ontouchstart" in window;
10
10
  var click_event_name = (touchable_device ? "touchstart" : "click");
11
11
 
12
+ var datatables_init_completed = false;
13
+
12
14
  // MEMO: table 全てに対して操作
13
15
  // table.$("tr.selected").removeClass("selected");
14
16
  // MEMO: .selected が付いている行を全て取得
@@ -20,13 +22,13 @@ $(document).ready(function() {
20
22
  {
21
23
  id: false, last_update: false, title: true, author: false, sitename: false,
22
24
  toc_url: false, novel_type: false, tags: false, status: false, menu: true,
23
- download: true, folder: false, general_lastup: false,
25
+ download: true, folder: false, update_button: false, general_lastup: false,
24
26
  }
25
27
  // PC
26
28
  : {
27
29
  id: true, last_update: true, title: true, author: true, sitename: true,
28
30
  toc_url: true, novel_type: true, tags: true, status: true, menu: true,
29
- download: true, folder: true, general_lastup: true,
31
+ download: true, folder: true, update_button: true, general_lastup: true,
30
32
  });
31
33
 
32
34
  $.fn.dataTable.Api.register("fireChangeSelect()", function() {
@@ -41,17 +43,6 @@ $(document).ready(function() {
41
43
  });
42
44
  });
43
45
 
44
- /*
45
- * タグ検索
46
- */
47
- var $tag_search = $("#tag-search");
48
- $.fn.dataTable.ext.search.push(function (settings, data, dataIndex) {
49
- var tag_name = $tag_search.val();
50
- if (tag_name.length == 0) return true;
51
- var tags = table.row(dataIndex).data().tags;
52
- return tags.match(new RegExp('data-tag="' + tag_name + '"'));
53
- });
54
-
55
46
  var ANNOTATION_COLOR_TIME_LIMIT = 6 * 60 * 60; // 6時間(秒)
56
47
  var GENERAL_LASTUP_CLASSES = [
57
48
  [60 * 60, "gl-60minutes"],
@@ -73,25 +64,37 @@ $(document).ready(function() {
73
64
 
74
65
  // initComplete や ajax.reload した時に再設定するために呼ばれる
75
66
  function resettingTableState() {
76
- table.$("[data-toggle=tooltip]").tooltip({
77
- animation: false,
78
- container: "body",
79
- });
67
+ refreshTooltop(table.$("[data-toggle=tooltip]"), null, "top");
80
68
  // バッジを更新しておく(.selectedの復元が終わってから呼ぶ)
81
69
  table.fireChangeSelect();
82
- // タグのイベントを反映
83
- tag.registerEvents(table);
84
70
 
85
71
  table.draw(false);
86
72
  }
87
73
 
74
+ function openContextMenuWithTr(tr, event) {
75
+ var $tr = $(tr);
76
+ var target_id = table.row(tr).data().id;
77
+ var $cover = $("<div class=context-menu-target-cover>");
78
+ // MEMO: IEだと要素を追加してから座標を指定しないと反映されない
79
+ $("body").append($cover);
80
+ $cover.offset($tr.offset()).width($tr.width()).height($tr.height());
81
+ $cover.one("contextmenu", function(e) {
82
+ e.preventDefault();
83
+ $cover.remove();
84
+ context_menu.close();
85
+ });
86
+ context_menu.open(target_id, get_event_position(event), function() {
87
+ $cover.remove();
88
+ });
89
+ }
90
+
88
91
  var table = t = $("#novel-list").DataTable({
89
92
  ajax: "/api/list",
90
93
  dom: (touchable_device ? 'lprtpi' : 'Rlprtpi'),
91
94
  stateSave: true,
92
95
  stateDuration: 0, // tableの状態保存を永続化
93
96
  paginationType: "full_numbers",
94
- lengthMenu: [[20, 50, 100, -1], [20, 50, 100, "全"]],
97
+ lengthMenu: [[20, 50, 100, 200, 500, -1], [20, 50, 100, 200, 500, "全"]],
95
98
  order: [[ 1, "desc" ]], // 最初は新着順でソートしておく
96
99
  columns: [
97
100
  // ID
@@ -121,7 +124,7 @@ $(document).ready(function() {
121
124
  display = "<span class=" + klass + ">" + display + "</span>";
122
125
  }
123
126
  }
124
- return display;
127
+ return "<div>" + display + "</div>";
125
128
  }
126
129
  return data;
127
130
  }
@@ -145,7 +148,7 @@ $(document).ready(function() {
145
148
  klass = value[1];
146
149
  if (value[0] === -1 || diff_time <= value[0]) break;
147
150
  }
148
- return '<span class="general-lastup ' + klass + '">' + display + '</span>';
151
+ return '<div><span class="general-lastup ' + klass + '">' + display + '</span></div>';
149
152
  }
150
153
  else if (type === "sort") {
151
154
  return data * 10000 + (row.id | 0);
@@ -201,27 +204,36 @@ $(document).ready(function() {
201
204
  },
202
205
  render: function() {}
203
206
  },
207
+ {
208
+ title: "更新",
209
+ data: "update_button", className: "text-center", orderable: false,
210
+ searchable: false, visible: cell_visible.update_button,
211
+ defaultContent: '<button class="btn btn-default btn-xs">' +
212
+ '<span class="glyphicon glyphicon-refresh"></span></button>',
213
+ createdCell: function(td, cellData, rowData, rowIndex, colIndex) {
214
+ $(td).children("button")
215
+ .data("targetId", rowData.id)
216
+ .on("click", function(e) {
217
+ e.stopPropagation();
218
+ var $this = $(this);
219
+ var target_id = $this.data("targetId");
220
+ action.updateForce(target_id);
221
+ });
222
+ },
223
+ render: function() {}
224
+ },
204
225
  {
205
226
  title: "個別",
206
227
  data: "menu", className: "text-center",
207
228
  defaultContent: '<button class="btn btn-default btn-xs">' +
208
- '<span class="glyphicon glyphicon-cog"></span></button>',
229
+ '<span class="glyphicon glyphicon-option-horizontal"></span></button>',
209
230
  visible: cell_visible.menu,
210
231
  createdCell: function(td, cellData, rowData, rowIndex, colIndex) {
211
232
  $(td).children("button")
212
233
  .data("targetId", rowData.id)
213
234
  .on("click", function(e) {
214
235
  e.stopPropagation();
215
- var $this = $(this);
216
- var $tr = $(e.target).closest("tr");
217
- var target_id = $this.data("targetId");
218
- var $cover = $("<div class=context-menu-target-cover>");
219
- // MEMO: IEだと要素を追加してから座標を指定しないと反映されない
220
- $("body").append($cover);
221
- $cover.offset($tr.offset()).width($tr.width()).height($tr.height());
222
- context_menu.open(target_id, get_event_position(e), function() {
223
- $cover.remove();
224
- });
236
+ openContextMenuWithTr($(e.target).closest("tr"), e);
225
237
  });
226
238
  },
227
239
  render: function() {},
@@ -246,24 +258,12 @@ $(document).ready(function() {
246
258
  }
247
259
  return hash;
248
260
  }),
249
- stateLoadParams: function (_settings, data) {
250
- // 独自フィルタボックスへセーブデータを反映
251
- if (data.search.search != "") {
252
- $("#myFilter").val(data.search.search);
253
- $("#myFilter-clear").show();
254
- }
255
- else {
256
- $("#myFilter-clear").hide();
257
- }
258
- },
259
261
  createdRow: function(row, data, data_index) {
260
262
  if (data.frozen) {
261
263
  $(row).addClass("frozen");
262
264
  }
263
265
  },
264
266
  initComplete: function(_settings, _json) {
265
- // タグ検索を復元
266
- $tag_search.val(storage.get("tag_search"));
267
267
  // 範囲選択モードでもリンクとボタンが使えるように
268
268
  table.on("mousedown", "a, button", function(e) {
269
269
  e.stopPropagation();
@@ -274,7 +274,9 @@ $(document).ready(function() {
274
274
  });
275
275
  resettingTableState();
276
276
 
277
- table.draw(false);
277
+ search.searchSync();
278
+
279
+ datatables_init_completed = true;
278
280
  },
279
281
  language: {
280
282
  //lengthMenu: "_MENU_ 件分表示 <span id='icon-refresh-table' class='glyphicon glyphicon-repeat'></span>",
@@ -298,18 +300,80 @@ $(document).ready(function() {
298
300
  * Extensions section
299
301
  */
300
302
  colReorder: {
301
- "realtime": false
303
+ realtime: false,
304
+
305
+ // colReorder でカラムの順番が入れ替えられた場合、createdCell 内で
306
+ // 設定している内容が全てリセットされてしまいイベント等が消えてしまうので、
307
+ // ここで一旦リロードしてイベントが再設定されるようにする。
308
+ // これは createdCell が初期化時に一度しか呼ばれないためで、dataTables の
309
+ // 今後のバージョンアップで予定されている updatedCell が実装されるまでの
310
+ // 暫定処置として reorderCallback で代用する。
311
+ // https://github.com/DataTables/ColReorder/issues/49
312
+ reorderCallback: function() {
313
+ // stateSave が有効な状態でカラムが入れ替えられていた場合、
314
+ // initComplete イベントが発火する前に一度この reorderCallback が発火されるので、
315
+ // 無駄なリロードをしないように初期化後のみ有効にする
316
+ if (datatables_init_completed) {
317
+ notification.trigger("table.reload");
318
+ }
319
+ },
302
320
  },
303
321
  });
304
322
 
305
323
  var storage = new Narou.Storage();
306
324
  var action = new Narou.Action(table);
307
325
  var notification = Narou.Notification.instance();
308
- var stream_console = new Narou.Console(notification);
326
+ var stream_console = new Narou.Console;
327
+ var search = Narou.Search.get(table);
309
328
  var tag = new Narou.Tag(table);
310
- var context_menu = new Narou.ContextMenu(action, notification, tag);
329
+ var context_menu = new Narou.ContextMenu(action, tag);
311
330
  var select_color_menu = new Narou.SelectColorMenu;
312
331
 
332
+ /*
333
+ * フィルターの文字列を復元
334
+ */
335
+ var filter_string = storage.get("filter_string");
336
+ if (filter_string) {
337
+ $("#myFilter").val(filter_string);
338
+ $("#myFilter-clear").show();
339
+ }
340
+ else {
341
+ $("#myFilter-clear").hide();
342
+ }
343
+
344
+ /*
345
+ * コントロールパネル(操作ボタン群)をフッターに移動させる/元に戻す
346
+ */
347
+ var fixed_buttons = storage.get("buttons_fix_footer");
348
+
349
+ function fix_buttons_to_footer() {
350
+ refreshTooltop(
351
+ $("#control-panel > div")
352
+ .appendTo("#footer-navbar > .container")
353
+ .addClass("in-footer dropup")
354
+ .find("[data-toggle=tooltip]")
355
+ .data("placement", "top")
356
+ );
357
+ table.fireChangeSelect();
358
+ $("#footer-navbar").removeClass("hide");
359
+ }
360
+
361
+ function cancel_fix_buttons() {
362
+ $("#footer-navbar").addClass("hide");
363
+ refreshTooltop(
364
+ $("#footer-navbar > .container > div")
365
+ .appendTo("#control-panel")
366
+ .removeClass("in-footer dropup")
367
+ .find("[data-toggle=tooltip]")
368
+ .data("placement", "bottom")
369
+ );
370
+ table.fireChangeSelect();
371
+ }
372
+
373
+ if (fixed_buttons) {
374
+ fix_buttons_to_footer();
375
+ }
376
+
313
377
  /*
314
378
  * 凍結の表示状態制御
315
379
  */
@@ -325,20 +389,28 @@ $(document).ready(function() {
325
389
  }
326
390
  });
327
391
 
328
- function restoreVisibilityForFrozen() {
392
+ function restoreVisibilityForViewMenu() {
329
393
  visibility_frozen_novel = storage.get("visibility_frozen_novel");
330
394
  visibility_nonfrozen_novel = storage.get("visibility_nonfrozen_novel");
395
+ var visibility_open_new_tab = storage.get("open_new_tab_setting_pages");
396
+ var visibility_buttons_fix_footer = storage.get("buttons_fix_footer");
331
397
  if (typeof visibility_frozen_novel === "undefined")
332
398
  visibility_frozen_novel = true;
333
399
  if (typeof visibility_nonfrozen_novel === "undefined")
334
400
  visibility_nonfrozen_novel = true;
401
+ if (typeof visibility_open_new_tab === "undefined")
402
+ visibility_open_new_tab = false;
403
+ if (typeof visibility_buttons_fix_footer === "undefined")
404
+ visibility_buttons_fix_footer = false;
335
405
  // メニューのチェック状態も復元
336
406
  var method = ["removeClass", "addClass"];
337
407
  $("#action-view-frozen")[method[visibility_frozen_novel | 0]]("active");
338
408
  $("#action-view-nonfrozen")[method[visibility_nonfrozen_novel | 0]]("active");
409
+ $("#action-view-toggle-setting-page-open-new-tab")[method[visibility_open_new_tab | 0]]("active");
410
+ $("#action-view-toggle-buttons-fix-footer")[method[visibility_buttons_fix_footer | 0]]("active");
339
411
  }
340
412
 
341
- restoreVisibilityForFrozen();
413
+ restoreVisibilityForViewMenu();
342
414
 
343
415
  function saveVisibilityFrozen() {
344
416
  storage.set("visibility_frozen_novel", visibility_frozen_novel);
@@ -347,11 +419,11 @@ $(document).ready(function() {
347
419
  }
348
420
 
349
421
  function toggleVisibilityFrozen() {
350
- visibility_frozen_novel = visibility_frozen_novel ? false : true;
422
+ visibility_frozen_novel = !visibility_frozen_novel;
351
423
  }
352
424
 
353
425
  function toggleVisibilityNonFrozen() {
354
- visibility_nonfrozen_novel = visibility_nonfrozen_novel ? false : true;
426
+ visibility_nonfrozen_novel = !visibility_nonfrozen_novel;
355
427
  }
356
428
 
357
429
  /*
@@ -377,32 +449,26 @@ $(document).ready(function() {
377
449
  }, false);
378
450
  });
379
451
 
452
+ /*
453
+ * サーバ再起動イベント定義
454
+ */
455
+ notification.on("server.rebooted", function() {
456
+ location.href = "/";
457
+ });
458
+
380
459
  notification.on("tag.updateCanvas", function() {
381
460
  tag.updateCanvas();
382
461
  });
383
462
 
384
- var action_select_view = $("#action-select-view");
385
- var myfilter_clear = $("#myFilter-clear");
386
-
387
463
  /*
388
- * カスタムフィルタボックスで入力値をフィルタする
464
+ * イジェクトボタンの show / hide イベント
389
465
  */
390
- $("#myFilter").on("keyup", function() {
391
- table.search($(this).val()).draw();
392
- if ($(this).val() === "") {
393
- myfilter_clear.hide();
394
- }
395
- else {
396
- myfilter_clear.show();
397
- }
398
- })
399
- /*
400
- * リセットボタン(X)
401
- */
402
- $("#myFilter-clear").on(click_event_name, function() {
403
- table.search("").draw();
404
- $("#myFilter").val("");
405
- myfilter_clear.hide();
466
+ var $button_eject = $("#button-eject");
467
+ notification.on("device.ejectable", function(ejectable) {
468
+ if (ejectable)
469
+ $button_eject.removeClass("hide");
470
+ else
471
+ $button_eject.addClass("hide");
406
472
  });
407
473
 
408
474
  /*************************************************************
@@ -489,6 +555,21 @@ $(document).ready(function() {
489
555
  $("#action-select-mode-rect").addClass("disabled");
490
556
  }
491
557
  else {
558
+ // 小説一覧テーブル内で右クリックしたら個別メニューを表示
559
+ $("#novel-list tbody").on("contextmenu", function(e) {
560
+ var target_tag_name = e.target.tagName.toLowerCase();
561
+ // ボタンとかリンクはブラウザの右クリックメニューをそのまま使いたい
562
+ if (target_tag_name === "button" || target_tag_name === "a" ||
563
+ $(e.target).parent("button, a")[0])
564
+ return;
565
+
566
+ e.preventDefault(); // ブラウザの右クリックメニューを抑制
567
+ e.stopPropagation();
568
+ var mousedowned_element = $.elementFromPoint(get_event_position(e));
569
+ var tr = mousedowned_element.parents("tr");
570
+ openContextMenuWithTr(tr, e);
571
+ });
572
+
492
573
  var initMode = function(mode) {
493
574
  if (select_mode != mode) {
494
575
  $("#action-select-mode-" + mode).addClass("active");
@@ -625,6 +706,11 @@ $(document).ready(function() {
625
706
  var mousedowned_element = $.elementFromPoint(_pos);
626
707
  if (!mousedowned_element || mousedowned_element.closest("#novel-list tbody").length === 0)
627
708
  return;
709
+
710
+ // 右クリックは無視(contextmenu イベント内で個別メニューを表示する)
711
+ if (e.which == 3)
712
+ return;
713
+
628
714
  e.preventDefault();
629
715
  e.stopPropagation();
630
716
  var self = this;
@@ -648,6 +734,7 @@ $(document).ready(function() {
648
734
  if (not_clicked_yet) {
649
735
  // 範囲選択開始
650
736
  $("#rect-select-menu").hide();
737
+ context_menu.close();
651
738
  unregister_close_handler();
652
739
  not_clicked_yet = false;
653
740
  start_position = _pos;
@@ -780,6 +867,36 @@ $(document).ready(function() {
780
867
  table.draw();
781
868
  });
782
869
 
870
+ /*
871
+ * メニュー
872
+ * 表示>変換設定ページは新規タブで開く
873
+ */
874
+ $("#action-view-toggle-setting-page-open-new-tab").on(click_event_name, function(e) {
875
+ e.preventDefault();
876
+ slideNavbar.slide();
877
+ $(this).toggleClass("active");
878
+ var open_new_tab = storage.get("open_new_tab_setting_pages");
879
+ storage.set("open_new_tab_setting_pages", !open_new_tab);
880
+ storage.save();
881
+ });
882
+
883
+ /*
884
+ * メニュー
885
+ * 表示>ボタンを画面下部に固定
886
+ */
887
+ $("#action-view-toggle-buttons-fix-footer").on(click_event_name, function(e) {
888
+ e.preventDefault();
889
+ slideNavbar.slide();
890
+ $(this).toggleClass("active");
891
+ fixed_buttons = !storage.get("buttons_fix_footer");
892
+ storage.set("buttons_fix_footer", fixed_buttons);
893
+ storage.save();
894
+ if (fixed_buttons)
895
+ fix_buttons_to_footer();
896
+ else
897
+ cancel_fix_buttons();
898
+ });
899
+
783
900
  /*
784
901
  * メニュー
785
902
  * 表示>表示設定を全てリセット
@@ -836,7 +953,7 @@ $(document).ready(function() {
836
953
  $("#action-tool-open-dnd-window").on(click_event_name, function(e) {
837
954
  e.preventDefault();
838
955
  slideNavbar.slide();
839
- window.open("/widget/drag_and_drop", "widget_window",
956
+ window.open("/widget/drag_and_drop", "dnd_window",
840
957
  "width=400,height=350,menubar=no,toolbar=no,scrollbars=no,resizable=yes");
841
958
  });
842
959
 
@@ -860,6 +977,17 @@ $(document).ready(function() {
860
977
  action.csvImport();
861
978
  });
862
979
 
980
+ /*
981
+ * メニュー
982
+ * ツール > メモ帳(ポップアップ)
983
+ */
984
+ $("#action-tool-notepad-window").on(click_event_name, function(e) {
985
+ e.preventDefault();
986
+ slideNavbar.slide();
987
+ window.open("/widget/notepad", "notepad_window",
988
+ "width=400,height=350,menubar=no,toolbar=no,scrollbars=no,resizable=yes");
989
+ });
990
+
863
991
  /*
864
992
  * メニュー
865
993
  * オプション>環境設定
@@ -892,6 +1020,16 @@ $(document).ready(function() {
892
1020
  });
893
1021
  });
894
1022
 
1023
+ /*
1024
+ * メニュー
1025
+ * オプション>サーバを再起動
1026
+ */
1027
+ $("#action-option-server-reboot").on(click_event_name, function(e) {
1028
+ e.preventDefault();
1029
+ slideNavbar.slide();
1030
+ action.rebootDialog();
1031
+ });
1032
+
895
1033
  /*
896
1034
  * メニュー
897
1035
  * オプション>サーバをシャットダウン
@@ -901,10 +1039,11 @@ $(document).ready(function() {
901
1039
  slideNavbar.slide();
902
1040
  bootbox.dialog({
903
1041
  title: '<span class="glyphicon glyphicon-off"></span> シャットダウン',
904
- message: "Narou.rb WEB UI サーバをシャットダウンします。<br>" +
905
- "シャットダウンすると再起動するまでアクセスは出来なくなります。<br>" +
906
- "再起動するにはコンソールでもう一度 <kbd>narou web</kbd> を実行" +
907
- "して下さい。",
1042
+ message: "<p>Narou.rb WEB UI サーバをシャットダウンします。<br>" +
1043
+ "シャットダウンすると再度立ち上げるまではアクセスは出来なくなります。<br>" +
1044
+ "再度立ち上げるにはコンソールでもう一度 <kbd>narou web</kbd> を実行して下さい。</p>" +
1045
+ "<p class=text-danger>アップデート中や変換中の小説がある場合は中断されます。<br>" +
1046
+ "終わったかどうか確認しましょう。</p>",
908
1047
  backdrop: true,
909
1048
  buttons: {
910
1049
  danger: {
@@ -924,6 +1063,25 @@ $(document).ready(function() {
924
1063
  });
925
1064
  });
926
1065
 
1066
+ // tooltip() にハッシュで設定を渡すと、DOMに設定されているdata-placementなどの
1067
+ // 値が無視されてしまうため、デフォルト値を変更しておく
1068
+ _.merge($.fn.tooltip.Constructor.DEFAULTS, {
1069
+ animation: false,
1070
+ container: "body",
1071
+ placement: "bottom"
1072
+ });
1073
+
1074
+ function refreshTooltop(id, title, placement) {
1075
+ $(id)
1076
+ .tooltip("destroy")
1077
+ .tooltip({ title: title, placement: placement })
1078
+ .on("shown.bs.tooltip", function(e) {
1079
+ setTimeout(function() {
1080
+ $(e.target).tooltip("hide");
1081
+ }, 3500);
1082
+ });
1083
+ }
1084
+
927
1085
  /*
928
1086
  * 「選択」メニューの横に現在選択中の小説数をバッジで表示
929
1087
  */
@@ -931,23 +1089,14 @@ $(document).ready(function() {
931
1089
  table.on("changeselect", function() {
932
1090
  var selected_count = table.rows(".selected").data().length;
933
1091
  $("#badge-number-of-selecting").text(selected_count);
934
- var tooltip_opt = {
935
- animation: false,
936
- container: "body",
937
- placement: "bottom"
938
- }
939
1092
  if (selected_count > 0) {
940
- tooltip_opt.title = "選択した小説を更新";
941
- $("#button-update").tooltip("destroy").tooltip(tooltip_opt);
942
- tooltip_opt.title = "選択した小説を送信";
943
- $("#button-send").tooltip("destroy").tooltip(tooltip_opt);
1093
+ refreshTooltop("#button-update", "選択した小説を更新");
1094
+ refreshTooltop("#button-send", "選択した小説を送信");
944
1095
  enable_menu_item(".enable-selected");
945
1096
  }
946
1097
  else {
947
- tooltip_opt.title = "すべての小説を更新";
948
- $("#button-update").tooltip("destroy").tooltip(tooltip_opt);
949
- tooltip_opt.title = "更新された小説を送信";
950
- $("#button-send").tooltip("destroy").tooltip(tooltip_opt);
1098
+ refreshTooltop("#button-update", "すべての小説を更新");
1099
+ refreshTooltop("#button-send", "更新された小説を送信");
951
1100
  disable_menu_item(".enable-selected");
952
1101
  }
953
1102
  });
@@ -957,17 +1106,7 @@ $(document).ready(function() {
957
1106
  /*
958
1107
  * Tooltip 化
959
1108
  */
960
- $("[data-toggle=tooltip]").tooltip({
961
- animation: false,
962
- container: "body",
963
- placement: "bottom",
964
- });
965
-
966
- $("#icon-refresh-table").tooltip({
967
- title: "リストを再読み込み",
968
- animation: false,
969
- container: "body",
970
- });
1109
+ refreshTooltop("[data-toggle=tooltip]");
971
1110
 
972
1111
  $("button").on("mouseleave", function() {
973
1112
  $(this).tooltip("hide");
@@ -1142,6 +1281,15 @@ $(document).ready(function() {
1142
1281
  action.settingBurn();
1143
1282
  });
1144
1283
 
1284
+ /*
1285
+ * ボタン
1286
+ * Eject
1287
+ */
1288
+ $("#button-eject").on("click", function(e) {
1289
+ e.preventDefault();
1290
+ action.eject();
1291
+ });
1292
+
1145
1293
  /*
1146
1294
  * アイコン
1147
1295
  * ○件表示の横のリロードアイコン
@@ -1187,6 +1335,9 @@ $(document).ready(function() {
1187
1335
  if ($("#rect-select-menu").is(":visible")) {
1188
1336
  close_rect_select_menu_handler();
1189
1337
  }
1338
+ if (!context_menu.closed) {
1339
+ context_menu.close();
1340
+ }
1190
1341
  else {
1191
1342
  action.selectClear();
1192
1343
  }