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.
- checksums.yaml +4 -4
- data/ChangeLog.md +77 -0
- data/Gemfile +2 -1
- data/README.md +69 -86
- data/lib/command/convert.rb +10 -11
- data/lib/command/init.rb +2 -2
- data/lib/command/list.rb +12 -3
- data/lib/command/setting.rb +20 -5
- data/lib/command/update.rb +0 -3
- data/lib/command/version.rb +2 -3
- data/lib/command/web.rb +58 -7
- data/lib/converterbase.rb +7 -24
- data/lib/database.rb +7 -6
- data/lib/device.rb +15 -1
- data/lib/device/ibunko.rb +1 -2
- data/lib/device/library/mac.rb +7 -0
- data/lib/downloader.rb +44 -21
- data/lib/extension.rb +25 -0
- data/lib/helper.rb +4 -3
- data/lib/html.rb +18 -2
- data/lib/narou.rb +20 -0
- data/lib/novelconverter.rb +11 -35
- data/lib/novelinfo.rb +19 -8
- data/lib/novelsetting.rb +0 -6
- data/lib/version.rb +1 -1
- data/lib/web/appserver.rb +134 -15
- data/lib/web/public/favicon.ico +0 -0
- data/lib/web/public/resources/common.ui.js +2 -2
- data/lib/web/public/resources/narou.library.js +657 -91
- data/lib/web/public/resources/narou.queue.js +1 -1
- data/lib/web/public/resources/narou.ui.js +253 -102
- data/lib/web/public/resources/sprintf.js +245 -0
- data/lib/web/pushserver.rb +14 -3
- data/lib/web/views/_about.haml +188 -0
- data/lib/web/views/{diff_list.haml → _diff_list.haml} +0 -0
- data/lib/web/views/{edit_replace_txt.haml → _edit_replace_txt.haml} +4 -4
- data/lib/web/views/_rebooting.haml +18 -0
- data/lib/web/views/bookmarklet/download.js.erb +2 -2
- data/lib/web/views/bookmarklet/insert_button.js.erb +1 -1
- data/lib/web/views/edit_menu.haml +223 -0
- data/lib/web/views/help.haml +29 -12
- data/lib/web/views/index.haml +99 -88
- data/lib/web/views/layout.haml +5 -3
- data/lib/web/views/notepad.haml +39 -0
- data/lib/web/views/novels/setting.haml +15 -5
- data/lib/web/views/settings.haml +2 -2
- data/lib/web/views/style.scss +72 -21
- data/lib/web/views/widget/download.haml +3 -2
- data/lib/web/views/widget/drag_and_drop.haml +3 -2
- data/lib/web/views/widget/notepad.haml +44 -0
- data/narou.gemspec +75 -6
- data/narou.rb +8 -14
- data/preset/ncode.syosetu.com/n5115cq/converter.rb +30 -0
- data/preset/ncode.syosetu.com/n7594ct/converter.rb +37 -0
- data/preset/ncode.syosetu.com/n8725k/converter.rb +1 -1
- data/preset/vertical_font.css +0 -10
- data/spec/generator/convert_spec_gen.rb +2 -0
- data/template/novel.txt.erb +3 -0
- data/webnovel/https.syosetu.org.yaml +1 -1
- data/webnovel/kakuyomu.jp.yaml +82 -0
- data/webnovel/ncode.syosetu.com.yaml +1 -0
- data/webnovel/syosetu.org.yaml +1 -1
- metadata +89 -12
- data/lib/web/views/about.haml +0 -85
- data/preset/DMincho.ttf +0 -0
data/lib/web/public/favicon.ico
CHANGED
Binary file
|
@@ -24,9 +24,9 @@ $(document).ready(function() {
|
|
24
24
|
$.moveTo();
|
25
25
|
|
26
26
|
/*
|
27
|
-
*
|
27
|
+
* 自然に消えるフラッシュメッセージ
|
28
28
|
*/
|
29
|
-
|
29
|
+
Narou.Flash.setEvents(".fadeout-alert");
|
30
30
|
|
31
31
|
/*************************************************************************
|
32
32
|
* Webサーバの方から入力を求められた時にモーダルを表示して返事を返す
|
@@ -184,21 +184,28 @@ var Narou = (function() {
|
|
184
184
|
});
|
185
185
|
},
|
186
186
|
|
187
|
-
on: function(event, block) {
|
187
|
+
on: function(event, block, once) {
|
188
188
|
if (typeof block !== "function") {
|
189
189
|
$.error("need a function");
|
190
190
|
}
|
191
191
|
var stack = this.events[event] || [];
|
192
|
-
stack.push(block);
|
192
|
+
stack.push([block, once]);
|
193
193
|
this.events[event] = stack;
|
194
194
|
},
|
195
195
|
|
196
|
+
one: function(event, block) {
|
197
|
+
this.on(event, block, true);
|
198
|
+
},
|
199
|
+
|
196
200
|
trigger: function(event, data) {
|
197
201
|
var self = this;
|
198
202
|
var stack = this.events[event] || [];
|
199
|
-
|
200
|
-
|
201
|
-
|
203
|
+
this.events[event] =
|
204
|
+
_.reject(stack, function(pair) {
|
205
|
+
var block = pair[0], once = pair[1];
|
206
|
+
block.call(self, data);
|
207
|
+
return once;
|
208
|
+
});
|
202
209
|
},
|
203
210
|
|
204
211
|
send: function(json) {
|
@@ -207,24 +214,25 @@ var Narou = (function() {
|
|
207
214
|
});
|
208
215
|
|
209
216
|
/*************************************************************************
|
210
|
-
*
|
217
|
+
* 個別メニュー
|
211
218
|
*************************************************************************/
|
212
|
-
var ContextMenu = Narou.ContextMenu = function(action,
|
219
|
+
var ContextMenu = Narou.ContextMenu = function(action, tag) {
|
213
220
|
this.action = action;
|
214
|
-
this.notification =
|
221
|
+
this.notification = Notification.instance();
|
215
222
|
this.tag = tag;
|
216
223
|
this.closed = true;
|
217
224
|
this.initializeConsoleDialog();
|
218
|
-
this.initializeMenuEvent();
|
219
225
|
this.initializeDiffListEvent();
|
226
|
+
this.initializeMenu();
|
220
227
|
};
|
221
228
|
|
222
229
|
$.extend(ContextMenu.prototype, {
|
223
230
|
open: function(target_id, pos, callback) {
|
231
|
+
var self = this;
|
224
232
|
this.target_id = target_id;
|
225
233
|
if (!this.closed) {
|
226
234
|
// メニューを開いた状態で直接ボタンを押した場合に一旦閉じるイベントを起こさせる
|
227
|
-
|
235
|
+
this.close();
|
228
236
|
}
|
229
237
|
this.closed = false;
|
230
238
|
var caller = function() {
|
@@ -232,15 +240,30 @@ var Narou = (function() {
|
|
232
240
|
callback();
|
233
241
|
};
|
234
242
|
$(document).one("show.bs.dropdown", function() {
|
235
|
-
|
243
|
+
self.object.hide();
|
244
|
+
self.closed = true;
|
236
245
|
caller();
|
237
246
|
});
|
238
|
-
Narou.popupMenu(
|
239
|
-
|
247
|
+
Narou.popupMenu(this.object, pos, function() {
|
248
|
+
self.object.hide();
|
249
|
+
self.closed = true;
|
240
250
|
caller();
|
241
251
|
});
|
242
252
|
},
|
243
253
|
|
254
|
+
close: function() {
|
255
|
+
$(document).trigger("click");
|
256
|
+
this.closed = true;
|
257
|
+
},
|
258
|
+
|
259
|
+
save: function(text) {
|
260
|
+
this.text = text;
|
261
|
+
storage.set("context_menu_text", text);
|
262
|
+
storage.save();
|
263
|
+
this.object.remove();
|
264
|
+
this.initializeMenu();
|
265
|
+
},
|
266
|
+
|
244
267
|
openConsoleDialog: function(callback) {
|
245
268
|
if (typeof callback !== "function") return;
|
246
269
|
var $console_dialog = $("#console-dialog");
|
@@ -253,7 +276,7 @@ var Narou = (function() {
|
|
253
276
|
},
|
254
277
|
|
255
278
|
initializeConsoleDialog: function() {
|
256
|
-
this.console = new Narou.Console(
|
279
|
+
this.console = new Narou.Console({
|
257
280
|
restore: false, buttons: false,
|
258
281
|
id: "#each-console"
|
259
282
|
});
|
@@ -298,66 +321,160 @@ var Narou = (function() {
|
|
298
321
|
});
|
299
322
|
},
|
300
323
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
$("
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
$("#context-menu-remove").on("click", function(e) {
|
325
|
-
e.preventDefault();
|
326
|
-
self.openConsoleDialog(function() {
|
327
|
-
self.action.remove(self.target_id);
|
328
|
-
});
|
329
|
-
});
|
330
|
-
$("#context-menu-edit-tag").on("click", function(e) {
|
331
|
-
e.preventDefault();
|
332
|
-
self.tag.openEditor(self.target_id);
|
333
|
-
});
|
334
|
-
$("#context-menu-convert").on("click", function(e) {
|
335
|
-
e.preventDefault();
|
336
|
-
self.openConsoleDialog(function() {
|
337
|
-
self.action.convert(self.target_id);
|
338
|
-
});
|
339
|
-
});
|
340
|
-
$("#context-menu-diff").on("click", function(e) {
|
341
|
-
e.preventDefault();
|
342
|
-
self.openSelectDiffListDialog(self.target_id);
|
324
|
+
initializeMenu: function() {
|
325
|
+
this.initializeMenuObject();
|
326
|
+
this.initializeMenuEvents();
|
327
|
+
$("body").append(this.object);
|
328
|
+
},
|
329
|
+
|
330
|
+
initializeMenuObject: function(text) {
|
331
|
+
this.text = text || storage.get("context_menu_text") || this.createDefaultMenuText();
|
332
|
+
this.object = this.createMenuObject(this.text);
|
333
|
+
},
|
334
|
+
|
335
|
+
createMenuObject: function(text) {
|
336
|
+
var object = $('<ul class="context-menu dropdown-menu" role="menu">');
|
337
|
+
_.each(text.split("\n"), function(line) {
|
338
|
+
var splited = line.split("<>");
|
339
|
+
var label = splited[0], command = splited[1];
|
340
|
+
var child;
|
341
|
+
if (!command) return;
|
342
|
+
if (command === "divider")
|
343
|
+
child = $('<li class="divider">');
|
344
|
+
else
|
345
|
+
child = $(sprintf('<li class="context-menu-%s"><a href="#">%s</a>', command, label));
|
346
|
+
object.append(child);
|
343
347
|
});
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
+
return object;
|
349
|
+
},
|
350
|
+
|
351
|
+
_default_commands: [
|
352
|
+
"setting", "diff", "edit_tag", "freeze_toggle", "update",
|
353
|
+
"send", "remove", "convert", "inspect", "folder", "backup"
|
354
|
+
],
|
355
|
+
|
356
|
+
items: [
|
357
|
+
{ label: "――――――――(区切り)", command: "divider" },
|
358
|
+
{ label: "小説の変換設定", command: "setting" },
|
359
|
+
{ label: "差分を表示", command: "diff" },
|
360
|
+
{ label: "タグを編集", command: "edit_tag" },
|
361
|
+
{ label: "凍結 or 解凍", command: "freeze_toggle" },
|
362
|
+
{ label: "更新", command: "update" },
|
363
|
+
{ label: "凍結済みでも更新", command: "update_force" },
|
364
|
+
{ label: "送信", command: "send" },
|
365
|
+
{ label: "削除", command: "remove" },
|
366
|
+
{ label: "変換", command: "convert" },
|
367
|
+
{ label: "調査状況ログを表示", command: "inspect" },
|
368
|
+
{ label: "保存フォルダを開く", command: "folder" },
|
369
|
+
{ label: "バックアップを作成", command: "backup" },
|
370
|
+
{ label: "再ダウンロード", command: "download_force" },
|
371
|
+
],
|
372
|
+
|
373
|
+
events: {
|
374
|
+
setting: function() {
|
375
|
+
var setting_page_path = "/novels/" + this.target_id + "/setting";
|
376
|
+
if (storage.get("open_new_tab_setting_pages")) {
|
377
|
+
window.open(setting_page_path);
|
378
|
+
}
|
379
|
+
else {
|
380
|
+
location.href = setting_page_path;
|
381
|
+
}
|
382
|
+
},
|
383
|
+
|
384
|
+
update: function() {
|
385
|
+
this.openConsoleDialog(function() {
|
386
|
+
this.action.update(this.target_id);
|
387
|
+
}.bind(this));
|
388
|
+
},
|
389
|
+
|
390
|
+
update_force: function() {
|
391
|
+
this.openConsoleDialog(function() {
|
392
|
+
this.action.updateForce(this.target_id);
|
393
|
+
}.bind(this));
|
394
|
+
},
|
395
|
+
|
396
|
+
send: function() {
|
397
|
+
this.openConsoleDialog(function() {
|
398
|
+
this.action.send(this.target_id);
|
399
|
+
}.bind(this));
|
400
|
+
},
|
401
|
+
|
402
|
+
freeze_toggle: function() {
|
403
|
+
this.action.freeze(this.target_id);
|
404
|
+
},
|
405
|
+
|
406
|
+
remove: function() {
|
407
|
+
this.openConsoleDialog(function() {
|
408
|
+
this.action.remove(this.target_id);
|
409
|
+
}.bind(this));
|
410
|
+
},
|
411
|
+
|
412
|
+
edit_tag: function() {
|
413
|
+
this.tag.openEditor(this.target_id);
|
414
|
+
},
|
415
|
+
|
416
|
+
convert: function() {
|
417
|
+
this.openConsoleDialog(function() {
|
418
|
+
this.action.convert(this.target_id);
|
419
|
+
}.bind(this));
|
420
|
+
},
|
421
|
+
|
422
|
+
diff: function() {
|
423
|
+
this.openSelectDiffListDialog(this.target_id);
|
424
|
+
},
|
425
|
+
|
426
|
+
inspect: function() {
|
427
|
+
this.openConsoleDialog(function() {
|
428
|
+
this.action.inspect(this.target_id);
|
429
|
+
}.bind(this));
|
430
|
+
},
|
431
|
+
|
432
|
+
folder: function() {
|
433
|
+
this.action.folder(this.target_id);
|
434
|
+
},
|
435
|
+
|
436
|
+
backup: function() {
|
437
|
+
this.openConsoleDialog(function() {
|
438
|
+
this.action.backup(this.target_id);
|
439
|
+
}.bind(this));
|
440
|
+
},
|
441
|
+
|
442
|
+
download_force: function() {
|
443
|
+
this.openConsoleDialog(function() {
|
444
|
+
this.action.downloadForce(this.target_id);
|
445
|
+
}.bind(this));
|
446
|
+
},
|
447
|
+
},
|
448
|
+
|
449
|
+
initializeMenuEvents: function() {
|
450
|
+
var object = this.object;
|
451
|
+
var self = this;
|
452
|
+
_.each(this.events, function(fn, command) {
|
453
|
+
object.find(".context-menu-" + command).on("click", function(e) {
|
454
|
+
e.preventDefault();
|
455
|
+
if (self.action)
|
456
|
+
fn.call(self);
|
348
457
|
});
|
349
458
|
});
|
350
|
-
|
351
|
-
|
352
|
-
|
459
|
+
},
|
460
|
+
|
461
|
+
findItem: function(command) {
|
462
|
+
return this.items.find(function(item) {
|
463
|
+
return item.command === command;
|
353
464
|
});
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
465
|
+
},
|
466
|
+
|
467
|
+
createDefaultMenuText: function() {
|
468
|
+
var self = this;
|
469
|
+
var menu_text_lines = [];
|
470
|
+
_.each(this._default_commands, function(command) {
|
471
|
+
var item = self.findItem(command);
|
472
|
+
if (!item)
|
473
|
+
$.error("invalid command(" + command + ")");
|
474
|
+
menu_text_lines.push(item.label + "<>" + command);
|
359
475
|
});
|
360
|
-
|
476
|
+
return menu_text_lines.join("\n");
|
477
|
+
},
|
361
478
|
});
|
362
479
|
|
363
480
|
/*************************************************************************
|
@@ -457,10 +574,10 @@ var Narou = (function() {
|
|
457
574
|
|
458
575
|
updateGeneralLastup: function() {
|
459
576
|
bootbox.dialog({
|
460
|
-
title:
|
461
|
-
message: "
|
462
|
-
"最新話掲載日は通常時の
|
463
|
-
"掲載日だけを調べて、選択的に
|
577
|
+
title: "最新話掲載日の更新",
|
578
|
+
message: "凍結済みを除く各小説の最新話掲載日のみを更新して反映させます。<br>" +
|
579
|
+
"最新話掲載日は通常時のUpdateでも更新されるので、手動で更新する必要は基本的にはありません。<br>" +
|
580
|
+
"掲載日だけを調べて、選択的にUpdateをかけるなど、用途を理解した上で小説サイトのサーバーに負荷をかけない範囲でご利用下さい。",
|
464
581
|
backdrop: true,
|
465
582
|
buttons: {
|
466
583
|
cancel: {
|
@@ -606,26 +723,94 @@ var Narou = (function() {
|
|
606
723
|
});
|
607
724
|
});
|
608
725
|
},
|
726
|
+
|
727
|
+
rebootDialog: function() {
|
728
|
+
var self = this;
|
729
|
+
bootbox.dialog({
|
730
|
+
title: '<span class="glyphicon glyphicon-refresh"></span> 再起動',
|
731
|
+
message: "<p>Narou.rb WEB UI サーバを再起動します。<br>" +
|
732
|
+
"バージョンを更新してある場合は最新バージョンで立ち上がります。</p>" +
|
733
|
+
"<p class=text-danger>アップデート中や変換中の小説がある場合は中断されます。<br>" +
|
734
|
+
"終わったかどうか確認しましょう。</p>",
|
735
|
+
backdrop: true,
|
736
|
+
buttons: {
|
737
|
+
danger: {
|
738
|
+
label: "再起動",
|
739
|
+
className: "btn-danger",
|
740
|
+
callback: function() {
|
741
|
+
self.reboot();
|
742
|
+
}
|
743
|
+
},
|
744
|
+
main: {
|
745
|
+
label: "キャンセル",
|
746
|
+
className: "btn-default",
|
747
|
+
}
|
748
|
+
}
|
749
|
+
});
|
750
|
+
},
|
751
|
+
|
752
|
+
reboot: function() {
|
753
|
+
$.post("/reboot", function(data) {
|
754
|
+
bootbox.hideAll();
|
755
|
+
bootbox.dialog({
|
756
|
+
title: "",
|
757
|
+
closeButton: false,
|
758
|
+
message: data
|
759
|
+
});
|
760
|
+
});
|
761
|
+
},
|
762
|
+
|
763
|
+
checkUpdatedSystem: function(funcs) {
|
764
|
+
$.post("/check_already_update_system", function(data) {
|
765
|
+
if (data.result) {
|
766
|
+
$.post("/gem_update_last_log", function(log) {
|
767
|
+
funcs.already_updated(log);
|
768
|
+
});
|
769
|
+
}
|
770
|
+
else {
|
771
|
+
funcs.not_updated();
|
772
|
+
}
|
773
|
+
});
|
774
|
+
},
|
775
|
+
|
776
|
+
updateSystem: function(callback) {
|
777
|
+
var notification = Notification.instance();
|
778
|
+
notification.one("server.update.success", function(log) {
|
779
|
+
callback("success", log);
|
780
|
+
});
|
781
|
+
notification.one("server.update.nothing", function(log) {
|
782
|
+
callback("nothing", log);
|
783
|
+
});
|
784
|
+
notification.one("server.update.failure", function(log) {
|
785
|
+
callback("failure", log);
|
786
|
+
});
|
787
|
+
$.post("/update_system");
|
788
|
+
},
|
789
|
+
|
790
|
+
eject: function() {
|
791
|
+
$.post("/api/eject");
|
792
|
+
},
|
793
|
+
|
609
794
|
});
|
610
795
|
|
611
796
|
/*************************************************************************
|
612
797
|
* コンソール
|
613
798
|
*************************************************************************/
|
614
|
-
var Console = Narou.Console = function(
|
799
|
+
var Console = Narou.Console = function(options) {
|
615
800
|
this.options = $.extend({
|
616
801
|
restore: true, // コンソールの大きさを復元・保存するか
|
617
802
|
buttons: true, // 拡大縮小等のコントロールボタンを使用するか
|
618
803
|
id: "#console", // コンソールのID名
|
619
804
|
buttons_id: "#console-buttons" // コントロールボタンを格納している要素のID名
|
620
805
|
}, options);
|
621
|
-
this.initialize(
|
806
|
+
this.initialize();
|
622
807
|
};
|
623
808
|
|
624
809
|
$.extend(Console.prototype, {
|
625
810
|
animate_duration: 200,
|
626
811
|
|
627
|
-
initialize: function(
|
628
|
-
this.notification =
|
812
|
+
initialize: function() {
|
813
|
+
this.notification = Notification.instance();
|
629
814
|
this.last_char_was_return = true;
|
630
815
|
this.console = $(this.options.id);
|
631
816
|
this.init_scrollbar();
|
@@ -820,14 +1005,197 @@ var Narou = (function() {
|
|
820
1005
|
}
|
821
1006
|
});
|
822
1007
|
|
1008
|
+
/*************************************************************************
|
1009
|
+
* 検索機能
|
1010
|
+
* dataTables の検索に、タグ検索機能を追加する
|
1011
|
+
*************************************************************************/
|
1012
|
+
var Search = Narou.Search = function(table) {
|
1013
|
+
this.initialize(table);
|
1014
|
+
};
|
1015
|
+
|
1016
|
+
Search.get = function(table) {
|
1017
|
+
if (!this.__instance) {
|
1018
|
+
this.__instance = new this(table);
|
1019
|
+
}
|
1020
|
+
return this.__instance;
|
1021
|
+
};
|
1022
|
+
|
1023
|
+
$.extend(Search.prototype, {
|
1024
|
+
initialize: function(table) {
|
1025
|
+
this.table = table;
|
1026
|
+
this.myfilter = $("#myFilter");
|
1027
|
+
this.filter_tags = [];
|
1028
|
+
this.exclusion_tags = []; // 除外タグ
|
1029
|
+
this.myfilter_clear = $("#myFilter-clear");
|
1030
|
+
this.myfilter_clear_timer_id = null;
|
1031
|
+
this.initializeEvents();
|
1032
|
+
},
|
1033
|
+
|
1034
|
+
initializeEvents: function() {
|
1035
|
+
var self = this;
|
1036
|
+
// フィルターのフックAPIでタグを検索する
|
1037
|
+
$.fn.dataTable.ext.search.push(function (settings, data, dataIndex) {
|
1038
|
+
if (_.isEmpty(self.filter_tags) && _.isEmpty(self.exclusion_tags))
|
1039
|
+
return true;
|
1040
|
+
|
1041
|
+
var tags = self.table.row(dataIndex).data().tags;
|
1042
|
+
var matched = true;
|
1043
|
+
// 一致タグ検索
|
1044
|
+
_.each(self.filter_tags, function(tag_name) {
|
1045
|
+
matched = !!tags.match(new RegExp('data-tag="' + tag_name + '"'));
|
1046
|
+
return matched;
|
1047
|
+
});
|
1048
|
+
if (matched) {
|
1049
|
+
// 除外タグ検索
|
1050
|
+
_.each(self.exclusion_tags, function(tag_name) {
|
1051
|
+
matched = !tags.match(new RegExp('data-tag="' + tag_name + '"'));
|
1052
|
+
return matched;
|
1053
|
+
});
|
1054
|
+
}
|
1055
|
+
return matched;
|
1056
|
+
});
|
1057
|
+
|
1058
|
+
// カスタムフィルタボックスの変更イベント
|
1059
|
+
this.myfilter.on("keyup", function() {
|
1060
|
+
clearTimeout(self.myfilter_clear_timer_id);
|
1061
|
+
self.myfilter_clear_timer_id = setTimeout(function() {
|
1062
|
+
self.search();
|
1063
|
+
}, 300);
|
1064
|
+
self.myFilterClearToggleVisiblity();
|
1065
|
+
});
|
1066
|
+
|
1067
|
+
// フィルタのリセットボタン
|
1068
|
+
this.myfilter_clear.on("click", function() {
|
1069
|
+
self.myfilter.val("");
|
1070
|
+
self.myfilter_clear.hide();
|
1071
|
+
self.search();
|
1072
|
+
});
|
1073
|
+
|
1074
|
+
// 検索アイコンも反応するようにしておく(コピペとかで反応しない場合用)
|
1075
|
+
$("#myFilter-search-icon").on("click", function() {
|
1076
|
+
self.search();
|
1077
|
+
self.myFilterClearToggleVisiblity();
|
1078
|
+
});
|
1079
|
+
},
|
1080
|
+
|
1081
|
+
appendTagToFilter: function(tag_name, exclude) {
|
1082
|
+
if (_.includes(this.filter_tags, tag_name) ||
|
1083
|
+
_.includes(this.exclusion_tags, tag_name)) {
|
1084
|
+
// すでにフィルターに入力済みなら何もしない
|
1085
|
+
return;
|
1086
|
+
}
|
1087
|
+
|
1088
|
+
if (tag_name) {
|
1089
|
+
var str = sprintf(
|
1090
|
+
"%(current)s %(exclude_flag)stag:%(tag_name)s",
|
1091
|
+
{
|
1092
|
+
current: this.myfilter.val(),
|
1093
|
+
exclude_flag: exclude ? "-" : "",
|
1094
|
+
tag_name: tag_name
|
1095
|
+
});
|
1096
|
+
this.myfilter.val(str.trim());
|
1097
|
+
}
|
1098
|
+
else {
|
1099
|
+
this.removeAllTagsByFilter();
|
1100
|
+
}
|
1101
|
+
this.search();
|
1102
|
+
this.myFilterClearToggleVisiblity();
|
1103
|
+
},
|
1104
|
+
|
1105
|
+
removeAllTagsByFilter: function() {
|
1106
|
+
var normal_words = this.splitFilter().normal;
|
1107
|
+
this.myfilter.val(normal_words.join(" "));
|
1108
|
+
this.clearTagCaches();
|
1109
|
+
},
|
1110
|
+
|
1111
|
+
splitFilter: function(string) {
|
1112
|
+
string = string || this.myfilter.val().trim();
|
1113
|
+
var result = {
|
1114
|
+
filter_tags: [], exclusion_tags: []
|
1115
|
+
};
|
1116
|
+
var words = string.split(/\s+/);
|
1117
|
+
result.normal =
|
1118
|
+
_.filter(words, function(word) {
|
1119
|
+
if (word.match(/^(-?)tag:(.+)$/i)) {
|
1120
|
+
var exclude_flag = !!RegExp.$1;
|
1121
|
+
var tag_name = RegExp.$2;
|
1122
|
+
if (exclude_flag)
|
1123
|
+
result.exclusion_tags.push(tag_name);
|
1124
|
+
else
|
1125
|
+
result.filter_tags.push(tag_name);
|
1126
|
+
return false;
|
1127
|
+
}
|
1128
|
+
else {
|
1129
|
+
return true;
|
1130
|
+
}
|
1131
|
+
}.bind(this));
|
1132
|
+
return result;
|
1133
|
+
},
|
1134
|
+
|
1135
|
+
clearTagCaches: function() {
|
1136
|
+
this.filter_tags.length = 0;
|
1137
|
+
this.exclusion_tags.length = 0;
|
1138
|
+
},
|
1139
|
+
|
1140
|
+
searchSync: function() {
|
1141
|
+
this.search(true);
|
1142
|
+
},
|
1143
|
+
|
1144
|
+
_flatPush: function(target, array) {
|
1145
|
+
return target.push.apply(target, array);
|
1146
|
+
},
|
1147
|
+
|
1148
|
+
_searchFn: function() {
|
1149
|
+
var filter_string = this.myfilter.val().trim();
|
1150
|
+
var words = this.splitFilter(filter_string);
|
1151
|
+
var normal_words = words.normal;
|
1152
|
+
|
1153
|
+
this.clearTagCaches();
|
1154
|
+
this._flatPush(this.filter_tags, words.filter_tags);
|
1155
|
+
this._flatPush(this.exclusion_tags, words.exclusion_tags);
|
1156
|
+
|
1157
|
+
// タグ以外の単語で通常検索し、タグ部分はフックAPIでフィルターする
|
1158
|
+
var normal_words_string = normal_words.join(" ");
|
1159
|
+
if (this.before_normal_words_string !== normal_words_string) {
|
1160
|
+
// 通常の検索は一度 table.search() を実行すれば維持されるので、
|
1161
|
+
// 検索語が変化しないかぎりは一度の実行でいい
|
1162
|
+
this.table.search(normal_words_string);
|
1163
|
+
this.before_normal_words_string = normal_words_string;
|
1164
|
+
}
|
1165
|
+
// table.draw() を実行することにより、table.search() のフックAPIが実行される
|
1166
|
+
this.table.draw();
|
1167
|
+
|
1168
|
+
// dataTables は search() に渡された文字列しか自動保存しないので、タグ含めて自前で保存
|
1169
|
+
storage.set("filter_string", filter_string).save();
|
1170
|
+
},
|
1171
|
+
|
1172
|
+
search: function(sync) {
|
1173
|
+
if (sync)
|
1174
|
+
this._searchFn();
|
1175
|
+
else
|
1176
|
+
setTimeout(this._searchFn.bind(this), 10);
|
1177
|
+
},
|
1178
|
+
|
1179
|
+
myFilterClearToggleVisiblity: function() {
|
1180
|
+
if (this.myfilter.val() === "") {
|
1181
|
+
this.myfilter_clear.hide();
|
1182
|
+
}
|
1183
|
+
else {
|
1184
|
+
this.myfilter_clear.show();
|
1185
|
+
}
|
1186
|
+
},
|
1187
|
+
});
|
1188
|
+
|
823
1189
|
/*************************************************************************
|
824
1190
|
* タグ機能
|
825
1191
|
*************************************************************************/
|
826
|
-
var Tag = Narou.Tag =
|
1192
|
+
var Tag = Narou.Tag = function(table) {
|
827
1193
|
this.table = table;
|
1194
|
+
this.search = Search.get(table);
|
828
1195
|
this.registerEvents($("#tag-list-canvas"));
|
1196
|
+
this.registerEvents($("#novel-list tbody"));
|
829
1197
|
this.updateCanvas();
|
830
|
-
}
|
1198
|
+
};
|
831
1199
|
|
832
1200
|
$.extend(Tag.prototype, {
|
833
1201
|
updateCanvas: function() {
|
@@ -841,18 +1209,17 @@ var Narou = (function() {
|
|
841
1209
|
var self = this;
|
842
1210
|
if (typeof stop_bubbling === "undefined") stop_bubbling = true;
|
843
1211
|
var args = { stop_bubbling: stop_bubbling };
|
844
|
-
$target
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
});
|
1212
|
+
$target
|
1213
|
+
.on("click", ".tag", args, function(e) {
|
1214
|
+
if (e.data.stop_bubbling) e.stopPropagation();
|
1215
|
+
var tag_name = String($(this).data("tag"));
|
1216
|
+
self.search.appendTagToFilter(tag_name, e.altKey);
|
1217
|
+
self.table.$("[data-toggle=tooltip]").tooltip("hide");
|
1218
|
+
})
|
1219
|
+
.on("mousedown", ".tag", args, function(e) {
|
1220
|
+
// 範囲選択モードでもクリック出来るように
|
1221
|
+
if (e.data.stop_bubbling) e.stopPropagation();
|
1222
|
+
});
|
856
1223
|
},
|
857
1224
|
|
858
1225
|
openEditor: function() {
|
@@ -1114,6 +1481,205 @@ var Narou = (function() {
|
|
1114
1481
|
},
|
1115
1482
|
});
|
1116
1483
|
|
1484
|
+
/*************************************************************************
|
1485
|
+
* 自動保存・同期機能付きメモ帳
|
1486
|
+
*************************************************************************/
|
1487
|
+
var Notepad = Narou.Notepad = (function() {
|
1488
|
+
this.object_id = this.createObjectId();
|
1489
|
+
});
|
1490
|
+
|
1491
|
+
Notepad.replace = function(id, options) {
|
1492
|
+
var notepad = new Notepad;
|
1493
|
+
notepad.replace(id, options);
|
1494
|
+
return notepad;
|
1495
|
+
};
|
1496
|
+
|
1497
|
+
$.extend(Notepad.prototype, {
|
1498
|
+
DEFAULTS: {
|
1499
|
+
autosave: true,
|
1500
|
+
readonly: false,
|
1501
|
+
synchronizing: true, // 別ウィンドウ同士で内容を同期するか
|
1502
|
+
rows: 20,
|
1503
|
+
// string or Deferred オブジェクト
|
1504
|
+
text: function() {
|
1505
|
+
return $.get("/api/notepad/read");
|
1506
|
+
}
|
1507
|
+
},
|
1508
|
+
|
1509
|
+
renderer: _.template(
|
1510
|
+
'<div id="<%= container_id %>" class="notepad-container">' +
|
1511
|
+
'<textarea class="form-control" rows="<%- rows %>" ' +
|
1512
|
+
'<% if (readonly) { %>readonly<% } %>' +
|
1513
|
+
'><%- text %></textarea>' +
|
1514
|
+
'<span class="notepad-icon glyphicon glyphicon-ok text-success hide"></span>' +
|
1515
|
+
'</div>'
|
1516
|
+
),
|
1517
|
+
|
1518
|
+
replace: function(id, options) {
|
1519
|
+
var opt = _.merge({}, this.DEFAULTS, options);
|
1520
|
+
this.id = id;
|
1521
|
+
this.autosave = opt.autosave;
|
1522
|
+
this.readonly = opt.readonly;
|
1523
|
+
this.synchronizing = opt.synchronizing;
|
1524
|
+
this.rows = opt.rows;
|
1525
|
+
this.text = opt.text;
|
1526
|
+
|
1527
|
+
this.createElements();
|
1528
|
+
},
|
1529
|
+
|
1530
|
+
save: function(textarea) {
|
1531
|
+
var self = this;
|
1532
|
+
textarea._old_value = textarea.value;
|
1533
|
+
return $.post("/api/notepad/save", {
|
1534
|
+
text: textarea.value,
|
1535
|
+
object_id: this.object_id
|
1536
|
+
})
|
1537
|
+
.done(function() {
|
1538
|
+
self.activeOkIcon();
|
1539
|
+
});
|
1540
|
+
},
|
1541
|
+
|
1542
|
+
createObjectId: function() {
|
1543
|
+
return String(_.now()) + _.random(0, 10000);
|
1544
|
+
},
|
1545
|
+
|
1546
|
+
createElements: function() {
|
1547
|
+
var self = this;
|
1548
|
+
var text = this.text;
|
1549
|
+
var render = function(stringified_text) {
|
1550
|
+
var rendered_html = self.renderer({
|
1551
|
+
container_id: self.containerId(),
|
1552
|
+
readonly: self.readonly,
|
1553
|
+
rows: self.rows,
|
1554
|
+
text: stringified_text
|
1555
|
+
});
|
1556
|
+
var elm = $("#" + self.id).html(rendered_html);
|
1557
|
+
var textarea = elm.find("textarea");
|
1558
|
+
if (self.autosave && !self.readonly) {
|
1559
|
+
self.attachAutoSaveEvents(textarea);
|
1560
|
+
}
|
1561
|
+
if (self.synchronizing) {
|
1562
|
+
self.attachSynchronizingEvents(textarea);
|
1563
|
+
}
|
1564
|
+
};
|
1565
|
+
|
1566
|
+
if (typeof text == "function") {
|
1567
|
+
text()
|
1568
|
+
.done(function(stringified_text) {
|
1569
|
+
render(stringified_text);
|
1570
|
+
})
|
1571
|
+
.fail(function() {
|
1572
|
+
render("");
|
1573
|
+
});
|
1574
|
+
}
|
1575
|
+
else {
|
1576
|
+
render(text);
|
1577
|
+
}
|
1578
|
+
},
|
1579
|
+
|
1580
|
+
attachAutoSaveEvents: function(textarea) {
|
1581
|
+
var timer_id = null;
|
1582
|
+
var self = this;
|
1583
|
+
|
1584
|
+
textarea
|
1585
|
+
.on("focus", function() {
|
1586
|
+
this._old_value = this.value;
|
1587
|
+
})
|
1588
|
+
.on("blur", function() {
|
1589
|
+
if (this.value !== this._old_value) {
|
1590
|
+
self.save(this);
|
1591
|
+
}
|
1592
|
+
})
|
1593
|
+
.on("keyup paste cut", function() {
|
1594
|
+
// paste, cut イベントは実行される「直前」に発生するので、
|
1595
|
+
// 実際にテキストボックスに反映されるまでに少し待つ
|
1596
|
+
setTimeout(function() {
|
1597
|
+
clearTimeout(timer_id);
|
1598
|
+
timer_id = setTimeout(function(value, old_value) {
|
1599
|
+
if (value !== old_value) {
|
1600
|
+
self.save(this);
|
1601
|
+
}
|
1602
|
+
}.bind(this), 1000, this.value, this._old_value);
|
1603
|
+
}.bind(this), 10);
|
1604
|
+
});
|
1605
|
+
},
|
1606
|
+
|
1607
|
+
activeOkIcon: function() {
|
1608
|
+
var icon = this.container().find(".notepad-icon");
|
1609
|
+
icon
|
1610
|
+
.removeClass("hide")
|
1611
|
+
.show()
|
1612
|
+
.delay(2000)
|
1613
|
+
.fadeOut(1000);
|
1614
|
+
},
|
1615
|
+
|
1616
|
+
attachSynchronizingEvents: function(textarea) {
|
1617
|
+
var self = this;
|
1618
|
+
var notification = Notification.instance();
|
1619
|
+
notification.on("notepad.change", function(data) {
|
1620
|
+
if (data.object_id == self.object_id)
|
1621
|
+
return;
|
1622
|
+
var dom = textarea[0];
|
1623
|
+
if (dom.value != data.text) {
|
1624
|
+
dom.value = dom._old_value = data.text;
|
1625
|
+
}
|
1626
|
+
});
|
1627
|
+
},
|
1628
|
+
|
1629
|
+
container: function() {
|
1630
|
+
return $("#" + this.containerId());
|
1631
|
+
},
|
1632
|
+
|
1633
|
+
containerId: function() {
|
1634
|
+
return this.id + "_notepad";
|
1635
|
+
},
|
1636
|
+
});
|
1637
|
+
|
1638
|
+
/*************************************************************************
|
1639
|
+
* 埋め込みテンプレート変換
|
1640
|
+
*************************************************************************/
|
1641
|
+
Narou.Template = {
|
1642
|
+
// role="template" を探して、そのテンプレートを処理したあと
|
1643
|
+
// 同じ場所にレンダリング結果を埋め込む。
|
1644
|
+
// 一回だけレンダリングすればいいもの向け
|
1645
|
+
replaceAll: function(hash) {
|
1646
|
+
$("[role=template]").each(function() {
|
1647
|
+
var renderer = _.template($(this).text());
|
1648
|
+
$(this).after(renderer(hash));
|
1649
|
+
});
|
1650
|
+
}
|
1651
|
+
};
|
1652
|
+
|
1653
|
+
/*************************************************************************
|
1654
|
+
* フラッシュメッセージ
|
1655
|
+
*************************************************************************/
|
1656
|
+
Narou.Flash = {
|
1657
|
+
renderer: _.template(
|
1658
|
+
'<div class="container">' +
|
1659
|
+
'<div class="fadeout-alert alert alert-<%= type %>">' +
|
1660
|
+
'<%= message %>' +
|
1661
|
+
'</div></div>'
|
1662
|
+
),
|
1663
|
+
|
1664
|
+
show: function(message, type) {
|
1665
|
+
var obj = $(Narou.Flash.renderer({
|
1666
|
+
message: message, type: type || "success"
|
1667
|
+
}));
|
1668
|
+
$("body").append(obj);
|
1669
|
+
this.setEvents(obj);
|
1670
|
+
},
|
1671
|
+
|
1672
|
+
setEvents: function(object) {
|
1673
|
+
$(object)
|
1674
|
+
.delay(2000)
|
1675
|
+
.animate({ opacity: "hide" }, 1500)
|
1676
|
+
.queue(function(next) {
|
1677
|
+
$(this).remove();
|
1678
|
+
next();
|
1679
|
+
});
|
1680
|
+
}
|
1681
|
+
};
|
1682
|
+
|
1117
1683
|
return Narou;
|
1118
1684
|
})();
|
1119
1685
|
|