コンテンツにスキップ

利用者:MawaruNeko/ShowSource.js

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

多くの WindowsLinux のブラウザ

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

Mac における Safari

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

Mac における ChromeFirefox

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

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

/*
 * ソースを表示するカスタムJS
 * Custom JS to show wiki source
 * 
 * 説明:
 *   カスタムJSとして導入して下さい。
 *   ページのWikiのソース・各章のWikiのソースを表示するリンクを作成します。
 *     * ページ上部の「閲覧」・「編集」タブの間に
 *     * ページ先頭
 *     * 目次が表示されている場合は、各章の見出し
 *   ソースは、Wikiのソースと、テンプレート展開後のソースを表示することができます。
 *   ダイアログのSourceタブではテキストエリアにWikiソースを、
 *   Linksタブでは内部リンクとテンプレートをリンク型式で表示します。
 *   内部リンクとテンプレートは、[[]]、{{}}で囲われたものをリンクと認識するため、
 *   <nowiki>タグ内などにあっても認識してしまいます。
 *   また、サブページのリンクには対応していません。
 * 
 * Description:
 *   Use this file as custom JS.
 *   This script creates links to show wiki source.
 *     * between the tab of view and edit
 *     * on the top of the page
 *     * on each header if the table of contents exists
 *   Wiki source can be shown in original or expanded.
 *   Wiki source is shown in dialog;
 *   Source tab in the dialog shows Wiki source in textarea.
 *   Links tab shows Wiki source with links of templates.
 *   The recognition of links has some limitations.
 * 
 * このファイルはパブリックドメインとします。
 * This file is public domain.
 */

(function () {
  'use strict';

  var spaceRegexp = /\s/gm;

  /* create link elements from wikiSource */
  function getLinkElements(wikiSource, config) {
    var ns_template = config.wgNamespaceIds.template;
    var pageName = config.wgPageName;

    function getLinkPageName(linkName) {
      linkName = linkName.replace(spaceRegexp, ' ');
      var firstChar = (linkName.slice(0, 1) === '/');
      if (firstChar === '/') {
        return pageName + linkName;
      } else {
        try {
          return new mw.Title(linkName).getPrefixedText();
        } catch (exception) {
          return null;
        }
      }
    }
    function getTemplatePageName(templateName) {
      templateName = templateName.replace(spaceRegexp, ' ');
      var firstChar = (templateName.slice(0, 1) === '/');
      if (firstChar === '/') {
        return pageName + templateName;
      } else {
        try {
          return new mw.Title(templateName, ns_template).getPrefixedText();
        } catch (exception) {
          return null;
        }
      }
    }

    var linkRegexp = /\[\[([^\{\}\[\]<>\#\|]+)(\]\]|\|)/mg;
    var linkNameIndex = 1;
    var linkNameIndexInRegexp = 2;
    var templateRegexp = /\{\{([^\{\}\[\]<>\#\|]+)(\}\}|\|)/mg;
    var templateNameIndex = 1;
    var templateNameIndexInRegexp = 2;

    function getPositions(regexp, nameIndex, nameIndexInRegexp, toPageName) {
      var r = new RegExp(regexp);
      var match;
      var results = [];
      while ((match = r.exec(wikiSource))) {
        var name = match[nameIndex];
        var pageName = toPageName(name);
        if (pageName) {
          results.push({
            startPos: (match.index + nameIndexInRegexp),
            name: name,
            pageName: pageName,
            url: mw.util.getUrl(pageName),
          });
        }
        /* if toPageName cannot parse name, just skip it */
      }
      return results;
    }

    var positionResults = getPositions(linkRegexp, linkNameIndex, linkNameIndexInRegexp, getLinkPageName).
      concat(getPositions(templateRegexp, templateNameIndex, templateNameIndexInRegexp, getTemplatePageName)).
      sort(function (a, b) { return a.startPos - b.startPos; });

    var elements = [];
    var restStrPos = 0;
    positionResults.forEach(function (position, index) {
      var pre = wikiSource.slice(restStrPos, position.startPos);
      elements.push(document.createTextNode(pre));
      elements.push($('<a>').attr('target', '_blank').attr('href', position.url).attr('title', position.pageName).text(position.name));
      restStrPos = position.startPos + position.name.length;
    });
    var restStr = wikiSource.slice(restStrPos);
    elements.push(document.createTextNode(restStr));

    return elements;
  }

  var sourceDialog = {
    windowManager: null,
    tabDialog: null,
    sourceTextInput: null,
    linksTabPanel: null,
    linksPanel: null,
  };
  function createSourceDialog() {
    /* MessageDialog with Tab IndexLayout */
    /* config.tabPanels: tab panels */
    function TabDialog(config) {
      TabDialog.super.call(this, config);
      this.tabPanels = config.tabPanels;
    }
    OO.inheritClass(TabDialog, OO.ui.MessageDialog);

    TabDialog.static.name = 'tabDialog';
    TabDialog.static.actions = [{ action: 'accept', label: 'Close' }];

    TabDialog.prototype.initialize = function () {
      TabDialog.super.prototype.initialize.apply(this, arguments);
      var dialog = this;
      this.indexLayout = new OO.ui.IndexLayout();
      this.indexLayout.addTabPanels(this.tabPanels);
      /*
        this.indexLayout.on('set', function (tabPanel) {
            dialog.updateSize();
          });
      */
      this.indexLayout.setTabPanel(this.tabPanels[0].name);
      this.container.$element.append(this.indexLayout.$element);
      this.bodyHeight = null;
    };

    TabDialog.prototype.getBodyHeight = function () {
      /* plus 5 for workaround to avoid showing scroll */
      this.bodyHeight = this.bodyHeight || TabDialog.super.prototype.getBodyHeight.call(this) +
        this.indexLayout.getCurrentTabPanel().$element.outerHeight(true) + 5;
      return this.bodyHeight;
    };

    TabDialog.prototype.getSetupProcess = function (data) {
      this.indexLayout.setTabPanel(this.tabPanels[0].name);
      return TabDialog.super.prototype.getSetupProcess.call(this, data);
    };


    sourceDialog.windowManager = new OO.ui.WindowManager();
    $('body').append(sourceDialog.windowManager.$element);
    var sourceTabPanel = new OO.ui.TabPanelLayout('source', { label: 'Source', padded: true, expanded: false });
    sourceDialog.linksTabPanel = new OO.ui.TabPanelLayout('links', { label: 'Links', padded: true, expanded: true });

    sourceDialog.sourceTextInput = new OO.ui.MultilineTextInputWidget({ readOnly: true, rows: 20 });
    sourceTabPanel.$element.append(sourceDialog.sourceTextInput.$element);

    sourceDialog.linksPanel = new OO.ui.PanelLayout({ padded: true, expanded: false, scrollable: true, classes: ['show-source-links-panel'] });
    sourceDialog.linksTabPanel.$element.append(sourceDialog.linksPanel.$element);

    sourceDialog.tabDialog = new TabDialog({ tabPanels: [ sourceTabPanel, sourceDialog.linksTabPanel ] });

    sourceDialog.windowManager.addWindows([sourceDialog.tabDialog]);
  }

  function showSourceDialog(source, config) {
    if (!sourceDialog.windowManager) {
      createSourceDialog();
    }
    sourceDialog.sourceTextInput.setValue('');
    sourceDialog.linksPanel.$element.empty();

    var instance = sourceDialog.windowManager.openWindow(sourceDialog.tabDialog, { size: 'large' });

    instance.opened.then(function () {
      sourceDialog.sourceTextInput.setValue(source);
    });
    sourceDialog.linksTabPanel.once('active', function(){
      var linksContent = getLinkElements(source, config);
      sourceDialog.linksPanel.$element.empty().append(linksContent);
    });
  }

  function setShowSourceInDialog(config) {
    $(document).on('click', '.show-source-in-dialog', function (event) {
      var url = $(this).attr('href');
      $.get(url, function (source) {
        showSourceDialog(source, config);
      });
      return false;
    });
  }

  /* section: nullable */
  /* needExpand: boolean */
  function getWikiSourceUrl(title, curid, oldid, section, needExpand) {
    var params = {
      action: 'raw',
    };
    if (curid) {
      params.curid = curid;
      params.oldid = oldid;
    } else {
      params.title = title;
    }
    if (section || section === 0) {
      params.section = section;
    }
    if (needExpand) {
      params.templates = 'expand';
    }
    return mw.util.wikiScript() + '?' + $.param(params);
  }
  
  function main(config) {
    $('span.mw-editsection a[href]').each(function () {
      var $editLink = $(this);
      var uri = new mw.Uri($(this).attr('href'));
      if (uri.query.action === 'edit') {
        var $headline = $editLink.closest('span.mw-editsection').prevAll('.mw-headline[id]');
        $headline.
          attr('data-section-number', uri.query.section.replace(/^T\-/, '')).
          attr('data-title', uri.query.title);
      }
    });
    $('#toc a .toctext').each(function (idx, toctextElem) {
      var tocsectionClassNamePrefix = 'tocsection-';
      var $toctextElem = $(toctextElem);
      var href = $toctextElem.closest('a[href]').attr('href');
      var tocsection = $toctextElem.closest('li').attr('class').split(' ').filter(function (className) {
        return className.substr(0, tocsectionClassNamePrefix.length) === tocsectionClassNamePrefix;
      }).map(function (className) {
        return className.substr(tocsectionClassNamePrefix.length);
      }) [0];
      if (href.substr(0, 1) === '#' && tocsection) {
        var anchor = href.substr(1);
        if (anchor !== '') {
          $(document.getElementById(anchor)).
            attr('data-section-number', tocsection).
            attr('data-curid', config.wgArticleId);
        }
      }
    });
    var topPlaceholder = $('<span>').attr('data-section-number', 0).attr('data-curid', config.wgArticleId);
    if ($('#firstHeading .mw-editsection') [0]) {
      $('#firstHeading .mw-editsection').before(topPlaceholder);
    } else {
      $('#firstHeading').append(topPlaceholder);
    }
    $('[data-section-number]').each(function (idx, elem) {
      var $elem = $(elem);
      var section = $elem.attr('data-section-number');
      var title = $elem.attr('data-title');
      var curid = $elem.attr('data-curid');
      var $sourcesection = $('<span>').addClass('mw-editsection').insertAfter($elem);
      $('<span>').addClass('mw-editsection-bracket').text('[').appendTo($sourcesection);
      $('<a>').addClass('show-source-in-dialog').text('ソース').
        attr('href', getWikiSourceUrl(title, curid, config.wgRevisionId, section)).appendTo($sourcesection);
      $('<span>').addClass('mw-editsection-divider').css('margin-right', '0.25em').text('|').appendTo($sourcesection);
      $('<a>').addClass('show-source-in-dialog').text('展開').
        attr('href', getWikiSourceUrl(title, curid, config.wgRevisionId, section, true)).appendTo($sourcesection);
      $('<span>').addClass('mw-editsection-bracket').text(']').appendTo($sourcesection);
    });

    /* show-source-in-dialog */
    var $nextLiAfterCaView = $('#ca-view+*');
    mw.util.addPortletLink ('p-views', getWikiSourceUrl(null, config.wgArticleId, config.wgRevisionId, null),
      'ソース', 'ca-source-view', 'ソースを表示', null, $nextLiAfterCaView);
    mw.util.addPortletLink ('p-views', getWikiSourceUrl(null, config.wgArticleId, config.wgRevisionId, null, true),
      '展開', 'ca-source-expand', 'ソースを展開', null, $nextLiAfterCaView);
    
    $('#ca-source-view a').addClass('show-source-in-dialog');
    $('#ca-source-expand a').addClass('show-source-in-dialog');

    setShowSourceInDialog(config);

    mw.util.addCSS(
      '.show-source-links-panel { white-space: pre-wrap; }\n' +
      ''
    );
    
    $('.mw-editsection-divider').show();
  }

  $(function () {
    mw.loader.using(['mediawiki.util', 'mediawiki.Title', 'oojs-ui']).then(function () {
      var config = mw.config.get(['wgAction', 'wgNamespaceNumber', 'wgArticleId', 'wgRevisionId', 'wgNamespaceIds', 'wgPageName']);
      if ((config.wgAction === 'view') && (config.wgNamespaceNumber >= 0) && config.wgArticleId) {
        main(config);
      }
    });
  });
}) ();