Wikipedia:カスタムJS – Wikipedia

ウィキペディアで各利用者が設定できるカスタムJavaScriptカスタムJS)について解説します。「ユーザースクリプト英語版」のウィキペディア版なので、そうとも呼ばれます。これは各利用者が記述できるJavaScriptのコードで、登録利用者の利用者空間(利用者ページ)のサブページに置く(公開する)などすることでページの表示時などに実行されるものです。様々な機能を実現することができます。

既存のカスタムJSを利用する場合はカスタムJSの一覧をご覧ください。ガジェットは共有されたJavascriptで個人設定からクリックで導入できます。

導入方法[編集]

標準の方法[編集]

  1. この場合、カスタムJSを使用するには、まずアカウントを取得してログインします。
  2. 各スキン共通の各利用者用のcommon.jsを開きます。あるいは、各スキンでだけ使うには、標準のベクタースキン用には各利用者のvector.jsを使います。異なるスキンを使っている場合、特別:個人設定の「表示」タブから、使用しているスキンの「カスタムJS」を開きます。これらの js のページは、本人だけが編集できます。
  3. 上記の通り開いた common.js などの js のページに、使いたいコードを記述し保存します。既存のカスタムJS一覧の中からウィキペディア日本語版の利用者:Hogehoge/hoge.js に置かれているコードを使用する場合、次のように書いて保存します。
    mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Hogehoge/hoge.js&action=raw&ctype=text/javascript');
    

    他言語版または他プロジェクトに置かれているコードを使用する、例えば英語版の User:Foo/bar.js に置かれているコードを使用する場合は、次のように記述します。

    mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Foo/bar.js&action=raw&ctype=text/javascript');
    
  4. キャッシュを消します。これで、各ページにて保存したスクリプトが実行されるようになります。

見本[編集]

どのように編集してよいかよく分からないときは、ウィキペディア内で検索することで、他の利用者のJSを閲覧できます。まずそれを参考に編集していくと良いでしょう。

ブックマークレットを使う方法[編集]

ブックマークレットとしてスクリプトのアドレスをブックマークしておくことで、使いたい時だけクリックして使用できます。たとえば利用者:Hogehoge/hoge.js にあるスクリプトをブックマークする場合、以下のURLで登録します。

javascript:(function(){mw.loader.load('//ja.wikipedia.org/w/index.php?title=User:Hogehoge/hoge.js&action=raw&ctype=text/javascript');})()

Greasemonkeyを使う方法[編集]

Firefoxを使用している場合、Greasemonkeyにスクリプトを登録しておくことで、適宜 有効・無効を切り替えてスクリプトを利用できます(Firefox Add-onsでの紹介ページ)。アドオンのインストール後、Firefoxのメニューから「ツール」->「Greasemonkey」->「新規ユーザスクリプト」を開き、各項目を埋めて登録します。登録の方法はGreasemonkey 作り方などで検索してください。

Chromeであれば、Tampermonkeyが互換性のある拡張機能として使用できます。

カスタムJS以外のJavascript[編集]

MediawikiにはカスタムJS以外にJSを置く場所があります。

独自の変数と関数[編集]

Mediawiki上でJavaScriptを動かす場合、次のような変数と関数を利用できます。

読み込まれるファイルの一覧は「en:Wikipedia:Catalogue of CSS classes(英語)」に記載されています。mw:ResourceLoader/Modulesには、MediaWikiの関数やjQueryについて説明されています。

初期化[編集]

  • $( /* 関数 */ ) – ページの読み込みが完了する前、DOMの構築が終わった後に関数を実行します。$( document ).ready( /* 関数 */ ) と同等です。DOMの構築完了時点ではなく、画像などがロード完了してから実行したい場合は、$(window).load( /* 関数 */ )などが使用できます。
$(function() {
  /* ここに初期化コードを書く */
});

ロード[編集]

Javascriptのロード[編集]

他のページにあるJavascriptのスクリプトをロードします。Javascriptのロードは基本的に非同期に行われます。他のJavascriptのスクリプトやモジュールに依存したコードを書きたい場合は、mw.loader.using( /* モジュール名 */ ).then( /* 関数 */ )$.getScript( /* URI */ ).then( /* 関数 */ )を使用します。

  • mw.loader.load( /* URI */ ) – 任意のサイトの任意のページにあるJavaScriptソースを読み込みます(非同期)。URIで指定します。
例:
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Foo/bar.js&action=raw&ctype=text/javascript');
  • mw.loader.load( /* モジュール名 */ )mw:ResourceLoader/Core modulesに記載されているJavaScriptのモジュールを読み込みます(非同期)。配列にすることで複数同時に読み込めます。モジュールを読み込んでから、依存する処理を行いたい場合はmw.loader.using( /* モジュール名 */ ).then( /* 関数 */ )を使用して下さい。
  • mw.loader.using( /* モジュール名 */ ).then( /* 関数 */ )mw:ResourceLoader/Core modulesに記載されているJavaScriptのモジュールを読み込んでから処理を行います。配列にすることで複数同時に読み込めます。
例:
mw.loader.using( 'mediawiki.util' ).then( function() {
  alert(mw.util.wikiUrlencode("ほげ"));
} );
  • $.getScript( /* URI */ ).then( /* 関数 */ ) – 任意のサイトの任意のページにあるJavaScriptソースを読み込んでから処理を行います。URIで指定します。リクエストがキャッシュされないため、下記の$.ajaxを用いた方がよいでしょう。
  • $.ajax( { dataType: "script", cache: true, url: /* URI */ } ).then( /* 関数 */ ) – 任意のサイトの任意のページにあるJavaScriptソースを読み込んでから処理を行います。URIで指定します。キャッシュを有効にしています。
関数化すると使いやすくなるでしょう。:
jQuery.getScriptWithCache = function(url, options) {
  return $.ajax($.extend(options || {}, {
    dataType: "script",
    cache: true,
    url: url,
  }));
};
 
$.getScriptWithCache('//en.wikipedia.org/w/index.php?title=User:foo/bar.js&action=raw&ctype=text/javascript').done(function() {
  /* 処理 */
});
  • $.when($.ajax(...), $.ajax(...), ...).then(/* 処理 */) – 複数のファイルを読み込む場合、$.whenを使うことで、すべての読み込みが完了するまで処理待ちをすることができます。

CSSのロード[編集]

  • mw.loader.load( /* URI */, 'text/css' ) – 任意のサイトの任意のページにあるCSSを読み込みます。URIで指定します。

ページ情報の取得[編集]

mw.config.get の引数に以下の変数名を渡すことで、システムや記事に関する情報を得ることが出来ます。例えば、wgPageName の場合、mw.config.get( 'wgPageName' ) と記述します。
変数名と同名のグローバル変数が用意されていますが、これを直接呼び出すことは現在は非推奨となっています。
以下に主なものを示します。

変数 値の例 説明
システム全体
wgArticlePath '/wiki/$1' $1を記事名で置き換える(mw.config.get('wgArticlePath').replace('$1', '記事名'))ことで、パスを生成できます。より簡潔なmw.util.getUrl('記事名')が用意されています。
wgScript '/w/index.php' index.phpのパスを取得します。index.phpはソースの取得などに使用できます。mw:Manual:Parameters to index.php/jaを参照して下さい。
wgFormattedNamespaces
{
  '0': '',
  '1': 'ノート',
  '2': '利用者',
  ...
}

番号から名前空間の名称を得るためのマッピング
wgNamespaceIds
{
  'メディア': - 2,
  '特別': - 1,
  '': 0,
  'ノート': 1,
  '利用者': 2,
  ...
}

名前空間の名称から番号を得るためのマッピング
wgContentNamespaces [0] 記事の名前空間
wgContentLanguage 'ja' 言語設定
記事ごと
wgPageName 'Wikipedia:カスタムJS' この記事の名前空間を含めた名前(スペースはアンダースコアで置き換え)
wgTitle 'カスタムJS' この記事の名前空間を含まない記事名(スペースを含み、アンダースコアは含まない)
wgNamespaceNumber 4 この記事の名前空間の番号
wgCanonicalNamespace Project 正規化された名前空間名(例えば”Wikipedia”名前空間は”Project”、”利用者”名前空間は”User”)
wgArticleId 2276878 記事ID(特別なページや存在しないページでは0になる)
wgRevisionId 66050435 現在見ているリビジョンのID
wgCurRevisionId 66050435 その記事の最新版のリビジョンのID
wgCategories ['ウィキペディア用ツール'] カテゴリ一覧(隠しカテゴリを区別しないので注意)
wgAction 'view' アクション(mw:Manual:Parameters to index.php/ja#Actionsを参照)
wgPageContentLanguage 'ja' 記事の言語
ユーザごと
wgUserId (ユーザのID) ユーザのID
wgUserEditCount (ユーザの編集回数) ユーザの編集回数
wgUserName (ユーザ名) ユーザ名
wgUserGroups (ユーザのグループ) ユーザのグループ
wgUserLanguage 'ja' ユーザの言語
skin 'vector' 現在使っているスキンの名前

ユーザ設定の取得[編集]

  • mw.user.options.get(/* オプション名 */)mw.config.getと同様に、ユーザ設定を取得します。(user.optionsモジュールの読み込みが必要)

要素の取得[編集]

  • document.editform.wpTextbox1 – 編集画面で、編集領域のテキストボックスを取得します。
  • mw.util.$content – 記事の領域のDivエレメントをjQueryオブジェクトで取得します。(mediawiki.utilmw.loader.usingでロードして使用して下さい。)

ユティリティ[編集]

  • mw.util.addCSS – CSSの定義を追加します。(mediawiki.utilmw.loader.usingでロードして使用して下さい。)
  • mw.util.addPortletLink – 各種ツールバーにリンクを追加します。(mediawiki.utilmw.loader.usingでロードして使用して下さい。)
詳細はw:Wikipedia:User_scripts/Guide#Portlets (add custom menus and tabs)(英語)を参照してください。
  • mw.util.getParamValue – ページのURLのパラメータを取得します。(mediawiki.utilmw.loader.usingでロードして使用して下さい。)
  • mw.util.getUrl – 記事名に対して、そのURLを取得します。(mediawiki.utilmw.loader.usingでロードして使用して下さい。)
例:
mw.util.getUrl('Wikipedia:カスタムJS/一覧'); // => "https://ja.wikipedia.org/wiki/Wikipedia:%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0JS/%E4%B8%80%E8%A6%A7"
  • mw.util.wikiScript – スクリプトのパスを取得します。引数に何も指定しない場合は、index.phpのパスを返します。(mediawiki.utilmw.loader.usingでロードして使用して下さい。)
例:
mw.util.wikiScript(); // => "/w/index.php"
mw.util.wikiScript('api'); // => "/w/api.php"
  • mw.util.wikiUrlencode – 文字列を、URLエンコードします。(mediawiki.utilmw.loader.usingでロードして使用して下さい。)
例:
mw.util.wikiUrlencode('Wikipedia:カスタムJS/一覧'); // => "Wikipedia:%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0JS/%E4%B8%80%E8%A6%A7"

ローカルストレージ[編集]

  • mw.storage – ブラウザのLocalStorageをget, set, remove で使用します。(mediawiki.storageモジュールの読み込みが必要)。mediawiki.storageを参照してください。既存のライブラリのLocalStorageの使用については、MediaWikiのJavaScriptのコーディング規約を確認して下さい。

コーディング規約[編集]

カスタムJSには特にコーディング規約は定められていませんが、MediaWikiのJavaScriptのコーディング規約などを参考にすると、わかりやすいコードを書くことが出来ます。

グローバル変数によるカスタムJS外とのやりとり[編集]

グローバル変数を使用することにより、カスタムJSに設定を渡したり、カスタムJSをロード後に、カスタムJSで定義した内容を使用したりすることが出来ます。
Place for Extensions Objects in JavaScriptによれば、第三者ライブラリはmw.libsを、拡張はmw.extを使用することが多いようです。

改良型編集ツールバーの拡張[編集]

mw:Extension:WikiEditorで提供されている、改良型編集ツールバーは、カスタマイズが可能です。

カスタマイズをするにあたってのスニペット:

function CustomizeEditform() {
  $(document.editform.wpTextbox1).wikiEditor('addToToolbar', {
    // ... ここにObjectで設定を書く
  });
}

$(function () {
  if (document.editform) {
    mw.loader.using('user.options').then(function () {
      if (mw.user.options.get('usebetatoolbar') == 1) {
        mw.loader.using('ext.wikiEditor.toolbar').then(CustomizeEditform);
      }
    });
  }
});

構成[編集]

  • 改良型編集ツールバーの上段の、ボタン(main)や「上級」(advanced)「特殊文字」(characters)「ヘルプ」(help)と並んでいる欄をsectionと呼びます。
  • 「上級」(advanced)をクリックしたときに表示される領域や、main sectionをtoolbarと呼びます。
    • toolbarの縦棒で区切られた領域 group でできています。
    • groupには、buttonまたはselect(プルダウン)が並んでいます。
  • 「特殊文字」(characters)や「ヘルプ」(help)をクリックしたときに表示される領域をbookletと呼びます。
    • bookletの左側はpagesからできており、各pageはbookletの右側のtable(「ヘルプ」(help)の場合など)またはcharacters(「特殊文字」(characters)の場合の各ボタン)からできています。
  • 各ボタン(button、charactersなど)の動作は、actionで記述できます。
    • actionのtypeで、動作種別をencapsulate, replace, callback, dialogから選択します。
    • actionのoptionsで、動作種別がencapsulate, replaceの場合の動作を記述します。
      • pre: カーソル位置またはテキスト選択の前に置かれます。
      • peri: テキストを選択していないときにカーソル位置に置かれます。
      • post: カーソル位置またはテキスト選択の後に置かれます。
      • regex: 置換の正規表現
      • regexReplace: 置換後の置き換える文字列、$1などの置換パターンを使用できます。
    • actionのexecuteで、動作種別がcallbackの場合に、contextを引数に持つコールバック関数を記述します。戻り値は無視されます。編集内容を変更するには、直接wpTextbox1のvalueなどを変更する形で使います。
  • filtersオプションにCSSセレクターを記述することで、そのCSSセレクターを含むページでのみに表示を限定することができます。特に、bodyのクラスに対するセレクタbody.ns-talkbody:not(ns-talk)などが使用できます。
  • 既存のボタン・ツールバーの動作はjquery.wikiEditor.toolbar.configをご覧ください。

カスタマイズ[編集]

  • sectionに新しくbookletを追加する場合:
    {
      sections: {
        '(新しいツールバー名)': {
          type: 'booklet',
          label: '(表示名)',
          pages: {
            '(ページ名)': {
              label: '(表示名)',
              layout: 'characters',
              characters: [
                '
    '
    , { label: '(表示名)', action: { type: 'encapsulate', options: { pre: '[[', peri: '', post: ']]' } }, }, // ... ] }, // ... }, } } }
例:
{
  sections: {
    'mytools': {
      type: 'booklet',
      label: 'ツール',
      pages: {
        'mysnippet': {
          label: 'スニペット',
          layout: 'characters',
          characters: [
            '
'
, { label: '{{要出典範囲}}', action: { type: 'encapsulate', options: { pre: '{{要出典|date=" + (new Date()).getUTCFullYear() + "年' + ((new Date()).getUTCMonth() + 1) + '月|', peri: '', post: '}}' } }, }, ], }, 'myedit': { label: '編集', layout: 'characters', characters: [ { label: '改行削除', action: { type: 'replace', options: { regex: /r|n/g, regexReplace: '', } }, }, ], }, }, } } }
  • 既存のsectionにgroupを追加:
    {
      section: 'advanced',
      groups: {
        '(グループ名)': {
          label: '(名前)',
        }
      }
    }
    
  • 既存のgroupにbuttonを追加:
    {
      section: 'advanced',
      group: 'heading',
      tools: {
        '(ボタン名)': {
          label: '(名前)',
          type: 'button',
          icon: '//upload.wikimedia.org/wikipedia/commons/thumb/3/36/Help-content.svg/22px-Help-content.svg.png',
          action: {
            type: 'encapsulate',
            options: {
              pre: '',
            }
          }
        },
        // ...
      }
    }
    

Mediawikiには各種のAPIが用意されています。APIを使用するメリットとして、各種の情報(投稿回数、カテゴリ一覧)を得られること、処理しやすい整形された情報が得られること、そして応答が早いこと、などが挙げられます。

APIへのアクセスはAjaxで行います。APIサンドボックスで、APIをテストすることが出来ます。他にREST APIも用意されています。

mediawiki.api モジュール[編集]

JavaScriptでAPIを使用するときは、mediawiki.apiモジュールを使用すると便利です。

mw.loader.using('mediawiki.api').then(function () {
  var api = new mw.Api();
  api.get({
    action: 'query',
    prop: 'revisions',
    pageids: '2276878',
    rvprop: 'user|content'
  }).done(function (data) {
    console.log(data.query.pages);
  });
});

mediawiki.apiモジュールには各種プラグインがあり、例えばmediawiki.api.parseを使用すると、WikiテキストからHTMLへの変換を簡単に行うことができます。

mw.loader.using('mediawiki.api.parse').then(function () {
  var api = new mw.Api();
  api.parse('== chapter ==').done(function (html) {
    console.log(html);
  });
});

Query アクションとcontinue[編集]

Query アクションは、Wikiの情報や一覧を取得するために使用するアクションです。
Query アクションの結果が、1回のAPIで取得できる件数の上限を超えた場合、APIの結果にcontinueが追加されます。
APIのリクエストのパラメータにcontinueの要素をマージすることで、APIの結果を継続して取得することができます。

以下のような関数を作成しておくとよいでしょう。

// overwrites obj1
// deepMerge({a: {b: [2], c: 3}, d: {e: {f: [4, 5]}}, g: 6}, {a: {b: [7], c: 8}, d: {e: {f: [9, 10]}}, h: 11})
//   => {a: {b: [2, 7], c: 8}, d: {e: {f: [4, 5, 9, 10]}}, g: 6, h: 11}
function deepMerge(obj1, obj2) {
  $.each(obj2, function (key, value2) {
    if (key in obj1) {
      var value1 = obj1[key];
      if (Array.isArray(value1)) {
        if (Array.isArray(value2)) {
          obj1[key] = value1.concat(value2);
        } else {
          value1.push(value);
        }
      } else if (typeof value1 === 'object') {
        if (typeof value2 === 'object') {
          deepMerge(value1, value2);
        } else {
          obj1[key] = value2;
        }
      } else {
        obj1[key] = value2;
      }
    } else {
      obj1[key] = value2;
    }
  });
  return obj1;
}

// iterate getting query api if request returned continue
// api: mw.Api
// options: Object, get options
// maxTry: integer, nullable (default 10), max of iterates count
// interval: integer, nullable (default 1000), milliseconds to sleep between each query
// deferred: jQuery.Deferred, nullable
// currentResult: Object, nullable, current query result
// returns deferred object
//   deferred return value: query result (data.query)
function iterateQuery(api, options, maxTry, interval, deferred, currentResult) {
  if (typeof (maxTry) !== 'number') {
    maxTry = 10;
  }
  interval = interval || 1000;
  deferred = deferred || $.Deferred();
  currentResult = currentResult || {
  };
  if (maxTry === 0) {
    deferred.reject('maxTry is 0');
    return deferred;
  }
  api.get($.extend({
    action: 'query',
  }, options)).done(function (data) {
    currentResult = deepMerge(currentResult, data.query);
    if (data.continue ) {
      setTimeout(function () {
        iterateQuery(api, $.extend(options, data.continue ), maxTry - 1, interval, deferred, currentResult);
      }, interval);
    } else {
      deferred.resolve(currentResult);
    }
  });
  return deferred.promise();
}

例: 管理者一覧を取得

iterateQuery(new mw.Api(), {
  list: 'allusers',
  augroup: 'sysop',
  aulimit: '20',
}).then(function(query){
  console.log(query.allusers);
});

別ドメインへのアクセス[編集]

JavaScriptでMediawikiAPIを使用する際のひとつの制約として、JavaScriptが持つ同一生成元ポリシー(別ドメインの情報に直接アクセスできない)により、そのままでは多言語版やウィキデータなどの他プロジェクトへのアクセスは出来ません。多言語版や他プロジェクトにアクセスするには、en:Cross-Origin Resource Sharing(CORS)を使います。mediawiki.ForeignApiモジュールを使用することで、CORSを簡単に実現できます。

mw.loader.using('mediawiki.ForeignApi').then(function () {
  var api = new mw.ForeignApi('https://en.wikipedia.org/w/api.php');
  api.get({
    action: 'query',
    list: 'recentchanges',
  }).done(function (data) {
    console.log(data);
  });
});

デバッグ[編集]

最も原始的なデバッグの方法はアラートとコメントアウトを使う方法です。

アラートとコメントアウト[編集]

アラート[編集]

アラートを使う方法は問題が起きていそうな場所に次のようなコードを入れて実行することです。

これにより変数がどういう値を取っているか、またif文などの分岐を含む文であればその場所が実行されているかが、ポップアップの有無とその内容により分かります。

コメントアウト[編集]

コメントアウトを使う方法も単純です。問題が起きていそうな場所で、行の先頭にスラッシュ二つを入れるか、または/* */で囲みます。

//abc = hoge.IndexOf('foo');

/*
abc = hoge.IndexOf('foo');
x = abc;
*/

こうした一部分のコメントアウトで今まで動かなかったコードが動き出したら、問題はコメントアウトした部分の周辺にあることが分かります。

ブラウザ別のデバッグ機能[編集]

現在は多くのブラウザで、F12 を押すとデバッグ機能が表示されます。

Firefox[編集]

  • CTRL Shift を押しながら K を押します。
    • あるいは、ウィンドウ右上の「三」のようにみえるマークを押す。メニューから「開発ツール」->「WEBコンソール」をクリックします。

アドオンのJavaScript Debuggerもあります。追加してからFirefoxを再起動し、メニューから「ツール」->「JavaScript Debugger」をクリックすると、デバッガが立ち上がります。ブレークポイントの指定、変数のウォッチなどが可能です。

Chrome[編集]

  • CTRL Shift を押しながら I を押します。
    • あるいはウィンドウ右上の「三」のようにみえるマークを押す ->「その他のツール」->「デベロッパーツール」をクリック。

表示されたペインの「Console」にエラーなどが表示されます。

Opera[編集]

画面上で右クリック->「要素を調べる」をクリック。表示されたペインで「Console」という所をクリックすると、デバッガが表示されます。

Microsoft Edge / Internet Explorer[編集]

F12 を押すとウィンドウが表示されます。または、メニューから「ツール」->「F12 開発者ツール」でも同様です。ここで「スクリプト」という所をクリックすると、デバッガが表示されます。

関連項目[編集]