narou 2.4.2 → 2.5.1

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/ChangeLog.md +56 -0
  4. data/README.md +56 -29
  5. data/lib/command/convert.rb +39 -7
  6. data/lib/command/diff.rb +42 -11
  7. data/lib/command/inspect.rb +1 -1
  8. data/lib/command/setting.rb +150 -45
  9. data/lib/command/tag.rb +8 -8
  10. data/lib/command/update.rb +57 -1
  11. data/lib/commandbase.rb +3 -0
  12. data/lib/converterbase.rb +17 -9
  13. data/lib/database.rb +4 -0
  14. data/lib/device/epub.rb +1 -1
  15. data/lib/device/ibooks.rb +1 -1
  16. data/lib/device/ibunko.rb +13 -6
  17. data/lib/device/kindle.rb +1 -1
  18. data/lib/device/kobo.rb +1 -1
  19. data/lib/device/reader.rb +1 -1
  20. data/lib/downloader.rb +10 -5
  21. data/lib/helper.rb +114 -3
  22. data/lib/ini.rb +3 -1
  23. data/lib/inventory.rb +3 -1
  24. data/lib/loadconverter.rb +1 -11
  25. data/lib/mailer.rb +1 -0
  26. data/lib/narou.rb +56 -5
  27. data/lib/novelconverter.rb +7 -5
  28. data/lib/novelsetting.rb +116 -63
  29. data/lib/template.rb +4 -4
  30. data/lib/version.rb +1 -1
  31. data/lib/web/appserver.rb +40 -9
  32. data/lib/web/public/resources/narou.library.js +35 -3
  33. data/lib/web/public/resources/narou.ui.js +16 -1
  34. data/lib/web/pushserver.rb +1 -0
  35. data/lib/web/settingmessages.rb +6 -3
  36. data/lib/web/views/diff_list.haml +11 -0
  37. data/lib/web/views/edit_replace_txt.haml +59 -0
  38. data/lib/web/views/help.haml +2 -2
  39. data/lib/web/views/index.haml +6 -4
  40. data/lib/web/views/layout.haml +11 -2
  41. data/lib/web/views/novels/setting.haml +51 -66
  42. data/lib/web/views/settings.haml +52 -15
  43. data/lib/web/views/style.scss +44 -2
  44. data/lib/web/views/widget.haml +6 -8
  45. data/narou.gemspec +45 -6
  46. data/spec/convert_spec.rb +1 -1
  47. data/spec/converterbase_spec.rb +25 -1
  48. data/spec/generator/convert_spec_gen.rb +1 -1
  49. data/spec/helper_spec.rb +8 -0
  50. data/spec/novelsetting_spec.rb +1 -1
  51. data/template/novel.txt.erb +1 -1
  52. data/template/setting.ini.erb +15 -3
  53. metadata +49 -8
@@ -19,14 +19,14 @@ class Template
19
19
  # _binding 変数とか設定したいスコープの binding 変数を渡す
20
20
  # overwrite 上書きするか
21
21
  #
22
- def self.write(src_filename, dest_filepath, _binding, overwrite = false)
22
+ def self.write(src_filename, dest_filepath, _binding, binary_version, overwrite = false)
23
23
  if File.directory?(dest_filepath)
24
24
  dest_filepath = File.join(dest_filepath, src_filename)
25
25
  end
26
26
  unless overwrite
27
27
  return if File.exist?(dest_filepath)
28
28
  end
29
- result = get(src_filename, _binding) or return nil
29
+ result = get(src_filename, _binding, binary_version) or return nil
30
30
  if Helper.os_windows?
31
31
  File.write(dest_filepath, result)
32
32
  else
@@ -41,13 +41,13 @@ class Template
41
41
  # 1. root_dir/template
42
42
  # 2. script_dir/template
43
43
  #
44
- def self.get(src_filename, _binding, binary_version = 1.0)
44
+ def self.get(src_filename, _binding, binary_version)
45
45
  @@binary_version = binary_version
46
46
  @@src_filename = src_filename
47
47
  [Narou.get_root_dir, Narou.get_script_dir].each do |dir|
48
48
  path = File.join(dir, TEMPLATE_DIR, src_filename + ".erb")
49
49
  next unless File.exist?(path)
50
- src = open(path, "r:BOM|UTF-8") { |fp| fp.read }
50
+ src = Helper::CacheLoader.load(path)
51
51
  result = ERB.new(src, nil, "-").result(_binding)
52
52
  return result
53
53
  end
@@ -3,5 +3,5 @@
3
3
  # Copyright 2013 whiteleaf. All rights reserved.
4
4
  #
5
5
 
6
- Version = "2.4.2"
6
+ Version = "2.5.1"
7
7
 
@@ -179,6 +179,8 @@ class Narou::AppServer < Sinatra::Base
179
179
  before do
180
180
  Narou::Worker.push_as_system_worker do
181
181
  Inventory.clear
182
+ Database.instance.refresh
183
+ Narou.load_global_replace_pattern
182
184
  end
183
185
  end
184
186
 
@@ -201,6 +203,7 @@ class Narou::AppServer < Sinatra::Base
201
203
  @view_invisible = params["view_invisible"] == "1"
202
204
  @setting_variables = Command::Setting.get_setting_variables
203
205
  @error_list = {}
206
+ @global_replace_pattern = @replace_pattern = Narou.global_replace_pattern
204
207
  end
205
208
 
206
209
  post "/settings" do
@@ -211,7 +214,7 @@ class Narou::AppServer < Sinatra::Base
211
214
  @setting_variables[scope].each do |name, info|
212
215
  param_data = params[name]
213
216
  argument = ""
214
- if info[0] == :boolean
217
+ if info[:type] == :boolean
215
218
  # :boolean 用のフォームデータは on, off, nil で渡される。
216
219
  # ただしチェックボックスはチェックした時だけ on が渡されるので、
217
220
  # 何もデータが無い=off を選択したと判断する。
@@ -222,6 +225,8 @@ class Narou::AppServer < Sinatra::Base
222
225
  else
223
226
  argument = "false"
224
227
  end
228
+ elsif param_data.kind_of?(Array)
229
+ argument = param_data.join(",")
225
230
  else
226
231
  argument = param_data
227
232
  end
@@ -245,6 +250,19 @@ class Narou::AppServer < Sinatra::Base
245
250
  end
246
251
  end
247
252
  end
253
+
254
+ # 置換設定保存
255
+ params_replace_pattern = params["replace_pattern"]
256
+ @global_replace_pattern.clear
257
+ if params_replace_pattern.kind_of?(Array)
258
+ params_replace_pattern.each do |pattern|
259
+ left, right = pattern["left"].strip, pattern["right"].strip
260
+ next if left == ""
261
+ @global_replace_pattern << [left, right]
262
+ end
263
+ end
264
+ Narou.save_global_replace_pattern
265
+
248
266
  if @error_list.empty?
249
267
  session[:alert] = [ "保存が完了しました", "success" ]
250
268
  else
@@ -285,28 +303,33 @@ class Narou::AppServer < Sinatra::Base
285
303
  @novel_title = @data["title"]
286
304
  @setting_variables = []
287
305
  @error_list = {}
288
- @novel_setting = NovelSetting.load(@id, true)
289
- @default_settings = NovelSetting::DEFAULT_SETTINGS
290
- @force_settings = Inventory.load("local_setting", :local).select { |n| n.start_with?("force.") }
306
+ @novel_setting = NovelSetting.new(@id, true, true) # 空っぽの設定を作成
307
+ @novel_setting.settings = @novel_setting.load_setting_ini["global"]
308
+ @original_settings = NovelSetting::ORIGINAL_SETTINGS
309
+ @force_settings = NovelSetting.load_force_settings
310
+ @default_settings = NovelSetting.load_default_settings
291
311
  @replace_pattern = @novel_setting.load_replace_pattern
292
312
  end
293
313
 
294
314
  post "/novels/:id/setting" do
295
315
  # 変換設定保存
296
- @default_settings.each do |info|
316
+ @original_settings.each do |info|
297
317
  name, type = info[:name], info[:type]
298
318
  param_data = params[name]
299
319
  value = nil
300
320
  begin
301
- case type
302
- when :boolean
321
+ if type == :boolean
303
322
  if param_data
304
323
  value = convert_on_off_to_boolean(param_data)
305
324
  else
306
325
  value = false
307
326
  end
327
+ elsif param_data.kind_of?(Array)
328
+ value = param_data.join(",")
308
329
  else
309
- value = Helper.string_cast_to_type(param_data, type)
330
+ if param_data.strip != ""
331
+ value = Helper.string_cast_to_type(param_data, type)
332
+ end
310
333
  end
311
334
  @novel_setting[name] = value
312
335
  rescue Helper::InvalidVariableType => e
@@ -482,16 +505,24 @@ class Narou::AppServer < Sinatra::Base
482
505
 
483
506
  post "/api/diff" do
484
507
  ids = select_valid_novel_ids(params["ids"]) or pass
508
+ number = params["number"] || "1"
485
509
  Narou::Worker.push do
486
510
  # diff コマンドは1度に一つのIDしか受け取らないので
487
511
  ids.each do |id|
488
512
  # セキュリティ的にWEB UIでは独自の差分表示のみ使う
489
- CommandLine.run!(["diff", "--no-tool", id])
513
+ CommandLine.run!(["diff", "--no-tool", id, "--number", number])
490
514
  Helper.print_horizontal_rule
491
515
  end
492
516
  end
493
517
  end
494
518
 
519
+ get "/api/diff_list" do
520
+ target = params["target"] or return ""
521
+ id = Downloader.get_id_by_target(target) or return ""
522
+ @list = Command::Diff.new.get_diff_list(id)
523
+ haml :diff_list, layout: false
524
+ end
525
+
495
526
  post "/api/inspect" do
496
527
  ids = select_valid_novel_ids(params["ids"]) or pass
497
528
  Narou::Worker.push do
@@ -181,12 +181,14 @@ var Narou = (function() {
181
181
  /*************************************************************************
182
182
  * コンテキストメニュー
183
183
  *************************************************************************/
184
- var ContextMenu = Narou.ContextMenu = function(action, notification) {
184
+ var ContextMenu = Narou.ContextMenu = function(action, notification, tag) {
185
185
  this.action = action;
186
186
  this.notification = notification;
187
+ this.tag = tag;
187
188
  this.closed = true;
188
189
  this.initializeConsoleDialog();
189
190
  this.initializeMenuEvent();
191
+ this.initializeDiffListEvent();
190
192
  };
191
193
 
192
194
  $.extend(ContextMenu.prototype, {
@@ -223,6 +225,32 @@ var Narou = (function() {
223
225
  });
224
226
  },
225
227
 
228
+ openSelectDiffListDialog: function(target_id) {
229
+ $.get("/api/diff_list", { target: target_id }, function(html) {
230
+ bootbox.dialog({
231
+ title: "表示したい差分を選択して下さい",
232
+ message: html,
233
+ backdrop: true,
234
+ className: "diff-list-modal",
235
+ buttons: {
236
+ main: {
237
+ label: "閉じる",
238
+ className: "btn-default"
239
+ },
240
+ }
241
+ });
242
+ });
243
+ },
244
+
245
+ initializeDiffListEvent: function() {
246
+ $(document).on("click", ".diff-list-container .list .item", function() {
247
+ var target = $(this).parent().data("diffTarget");
248
+ var number = $(this).data("diffItemNumber");
249
+ $.post("/api/diff", { ids: [ target ], number: number });
250
+ bootbox.hideAll();
251
+ });
252
+ },
253
+
226
254
  initializeMenuEvent: function() {
227
255
  var $context_menu = $("#context-menu");
228
256
  var self = this;
@@ -258,6 +286,10 @@ var Narou = (function() {
258
286
  self.action.removeWithFile(self.target_id);
259
287
  });
260
288
  });
289
+ $("#context-menu-edit-tag").on("click", function(e) {
290
+ e.preventDefault();
291
+ self.tag.openEditor(self.target_id);
292
+ });
261
293
  $("#context-menu-convert").on("click", function(e) {
262
294
  e.preventDefault();
263
295
  self.openConsoleDialog(function() {
@@ -266,7 +298,7 @@ var Narou = (function() {
266
298
  });
267
299
  $("#context-menu-diff").on("click", function(e) {
268
300
  e.preventDefault();
269
- self.action.diff(self.target_id);
301
+ self.openSelectDiffListDialog(self.target_id);
270
302
  });
271
303
  $("#context-menu-inspect").on("click", function(e) {
272
304
  e.preventDefault();
@@ -757,7 +789,7 @@ var Narou = (function() {
757
789
  },
758
790
 
759
791
  openEditor: function() {
760
- var ids = Action.prototype._getSelectedIds.call(this);
792
+ var ids = Action.prototype._getSelectedIds.call(this, arguments);
761
793
  if (ids.length === 0) return;
762
794
  this._createEditorField(ids, function(field) {
763
795
  bootbox.dialog({
@@ -266,6 +266,7 @@ $(document).ready(function() {
266
266
  table.draw(false);
267
267
  },
268
268
  language: {
269
+ //lengthMenu: "_MENU_ 件分表示 <span id='icon-refresh-table' class='glyphicon glyphicon-repeat'></span>",
269
270
  lengthMenu: "_MENU_ 件分表示",
270
271
  zeroRecords: "データがひとつもないか、処理中です",
271
272
  info: "Page _PAGE_ of _PAGES_",
@@ -295,8 +296,8 @@ $(document).ready(function() {
295
296
  var action = new Narou.Action(table);
296
297
  var notification = Narou.Notification.instance();
297
298
  var stream_console = new Narou.Console(notification);
298
- var context_menu = new Narou.ContextMenu(action, notification);
299
299
  var tag = new Narou.Tag(table);
300
+ var context_menu = new Narou.ContextMenu(action, notification, tag);
300
301
 
301
302
  notification.on("table.reload", function() {
302
303
  // リロードする前に現在の状態を保存
@@ -865,6 +866,12 @@ $(document).ready(function() {
865
866
  placement: "bottom",
866
867
  });
867
868
 
869
+ $("#icon-refresh-table").tooltip({
870
+ title: "リストを再読み込み",
871
+ animation: false,
872
+ container: "body",
873
+ });
874
+
868
875
  /*
869
876
  * ボタンを一時的に非アクティブ化
870
877
  */
@@ -1014,6 +1021,14 @@ $(document).ready(function() {
1014
1021
  action.backup();
1015
1022
  });
1016
1023
 
1024
+ /*
1025
+ * アイコン
1026
+ * ○件表示の横のリロードアイコン
1027
+ */
1028
+ $("#icon-refresh-table").on("click", function(e) {
1029
+ notification.trigger("table.reload");
1030
+ });
1031
+
1017
1032
  /*
1018
1033
  * ショートカット設定
1019
1034
  * http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php
@@ -4,6 +4,7 @@
4
4
  #
5
5
 
6
6
  require "json"
7
+ require "singleton"
7
8
  require_relative "web-socket-ruby/lib/web_socket"
8
9
  require_relative "../eventable"
9
10
 
@@ -7,9 +7,12 @@ module Narou
7
7
  # WEB UI > 環境設定画面で表示する各項目の説明
8
8
  # ここになければ元々の説明が表示される
9
9
  SETTING_VARIABLES_WEBUI_MESSAGES = {
10
- "convert.multi-device" => "複数の端末用に同時に変換する。deviceよりも優先される。\n端末名をカンマ区切りで入力(" + Device::DEVICES.keys.join(", ") + ")\nただのEPUBを出力したい場合はepubを指定",
11
- "device" => "変換、送信対象の端末(" + Device::DEVICES.keys.join(", ") + ")",
12
- "difftool" => "Diffで使うツールのパスを指定する\n※WEB UIでは使われません"
10
+ "convert.multi-device" => "複数の端末用に同時に変換する。deviceよりも優先される。\nただのEPUBを出力したい場合はepubを指定",
11
+ "device" => "変換、送信対象の端末",
12
+ "difftool" => "Diffで使うツールのパスを指定する\n※WEB UIでは使われません",
13
+ "update.sort-by" => "アップデートを指定した項目順で行う",
14
+ "default.title_date_align" => "enable_add_date_to_title で付与する日付の位置",
15
+ "force.title_date_align" => "enable_add_date_to_title で付与する日付の位置",
13
16
  }
14
17
  end
15
18
 
@@ -0,0 +1,11 @@
1
+ .diff-list-container
2
+ .title #{@list[:title]}
3
+ .list{ "data-diff-target" => @list[:id] }
4
+ - @list[:list].each do |data|
5
+ - next if data[:objects].empty?
6
+ .item{ "data-diff-item-number" => data[:number] }
7
+ .date #{data[:time].strftime("%Y/%m/%d %H:%M")}
8
+ %ul.subtitles
9
+ - data[:objects].each do |object|
10
+ %li
11
+ 第#{object[:index]}部分 #{object[:subtitle]}
@@ -0,0 +1,59 @@
1
+ :javascript
2
+ local_initialize_functions.push(function($) {
3
+ var tooltip_opt = {
4
+ animation: false,
5
+ container: "body",
6
+ };
7
+ // replace.txt 設定パネル
8
+ $(".btn-replace-remove").on("click", function(e) {
9
+ e.preventDefault();
10
+ if ($(".replace-pattern-tr").size() === 1) {
11
+ // 入力欄のtrが全部なくなると追加出来なくなるので、消す前に1個追加しておく
12
+ $("#btn-replace-add").trigger("click");
13
+ }
14
+ $(this).tooltip("destroy").closest("tr").remove();
15
+ });
16
+ $blank_tr = $(".replace-pattern-tr:last").clone(true); // 読み込み直後の末尾は空なので流用
17
+ $("#btn-replace-add").on("click", function(e) {
18
+ e.preventDefault();
19
+ var $clone = $blank_tr.clone(true);
20
+ $clone.find("button").tooltip(tooltip_opt);
21
+ $(".replace-pattern-tr:last").after($clone);
22
+ $(this).tooltip("hide");
23
+ });
24
+ $("[data-toggle=remove-tooltip]").tooltip(tooltip_opt);
25
+ });
26
+
27
+ #replace-panel.panel.panel-settings{ class: @replace_panel_var[:panel_class] }
28
+ .panel-heading
29
+ = @replace_panel_var[:title]
30
+ .panel-body{ class: @replace_panel_var[:body_class] }
31
+ = @replace_panel_var[:body].join("<br>")
32
+ .list-group
33
+ .list-group-item.form-group
34
+ .list-group-item-text
35
+ %table#replace-txt-table
36
+ %thead
37
+ %tr
38
+ %td 対象文字列
39
+ %td &nbsp;
40
+ %td 置き換える文字列
41
+ %td &nbsp;
42
+ %tbody
43
+ -# 末尾に新規入力用の空要素を追加
44
+ - (@replace_pattern + [["",""]]).each do |pattern|
45
+ %tr.replace-pattern-tr
46
+ %td
47
+ %input{type:"text", name:"replace_pattern[][left]", value:pattern[0], class:"form-control"}
48
+ %td
49
+ %span.glyphicon.glyphicon-chevron-right
50
+ %td
51
+ %input{type:"text", name:"replace_pattern[][right]", value:pattern[1], class:"form-control"}
52
+ %td
53
+ %button.btn-replace-remove.btn.btn-default(data-toggle="remove-tooltip" title="削除" data-placement="right")
54
+ %span.glyphicon.glyphicon-remove
55
+ %tr
56
+ %td(colspan=4)
57
+ %button#btn-replace-add.btn.btn-default(data-toggle="tooltip" title="1行追加" data-placement="right")
58
+ %span.glyphicon.glyphicon-plus
59
+
@@ -1,5 +1,5 @@
1
1
  :javascript
2
- var local_initialize_function = function($) {
2
+ local_initialize_functions.push(function($) {
3
3
  $("[data-toggle=tooltip]").tooltip({
4
4
  animation: false,
5
5
  container: "body",
@@ -34,7 +34,7 @@
34
34
  }
35
35
 
36
36
  $("#bookmarklet").attr("href", createBookmarkletStrings(bookmarklet));
37
- };
37
+ });
38
38
 
39
39
  .navbar.navbar-default.navbar-fixed-top(role="navigation")
40
40
  .container
@@ -218,20 +218,22 @@
218
218
  %ul#context-menu.dropdown-menu(role="menu")
219
219
  %li#context-menu-setting
220
220
  %a(href="#") 小説の変換設定
221
+ %li#context-menu-diff
222
+ %a(href="#" data-move-to="top") 差分を表示
223
+ %li#context-menu-edit-tag
224
+ %a(href="#") タグを編集
225
+ %li#context-menu-freeze-toggle
226
+ %a(href="#") 凍結 or 解凍
221
227
  %li#context-menu-update
222
228
  %a(href="#") 更新
223
229
  %li#context-menu-send
224
230
  %a(href="#") 送信
225
- %li#context-menu-freeze-toggle
226
- %a(href="#") 凍結 or 解凍
227
231
  %li#context-menu-remove
228
232
  %a(href="#") 削除
229
233
  %li#context-menu-remove-with-file
230
234
  %a(href="#") 完全に削除
231
235
  %li#context-menu-convert
232
236
  %a(href="#") 変換
233
- %li#context-menu-diff
234
- %a(href="#" data-move-to="top") 差分表示
235
237
  %li#context-menu-inspect
236
238
  %a(href="#") 調査状況ログを表示
237
239
  %li#context-menu-folder
@@ -12,6 +12,7 @@
12
12
  %link{:href => "//cdn.datatables.net/plug-ins/725b2a2115b/integration/bootstrap/3/dataTables.bootstrap.css", :rel => "stylesheet"}/
13
13
  %link{:href => "//cdn.datatables.net/colreorder/1.1.2/css/dataTables.colReorder.css", :rel => "stylesheet"}/
14
14
  %link{:href => "//cdn.datatables.net/colvis/1.1.1/css/dataTables.colVis.css", :rel => "stylesheet"}/
15
+ %link{:href => "//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.6.5/css/bootstrap-select.min.css", :rel => "stylesheet"}/
15
16
  %link{:href => "/resources/perfect-scrollbar.min.css", :rel => "stylesheet"}/
16
17
  %link{:href => "/resources/toggle-switch.css", :rel => "stylesheet"}/
17
18
  %link{:href => "/style.css", :rel => "stylesheet"}/
@@ -19,6 +20,9 @@
19
20
  /[if lt IE 9]
20
21
  <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
21
22
  <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
23
+ :javascript
24
+ var local_initialize_functions = [];
25
+ var slideNavbar;
22
26
  %body
23
27
  - if session[:alert]
24
28
  - message, type = session[:alert]
@@ -32,6 +36,7 @@
32
36
  %script{:src => "//cdn.datatables.net/1.10.2/js/jquery.dataTables.min.js"}
33
37
  %script{:src => "//cdn.datatables.net/plug-ins/725b2a2115b/integration/bootstrap/3/dataTables.bootstrap.js"}
34
38
  %script{:src => "//cdn.datatables.net/colreorder/1.1.2/js/dataTables.colReorder.min.js"}
39
+ %script{:src => "//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.6.5/js/bootstrap-select.min.js"}
35
40
  %script{:src => "/resources/dataTables.colVis.js"}
36
41
  %script{:src => "/resources/bootbox.min.js"}
37
42
  %script{:src => "/resources/shortcut.js"}
@@ -42,8 +47,12 @@
42
47
  %script{:src => "/resources/narou.library.js"}
43
48
  %script{:src => "/resources/common.ui.js"}
44
49
  :javascript
45
- var slideNavbar = $(".navbar-collapse").slideNavbar();
46
- if (typeof local_initialize_function !== "undefined") local_initialize_function($);
50
+ $(document).ready(function($) {
51
+ slideNavbar = $(".navbar-collapse").slideNavbar();
52
+ $.each(local_initialize_functions, function() {
53
+ this($);
54
+ });
55
+ });
47
56
  - if env["PATH_INFO"] == "/"
48
57
  %script{:src => "/resources/narou.ui.js"}
49
58