MediaWiki:Gadget-vpTagHelper.js
表示
お知らせ: 保存した後、ブラウザのキャッシュをクリアしてページを再読み込みする必要があります。
多くの Windows や Linux のブラウザ
- Ctrl を押しながら F5 を押す。
Mac における Safari
Mac における Chrome や Firefox
- ⌘ Cmd と ⇧ Shift を押しながら R を押す。
詳細についてはWikipedia:キャッシュを消すをご覧ください。
/*
* vpTagHelper.js
* 井戸端タグ支援ツール
*
* 井戸端サブページの編集画面に、タグ検索・編集機能を追加する
*/
if (
mw.config.get('wgPageName').indexOf('Wikipedia:井戸端/subj/') === 0 && //井戸端サブページ、かつ
(mw.config.get('wgAction') == 'edit' || mw.config.get('wgAction') == 'submit') && //編集・プレビュー・差分確認時のみで
(!jQuery('[name=wpSection]').length || jQuery('[name=wpSection]').val().match(/^0?$/)) //(導入部除き)節編集モードではない
) {
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'mediawiki.template.mustache']).then(function () {
/** タグ一覧のロード・保持・抽出を管理するオブジェクト */
var TagKeeper = (function () {
var TEMPLATE_EMBEDDED_FOR_SEARCH = 'Template:井戸端タグカテゴリ'; //このテンプレートの呼出元が完全リストとなる
var CATEGORY_PREFIX = 'Category:井戸端の話題/'; //見つかったタグカテゴリから取り除くべきプレフィクス
var SEARCH_LIMIT = 500; //1回の検索数上限
var allTags = [];
var currentTags = [];
var recommendTags = [];
var api = new mw.Api();
/** APIのクエリ結果から全タグの配列を作成、allTagsに格納する */
function loadAllTagsFromAPI(callback, continueInfo) {
var apiParams = {
format: 'json',
action: 'query',
list: 'embeddedin',
einamespace: 14,
eilimit: SEARCH_LIMIT,
eititle: TEMPLATE_EMBEDDED_FOR_SEARCH,
continue: '',
};
$.extend(apiParams, continueInfo);
api.get(apiParams).done(function (data) {
if (!data.query || !data.query.embeddedin) return;
var tags = data.query.embeddedin.map(function (ent) {
return ent.title.substring(CATEGORY_PREFIX.length);
});
allTags = allTags.concat(tags);
//1回のロード件数 (SEARCH_LIMIT) より多ければ続きをロードする
if (data['continue']) {
loadAllTagsFromAPI(callback, data['continue']);
} else {
//ロード終了後処理
callback();
}
});
}
return {
/** 全タグのロードとページ解析 完了後コールバック実行 */
initialize: function (pageText, callback) {
loadAllTagsFromAPI(function () {
TagKeeper.parse(pageText);
callback();
});
},
/** タグの選択状態切り替え */
toggle: function (tag) {
var i = currentTags.indexOf(tag);
var toggledOn = i < 0;
if (toggledOn) currentTags.push(tag);
else currentTags.splice(i, 1);
return toggledOn;
},
/** 現在のタグ */
current: function () {
return currentTags;
},
/** 使用可能なすべてのタグ */
all: function () {
return allTags;
},
/** 編集エリア内で見つかったタグ候補 */
recommend: function () {
return recommendTags;
},
/** クエリ文字列に中間一致するタグの配列を返す */
search: function (query) {
return allTags.filter(function (tag) {
return tag.toUpperCase().indexOf(query.toUpperCase()) >= 0;
});
},
/** 編集エリアのウィキテキストを解析する */
parse: function (pageText) {
//現在記入済みのタグを取得
var m = pageText.match(/\{\{vptag(?:\s*\|\s*([^{}]*?)\s*)?\}\}/i);
if (m && m[1] && m[1].length > 0) {
currentTags = m[1].split(/\s*\|\s*/);
}
pageText = pageText
.toUpperCase()
.replace(/\(UTC\)/g, '') //署名の(UTC)は拾わないように除去
.replace(/<!--.+?-->/g, ''); //コメント内を拾わない
//井戸端タグ以降を検索対象とする
var searchStart = pageText.indexOf('{{VPTAG');
if (searchStart < 0) searchStart = 0;
recommendTags = allTags.filter(function (tag) {
return pageText.indexOf(tag.toUpperCase(), searchStart) >= 0;
});
},
};
})();
/** タグの追加/削除履歴を保持するオブジェクト(要約欄記載用) */
var TagHistory = (function () {
var added = [],
removed = [];
var SUMMARY_HEAD = '[[WP:VPTAG|井戸端タグ]]';
var SUMMARY_TAIL = '(vpTagHelper)';
function toggleTag(tag, fromArray, toArray) {
var i = fromArray.indexOf(tag);
if (i >= 0) {
fromArray.splice(i, 1);
return;
}
if (toArray.indexOf(tag) < 0) {
toArray.push(tag);
}
}
return {
//メソッド
/** タグ追加履歴を追加する */
add: function (tag) {
toggleTag(tag, removed, added);
},
/** タグ削除履歴を追加する */
remove: function (tag) {
toggleTag(tag, added, removed);
},
/** 要約欄テキストを生成する */
generateSummary: function () {
if (added.length === 0 && removed.length === 0) {
return '';
}
return [
SUMMARY_HEAD,
added
.map(function (tag) {
return '+' + tag;
})
.join(' '),
removed
.map(function (tag) {
return '-' + tag;
})
.join(' '),
SUMMARY_TAIL,
]
.join(' ')
.replace(/ {2,}/g, ' ');
},
/** 要約欄テキストを解析し履歴を作成する */
parseSummary: function (summary) {
added = [];
removed = [];
var summaryTailIndex = summary.length - SUMMARY_TAIL.length;
if (summary.indexOf(SUMMARY_HEAD) !== 0 || summary.indexOf(SUMMARY_TAIL, summaryTailIndex) == -1) {
return;
}
var tags = summary.substring(SUMMARY_HEAD.length + 1, summaryTailIndex - 1).split(' ');
tags.forEach(function (i, tag) {
if (!/^[+-]/.exec(tag)) return;
var hist = RegExp.lastMatch == '+' ? added : removed;
hist.push(tag.substring(1));
});
},
};
})();
/** MediaWikiページの要素にアクセスするオブジェクト */
var PageUI = (function () {
//MediaWikiの使用するid
var ID_TEXTAREA = 'wpTextbox1'; //編集エリア
var ID_SUMMARY = 'wpSummary'; //要約欄
var ID_INSERT_BEFORE = 'editform'; //ツール配置用。このidを持つ要素の直前にツールを挿入する
var _$textarea;
//ページロード後のイベント設定
$(function () {
//手動でテキスト変更した場合にタグ再読み込み
$textarea().on('change', function () {
TagKeeper.parse(PageUI.getWikiText());
});
});
function $textarea() {
_$textarea = _$textarea || $('#' + ID_TEXTAREA);
return _$textarea;
}
return {
/** 編集エリアのウィキテキストを取得する */
getWikiText: function () {
return $textarea().val();
},
/** 編集エリアにタグリストを反映する */
applyTags: function (tags) {
var wikiText = $textarea().val();
var reg = /\{\{vptag(?:\s*\|\s*([^{}]*?)\s*)?\}\}/i;
if (!reg.test(wikiText)) {
//テンプレートが記述されていなければ </noinclude> の直前にタグ追加
reg = /\s*(?=<\/noinclude>)/i;
if (!reg.test(wikiText)) return;
}
var dest = '{' + '{vptag|\n' + tags.join(' | ') + '\n}}';
$textarea().val(wikiText.replace(reg, dest));
},
/** 要約欄テキストを取得する */
getSummary: function () {
return $('#' + ID_SUMMARY).val();
},
/** 要約欄にテキストを適用する */
applySummary: function (summary) {
$('#' + ID_SUMMARY).val(summary);
},
/** 所定の位置にタグ操作UIを挿入する */
appendConsole: function (console) {
$('#' + ID_INSERT_BEFORE).before(console);
},
};
})();
/** タグ操作UI */
var VpTagUI = (function () {
//このツールで定義するid/class
var ID_RECOMMEND_CONTAINER = 'vptag-recommend'; //タグ候補表示部のid
var ID_SEARCH_RESULT_CONTAINER = 'vptag-searchresult'; //検索結果表示部のid
var ID_SEARCH_INPUT = 'vptag-searchinput'; //検索文字入力欄のid
var ID_APPEND_BUTTON = 'vptag-append'; //タグ追加ボタンのid
var CLASS_TOGGLED = 'vptag-toggled'; //ボタントグル状態のclass
var CONTAINER_HTML = //ツール本体HTML
'<div id="vptag-edittools">\n' +
'<form action="#"><p>\n' +
'<label for="' +
ID_SEARCH_INPUT +
'" class="vptag-container-label">井戸端タグ検索:</label>\n' +
'<input id="' +
ID_SEARCH_INPUT +
'" type="text" />\n' +
'<input type="text" style="position:absolute; visibility:hidden" />\n' + //dummy 誤送信防止
'<button id="' +
ID_APPEND_BUTTON +
'" type="button">追加</button>\n' +
'</p></form>\n' +
'<p id="' +
ID_SEARCH_RESULT_CONTAINER +
'" class="vptag-container"></p>\n' +
'<p class="vptag-container">\n' +
'<span class="vptag-container-label">タグ候補:</span>\n' +
'<span id="' +
ID_RECOMMEND_CONTAINER +
'"></span>\n' +
'</p>\n' +
'</div>';
var tagButtonsTemplate =
'{{#tags}}' +
'<a class="vptag-button' +
'{{#toggled}} ' +
CLASS_TOGGLED +
'{{/toggled}}">' +
'{{name}}' +
'</a> ' +
'{{/tags}}';
/** タグボタンの生成 */
function createTagButtons(tags) {
var data = {};
data.tags = tags.map(function (tag) {
return {
name: tag,
toggled: TagKeeper.current().indexOf(tag) >= 0,
};
});
return Mustache.render(tagButtonsTemplate, data);
}
/** タグボタンクリック時処理 */
function tagButtonOnclick(e) {
var tag = $(this).text();
var toggledOn = TagKeeper.toggle(tag);
PageUI.applyTags(TagKeeper.current());
$(this).toggleClass(CLASS_TOGGLED, toggledOn);
if (toggledOn) {
TagHistory.add(tag);
} else {
TagHistory.remove(tag);
}
PageUI.applySummary(TagHistory.generateSummary());
}
/** 検索結果を表示する */
function searchTags(query) {
$('#' + ID_SEARCH_RESULT_CONTAINER).html('');
if (query === '') return;
$('#' + ID_SEARCH_RESULT_CONTAINER)
.empty()
.append(createTagButtons(TagKeeper.search(query)));
}
return {
/** 操作UIの生成・イベント設定 */
setup: function () {
//ツールフォームの生成・挿入
PageUI.appendConsole($(CONTAINER_HTML));
//イベント設定
$('#' + ID_SEARCH_INPUT).keyup(function (e) {
searchTags($(this).val());
});
$('#' + ID_APPEND_BUTTON).click(function (e) {
TagKeeper.toggle($('#' + ID_SEARCH_INPUT).val());
PageUI.applyTags(TagKeeper.current());
});
$('#' + ID_RECOMMEND_CONTAINER + ', #' + ID_SEARCH_RESULT_CONTAINER).on('click', 'a', tagButtonOnclick);
},
/** タグ候補を更新する */
refreshRecommend: function () {
$('#' + ID_RECOMMEND_CONTAINER)
.empty()
.append(createTagButtons(TagKeeper.recommend()));
},
};
})();
/* ページロード後処理 */
$(function () {
VpTagUI.setup();
TagKeeper.initialize(PageUI.getWikiText(), VpTagUI.refreshRecommend);
//プレビュー時、要約欄に記載済みの操作履歴を取得
TagHistory.parseSummary(PageUI.getSummary());
});
});
} //end if