コンテンツにスキップ

利用者:Cpro/TitleChecker.js

お知らせ: 保存した後、ブラウザのキャッシュをクリアしてページを再読み込みする必要があります。

多くの WindowsLinux のブラウザ

  • Ctrl を押しながら F5 を押す。

Mac における Safari

  • Shift を押しながら、更新ボタン をクリックする。

Mac における ChromeFirefox

  • Cmd Shift を押しながら R を押す。

詳細についてはWikipedia:キャッシュを消すをご覧ください。

/////////////////////////////////////////////////////////////////
// 記事名チェッカ
//   1.0 written by Tietew and Hatukanezumi
//
// See [[MediaWiki‐ノート:Common.js/記事名チェッカ/仕様]].
/////////////////////////////////////////////////////////////////

/*
 * 無効化のフラグ変数。無効化は、利用者のユーザスクリプトで真に設定することでする。
 */
//window.disableTitleChecker = false;

if(
	// 検査は標準名前空間の記事に対してのみ行う。
	mw.config.get('wgNamespaceNumber') === 0 && 
	// 検査は利用者が新規作成または編集しようとしたときにのみ行う。
	(mw.config.get('wgAction') == 'edit' || mw.config.get('wgAction') == 'submit')
) {
mw.loader.using(['mediawiki.util', 'mediawiki.template.mustache', 'oojs-ui-core', 'oojs-ui-widgets']).done(function() {
    /** mw.config の内容を保持 */
    var conf = mw.config.get([
		'wgArticleId',
        'wgArticlePath',
		'wgIsRedirect',
        'wgPageName',
        'wgServer',
		'wgTitle',
		'skin'
    ]);
	
	/** 記事名チェッカによる処理を適用しない記事名のリスト (除外リスト)。 */
	var EXCLUDED_TITLES = [
		// 項目名, ...
	];

	// 記事ごとの無効化。除外リストにあれば検査をしない。
	if ($.inArray(EXCLUDED_TITLES, conf.wgTitle) >= 0 ||
		$.inArray(EXCLUDED_TITLES, conf.wgTitle.replace(/_/g, ' ')) >= 0) {
		return;
	}
	
	//警告レベル
	var PERMIT = 0; //許可する
	var WARN = 1;   //警告する
	var DENY = 2;   //拒否する
	
	/** 新規作成記事であるかどうか */
	var isNewArticle = (conf.wgArticleId === 0);
	
	/**
	 * テスト項目の定義
	 * 以下のパラメータを持つオブジェクトの配列。
	 *   pattern: テストする正規表現のパターン
	 *   level:   警告レベル (PERMIT|WARN|DENY)
	 *   message: 不合格時の表示テキスト
	 *   guides:  不合格時に表示するガイドラインの配列
	 *   skip:    特定条件で検査をスキップする場合 true を設定する
	 */
	var tests = [
		/*
		 * 書式の検査
		 */

		// 記事名の全体を「」または『』などでくくっているもの、ないしは、その後に曖昧さ回避の括弧があるもの。
		{
			pattern: '^[「『].*[」』]([ _]+[(][^)]+[)])?$',
			level:   WARN,
			message: '記事名が鈎括弧でくくられています。芸術作品のタイトルは鈎括弧でくくるべきではありません(正式名称に鉤括弧を含むものは例外です)。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト", "Wikipedia:記事名の付け方#小説・詩・映画・舞台・音楽・絵画など芸術作品のタイトル"],
			skip:    false
		},
		// 記事名の末尾の括弧書きに半角の小括弧(丸括弧)を使用する場合に、左括弧の前に半角スペースがないもの。
		// 注: 入れ子は2重まで。
		{
			pattern: '[^ ][(]([(][^()]*[)]|[^()])*[)]$',
			level:   WARN,
			message: '記事名の最後の左括弧の前に半角スペースがありません: %s。曖昧さ回避の括弧である場合は、括弧の前に半角スペースを入れてください。名称自体に括弧を含んでいる場合はこの限りではありません。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト", "Wikipedia:曖昧さ回避#曖昧さ回避の種類と方法"],
			skip:    false
		},
		// 記事名に半角の小括弧(丸括弧)を使用する場合に、括弧の左右が対称でないもの。
		// 注: 入れ子は2重まで。
		{
			pattern: '[(]([(][^()()]*[)]|[(][^()()]*[)]|[^()()])*[)]|[(]([(][^()()]*[)]|[(][^()()]*[)]|[^()()])*[)]',
			level:   WARN,
			message: '括弧の左右が対称ではありません: %s。両方を、半角括弧か全角括弧に統一してください。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト"],
			skip:    false
		},
		// 記事名に実体参照または数値文字参照を含んでいるもの。
		{
			pattern: '([&][#]?[\dA-Za-z]+;)+',
			level:   DENY,
			message: '記事名に実体参照を含んでいます: %s。実体参照を記事名に使うべきではありません。',
			guides:  ["Wikipedia:記事名の付け方#特殊記号の使用は慎重にすること"],
			skip:    false
		},

		/*
		 * 使用文字種の検査
		 */

		// 技術的な考慮 (拒否)
		{
			pattern: '[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDFFE\uDFFF]',
			level:   DENY,
			message: '文字でないものを含んでいます。文字でないものを記事名に使うべきではありません。',
			guides:  [],
			skip:    false
		},
		{
			pattern: '[\u0080-\u009F\uFFF0-\uFFFD\uDB40-\uDB7F]',
			level:   DENY,
			message: '制御文字を含んでいます。制御文字を記事名に使うべきではありません。',
			guides:  [],
			skip:    false
		},
		{
			pattern: '[\u00A0]',
			level:   DENY,
			message: 'ノーブレークスペースを含んでいます。通常のスペースを使ってください。',
			guides:  [],
			skip:    false
		},
		{
			pattern: '[\u00AD]',
			level:   DENY,
			message: 'ソフトハイフンを含んでいます。ソフトハイフンを記事名に使うべきではありません。',
			guides:  [],
			skip:    false
		},
		{
			pattern: '[\u2000-\u200A\u200B\u205F]',
			level:   DENY,
			message: '特別な幅のスペースを含んでいます。通常のスペースを使ってください。',
			guides:  [],
			skip:    false
		},
		{
			pattern: '[\u200C\u200D\u2060]',
			level:   DENY,
			message: '書式制御文字を含んでいます。一部の言語ではこの文字を使いますが、記事名には日本語を使ってください。',
			guides:  ["Wikipedia:記事名の付け方#日本語を使うこと"],
			skip:    false
		},
		{
			pattern: '[\u2011]',
			level:   DENY,
			message: 'ノンプレーキングハイフンを含んでいます。ハイフンマイナス (-) を使ってください。',
			guides:  [],
			skip:    false
		},
		{
			pattern: '[\u2028-\u202E\u2061-\u206F]',
			level:   DENY,
			message: '書式制御文字を含んでいます。書式制御文字を記事名に使うべきではありません。',
			guides:  [],
			skip:    false
		},
		{
			pattern: '[\u202F]',
			level:   DENY,
			message: '特別な幅のノーブレークスペースを含んでいます。通常のスペースを使ってください。',
			guides:  [],
			skip:    false
		},
		{
			pattern: '[\uE000-\uF8FF\uDB80-\uDBFF]',
			level:   DENY,
			message: '私用文字を含んでいます。私用文字 (外字) を記事名に使うべきではありません。',
			guides:  [],
			skip:    false
		},
		{
			pattern: '[\uFEFF]',
			level:   DENY,
			message: '不可視な文字を含んでいます。この文字を記事名に使うべきではありません。',
			guides:  [],
			skip:    false
		},

		// その他のガイドライン等 (警告)
		{
			pattern: '[\u2160-\u217F]+',
			level:   WARN,
			message: 'ローマ数字を含んでいます: %s。これは機種依存文字です。ローマ数字は半角英字 (iやVなど) を並べて表記してください。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト", "Wikipedia:表記ガイド#ローマ数字"],
			skip:    false
		},
		{
			pattern: '[\u2460-\u2473\u24EA-\u24FF\u3251-\u325F\u32B1-\u32BF]+',
			level:   WARN,
			message: '丸数字を含んでいます: %s。これは機種依存文字です。代わりに (1), (2), (3) を使用してください。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト", "Wikipedia:表記ガイド#丸数字"],
			skip:    false
		},
		{
			pattern: '[\u2474-\u24B5\u3200-\u3250\u32C0-\u32CF\u3300-\u33FF]+',
			level:   WARN,
			message: '組文字を含んでいます: %s。片仮名や、漢字、英数字で表記してください。これは機種依存文字である場合もあります。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト", "Wikipedia:表記ガイド#略記号", "Wikipedia:表記ガイド#文字コード"],
			skip:    false
		},
		{
			pattern: '[\u3000]',
			level:   WARN,
			message: '全角空白を含んでいます。全角空白を記事名に使うべきではありません。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト", "Wikipedia:記事名の付け方#全角と半角の使い分け"],
			skip:    false
		},
		{
			pattern: '[\uFB00-\uFEFE\uFFE0-\uFFE7\uFFE8-\uFFEF]+',
			level:   WARN,
			message: '機種依存文字を含んでいます: %s。機種依存文字を記事名に使うべきではありません。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト", "Wikipedia:表記ガイド#文字コード"],
			skip:    false
		},
		{
			pattern: '[\uFF10-\uFF19]+',
			level:   WARN,
			message: '全角数字を含んでいます: %s。全角英数字を記事名に使うべきではありません。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト", "Wikipedia:記事名の付け方#全角と半角の使い分け"],
			skip:    false
		},
		{
			pattern: '[\uFF21-\uFF3A\uFF41-\uFF5A]+',
			level:   WARN,
			message: '全角英字を含んでいます: %s。全角英数字を記事名に使うべきではありません。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト", "Wikipedia:記事名の付け方#全角と半角の使い分け"],
			skip:    false
		},
		{
			pattern: '[\uFF00\uFF02\uFF04-\uFF07\uFF0A-\uFF0F\uFF1B\uFF20\uFF3C\uFF3E-\uFF40\uFF5F-\uFF60]+',
			level:   WARN,
			message: '全角英記号を含んでいます: %s。全角英記号を記事名に使うべきではありません。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト", "Wikipedia:記事名の付け方#全角と半角の使い分け"],
			skip:    false
		},
		{
			pattern: '[\uFF5E]+',
			level:   WARN,
			message: '全角チルダを含んでいます。この文字は、一部の環境で正しく表示されません。波ダッシュ (〜) か、できればハイフンマイナス (-) を使ってください。波ダッシュを使った記事名へのリダイレクトを作成しようとしている場合は、この限りではありません。',
			guides:  ["Wikipedia:表記ガイド#波ダッシュ"],
			skip:    conf.wgIsRedirect // リダイレクトなら全角チルダを許容
		},
		{
			pattern: '[\uFF61-\uFF9F]+',
			level:   WARN,
			message: '半角片仮名を含んでいます: %s。半角片仮名を記事名に使うべきではありません。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト", "Wikipedia:記事名の付け方#全角と半角の使い分け"],
			skip:    false
		},
		{
			pattern: '[\uFFA0-\uFFDF]+',
			level:   WARN,
			message: '半角ハングル字母を含んでいます: %s。半角ハングル字母を記事名に使うべきではありません。',
			guides:  ["Wikipedia:即時削除の方針#リダイレクト", "Wikipedia:記事名の付け方#全角と半角の使い分け"],
			skip:    false
		},
	];

	/**
	 * 記事名に対してテスト項目1つの結果を返す
	 * 合格していれば null を返す
	 * @param {string} title 記事名
	 * @param {{pattern: string, level: number, message: string, guides: string[], skip: boolean}} test 上記テスト項目の要素
	 * @returns {{level: number, warning: {message: string, guides: string[]}, forRedirect: boolean}|null}
	 */
	var doTest = function (title, test) {
		if(test.skip) return null;
		
		var reg = new RegExp(test.pattern, "g");
		var m = title.match(reg);
		if(!m) return null;

		var message = test.message.replace("%s", m.join(" ")).replace("%%", "%");
		
		var forRedirect = false;
		if (test.guides.length > 0 && conf.wgIsRedirect) {
			forRedirect = (test.guides.indexOf('Wikipedia:即時削除の方針#リダイレクト') >= 0);
		}

		return {
			level: test.level,
			warning: {
				message: message,
				guides: test.guides
			},
			forRedirect: forRedirect
		};
	};

	/**
	 * 警告文を画面に挿入する
	 * @param {HTMLElement} insertRef 警告文の挿入基点となるHTML要素
	 * @param {{message: string, guides: string[]}[]} warnings 警告内容
	 * @param {boolean} forRedirect リダイレクトの即時削除対象でありうるかどうか
	 */
	var insertWarnings = function(insertRef, warnings, forRedirect) {  
		/** 記事名からウィキ内の記事URIを取得する */
		var getUri = function(title) {
			return conf.wgServer +
				conf.wgArticlePath.replace('$1', mw.util.wikiUrlencode(title));
		};
		/** Wiki内のリンクを作成 */
		var wl = function(title, alias) {
			title = title.replace(/_/g, ' ');
			var uri = getUri(title);
			return '<a href="' + uri + '" title="' + title + '">' +
				(alias || title) + '</a>';
		};

		var templateBody = //<!--
			'<p><strong>警告: このページの記事名の付け方は、' +
			'当ウィキペディアのガイドラインなどにそっていないかもしれません。' +
			'理由は以下のとおりです。</strong></p>' +
			'<ul>{{#warnings}}' +
				'<li>' +
					'{{message}}' +
					'{{#guides.length}}' +
						'詳しくは、' +
						'{{#guides}}' +
							'{{^first}}、{{/first}}' +
							'<a href="{{uri}}" title="{{title}}">{{title}}</a>' +
						'{{/guides}}' +
						'を参照してください。' +
					'{{/guides.length}}' +
				'</li>' +
			'{{/warnings}}</ul>' +
			'<p>ガイドラインにそっていないときは、記事名の変更を検討してみてください。' +
			'なお、記事名を変更したときは、' +
			wl('特別:Whatlinkshere/' + conf.wgPageName, 'このページのリンク元') +
			'を調べて、新しい記事へのリンクに変更するようにしてください。</p>' +
			'<p>記事名チェック機能の詳細は、' +
			wl('Help:記事名のチェック') +
			'をご覧ください。</p>' +
			'{{#forRedirect}}' +
				'<p>編集中のページは' + wl('Wikipedia:リダイレクト', 'リダイレクト') + 'ですが、' +
				'即時削除に出せるかもしれません。' +
				'リダイレクトの即時削除に出すことができるのは、以下のすべてが該当する場合です。</p><ul>' +
					'<li>項目名の書き誤りで、それが誰が見ても明らかに誤りだとわかる。</li>' +
					'<li>項目が有益な履歴を持っていない。</li>' +
					'<li>項目がどこからもリンクされていない。</li>' +
				'</ul><p>リダイレクトの即時削除についての詳細は、' + wl('Wikipedia:即時削除の方針') + 'を参照してください。</p>' +
			'{{/forRedirect}}'; //-->

		// warnings を加工: 提示ガイドライン名の配列を {title: string, uri: string} の配列にする
		warnings.forEach(function(warning) {
			warning.guides = warning.guides.map(function(guide, i) {
				return {
					title: guide,
					uri: getUri(guide),
					first: (i === 0) // 最初の要素をテンプレートに判別させるためのフラグ
				};
			});
		});

		var data = {
			warnings: warnings,
			forRedirect: forRedirect && !isNewArticle
		};

		$(insertRef).after('<div>' + Mustache.render(templateBody, data) + '</div>');
	};

	/** 編集UIを使用不可にする */
	var disableEditUi = function() {
		// 新規記事の場合は編集画面ごと隠す
		if(isNewArticle) {
			$('#editform').hide();
			return;			
		}

		var editUiIds = ['wpSummary', 'wpMinoredit', 'wpWatchthis', 'wpSave', 'wpPreview', 'wpDiff'];
		editUiIds.forEach(function(id) {
			var widgetId = id + 'Widget';
			if($('#' + widgetId)[0]) {
				// OOjs UIでラップされていたらそちらのメソッドを使う
				OO.ui.infuse(widgetId).setDisabled(true);
			} else {
				$('#' + id).prop('disabled', true);
			}
		});
		$('#wpTextbox1').prop('readonly', true);
	};
	
	/** 
	 * 警告文の挿入基点となる要素を取得する
	 * @returns {HTMLElement}
	 */
	var getInsertRefOfWarnings = function() {
		var insertRef;
		switch (conf.skin) {
			case 'cologneblue':
				insertRef = $('#specialpages')[0] ?
					$('#topbar')[0] :
					$('h1.pagetitle')[0].nextSibling;
				break;
			default:
				insertRef = $('#jump-to-nav')[0] ||
					$('#contentSub')[0];
		}
		return insertRef;
	};

	// ロード時処理: 検査の実行と不合格項目があれば警告の表示
	$(function() {
		// 利用者ごとの無効化。
		//if(window.disableTitleChecker) return;

		// 全テストを実施し結果を配列で取得する
		var results = tests.map(function(test) {
			return doTest(conf.wgTitle, test);
		}).filter(function(r) {
			// 合格時はnullが返るので除外する
			return r !== null;
		});

		// すべて合格なら終了
		if(results.length === 0) return;

		// いずれかに拒否が含まれる場合編集を禁止する
		if(results.some(function(r) { return r.level >= DENY; })) {
			disableEditUi();
		}

		var warnings = results.map(function(r) { return r.warning; });
		var forRedirect = results.some(function(r) { return r.forRedirect; });

		// 警告と拒否: 説明文を表示する。
		var insertRef = getInsertRefOfWarnings();
		insertWarnings(insertRef, warnings, forRedirect);
	});

});
} //end if: TitleChecker