|
// ==UserScript== |
|
// @name Twipla printlist enhance |
|
// @namespace milly.ca |
|
// @include https://twipla.jp/events/printlist/* |
|
// @version 2.4.0 |
|
// @require https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js |
|
// @require https://cdnjs.cloudflare.com/ajax/libs/store.js/1.3.17/store.min.js |
|
// @require https://raw.githubusercontent.com/eligrey/FileSaver.js/1.3.3/FileSaver.min.js |
|
// @require https://evidenceprime.github.io/html-docx-js/build/html-docx.js |
|
// @grant GM_xmlhttpRequest |
|
// @source https://gist.github.com/Milly/87c9b0bfbcf89ab98d77 |
|
// @updateURL https://gist.github.com/Milly/87c9b0bfbcf89ab98d77/raw/Twipla_printlist_enhance.user.js |
|
// ==/UserScript== |
|
/* eslint new-cap:0 */ |
|
/* global GM_xmlhttpRequest */ |
|
|
|
|
|
(function main() { |
|
'use strict'; |
|
|
|
var CHANGE_ICON_TO_LARGER = false; |
|
var CREATE_ICON_LINK = true; |
|
var CREATE_TWITTER_ID_LINK = true; |
|
var SEPARATE_ID_AND_NAME_COLUMN = true; |
|
var ENABLE_INITIAL_DISTINCT = true; |
|
var CREATE_SERIAL_NUMBER = true; |
|
var CREATE_ICON_FILENAME = true; |
|
var CREATE_RESERVE_CHECKBOX = true; |
|
var CREATE_U19_CHECKBOX = true; |
|
var ENABLE_NAMEPLATE = true; |
|
var ENABLE_NAMEEDIT = true; |
|
var ENABLE_ORPHAN_SEARCH = true; |
|
var NAMEPLATE_SAVE_AS_DOCX = false; |
|
var RESERVE_INITIAL = '\xff'; |
|
var STORAGE_PREFIX = location.pathname + ':'; |
|
|
|
var $ = jQuery; |
|
var $store = store; |
|
|
|
var TITLE = $('body>.container>h1:nth(1)').text(); |
|
|
|
$('<style type="text/css"></style>').text([ |
|
'#entries { width: auto !important; }', |
|
'#entries th { white-space: nowrap !important; background-color: #e0e0e0; }', |
|
'#entries tr:nth-child(odd) { background-color: #e0e8ff; }', |
|
'#entries th, #entries td { border: 2px solid #000000; }', |
|
'.head-initial { width: 3.4em !important; }', |
|
'.head-serialno { width: 4.4em !important; }', |
|
'.head-icon { width: 50px !important; }', |
|
'.head-filename { width: 8em !important; }', |
|
'.head-id { width: 8em !important; }', |
|
'.head-name { width: 20em !important; }', |
|
'.head-profile { width: auto !important; }', |
|
'.head-checkbox { width: 2.4em !important; }', |
|
'.initial { vertical-align: top; background-color: #ffffff; text-align: center; font-weight: bold; }', |
|
'.serialno { text-align: right; }', |
|
'.reserve,.u19 { text-align: center; }', |
|
'#nameplate-edit { margin-top: 1em; }', |
|
'#nameplate-style, #nameplate-body { margin-bottom: 1ex; width: 100%; height: 20em; }', |
|
'#u19label { width: 5em; }', |
|
'#nameplate-sample { margin: 1em 0 2em 0; width: 100%; height: 300pt; }', |
|
'#control button, #nameplate-edit-control button { margin-left: 0.5ex; height: 1.5em; vertical-align: middle; }', |
|
'#nameplate-edit-control input { vertical-align: middle; }', |
|
'#orphan-search-base { position: fixed; width: 100%; height: 100%; top: 0; left: 0;', |
|
' z-index: 99999; background-color: rgba(0, 0, 0, 0.8); }', |
|
'#orphan-search-progress { display: block; position: absolute; width: 50%; height: 2em;', |
|
' margin: auto; top: 0; bottom: 0; left: 0; right: 0; }', |
|
].join('')).appendTo(document.head); |
|
|
|
var LABELS = { |
|
initial : '頭文字', |
|
serialno : '整理番号', |
|
icon : 'Icon', |
|
filename : 'ファイル名', |
|
id : 'ID', |
|
name : '名前', |
|
profile : 'プロフィール', |
|
reserve : '補欠', |
|
u19 : 'U19', |
|
}; |
|
|
|
var NAMEPLATE_DOCX_NAME = 'nameplates.docx'; |
|
var NAMEPLATE_HTML_NAME = 'nameplates.html'; |
|
|
|
var NAMEPLATE_STYLE = ''; |
|
var NAMEPLATE_TEMPLATE = [ |
|
'<table border="0" style="table-layout:fixed; width:285pt; margin: 0 -1px -1px 0; border:solid black 1px; padding:0; float:left; overflow:hidden;">', |
|
' <tr>', |
|
' <td style="height:40pt; width:31%; border:none; text-align:center; vertical-align:middle;">', |
|
' <div style="font-size:10pt;">整理番号:@{serialno}</div>', |
|
' </td>', |
|
' <td style="height:136pt; border:none; text-align:center; vertical-align:middle;" colspan="2" rowspan="2">', |
|
' <div style="font-size:24pt; color:red;">@{u19}</div>', |
|
' <div style="font-size:24pt;">@{name}</div>', |
|
' <div style="font-size:24pt;">@{id}</div>', |
|
' </td>', |
|
' </tr>', |
|
' <tr>', |
|
' <td style="height:96pt; border:none; text-align:center; vertical-align:middle; overflow:hidden;">', |
|
' <img src="@{icon}" style="height:80pt;">', |
|
' </td>', |
|
' </tr>', |
|
' <tr>', |
|
' <td style="height:52pt; width:69%; border:none; text-align:left; vertical-align:bottom;" colspan="2">', |
|
' <ul style="padding-left:15pt; font-size:9pt;">', |
|
' <li style="margin-bottom:4pt;">この名札はお帰りの際にご返却下さい。</li>', |
|
' <li>この名札を必ず着用してください。</li>', |
|
' </ul>', |
|
' </td>', |
|
' <td style="width:31%; border:none; text-align:center; vertical-align:middle; " rowspan="2">', |
|
' <img src="http://tsunagarumirai.com/images/tsunagarumirai2017_mv_day1.jpg" style="height:110pt;">', |
|
' </td>', |
|
' </tr>', |
|
' <tr>', |
|
' <td style="height:74pt; border:none; text-align:center; vertical-align:middle;" colspan="2">', |
|
' <img src="http://tsunagarumirai.com/images/tsunagarumirai2017_logo_w1500px.png" style="width:90%;">', |
|
' </td>', |
|
' </tr>', |
|
'</table>', |
|
].join('\n'); |
|
var U19_LABEL = 'U19'; |
|
|
|
var NAMEPLATE_DOCUMENT_OPTIONS = { |
|
orientation: 'portrait', |
|
margins: { |
|
top: 181, |
|
bottom: 181, |
|
left: 357, |
|
right: 357, |
|
}, |
|
}; |
|
|
|
var table = $('table').prop('id', 'entries'); |
|
var control = $('<span id="control"></span>').insertBefore(table); |
|
|
|
function toNormalizedId(str) { |
|
return str |
|
.replace(new RegExp('^'+RESERVE_INITIAL+'?@'), '') |
|
.toUpperCase() |
|
.replace(/[^0-9A-Z]/g, '_'); |
|
} |
|
function toSortString(str) { |
|
var reserve = (str[0] == RESERVE_INITIAL) ? RESERVE_INITIAL : ''; |
|
return reserve + toNormalizedId(str).replace(/_/g, '!'); |
|
} |
|
function toInitial(str) { |
|
var c = toSortString(str)[0]; |
|
if (c == RESERVE_INITIAL) return '補欠'; |
|
if (c.match(/^[0-9]/)) return '数字'; |
|
if (c.match(/^[^0-9A-Z]/)) return '記号'; |
|
return c; |
|
} |
|
function compareRowById(a, b) { |
|
a = toSortString($(a).getRowId()); |
|
b = toSortString($(b).getRowId()); |
|
return (a > b) ? 1 : (a < b) ? -1 : 0; |
|
} |
|
|
|
function loadState() { |
|
if (CREATE_RESERVE_CHECKBOX) loadCheckboxes('reserve'); |
|
if (CREATE_U19_CHECKBOX) loadCheckboxes('u19'); |
|
if (ENABLE_NAMEEDIT) loadTexts('name'); |
|
if (ENABLE_ORPHAN_SEARCH) { |
|
loadTexts('id', /* @this HTMLElement */ function(id) { |
|
var url = 'https://twitter.com/' + id.replace(/^@/, ''); |
|
$(this).find('a').attr('href', url).text(id); |
|
}); |
|
loadTexts('icon', /* @this HTMLElement */ function(url) { |
|
$(this).find('a').attr('href', url) |
|
.find('img').attr('src', url); |
|
}); |
|
} |
|
if (ENABLE_INITIAL_DISTINCT) createOrUpdateInitialColumn(); |
|
updateSerialNumbers(); |
|
} |
|
|
|
function saveCheckboxes(type) { |
|
var sets = table.find('tr:has(td.'+type+' :checked)') |
|
.map(/* @this HTMLElement */ function() { return $(this).data('entry-id'); }).toArray(); |
|
if (sets.length) { |
|
$store.set(STORAGE_PREFIX + type + 's', sets); |
|
} else { |
|
$store.remove(STORAGE_PREFIX + type + 's'); |
|
} |
|
} |
|
function loadCheckboxes(type) { |
|
var sets = $store.get(STORAGE_PREFIX + type + 's') || []; |
|
table.find('>tbody>tr') |
|
.find('td.'+type+'>input').prop('checked', false).end() |
|
.filter(/* @this HTMLElement */ function() { return 0 <= $.inArray($(this).data('entry-id'), sets); }) |
|
.find('td.'+type+'>input').prop('checked', true); |
|
} |
|
|
|
function saveTexts(type, getValue) { |
|
getValue = getValue || /* @this HTMLElement */ function() { return $(this).text(); }; |
|
var texts = {}; |
|
var changed = table.find('td.'+type) |
|
.filter(/* @this HTMLElement */ function() { return $(this).data('changed') || false; }) |
|
.each(/* @this HTMLElement */ function() { texts[$(this).closest('tr').data('entry-id')] = getValue.call(this); }); |
|
if (changed.length) { |
|
$store.set(STORAGE_PREFIX + type + 's', texts); |
|
} else { |
|
$store.remove(STORAGE_PREFIX + type + 's'); |
|
} |
|
} |
|
function loadTexts(type, setValue) { |
|
setValue = setValue || /* @this HTMLElement */ function(text) { $(this).text(text); }; |
|
var texts = $store.get(STORAGE_PREFIX + type + 's') || {}; |
|
table.find('td.'+type).each(/* @this HTMLElement */ function() { |
|
var id = $(this).closest('tr').data('entry-id'); |
|
if (texts.hasOwnProperty(id)) { |
|
$(this).data('changed', true); |
|
setValue.call(this, texts[id]); |
|
} |
|
}); |
|
} |
|
|
|
$.fn.extend({ |
|
addButton: function(label, id, proc) { |
|
return $('<button>').prop({id: 'button-'+id}).text(label).appendTo(this).click(proc); |
|
}, |
|
addToggleButton: function(label, id) { |
|
return $(this).addButton(label, id, function(ev) { table.find('.head-'+id+',.'+id).toggle(); }); |
|
}, |
|
getRowId: function() { |
|
var id = $(this).find('td.id').text(); |
|
if (id === '') return ''; |
|
var reserve = $(this).find('td.reserve>input').prop('checked'); |
|
return reserve ? RESERVE_INITIAL + id : id; |
|
}, |
|
findRowId: function(id) { |
|
return $(this).find('#entry-'.toNormalizedId(id)); |
|
}, |
|
getRowData: function() { |
|
var row = $(this); |
|
return { |
|
'serialno': row.find('.serialno').text(), |
|
'icon': row.find('.icon a').prop('href'), |
|
'id': row.find('.id').text(), |
|
'name': row.find('.name').text(), |
|
'u19': row.find('.u19 input').is(':checked') ? $('#u19label').val() : '', |
|
}; |
|
}, |
|
}); |
|
|
|
// create thead |
|
{ |
|
var ids = ['icon', 'id', 'profile']; |
|
var head_row = table.find('>tbody>tr:has(th)'); |
|
head_row.find('th').removeAttr('width'); |
|
$('<thead></thead>').append(head_row).prependTo(table); |
|
$.each(ids, function(idx, id) { |
|
table.find('>thead>tr>th:nth-child('+(idx+1)+')').addClass('head-' + id); |
|
table.find('>tbody>tr>td:nth-child('+(idx+1)+')').addClass(id); |
|
}); |
|
table.find('th.head-icon').text('Icon'); |
|
} |
|
|
|
// change icon to larger |
|
if (CHANGE_ICON_TO_LARGER) { |
|
table.find('img').prop('src', function(_, value) { |
|
return value.replace(/_normal\b/, ''); |
|
}); |
|
} |
|
|
|
// create icon link |
|
if (CREATE_ICON_LINK) { |
|
table.find('img').wrap(/* @this HTMLElement */ function() { |
|
return $('<a>').prop({ |
|
href: this.src.replace(/_normal\b/, ''), |
|
title: this.parentNode.nextSibling.firstChild.textContent, |
|
}); |
|
}); |
|
} |
|
|
|
// create icon filename |
|
if (CREATE_ICON_FILENAME) { |
|
table.find('th.head-icon').after('<th class="head-filename">ファイル名</th>'); |
|
table.find('td.icon').after('<td class="filename"></td>'); |
|
table.find('td.filename').text(/* @this HTMLElement */ function() { return $(this).prev('td.icon').find('img').prop('src').replace(/.*\//, ''); }); |
|
} |
|
|
|
// separate ID and Name column |
|
if (SEPARATE_ID_AND_NAME_COLUMN) { |
|
table.find('th.head-id').replaceWith('<th class="head-id">ID</th><th class="head-name">名前</th>'); |
|
table.find('td.id').replaceWith(/* @this HTMLElement */ function() { |
|
var id = this.firstChild.textContent; |
|
var name = this.lastChild.textContent; |
|
$(this).closest('tr').prop('id', 'entry-'+toNormalizedId(id)).data('entry-id', id); |
|
var twitter_url = null; |
|
if (CREATE_TWITTER_ID_LINK && id[0] == '@') { |
|
twitter_url = 'https://twitter.com/' + id.substr(1); |
|
} |
|
var col_id = (twitter_url) ? |
|
$('<td class="id"><a></a></td>').children('a').prop({href: twitter_url}).text(id).end(): |
|
$('<td class="id"></td>').text(id); |
|
var col_name = $('<td class="name"></td>').text(name); |
|
return col_id.add(col_name); |
|
}); |
|
} |
|
|
|
// initial distinct |
|
if (ENABLE_INITIAL_DISTINCT) createOrUpdateInitialColumn(); |
|
function createOrUpdateInitialColumn() { |
|
table.find('.head-initial,.initial').remove(); |
|
var rows = table.find('>tbody>tr').sort(compareRowById); |
|
var prevInitial = null; |
|
var startIndex = null; |
|
for (var idx = 0; idx <= rows.length; ++idx) { |
|
var initial = rows[idx] ? toInitial($(rows[idx]).getRowId()) : null; |
|
if (initial !== prevInitial) { |
|
if (startIndex !== null) { |
|
$('<td class="initial"></td>').prop('rowspan', idx - startIndex).text(prevInitial) |
|
.prependTo(rows[startIndex]); |
|
} |
|
startIndex = idx; |
|
prevInitial = initial; |
|
} |
|
} |
|
table.find('>thead>tr').prepend('<th class="head-initial">頭文字</th>'); |
|
table.find('>tbody').append(rows); |
|
} |
|
|
|
// create serial number |
|
if (CREATE_SERIAL_NUMBER) createSerialNumberColumn(); |
|
function createSerialNumberColumn() { |
|
table.find('th.head-icon').before('<th class="head-serialno">整理番号</th>'); |
|
table.find('td.icon').before('<td class="serialno"></td>'); |
|
updateSerialNumbers(); |
|
} |
|
function updateSerialNumbers() { |
|
table.find('td.serialno').text(function(idx) { return idx + 1; }); |
|
} |
|
|
|
// create reserve checkbox |
|
if (CREATE_RESERVE_CHECKBOX) { |
|
table.find('>thead>tr').append('<th class="head-reserve head-checkbox">補欠</th>'); |
|
table.find('>tbody>tr').append('<td class="reserve"><input type="checkbox"></td>') |
|
.find('>td>input').prop('name', /* @this HTMLElement */ function() { return $(this).closest('tr').prop('entry-id') + '-reserve'; }); |
|
table.on('click', 'td.reserve>input', function(ev) { |
|
createOrUpdateInitialColumn(); |
|
updateSerialNumbers(); |
|
saveCheckboxes('reserve'); |
|
}); |
|
} |
|
|
|
// create U19 checkbox |
|
if (CREATE_U19_CHECKBOX) { |
|
table.find('>thead>tr').append('<th class="head-u19 head-checkbox">補欠</th>'); |
|
table.find('>tbody>tr').append('<td class="u19"><input type="checkbox"></td>') |
|
.find('>td>input').prop('name', /* @this HTMLElement */ function() { return $(this).closest('tr').data('entry-id') + '-u19'; }); |
|
table.on('click', 'td.u19>input', function(ev) { |
|
saveCheckboxes('u19'); |
|
}); |
|
} |
|
|
|
// add controls |
|
control.addButton('リスト', 'entries', function(ev) { table.toggle(); }); |
|
table.find('>thead>tr>th').each(/* @this HTMLElement */ function() { |
|
var id = this.className.replace(/ .*$/, '').replace(/head-/, ''); |
|
var label = LABELS[id]; |
|
$(this).text(label); |
|
control.addToggleButton(label, id); |
|
}); |
|
table.find('.head-profile,.profile').toggle(); |
|
|
|
if (ENABLE_NAMEPLATE) createNameplateEditor(); |
|
function createNameplateEditor() { |
|
$('<div id="nameplate-edit">' |
|
+ '<textarea id="nameplate-style"></textarea>' |
|
+ '<textarea id="nameplate-body"></textarea>' |
|
+ '<div id="nameplate-edit-control">' |
|
+ '<label for="u19label">U19表示文字列</label>' |
|
+ '<input id="u19label">' |
|
+ '<button id="nameplate-save">テンプレートをブラウザにセーブ</button>' |
|
+ '<button id="nameplate-test">別ウィンドウでプレビューを開く</button>' |
|
+ '<button id="nameplate-download">HTMLファイルをダウンロード</button>' |
|
+ '</div>' |
|
+ '<iframe id="nameplate-sample"></iframe>' |
|
+ '</div>') |
|
.insertAfter(control).hide(); |
|
control.addButton('名札編集', 'nameplate-edit', function(ev) { |
|
if ($('#nameplate-edit').toggle().is(':visible')) { |
|
updateNameplateSample(); |
|
} |
|
}); |
|
|
|
var nameplateUpdateTimer = null; |
|
$('#nameplate-style,#nameplate-body').on('keyup', /* @this HTMLElement */ function(ev) { |
|
var self = this; |
|
clearTimeout(nameplateUpdateTimer); |
|
nameplateUpdateTimer = setTimeout(nameplateUpdateTimeout, 500); |
|
function nameplateUpdateTimeout() { |
|
var newText = $(self).val(); |
|
var lastText = $(self).data('lastText'); |
|
if (lastText != newText) { |
|
$(self).data('lastText', newText); |
|
updateNameplateSample(); |
|
} |
|
} |
|
}); |
|
|
|
var testWindow = null; |
|
var testBlobUrl = null; |
|
$('#nameplate-test').click(function(ev) { |
|
ev.preventDefault(); |
|
testWindow = window.open('', 'twipla-nameplate-test'); |
|
updateNameplateSample(); |
|
var contentHtml = wrapBody(getNameplateHtml()); |
|
var blob = new Blob([contentHtml], {type: 'text/html;charset=utf-8'}); |
|
if (testBlobUrl) { |
|
URL.revokeObjectURL(testBlobUrl); |
|
} |
|
testBlobUrl = URL.createObjectURL(blob); |
|
testWindow.location = testBlobUrl; |
|
}); |
|
|
|
$('#nameplate-save').click(function(ev) { |
|
ev.preventDefault(); |
|
updateNameplateSample(); |
|
var baseContentStyle = $('#nameplate-style').val(); |
|
var baseContentHtml = $('#nameplate-body').val(); |
|
var u19label = $('u19label').val(); |
|
$store.set(STORAGE_PREFIX + 'nameplate-style', baseContentStyle); |
|
$store.set(STORAGE_PREFIX + 'nameplate', baseContentHtml); |
|
$store.set(STORAGE_PREFIX + 'u19label', u19label); |
|
}); |
|
|
|
$('#nameplate-download').click(function(ev) { |
|
ev.preventDefault(); |
|
updateNameplateSample(); |
|
if (NAMEPLATE_SAVE_AS_DOCX) { |
|
downloadNameplateDocx(); |
|
} else { |
|
downloadNameplateHtml(); |
|
} |
|
}); |
|
|
|
var sampleBlobUrl = null; |
|
function updateNameplateSample() { |
|
var contentHtml = wrapBody(getNameplateHtml(0, 2)); |
|
var blob = new Blob([contentHtml], {type: 'text/html;charset=utf-8'}); |
|
if (sampleBlobUrl) { |
|
URL.revokeObjectURL(sampleBlobUrl); |
|
} |
|
sampleBlobUrl = URL.createObjectURL(blob); |
|
$('#nameplate-sample').prop('src', sampleBlobUrl); |
|
} |
|
|
|
function getNameplateHtml(start, count) { |
|
var baseContentStyle = $('#nameplate-style').val(); |
|
var baseContentHtml = $('#nameplate-body').val(); |
|
var rows = table.find('tbody tr'); |
|
if (count) { |
|
rows = rows.slice(start, count); |
|
} |
|
var style = '<style>' + baseContentStyle + '</style>'; |
|
var body = rows.map(/* @this HTMLElement */ function() { |
|
var data = $(this).getRowData(); |
|
var html = baseContentHtml; |
|
$.each(data, function(key, value) { |
|
html = html.replace(new RegExp('@\{'+key+'\}', 'g'), value); |
|
}); |
|
return html; |
|
}).toArray().join('\n'); |
|
return style + body; |
|
} |
|
function wrapBody(html) { |
|
return '<!DOCTYPE html><html>' |
|
+ '<head>' |
|
+ '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' |
|
+ '<title>Twipla 名札 - ' + TITLE + '</title>' |
|
+ '</head>' |
|
+ '<body>' + html + '</body></html>'; |
|
} |
|
|
|
function downloadNameplateDocx() { |
|
var contentHtml = getNameplateHtml(); |
|
var contentDocument = $('<body>').html(contentHtml); |
|
var imageCount = 0; |
|
contentDocument.find('img').each(/* @this HTMLElement */ function() { |
|
++imageCount; |
|
var img = this; |
|
GM_xmlhttpRequest({ |
|
method: 'GET', |
|
url: img.src, |
|
responseType: 'blob', |
|
onload: function(res) { |
|
var reader = new FileReader(); |
|
reader.onload = function() { |
|
img.src = reader.result; |
|
--imageCount; |
|
}; |
|
reader.readAsDataURL(res.response); |
|
} |
|
}); |
|
}); |
|
(function onloadAllImages() { |
|
if (imageCount) { |
|
return setTimeout(onloadAllImages, 500); |
|
} |
|
var content = wrapBody(contentDocument.html()); |
|
var blob = htmlDocx.asBlob(content, NAMEPLATE_DOCUMENT_OPTIONS); |
|
saveAs(blob, NAMEPLATE_DOCX_NAME); |
|
})(); |
|
} |
|
function downloadNameplateHtml() { |
|
var contentHtml = wrapBody(getNameplateHtml()); |
|
var blob = new Blob([contentHtml], {type: 'text/html;charset=utf-8'}); |
|
saveAs(blob, NAMEPLATE_HTML_NAME); |
|
} |
|
|
|
$('#nameplate-style').val($store.get(STORAGE_PREFIX + 'nameplate-style') || NAMEPLATE_STYLE); |
|
$('#nameplate-body').val($store.get(STORAGE_PREFIX + 'nameplate') || NAMEPLATE_TEMPLATE); |
|
$('#u19label').val($store.get(STORAGE_PREFIX + 'u19label') || U19_LABEL); |
|
} |
|
|
|
if (ENABLE_NAMEEDIT) createNameEditor(); |
|
function createNameEditor() { |
|
table.find('td.name').each(/* @this HTMLElement */ function() { |
|
var cell = $(this); |
|
cell.data('origVal', cell.text()); |
|
}); |
|
$(' <span>\ud83d\udcac</span>').appendTo('.head-name') |
|
.attr('title', 'ダブルクリック: 編集開始\nEnter / フォーカス移動: 確定\nEsc: 初期値に戻す'); |
|
table.on('dblclick', 'td.name', /* @this HTMLElement */ function(ev) { |
|
ev.preventDefault(); |
|
var cell = $(this); |
|
var oldValue = cell.text(); |
|
var origValue = cell.data('origVal'); |
|
var input = $('<input>').css('width', '100%').val(oldValue); |
|
cell.empty().append(input); |
|
input.on('keypress blur', function(ev) { |
|
if (ev.type === 'keypress' && ev.which === 13 || ev.type === 'blur') { |
|
ev.preventDefault(); |
|
var newValue = input.val(); |
|
cell.empty().text(newValue); |
|
if (newValue !== oldValue) { |
|
cell.data('changed', (newValue !== origValue)); |
|
saveTexts('name'); |
|
} |
|
} |
|
}); |
|
input.on('keyup', function(ev) { |
|
if (ev.which === 27) { |
|
ev.preventDefault(); |
|
cell.empty().text(origValue).data('changed', false); |
|
} |
|
}); |
|
input.focus(); |
|
}); |
|
} |
|
|
|
if (ENABLE_ORPHAN_SEARCH) createOrphanSearcher(); |
|
function createOrphanSearcher() { |
|
var base = $('<div id="orphan-search-base"><progress id="orphan-search-progress">') |
|
.prependTo(document.body).hide(); |
|
control.addButton('行方不明検索', 'orphan-search', function(ev) { |
|
var orphans = table.find('.icon img') |
|
.filter(/* @this HTMLElement */ function() { return $(this).prop('naturalWidth') === 0; }) |
|
.closest('tr'); |
|
var count = 0, errorCount = 0, max = orphans.length; |
|
if (max === 0) { |
|
alert('行方不明はありません。'); |
|
return; |
|
} |
|
|
|
var progress = $('#orphan-search-progress').prop({max: max}).val(0); |
|
progress.hide(); |
|
base.show(); |
|
if (!confirm('この処理は時間がかかる場合があります。\n開始しますか?')) { |
|
base.hide(); |
|
return; |
|
} |
|
progress.show(); |
|
|
|
function updateProgress() { |
|
var current = count + errorCount; |
|
progress.val(current); |
|
if (current >= max) { |
|
// progress done |
|
saveTexts('id'); |
|
saveTexts('icon', /* @this HTMLElement */ function getIconUrl() { |
|
return $(this).find('a').prop('href'); |
|
}); |
|
base.hide(); |
|
} |
|
} |
|
|
|
orphans.find('.id a').each(/* @this HTMLElement */ function() { |
|
var idlink = $(this); |
|
var icon = idlink.closest('tr').find('.icon img'); |
|
searchUserOnTwitter(this.href, true); |
|
|
|
function searchUserOnTwitter(url, fallbackGoogle) { |
|
GM_xmlhttpRequest({ |
|
method: 'GET', |
|
url: url, |
|
onload: function(res) { |
|
if (res.status === 200) { |
|
var regIcon = /<a (?=[^>]*ProfileCardMini-avatar)[^>]*\bdata-url="([^"]+)"/m; |
|
var matchIcon = regIcon.exec(res.responseText); |
|
if (matchIcon) { |
|
icon.attr('src', matchIcon[1]) |
|
.closest('a').attr('href', matchIcon[1]) |
|
.closest('td').data('changed', true); |
|
} |
|
var regId = /<link (?=[^>]*\brel="canonical")[^>]*\bhref="(https:\/\/twitter\.com\/([^\/"]+))\/[^"]*"/m; |
|
var matchId = regId.exec(res.responseText); |
|
if (matchId) { |
|
idlink.attr('href', matchId[1]).text('@' + matchId[2]) |
|
.closest('td').data('changed', true); |
|
} |
|
++count; |
|
} else if (fallbackGoogle) { |
|
searchStatusOnGoogle(); |
|
} else { |
|
++errorCount; |
|
} |
|
updateProgress(); |
|
}, |
|
}); |
|
} |
|
|
|
function searchStatusOnGoogle() { |
|
var id = idlink.text().replace(/^@/, ''); |
|
var statusUrl = 'https://twitter.com/' + id + '/status/'; |
|
var searchUrl = 'https://www.google.co.jp/search?q=' + encodeURIComponent(statusUrl); |
|
GM_xmlhttpRequest({ |
|
method: 'GET', |
|
url: searchUrl, |
|
onload: function(res) { |
|
var regStatus = new RegExp('<a href="('+statusUrl+'[0-9]+)"', 'm'); |
|
var matchStatus = regStatus.exec(res.responseText); |
|
if (matchStatus) { |
|
searchUserOnTwitter(matchStatus[1], false); |
|
} |
|
} |
|
}); |
|
} |
|
}); |
|
}); |
|
} |
|
|
|
loadState(); |
|
})(); |