OSDN Git Service

initial
[charactermanaj/CharacterManaJStorage.git] / htdocs / upload.js
1 // 2018/6時点のIE11を想定しているのでES6のうちconst, letぐらいしか使わない
2
3 const MAX_UPLOAD_SIZE = 4 * 1024 * 1024; // 4MB
4 const MAX_COMMENT_SIZE = 3000; // 3000文字
5
6 function escapeHTML(str) {
7     "use strict";
8     if (str) {
9         return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
10     }
11     return '';
12 }
13
14 // https://ja.stackoverflow.com/questions/2582/
15 function commafy(n) {
16     "use strict";
17     if (n) {
18         let parts = n.toString().split('.');
19         parts[0] = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');
20         return parts.join('.');
21     }
22     return '';
23 }
24
25 function get_display_license(lic) {
26     "use strict";
27     switch (lic) {
28         case '0': return '商用可・改変可・再頒布可';
29         case '1': return '商用可・改変不可・再頒布可';
30         case '2': return '商用不可・改変可・再頒布可';
31         case '3': return '商用不可・改変不可・再頒布可';
32     }
33     return lic;
34 }
35
36 function reportForm(form) {
37     "use strict";
38     try {
39         $.messager.confirm({
40             title: '通報の確認',
41             msg: 'このIPアドレスからは、ただちにファイルにアクセスできなくなります。取り消しはできません。',
42             fn: function (r) {
43                 if (r) {
44                     let formData = new FormData(form);
45                     $.ajax({
46                         url: '/cgi-bin/report.php',
47                         data: formData,
48                         type: 'POST',
49                         dataType: 'json', // レスポンスをJSONとして解析
50                         processData: false, // Ajaxがdataを整形しない指定
51                         contentType: false // マルチパート送信のおまじない
52                     }).done(function (res) {
53                         if (res.result == 'ok') {
54                             $('#zip_comment_area').text('');
55                             $('#filetable').datagrid('reload');
56                             $('#taglist').datagrid('reload');
57                             $.messager.alert('完了', '通報しました', 'info', function () {
58                                 form.reset();
59                                 $('#reportDlg').dialog('close');
60                             });
61                         } else {
62                             $.messager.alert('通報に失敗', '通報できませんでした。\n' + res.message, 'error');
63                         }
64
65                     }).fail(function (jqXHR, textStatus, errorThrown) {
66                         alert('通報に失敗しました。:' + textStatus + ":" + errorThrown);
67                     });
68                 }
69             }
70         });
71
72     } catch (e) {
73         alert(e);
74     }
75     return false;
76 }
77
78 function uploadForm(form) {
79     "use strict";
80     try {
81         let comment = form['comment'];
82         let title = form['title'];
83         if (comment.value.length > MAX_COMMENT_SIZE || title.value.length > MAX_COMMENT_SIZE) {
84             $.messager.alert('アップロードに失敗',
85                 'コメントまたはタイトルが長すぎます。\nlimit ' + MAX_COMMENT_SIZE + 'bytes', 'error');
86             return false;
87         }
88
89         let author = form['author'].value;
90         let delkey = form['delkey'].value;
91         let license = form['license'].value;
92         if (author.length > 100 || delkey.length > 100) {
93             $.messager.alert('アップロードに失敗', '入力テキストが長すぎます', 'error');
94             return false;
95         }
96
97         // authorとdelkeyはブラウザのローカルストレージに記憶しておく
98         localStorage.setItem('uploadForm.author', author);
99         localStorage.setItem('uploadForm.delkey', delkey);
100         localStorage.setItem('uploadForm.license', license);
101
102         let file = form['file'];
103         if (file && file.files.length == 1) {
104             if (file.files[0].size > MAX_UPLOAD_SIZE) {
105                 $.messager.alert('アップロードに失敗',
106                     'ファイルが大きすぎます。\nlimit ' + MAX_UPLOAD_SIZE + 'bytes', 'error');
107                 return false;
108             }
109             let fname = file.files[0].name;
110             let pos = fname.lastIndexOf('.');
111             let ext = '';
112             if (pos > 0) {
113                 ext = fname.substring(pos + 1).toLowerCase();
114             }
115             if (ext != 'zip' && ext != 'cmj') {
116                 $.messager.alert('アップロードに失敗',
117                     'zipファイルのみアップロードできます。', 'error');
118                 return false;
119             }
120
121             let formData = new FormData(form);
122             $.ajax({
123                 url: '/cgi-bin/upload.php',
124                 data: formData,
125                 type: 'POST',
126                 dataType: 'json', // レスポンスをJSONとして解析
127                 processData: false, // Ajaxがdataを整形しない指定
128                 contentType: false // マルチパート送信のおまじない
129             }).done(function (res) {
130                 if (res.result == 'ok') {
131                     $('#filetable').datagrid('reload');
132                     $('#taglist').datagrid('reload');
133                     $.messager.alert('アップロード完了', 'アップロードしました', 'info', function () {
134                         form['file'].value = '';
135                         form['title'].value = '';
136                     });
137                 } else {
138                     let msg = res.message; 
139                     if (res.error_files && res.error_files.length > 0) {
140                         msg += '<div><ul>';
141                         $.each(res.error_files, function (idx, err_file_name) {
142                             msg += '<li>' + escapeHTML(err_file_name) + '</li>';
143                         });
144                         msg += '</ul></div>';
145                     }
146                     $.messager.alert('アップロードに失敗', 'アップロードできません。<br>' + msg, 'error');
147                 }
148
149             }).fail(function (jqXHR, textStatus, errorThrown) {
150                 alert('アップロードに失敗しました。:' + textStatus + ":" + errorThrown);
151             });
152         }
153
154     } catch (e) {
155         alert(e);
156     }
157     return false;
158 };
159
160 function deleteForm(form) {
161     "use strict";
162     try {
163         let formData = new FormData(form);
164         $.ajax({
165             url: '/cgi-bin/delete.php',
166             data: formData,
167             type: 'POST',
168             dataType: 'json', // レスポンスをJSONとして解析
169             // Ajaxがdataを整形しない指定
170             processData: false,
171             // contentTypeもfalseに指定
172             contentType: false
173
174         }).done(function (res) {
175             if (res.result == 'ok') {
176                 $('#filetable').datagrid('reload');
177                 $('#taglist').datagrid('reload');
178                 $.messager.alert('削除完了', '削除しました', 'info', function () {
179                     form.reset();
180                     $('#delDlg').dialog('close');
181                 });
182             } else {
183                 $.messager.alert('削除に失敗', '削除できません。\n' + res.message, 'error');
184             }
185
186         }).fail(function (jqXHR, textStatus, errorThrown) {
187             alert('削除に失敗しました。:' + textStatus + ":" + errorThrown);
188         });
189
190     } catch (e) {
191         alert(e);
192     }
193     return false;
194 };
195
196 function formatTitle(val, row, idx) {
197     "use strict";
198     return '<a href="#" class="easyui-tooltip" title="File ID: ' + row['id'] +
199         '" onclick="clickTitle(' + row['id'] + ',' + idx + ')">' + escapeHTML(val) + '</a>';
200 }
201
202 function clickTitle(id, idx) {
203     "use strict";
204     let data = $('#filetable').datagrid('getData');
205     if (!data || !data.rows) {
206         return false;
207     }
208     try {
209         let row = data.rows[idx];
210         let datafile = row['fname'];
211         let pos = datafile.lastIndexOf('.');
212         let ext = (pos > 0) ? datafile.substring(pos) : '.zip';
213         let filename = row['title'] + ext;
214
215         // https://qiita.com/tom_konda/items/484955b8332e0305ebc4
216         let xhr = new XMLHttpRequest();
217         xhr.open('GET', '/cgi-bin/download.php?id=' + id, true);
218         xhr.responseType = 'blob';
219         xhr.onload = function (oEvent) {
220             let blob = xhr.response;
221             if (blob) {
222                 // https://gist.github.com/wemersonjanuario/45d302337b45d6aad866051f0473c646
223                 if (navigator.appVersion.toString().indexOf('.NET') > 0) {
224                     //IE 10+
225                     window.navigator.msSaveBlob(blob, filename);
226                 } else {
227                     //Firefox, Chrome
228                     let a = document.createElement("a");
229                     let blobUrl = window.URL.createObjectURL(new Blob([blob], { type: blob.type }));
230                     document.body.appendChild(a);
231                     a.style = "display: none";
232                     a.href = blobUrl;
233                     a.download = filename;
234                     a.click();
235                 }
236             }
237             $('#filetable').datagrid('reload');
238         };
239         xhr.send();
240         return false;
241
242     } catch (e) {
243         alert(e);
244         return false;
245     }
246 }
247
248 $(function () {
249     "use strict";
250
251     var currentZipId = null;
252     let onSelectZipHandler = function (rowIndex, rowData) {
253         // 選択されたzipの情報を右ペインに表示するためのajax
254         $('#zip_comment_area').text(''); // ajax完了までは空白にしておく
255         let zipid = rowData['id'];
256         currentZipId = zipid;
257         $.ajax({
258             url: '/cgi-bin/zipinfo.php',
259             data: {
260                 'zipid': zipid,
261                 'contentsTypes': ['tag', 'parts', 'comment'],
262                 'time': new Date().getTime()
263             },
264             type: 'GET',
265             dataType: 'json', // レスポンスをJSONとして解析
266         }).done(function (res) {
267             if (zipid === currentZipId) {
268                 // ajax要求時点とzipidが歩変更なければ画面を更新する
269                 // (変更があれば、このレスポンスは捨てる)
270                 let msg = '';
271                 if (res.tags && res.tags.length > 0) {
272                     msg += '<b>[タグ]</b>\r\n';
273                     $.each(res.tags, function (idx, tag) {
274                         msg += escapeHTML(tag) + '\r\n';
275                     });
276                     msg += '\r\n';
277                 }
278                 if (res.partsnames && res.partsnames.length > 0) {
279                     msg += '<b>[パーツ一覧]</b>\r\n';
280                     $.each(res.partsnames, function (idx, partsname) {
281                         msg += escapeHTML(partsname) + '\r\n';
282                     });
283                     msg += '\r\n';
284                 }
285                 if (res.comment) {
286                     msg += '<b>[コメント]</b>\r\n';
287                     msg += escapeHTML(res.comment) + '\r\n';
288                 }
289                 $('#zip_comment_area').html(msg);
290             }
291
292         }).fail(function (jqXHR, textStatus, errorThrown) {
293             $('#zip_comment_area').text(textStatus + ":" + errorThrown);
294         });
295     };
296
297     var currentPartsZipId = null;
298     let onSelectPartsZipHandler = function (rowIndex, rowData) {
299         // 選択されたzipの情報を右ペインに表示するためのajax
300         $('#zip_comment_area').text(''); // ajax完了までは空白にしておく
301         let zipid = rowData['id'];
302         if (currentPartsZipId != zipid) {
303             currentPartsZipId = zipid;
304             $.ajax({
305                 url: '/cgi-bin/zipinfo.php',
306                 data: {
307                     'zipid': zipid,
308                     'contentsTypes': ['comment'],
309                     'time': new Date().getTime()
310                 },
311                 type: 'GET',
312                 dataType: 'json', // レスポンスをJSONとして解析
313             }).done(function (res) {
314                 if (zipid === currentPartsZipId) {
315                     // ajax要求時点とzipidが歩変更なければ画面を更新する
316                     // (変更があれば、このレスポンスは捨てる)
317                     var msg = '';
318                     if (res.comment) {
319                         msg += escapeHTML(res.comment) + '\r\n';
320                     }
321                     $('#parts_comment_area').html(msg);
322                 }
323             }).fail(function (jqXHR, textStatus, errorThrown) {
324                 $('#parts_comment_area').text(textStatus + ":" + errorThrown);
325             });
326         }
327     };
328
329     let checked_tagids = {};
330     var request_search_parts = false;
331     let search_parts = function () {
332         if (!request_search_parts) {
333             return;
334         }
335         request_search_parts = false;
336         $('#partslist').datagrid('reload');
337     };
338
339     $('#taglist').datagrid({
340         pagination: true,
341         url: '/cgi-bin/taglist.php',
342         method: 'get',
343         remoteSort: true,
344         queryParams: {
345             tzoffset: -(new Date().getTimezoneOffset()), // GMTとの差
346             time: new Date().getTime()
347         },
348         toolbar: [{
349             text: 'リセット',
350             iconCls: 'icon-cancel',
351             handler: function () {
352                 $('#taglist').datagrid('clearChecked');
353                 Object.keys(checked_tagids).forEach(function (prop) {
354                     delete checked_tagids[prop];
355                 });
356                 request_search_parts = true;
357                 setTimeout(search_parts, 1);
358             }
359         }],
360         onLoadSuccess: function () {
361             let rows = $(this).datagrid('getRows');
362             for (var i = 0; i < rows.length; i++) {
363                 let tagid = rows[i].id;
364                 if (checked_tagids[tagid]) {
365                     $(this).datagrid('checkRow', i);
366                 }
367             }
368         },
369         onSelect: function (rowIndex, rowData) {
370             let tagid = rowData.id;
371             checked_tagids[tagid] = true;
372             request_search_parts = true;
373             setTimeout(search_parts, 1);
374         },
375         onUnselect: function (rowIndex, rowData) {
376             let tagid = rowData.id;
377             checked_tagids[tagid] = false;
378             request_search_parts = true;
379             setTimeout(search_parts, 1);
380         },
381         onSelectAll: function () {
382             let rows = $(this).datagrid('getRows');
383             for (var i = 0; i < rows.length; i++) {
384                 let tagid = rows[i].id;
385                 checked_tagids[tagid] = true;
386             }
387             request_search_parts = true;
388             setTimeout(search_parts, 1);
389         },
390         onUnselectAll: function () {
391             let rows = $(this).datagrid('getRows');
392             for (var i = 0; i < rows.length; i++) {
393                 let tagid = rows[i].id;
394                 checked_tagids[tagid] = false;
395             }
396             request_search_parts = true;
397             setTimeout(search_parts, 1);
398         },
399         pageList: [30, 60, 90, 120]
400     });
401
402     // ログイン済みクッキーがあるか? (有効かは問わない。表示切り替えのみ)
403     let hasCmjSid = document.cookie.indexOf('cmj-uploader-sid=') >= 0;
404
405     let doLogin = function () {
406         $.messager.confirm('ログイン',
407             'OSDNユーザとしてログインすることでアップロード制限を解除できます',
408             function (r) {
409                 if (r) {
410                     let url = 'https://osdn.net/account/oauth2ui/authorize?' +
411                         'client_id=c10e635f25f1875ac9add68528a735d49dbd110ae15fe1e7b4d01eccc47db591&' +
412                         'response_type=code&' +
413                         'scope=profile&' +
414                         'state=' + window.location.hostname;
415                     window.location = url;
416                 }
417             });
418     };
419
420     // ログインキー(Cookie)の表示
421     let showLogin = function () {
422         var cookies = document.cookie;
423         var result = [];
424         if (cookies != '') {
425             var cookieArray = cookies.split(';');
426             for (var i = 0; i < cookieArray.length; i++) {
427                 var cookie = cookieArray[i].split('=');
428                 result[cookie[0]] = decodeURIComponent(cookie[1]);
429             }
430         }
431         if (result['cmj-uploader-sid']) {
432             $('#txtSid').val(result['cmj-uploader-sid']);
433             $('#showSidDlg').dialog('open');
434         }
435     };
436
437     // ZIPファイル一覧用データグリッド
438     $('#filetable').datagrid({
439         pagination: true,
440         url: '/cgi-bin/ziplist.php',
441         method: 'get',
442         remoteSort: true,
443         toolbar: [{
444             text: 'アップロードする',
445             iconCls: 'icon-add',
446             handler: function () {
447                 $('#upDlg').dialog('open');
448             }
449         }, '-', {
450             text: '削除する',
451             iconCls: 'icon-remove',
452             handler: function () {
453                 let row = $('#filetable').datagrid('getSelected');
454                 if (row) {
455                     $('input[name="selected_id"]').val(row['id']);
456                     $('#delDlg').dialog('open');
457                 }
458             }
459         }, {
460             text: '通報する',
461             iconCls: 'icon-cancel',
462             handler: function () {
463                 let row = $('#filetable').datagrid('getSelected');
464                 if (row) {
465                     $('input[name="selected_id"]').val(row['id']);
466                     $('#report_zip_title').text(row['title']);
467                     $('#reportDlg').dialog('open');
468                 }
469             }
470         }, {
471             text: (hasCmjSid ? 'ログイン中(キーの表示)' : 'アップロード制限の解除'),
472             iconCls: 'icon-edit',
473             handler: hasCmjSid ? showLogin : doLogin
474         }],
475         queryParams: {
476             tzoffset: -(new Date().getTimezoneOffset()), // GMTとの差
477             time: new Date().getTime()
478         },
479         onSelect: onSelectZipHandler,
480         pageList: [30, 60, 90, 120]
481     });
482
483     // パーツファイル一覧用データグリッド
484     $('#partslist').datagrid({
485         pagination: true,
486         url: '/cgi-bin/partslist.php',
487         method: 'post',
488         remoteSort: true,
489         queryParams: {
490             tzoffset: -(new Date().getTimezoneOffset()), // GMTとの差
491             time: new Date().getTime()
492         },
493         onBeforeLoad: function (param) {
494             var tagids = [];
495             Object.keys(checked_tagids).forEach(function (key) {
496                 if (checked_tagids[key]) {
497                     tagids.push(key);
498                 }
499             });
500             param.tagids = tagids;
501         },
502         onSelect: onSelectPartsZipHandler,
503         onLoadSuccess: function () {
504             currentPartsZipId = null;
505             $('#parts_comment_area').html('');
506         },
507         pageList: [30, 60, 90, 120]
508     });
509
510     // ファィルを選択したときにタイトルをファイル名から設定する
511     $('input[name="file"]').on('change', function () {
512         if (this.files.length > 0) {
513             let fname = this.files[0].name;
514             // 拡張子を除去する
515             let pos = fname.lastIndexOf('.');
516             if (pos > 0) {
517                 fname = fname.substring(0, pos);
518             }
519             // ファイル名に$があれば削除キーとして使用する
520             pos = fname.lastIndexOf('$');
521             if (pos > 0) {
522                 let delkey = fname.substring(pos + 1);
523                 fname = fname.substring(0, pos);
524                 $('input[name="delkey"').val(delkey);
525             }
526             // ファイル名に@があれば作者名として使用する
527             pos = fname.lastIndexOf('@');
528             if (pos > 0) {
529                 let author = fname.substring(pos + 1);
530                 fname = fname.substring(0, pos);
531                 $('input[name="author"]').val(author);
532             }
533             // タイトルの設定
534             $('input[name="title"]').val(fname);
535         }
536     });
537
538     // 削除キーのリセット処理
539     let $form = $('form');
540     $form.on('reset', function () {
541         setTimeout(function () {
542             // delkeyが空欄であればランダムの割り当て
543             if ($('input[name="delkey"]').val() == '') {
544                 let autoDelKey = Math.floor(Math.random() * 65535).toString(16);
545                 $('input[name="delkey"]').val(autoDelKey);
546             }
547         }, 1);
548     });
549     $form[0].reset();
550
551     // 削除ダイアログの入力項目の活性制御
552     $('input[name="keytype"]:radio').on('change', function () {
553         let val = $(this).val();
554         if (val == 'TRIP') {
555             $('input[name="verifydelkey"]').attr('disabled', 'disabled');
556             $('input[name="verifytrip"]').removeAttr('disabled');
557         } else {
558             $('input[name="verifydelkey"]').removeAttr('disabled');
559             $('input[name="verifytrip"]').attr('disabled', 'disabled');
560         }
561     });
562
563     // ローカルストレージに記憶されているauthorとdelkeyを復元する
564     if (localStorage['uploadForm.author']) {
565         $('input[name="author"]').val(localStorage['uploadForm.author']);
566     }
567     if (localStorage['uploadForm.delkey']) {
568         $('input[name="delkey"]').val(localStorage['uploadForm.delkey']);
569     }
570     if (localStorage['uploadForm.license']) {
571         $('select[name="license"]').val(localStorage['uploadForm.license']);
572     }
573 });