MediaWiki:Gadget-pcomment.js

注意: 保存後、変更を確認するにはブラウザーのキャッシュを消去する必要がある場合があります。

  • Firefox / Safari: Shift を押しながら 再読み込み をクリックするか、Ctrl-F5 または Ctrl-R を押してください (Mac では ⌘-R)
  • Google Chrome: Ctrl-Shift-R を押してください (Mac では ⌘-Shift-R)
  • Internet Explorer / Microsoft Edge: Ctrl を押しながら 最新の情報に更新 をクリックするか、Ctrl-F5 を押してください
  • Opera: Ctrl-F5を押してください
/***********************************************
 * PukiWiki pcommentプラグイン風コメントボックス
 * 
 * by pneuma01
 ***********************************************/
var i18n = {
    summary_edit_with_pcomment: "PCommentでコメント",
    send_button_caption: "コメントを追加"
};

$(function () {

    const intersectionObserver = new IntersectionObserver(function (entries) {
        entries.forEach(function (entry) {
            //画面内に入った
            if (entry.isIntersecting) {

                //コメント読み込み
                loadComment(entry);

                //監視対象の解除
                intersectionObserver.unobserve(entry.target);
            }
        });
    });

    //監視開始
    var observe_target = document.querySelectorAll(".pcomment-widget");
    observe_target.forEach(function (element) {
        intersectionObserver.observe(element);
    });

});

//コメント読み込み
function loadComment(entry) {
    
    //トークページ名を取得(存在しない場合でも)
    var targetPage = entry.target.getAttribute("data-page") || mw.config.get('wgPageName');
    var talkPage = new mw.Title( targetPage ).getTalkPage().getPrefixedText();

    //トークページのwikitextを取得
    $.when(new mw.Api().get({
            action: 'query',
            format: 'json',
            titles: talkPage,
            prop: 'revisions',
            meta: 'tokens',
            type: 'csrf',
            rvprop: 'content|ids',
            indexpageids: 1,
            formatversion: '2',
            rvslots: 'main'
        }),
        mw.user.getGroups()
    ).done(function (result, userGroups) {
        result = result[0].query;
        var page = result.pages[0];

        //衝突防止のためにリビジョンを記録
        if(page.revisions){
            entry.target.dataset.revid = page.revisions[0].revid;
        }
        entry.target.dataset.csrftoken = result.tokens.csrftoken;

        //非ログインユーザーにもコメントできるか?
        var GuestUser = false;
        if(entry.target.getAttribute("data-deny-guest")){
            GuestUser = (userGroups.indexOf("user") == -1);
        }

        var wikitext = page.revisions && page.revisions[0].slots.main.content || "";
        var lines = wikitext.split(/\r\n|\n|\r/);
        var sections = [];
        var lists = {};
        var lists_count = 0;
        var tree = {children: [], level: 0, text: "root"};
        var curtree = tree;
        var last_lvl = 1;
        for (var i = 0; i < lines.length; i++) {
            //セクションの位置を取得
            if (lines[i].match(/^=+[^=]+=+$/)) {
                sections.push({ index: sections.length, line: i, title: lines[i].match(/^=+([^=]+)=+$/)[1].trim() });
                continue;
            }

            //*(<li>)の行番号を取得
            if (lines[i].match(/^\*+/)) {
                lists[i] = { index: lists_count++, line: i, section: sections.length - 1};

                var m = lines[i].match(/^(\*+)/);
                var astarisk = m[1];
                
                //ツリーの始まりを記憶
                if(astarisk.length == 1){
                    lists[i].toplevel = true;
                }

                //ツリーを記憶
                {
                    var treedata = {line: i, level: astarisk.length, children: []};

                    if(astarisk.length < last_lvl){
                        do{
                            curtree = curtree.parent;
                        } while (curtree && curtree.level >= astarisk.length);
                        treedata.parent = curtree;
                    }

                    if(astarisk.length > last_lvl){
                        curtree = curtree.children[curtree.children.length-1];
                    }

                    treedata.parent = curtree;

                    curtree.children.push( treedata );
                    
                    lists[i].tree = treedata;
                }
                
                last_lvl = astarisk.length;
            }
        }

        //セクションがまだ存在しないならデフォルト
        if (sections.length == 0) {
            sections.push(
                { index: 0, line: -1, title: "PComment" }
            );
        }

        //引数を取得
        var sectionName = entry.target.getAttribute("data-section") || sections[sections.length - 1].title;
        var sectionId = sections.length - 1;
        var max = entry.target.getAttribute("data-max") || 10;

        //引数の名前からセクションを特定する
        for (i = 0; i < sections.length; i++) {
            if (sections[i].title == sectionName) {
                sectionId = sections[i].index;
                break;
            }
        }

        //次のセクションの先頭行を取得(セクション範囲識別に使用)
        var nextSectionLine = lines.length;
        if (sectionId > -1 && sections.length - 1 > sectionId) {
            nextSectionLine = sections[sectionId + 1].line;
        }

        //コメントツリーの最後の行を取得するための関数
        function getLast(obj){
            if(obj.children.length){
                return getLast(obj.children[obj.children.length -1]);
            }
            return obj.line;
        }

        //wikitextを指定レス分で抽出する
        var new_lines = [];
        var count = 1;
        for (i = lines.length - 1; i >= 0; i--) {
            //関係ないセクションはスキップ
            if (i <= sections[sectionId].line || i >= nextSectionLine) {
                continue;
            }

            var line = lines[i];

            if (!GuestUser && lists[i]) {
                //リスト構文の先頭にラジオボタン(予定地)を挿入する
                var m = line.match(/^(\*+)([^\*].+)$/);
                var astarisk = m[1];
                var comment = m[2];

                //最後のレスの行を取得
                var lastlinenum = i;
                if(lists[i].tree){
                    lastlinenum = getLast(lists[i].tree);
                }

                line = astarisk + "<span class='pcomment-radio' data-group='sec" + lists[i].section + "' data-line='" + lastlinenum + "' data-level='" + astarisk + "'></span>" + comment;

                if (lists[i].toplevel) {
                    count++;
                }
            }
            new_lines.push(line);

            //指定のレス数に達したのでループを抜ける
            if (count > max) break;
        }

        //抜き取った行をwikitextに変換する
        wikitext = new_lines.reverse().join("\n");

        // APIでWikitextをパースする
        new mw.Api().parse(wikitext).done(function (data) {
            //htmlを生成
            var $html = $(data).removeClass('noscript');

            //OOUIのラジオボタンを生成して挿入
            $html.find("span.pcomment-radio").each(function () {

                var val = {
                    line: $(this).attr('data-line'),
                    level: $(this).attr('data-level')
                };

                $(this).replaceWith(
                    new OO.ui.RadioInputWidget({ name: $(this).attr('data-group'), value: JSON.stringify(val) }).$element
                );
            });

            //テキストボックスと送信ボタンを追加
            if(!GuestUser){
                var $text = new OO.ui.TextInputWidget();
                var $button = new OO.ui.ButtonWidget({ label: i18n.send_button_caption, flags: ["primary", "progressive"] });
                $html.append(
                    $("<div>", { class: "pcomment-toolbar", "data-section-end": nextSectionLine}).append(
                        $text.$element.addClass('pcomment-text').css("display", "inline-block"),
                        $button.$element.on('click', function(){

                            var selected = $(this).parents(".pcomment-widget").find("input:checked");
                            var section_end = $(this).parents(".pcomment-toolbar").attr("data-section-end");
                            var text = $(this).parent().find('.pcomment-text input').val();

                            if(text.trim().length == 0) {
                                //空送信を防止する
                                return;
                            }
                            $text.setDisabled(true);
                            $button.setDisabled(true);

                            $(this).parents(".pcomment-toolbar").prepend(
                                new OO.ui.ProgressBarWidget({ progress:false }).$element.addClass("oo-ui-pendingElement-pending")
                            );

                            var comment = {
                                targetPage: talkPage,
                                section_end: section_end,
                                wikitext: "* " + text + "--~" + "~" + "~" + "~",
                                entry: entry
                            };

                            if(selected.length){
                                var opt = JSON.parse(selected.val());
                                comment.line = opt.line;
                                comment.level = opt.level;
                            }

                            postComment(comment);
                        })
                    )
                );
            }

            // できたHTMLの差し込み
            $(entry.target).html("").append($html);

            // 他のスクリプトへ読み込み完了通知
            mw.hook('wikipage.content').fire($(entry.target));
        });

    });
}

function getRevIndex(revisions, revid){
    for(var i=0; i<revisions.length; i++) {
        if(revisions[i].revid == revid){
            return i;
        }
    }
    return -1;
}

//コメント追加用
function postComment(option) {

    var query = {
        action: 'query',
        format: 'json',
        prop: 'revisions',
        titles: option.targetPage,
        rvprop: 'content|ids',
        rvslots: 'main',
        indexpageids: 1,
        formatversion: '2'
    };

    var oldrevid = option.entry.target.dataset.revid;

    //元の版で取得
    if(oldrevid) {
        query.rvendid = oldrevid;
    }

    //最後のページ内容を取得
    new mw.Api().get(query).done(function (result) {
        result = result.query;
        var page = result.pages[0];
        var oldText = "";
        var currevid = page.revisions && page.revisions[0].revid;
        var linenum = option.line && option.line*1;
        var section_end = option.section_end && option.section_end*1;

        if(page.revisions){
            oldText = page.revisions[0].slots.main.content;
        }

        //行で分割する
        var new_lines = oldText.split(/\r\n|\n|\r/);

        //リビジョンが更新されていた場合、該当する行番号へ更新
        if(currevid != oldrevid) {
            var index = getRevIndex(oldrevid);
            oldText = page.revisions[ index ].slots.main.content;
            var old_lines = oldText.split(/\r\n|\n|\r/);
            var target_line;

            if(option.line) {
                target_line = old_lines[ linenum ];
                linenum = new_lines.indexOf( target_line );
            }

            if(option.section_end){
                if(section_end < old_lines.length - 2){
                    target_line = old_lines[ section_end + 1 ];
                    section_end = new_lines.indexOf( target_line ) - 1;
                } else {
                    section_end = old_lines.length - 1;
                }
            }
        }
        
        if(linenum){
            //レスする行が指定されているならコメントを挿入
            new_lines.splice(linenum + 1, 0, option.level + option.wikitext);
        } else if(section_end){
            //レス行未指定
            new_lines.splice(section_end, 0, option.wikitext);
        } else {
            new_lines.push(option.wikitext);
        }

        var newText = new_lines.join("\n");

        //あたらしい内容を送信する
        new mw.Api().post({
            action: 'edit',
            title: option.targetPage,
            text: newText,
            summary: i18n.summary_edit_with_pcomment,
            token: option.entry.target.dataset.csrftoken
        }).always(function () {
            loadComment(option.entry);
        });
    });
}