1 // 2018/6時点のIE11を想定しているのでES6のうちconst, letぐらいしか使わない
3 const MAX_UPLOAD_SIZE = 4 * 1024 * 1024; // 4MB
4 const MAX_COMMENT_SIZE = 3000; // 3000文字
6 function escapeHTML(str) {
9 return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
14 // https://ja.stackoverflow.com/questions/2582/
18 let parts = n.toString().split('.');
19 parts[0] = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');
20 return parts.join('.');
25 function get_display_license(lic) {
28 case '0': return '商用可・改変可・再頒布可';
29 case '1': return '商用可・改変不可・再頒布可';
30 case '2': return '商用不可・改変可・再頒布可';
31 case '3': return '商用不可・改変不可・再頒布可';
36 function reportForm(form) {
41 msg: 'このIPアドレスからは、ただちにファイルにアクセスできなくなります。取り消しはできません。',
44 let formData = new FormData(form);
46 url: '/cgi-bin/report.php',
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 () {
59 $('#reportDlg').dialog('close');
62 $.messager.alert('通報に失敗', '通報できませんでした。\n' + res.message, 'error');
65 }).fail(function (jqXHR, textStatus, errorThrown) {
66 alert('通報に失敗しました。:' + textStatus + ":" + errorThrown);
78 function uploadForm(form) {
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');
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');
97 // authorとdelkeyはブラウザのローカルストレージに記憶しておく
98 localStorage.setItem('uploadForm.author', author);
99 localStorage.setItem('uploadForm.delkey', delkey);
100 localStorage.setItem('uploadForm.license', license);
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');
109 let fname = file.files[0].name;
110 let pos = fname.lastIndexOf('.');
113 ext = fname.substring(pos + 1).toLowerCase();
115 if (ext != 'zip' && ext != 'cmj') {
116 $.messager.alert('アップロードに失敗',
117 'zipファイルのみアップロードできます。', 'error');
121 let formData = new FormData(form);
123 url: '/cgi-bin/upload.php',
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 = '';
138 let msg = res.message;
139 if (res.error_files && res.error_files.length > 0) {
141 $.each(res.error_files, function (idx, err_file_name) {
142 msg += '<li>' + escapeHTML(err_file_name) + '</li>';
144 msg += '</ul></div>';
146 $.messager.alert('アップロードに失敗', 'アップロードできません。<br>' + msg, 'error');
149 }).fail(function (jqXHR, textStatus, errorThrown) {
150 alert('アップロードに失敗しました。:' + textStatus + ":" + errorThrown);
160 function deleteForm(form) {
163 let formData = new FormData(form);
165 url: '/cgi-bin/delete.php',
168 dataType: 'json', // レスポンスをJSONとして解析
171 // contentTypeもfalseに指定
174 }).done(function (res) {
175 if (res.result == 'ok') {
176 $('#filetable').datagrid('reload');
177 $('#taglist').datagrid('reload');
178 $.messager.alert('削除完了', '削除しました', 'info', function () {
180 $('#delDlg').dialog('close');
183 $.messager.alert('削除に失敗', '削除できません。\n' + res.message, 'error');
186 }).fail(function (jqXHR, textStatus, errorThrown) {
187 alert('削除に失敗しました。:' + textStatus + ":" + errorThrown);
196 function formatTitle(val, row, idx) {
198 return '<a href="#" class="easyui-tooltip" title="File ID: ' + row['id'] +
199 '" onclick="clickTitle(' + row['id'] + ',' + idx + ')">' + escapeHTML(val) + '</a>';
202 function clickTitle(id, idx) {
204 let data = $('#filetable').datagrid('getData');
205 if (!data || !data.rows) {
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;
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;
222 // https://gist.github.com/wemersonjanuario/45d302337b45d6aad866051f0473c646
223 if (navigator.appVersion.toString().indexOf('.NET') > 0) {
225 window.navigator.msSaveBlob(blob, filename);
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";
233 a.download = filename;
237 $('#filetable').datagrid('reload');
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;
258 url: '/cgi-bin/zipinfo.php',
261 'contentsTypes': ['tag', 'parts', 'comment'],
262 'time': new Date().getTime()
265 dataType: 'json', // レスポンスをJSONとして解析
266 }).done(function (res) {
267 if (zipid === currentZipId) {
268 // ajax要求時点とzipidが歩変更なければ画面を更新する
269 // (変更があれば、このレスポンスは捨てる)
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';
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';
286 msg += '<b>[コメント]</b>\r\n';
287 msg += escapeHTML(res.comment) + '\r\n';
289 $('#zip_comment_area').html(msg);
292 }).fail(function (jqXHR, textStatus, errorThrown) {
293 $('#zip_comment_area').text(textStatus + ":" + errorThrown);
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;
305 url: '/cgi-bin/zipinfo.php',
308 'contentsTypes': ['comment'],
309 'time': new Date().getTime()
312 dataType: 'json', // レスポンスをJSONとして解析
313 }).done(function (res) {
314 if (zipid === currentPartsZipId) {
315 // ajax要求時点とzipidが歩変更なければ画面を更新する
316 // (変更があれば、このレスポンスは捨てる)
319 msg += escapeHTML(res.comment) + '\r\n';
321 $('#parts_comment_area').html(msg);
323 }).fail(function (jqXHR, textStatus, errorThrown) {
324 $('#parts_comment_area').text(textStatus + ":" + errorThrown);
329 let checked_tagids = {};
330 var request_search_parts = false;
331 let search_parts = function () {
332 if (!request_search_parts) {
335 request_search_parts = false;
336 $('#partslist').datagrid('reload');
339 $('#taglist').datagrid({
341 url: '/cgi-bin/taglist.php',
345 tzoffset: -(new Date().getTimezoneOffset()), // GMTとの差
346 time: new Date().getTime()
350 iconCls: 'icon-cancel',
351 handler: function () {
352 $('#taglist').datagrid('clearChecked');
353 Object.keys(checked_tagids).forEach(function (prop) {
354 delete checked_tagids[prop];
356 request_search_parts = true;
357 setTimeout(search_parts, 1);
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);
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);
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);
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;
387 request_search_parts = true;
388 setTimeout(search_parts, 1);
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;
396 request_search_parts = true;
397 setTimeout(search_parts, 1);
399 pageList: [30, 60, 90, 120]
402 // ログイン済みクッキーがあるか? (有効かは問わない。表示切り替えのみ)
403 let hasCmjSid = document.cookie.indexOf('cmj-uploader-sid=') >= 0;
405 let doLogin = function () {
406 $.messager.confirm('ログイン',
407 'OSDNユーザとしてログインすることでアップロード制限を解除できます',
410 let url = 'https://osdn.net/account/oauth2ui/authorize?' +
411 'client_id=c10e635f25f1875ac9add68528a735d49dbd110ae15fe1e7b4d01eccc47db591&' +
412 'response_type=code&' +
414 'state=' + window.location.hostname;
415 window.location = url;
421 let showLogin = function () {
422 var cookies = document.cookie;
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]);
431 if (result['cmj-uploader-sid']) {
432 $('#txtSid').val(result['cmj-uploader-sid']);
433 $('#showSidDlg').dialog('open');
438 $('#filetable').datagrid({
440 url: '/cgi-bin/ziplist.php',
446 handler: function () {
447 $('#upDlg').dialog('open');
451 iconCls: 'icon-remove',
452 handler: function () {
453 let row = $('#filetable').datagrid('getSelected');
455 $('input[name="selected_id"]').val(row['id']);
456 $('#delDlg').dialog('open');
461 iconCls: 'icon-cancel',
462 handler: function () {
463 let row = $('#filetable').datagrid('getSelected');
465 $('input[name="selected_id"]').val(row['id']);
466 $('#report_zip_title').text(row['title']);
467 $('#reportDlg').dialog('open');
471 text: (hasCmjSid ? 'ログイン中(キーの表示)' : 'アップロード制限の解除'),
472 iconCls: 'icon-edit',
473 handler: hasCmjSid ? showLogin : doLogin
476 tzoffset: -(new Date().getTimezoneOffset()), // GMTとの差
477 time: new Date().getTime()
479 onSelect: onSelectZipHandler,
480 pageList: [30, 60, 90, 120]
484 $('#partslist').datagrid({
486 url: '/cgi-bin/partslist.php',
490 tzoffset: -(new Date().getTimezoneOffset()), // GMTとの差
491 time: new Date().getTime()
493 onBeforeLoad: function (param) {
495 Object.keys(checked_tagids).forEach(function (key) {
496 if (checked_tagids[key]) {
500 param.tagids = tagids;
502 onSelect: onSelectPartsZipHandler,
503 onLoadSuccess: function () {
504 currentPartsZipId = null;
505 $('#parts_comment_area').html('');
507 pageList: [30, 60, 90, 120]
510 // ファィルを選択したときにタイトルをファイル名から設定する
511 $('input[name="file"]').on('change', function () {
512 if (this.files.length > 0) {
513 let fname = this.files[0].name;
515 let pos = fname.lastIndexOf('.');
517 fname = fname.substring(0, pos);
519 // ファイル名に$があれば削除キーとして使用する
520 pos = fname.lastIndexOf('$');
522 let delkey = fname.substring(pos + 1);
523 fname = fname.substring(0, pos);
524 $('input[name="delkey"').val(delkey);
526 // ファイル名に@があれば作者名として使用する
527 pos = fname.lastIndexOf('@');
529 let author = fname.substring(pos + 1);
530 fname = fname.substring(0, pos);
531 $('input[name="author"]').val(author);
534 $('input[name="title"]').val(fname);
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);
552 $('input[name="keytype"]:radio').on('change', function () {
553 let val = $(this).val();
555 $('input[name="verifydelkey"]').attr('disabled', 'disabled');
556 $('input[name="verifytrip"]').removeAttr('disabled');
558 $('input[name="verifydelkey"]').removeAttr('disabled');
559 $('input[name="verifytrip"]').attr('disabled', 'disabled');
563 // ローカルストレージに記憶されているauthorとdelkeyを復元する
564 if (localStorage['uploadForm.author']) {
565 $('input[name="author"]').val(localStorage['uploadForm.author']);
567 if (localStorage['uploadForm.delkey']) {
568 $('input[name="delkey"]').val(localStorage['uploadForm.delkey']);
570 if (localStorage['uploadForm.license']) {
571 $('select[name="license"]').val(localStorage['uploadForm.license']);