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

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

導入方法 編集

標準の方法 編集

  1. この場合、カスタムJSを使用するには、まずアカウントを取得してログインします。
  2. スキン共通の各利用者用のcommon.jsを開きます。あるいは、各スキンでだけ使うには、標準のベクター2022年版スキン用には各利用者のvector-2022.jsを、ベクタークラシックスキン用には各利用者のvector.jsを使います。異なるスキンを使っている場合、特別:個人設定の「表示」タブから、使用しているスキンの「カスタムJS」を開きます。これらの js のページは、本人だけが編集できます。
  3. 上記の通り開いた common.js などの js のページに、使いたいコードを記述し保存します。既存のカスタムJS一覧の中からウィキペディア日本語版の利用者:ウィキ助/hoge.js に置かれているコードを使用する場合、次のように書いて保存します。
    mw.loader.load('//ja.wikipedia.org/w/index.php?title=利用者:ウィキ助/hoge.js&action=raw&ctype=text/javascript');
    
    他言語版または他プロジェクトに置かれているコードを使用する、例えば英語版User:Example/bar.js (実際にはありません)に置かれているコードを使用する場合は、次のように記述します。
    mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Example/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/一覧'); // => "/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: [
                '<br/>',
                {
                  label: '(表示名)',
                  action: {
                    type: 'encapsulate',
                    options: {
                      pre: '[[',
                      peri: '',
                      post: ']]'
                    }
                  },
                },
                // ...
              ]
            },
            // ...
          },
        }
      }
    }
    
例:
{
  sections: {
    'mytools': {
      type: 'booklet',
      label: 'ツール',
      pages: {
        'mysnippet': {
          label: 'スニペット',
          layout: 'characters',
          characters: [
            '<br/>',
            {
              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(Representational State Transfer)を使用する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);
  });
});

デバッグ 編集

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

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

アラート 編集

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

alert(変数名);

これにより変数がどういう値を取っているか、また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 開発者ツール」でも同様です。ここで「スクリプト」という所をクリックすると、デバッガが表示されます。

関連項目 編集