미디어위키:Gadget-PluginCore.js: 두 판 사이의 차이

리버티게임(개발), 모두가 만들어가는 자유로운 게임
둘러보기로 이동 검색으로 이동
imported>Senouis
(이동해옴)
 
imported>Senouis
(20230604 요청 - MediawikiAPI 독자 구현에서 mw.Api 기반으로 Migration)
1번째 줄: 1번째 줄:
/** [[틀:플러그인]]을 사용 가능하게 해 줍니다. 사용자의 허락을 맡고 사용자의 commonjs 편집을 허가 할 수 있는 문서입니다.
/** [[틀:플러그인]]을 사용 가능하게 해 줍니다. 사용자의 허락을 맡고 사용자의 commonjs 편집을 허가 할 수 있는 문서입니다.
* 작성자: [[사용자:BANIP|BANIP]]
    * 작성자: [[사용자:BANIP|BANIP]]
* [[백괴게임:관리자 요청/2018년 1월]]에서 BANIP님 요청으로 퍼왔습니다.
    * - [[백괴게임:관리자 요청/2018년 1월]]에서 BANIP님 요청으로 퍼왔습니다.
  */
    * - 2023.06.04 2.0.0 전체적인 소스 리팩토링/MediaWikiAPI의 의존성 삭제 --[[사용자:BANIP|BANIP]] ([[사용자토론:BANIP|토론]]) 2023년 6월 4일 (일) 13:52 (KST)
function pluginCore() {
*/
     // 사용자의 commonjs의 문서 이름을 획득
    // 페이지 로드 후 실행
     var commonjs = "사용자:" + mw.config.get("wgUserName") + "/common.js";
$(function(){
     var plugins = {},
     // 구현필요
         docPlugins = {},
     var repo = {
         uninstalledPlugins = {},
        api: new mw.Api(),
         unupdatedPlugins = {};
        getRawDocument: function(title) {
    var preloadedplugins = [],
            return new Promise(function (resolve, reject){
        needPlugins = [];
                repo.api.get({
    var api = MediaWikiAPI();
                    action: 'query',
    var jsdoc = api.getDocument(commonjs);
                    prop: 'revisions',
                    rvprop: 'content',
                    titles: title,
                    formatversion: 2
                }).then(function(data){
                    var page = data.query.pages[0];
                    if (page.missing) {
                        reject("The page does not exist.");
                    } else {
                        resolve(page.revisions[0].content);
                    }
                }, function(e){
                    reject(e);
                });
            });
        },
      
        changeDocument: function(title, text, summary) {
            return new Promise(function(resolve, reject){
                repo.api.postWithEditToken({
                    action: 'edit',
                    title: title,
                    text: text,
                    summary: summary,
                    nocreate: true
                }).then(function(data){
                    resolve(data);
                }, function(e){
                    reject(e);
                });
            });
        },
          
        getDocument: function(title) {
            return new Promise(function(resolve, reject){
                repo.api.get({
                    action: 'parse',
                    page: title,
                    formatversion: 2
                }).then(function(data){
                    resolve(data.parse.text);
                }, function(e){
                    reject(e);
                });
            });
        },
         getPluginMetadata: function(page) {
            var revisionPromise = repo.api.get({
                action: 'query',
                prop: 'revisions',
                rvlimit: 1,
                rvprop: 'timestamp|user|comment',
                titles: page,
                formatversion: 2
            });
          
            var parsedPagePromise = repo.api.get({
                action: 'parse',
                page: page,
                formatversion: 2
            });
       
            return Promise.all([revisionPromise, parsedPagePromise])
                .then(function(results) {
                    var revisionResponse = results[0];
                    var parsedPageResponse = results[1];
                   
                    var pages = revisionResponse.query.pages;
                    var pageId = Object.keys(pages)[0];
                    var revision = pages[pageId].revisions[0];
                   
                    var parsedContent = parsedPageResponse.parse.text;
       
                    return {
                        author: revision.user,
                        timestamp: revision.timestamp,
                        comment: revision.comment,
                        code: $(parsedContent).find("pre.script").text()
                    };
                });
        }


     //commonjs에서 특정 플러그인 제거
     };
    function removePluginByDoc(pluginTitle, doc) {
        var reg = new RegExp("\\/\\*\\* 플러그인 " + pluginTitle + "([\\s\\S]*)\\/\\* " + pluginTitle + " 끝 \\*\\/", "g");
        doc = doc.replace(reg, "");
        return doc;
    }


     // 플러그인이 비어있는지 확인
     // 플러그인이 비어있는지 확인
35번째 줄: 110번째 줄:
     }
     }


     // 문서에서 사용하는 플러그인들을 체크합니다. use-script클래스를 가진 모든 돔 요소를 조사하고
     var getPlugin = {
    // 그 돔에 내장된 플러그인 데이터를 docPlugins에 추가합니다.
        // 문서에서 사용하는 플러그인들을 체크합니다. use-script클래스를 가진 모든 돔 요소를 조사하고
    var checkDocPlugin = (function(docPlugins) {
        // 그 돔에 내장된 플러그인 데이터를 docPlugins에 추가합니다.
        function getPluginData($this) {
        document: function(){
            return {
            // 현재 문서에 사용되는 플러그인들의 데이터를 docPlugins에 추가합니다.
                name: $this.attr("data-name"), // 플러그인 이름
            return $(".use-script").toArray().map(function(el){
                descript: $this.attr("data-descript"), // 플러그인 내용
                var $this = $(el);
                version: $this.attr("data-version"), // 플러그인 이름
                return {
                local: ($this.attr("data-local") === "true") ? true : false,
                    name: $this.attr("data-name"), // 플러그인 이름
                creat: $this.attr("data-creat"),
                    descript: $this.attr("data-descript"), // 플러그인 내용
                state: $this.attr("data-state"),
                    version: $this.attr("data-version"), // 플러그인 이름
                link: $this.attr("data-link"),
                    local: $this.attr("data-local") === "true", // 즉시 실행 여부
                executable: $this.attr("data-executable") === "true" ? true : false,
                    creat: $this.attr("data-creat"), // 플러그인 작성자 이름
            };
                    state: $this.attr("data-state"),
        }
                    link: $this.attr("data-link"),
 
                    executable: $this.attr("data-executable") === "true",
        function getAllPluginsName(plugins) {
                };
            var pluginNames = [];
            forEach(plugins, function(plugin) {
                var pluginName = plugin.name;
                pluginNames.push(pluginName);
             });
             });
 
         },
            return pluginNames;
         user: function(commonjs){
        }
 
        var loadAllPlugins2 = (function(docPlugins) {
            $(".use-script").each(function() {
                const plugin = getPluginData($(this));
                docPlugins[plugin.name] = plugin;
            });
         })(docPlugins);
 
        var loadAllPlugins = (function(docPlugins) {
            if (!isPluginsEmpty(docPlugins)) {
                var subTitle = " " + getAllPluginsName(docPlugins).join(", ") + " 플러그인 가동중";
                $("#siteSub").text(function(i, v) {
                    return v + subTitle;
                });
            }
        })(docPlugins);
    })(docPlugins);
 
    // 사용자가 가지고 있는 플러그인들을 체크합니다.
    var checkHavePlugin = (function() {
         function getUserPlugins(jsdoc) {
             var userplugins = [];
             var userplugins = [];
             // plugins.---가 있는지 체크하는 정규식
             // plugins.---가 있는지 체크하는 정규식
             var pluginreg = /JSON \=\> ([\S]+) = (\{.*\})/g;
             var pluginreg = /JSON \=\> ([\S]+) = (\{.*\})/g;
             var nameMatch = pluginreg.exec(jsdoc);
             var nameMatch = pluginreg.exec(commonjs);
             while (nameMatch) {
             while (nameMatch) {
                 userplugins.push(JSON.parse(nameMatch[2]));
                 userplugins.push(JSON.parse(nameMatch[2]));
                 nameMatch = pluginreg.exec(jsdoc);
                 nameMatch = pluginreg.exec(commonjs);
             }
             }
             return userplugins;
             return userplugins;
         }
         },
    };


        function isSamePlugin(pluginFirst, pluginSecond) {
    // 문서의 서브타이틀을 플러그인들의 이름으로 설정합니다.
            return pluginFirst.name == pluginSecond.name;
    function setSubtitleByPlugins(plugins){
        }
        // 서브타이틀에 현재 가동중인 플러그인들의 이름을 추가합니다.
 
        var pluginNames = plugins.map(function(plugin){
        function isSameVersionPlugin(pluginFirst, plugiSecond) {
             return plugin.name;
             return pluginFirst.version == plugiSecond.version;
         });
         }
         var subTitle = " " + pluginNames.join(", ") + " 플러그인 가동중";
 
         $("#siteSub").text(function(i, v) {
         function addNeedPlugins(targetPlugins) {
            return v + subTitle;
            //global docPlugins, needPlugins
        });
            for (var key in targetPlugins) {
    }
                var pluginName = docPlugins[key].name;
                needPlugins.push(pluginName);
            }
        }
 
         if (!isPluginsEmpty(docPlugins)) {
            preloadedplugins = getUserPlugins(jsdoc);
            forEach(docPlugins, function(docplugin) {
                var isHavePlugin = false;
                forEach(preloadedplugins, function(myplugin) {
                    if (!isSamePlugin(docplugin, myplugin)) return;
                    if (!isSameVersionPlugin(docplugin, myplugin)) {
                        unupdatedPlugins[docplugin.name] = docplugin;
                    }
                    isHavePlugin = true;
                });


                if (!isHavePlugin) {
    // 플러그인을 사용자문서에 추가합니다.
                    uninstalledPlugins[docplugin.name] = docplugin;
     function getUpdatedCommonjs(plugins, commonjs, pluginMeta) {
                }
            });
            addNeedPlugins(uninstalledPlugins);
            addNeedPlugins(unupdatedPlugins);
        }
    })();
 
     function onPluginInstall() {
         function getPluginCode(plugin) {
         function getPluginCode(plugin) {
            // html로 구성된 코드를 텍스트로
             function entityDecode(doc) {
             function entityDecode(doc) {
                return $('<p></p>').html(doc).text();
            return $('<p></p>').html(doc).text();
             }
             }
 
       
             function getDocHead(plugin) {
             function getDocHead(plugin) {
                var docHead = "";
            var docHead = "";
                var toJSONPlugin = Object.assign({}, plugin);
            var toJSONPlugin = Object.assign({}, plugin);
                toJSONPlugin.code = undefined;
            toJSONPlugin.code = undefined;
                toJSONPlugin.link = undefined;
            toJSONPlugin.link = undefined;
                docHead += "\n";
            docHead += "\n";
                docHead += "\n/** 플러그인 " + plugin["name"] + "***************************\n";
            docHead += "\n/** 플러그인 " + plugin.name + "***************************\n";
                docHead += "* " + plugin["descript"] + "\n";
            docHead += "* " + plugin.descript + "\n";
                docHead += "* 버전 => " + plugin["version"] + "\n";
            docHead += "* 버전 => " + plugin.version + "\n";
                docHead += "* 작성자 : [[사용자:" + plugin["creat"] + "|" + plugin["creat"] + "]] \n";
            docHead += "* 작성자 : [[사용자:" + plugin.creat + "|" + plugin.creat + "]] \n";
                docHead += "* JSON => " + plugin["name"] + " = " + JSON.stringify(toJSONPlugin) + "; \n";
            docHead += "* JSON => " + plugin.name + " = " + JSON.stringify(toJSONPlugin) + "; \n";
                docHead += "*/ \n";
            docHead += "*/ \n";
                docHead += "function plugin_" + plugin["name"] + "(){\n";
            docHead += "function plugin_" + plugin.name + "(){\n";
                if (plugin.local) docHead += "  if($(\"[data-name='" + plugin["name"] + "']\").length >= 1){\n";
            if (plugin.local) docHead += "  if($(\"[data-name='" + plugin.name + "']\").length >= 1){\n";
                return docHead;
            return docHead;
             }
             }
 
       
             function getDocFoot(plugin) {
             function getDocFoot(plugin) {
                var docFoot = "";
            var docFoot = "";
                if (plugin.local) docFoot += "\n  }\n";
            if (plugin.local) docFoot += "\n  }\n";
                docFoot += "\n}\n";
            docFoot += "\n}\n";
                if (plugin.executable) docFoot += "$( plugin_" + plugin["name"] + " );\n";
            if (plugin.executable) docFoot += "$( plugin_" + plugin.name + " );\n";
                docFoot += "/* " + plugin["name"] + " 끝 */\n\n";
            docFoot += "/* " + plugin.name + " 끝 */\n\n";
                return docFoot;
            return docFoot;
             }
             }
       
            var docHead = getDocHead(plugin);
            var docFoot = getDocFoot(plugin);
       
            return entityDecode(docHead + pluginMeta[plugin.name].code + docFoot);
        }


             var docHead = getDocHead(plugin),
        //commonjs에서 특정 플러그인 제거
                docFoot = getDocFoot(plugin);
        function removePluginByDoc(pluginTitle, commonjs) {
             return entityDecode(docHead + plugin["code"] + docFoot);
             var reg = new RegExp("\\/\\*\\* 플러그인 " + pluginTitle + "([\\s\\S]*)\\/\\* " + pluginTitle + " 끝 \\*\\/", "g");
            commonjs = commonjs.replace(reg, "");
             return commonjs;
         }
         }


         $(".install-button").text("설치중..");
         plugins.forEach(function(plugin) {
        $(".install-button").off("click");
            commonjs = removePluginByDoc(plugin.name, commonjs);
            commonjs += getPluginCode(plugin);
        });


         var doc = "";
         return commonjs;
        forEach(needPlugins, function(pluginName) {
            var plugin = docPlugins[pluginName];
            jsdoc = removePluginByDoc(pluginName, jsdoc);
            doc += getPluginCode(plugin);
        });
        api.changeDocument(commonjs, "플러그인 " + needPlugins + "설치", jsdoc + doc);
     }
     }


     var checkinstalledPlugin = function() {
     // 플러그인 안내 템플릿을 반환합니다.
         function appendBox(plugin, status) {
    function getInstallPagePromise(userPlugins, uninstalledPlugins, unupdatedPlugins, pluginMeta) {
         function getPluginCard($template,plugin, status) {
             var pluginName = plugin.name;
             var pluginName = plugin.name;
             var box = $(".cloneable.p-box").clone().removeClass("cloneable");
             var $box = $template.find(".cloneable.p-box").clone().removeClass("cloneable");
             var code = api.readDocument(plugin.state).find("pre.script").html();
             var code = pluginMeta[pluginName].code;
            docPlugins[pluginName].code = code;
             $box.find(".p-status").html(status);
             box.find(".p-status").html(status);
             $box.find(".p-code").html(code.replace(/\s{1,}$/, ""));
             box.find(".p-code").html(code.replace(/\s{1,}$/, ""));
             $box.find(".p-name").text(pluginName);
             box.find(".p-name").text(pluginName);
             $box.find(".p-descript").text(plugin.descript);
             box.find(".p-descript").text(plugin["descript"]);
             if (status == "버전업") {
             if (status == "버전업") {
                 var thisVersion;
                 var prevVersion = userPlugins.find(function(userPlugin) {
                for (var key in preloadedplugins) {
                     return userPlugin.name == pluginName;
                     if (preloadedplugins[key].name == plugin["name"]) {
                }).version;
                        thisVersion = preloadedplugins[key].version;
                 $box.find(".p-version").text(prevVersion + " => " + plugin.version);
                    }
                }
                 box.find(".p-version").text(thisVersion + " => " + plugin["version"]);
             } else {
             } else {
                 box.find(".p-version").text(plugin["version"]);
                 $box.find(".p-version").text(plugin.version);
             }
             }
             box.find(".p-local").text(plugin["local"] == true ? "일부 문서만" : "문서 전체");
             $box.find(".p-local").text(plugin.local == true ? "일부 문서만" : "문서 전체");
             box.find(".p-creat").text(plugin["creat"]);
             $box.find(".p-creat").text(pluginMeta[pluginName].author);
             $.ajax({
             $box.find(".p-last").text(pluginMeta[pluginName].timestamp);
                url: "/w/api.php?action=query&prop=revisions&rvdir=older&titles=" + plugin.state,
            $box.find(".p-comment").text(pluginMeta[pluginName].comment);
                success: function(v, i) {
             return $box;
                    var datas = JSON.parse($(v).find("pre").text());
                    var titleKey = Object.keys(datas["query"]["pages"])[0];
                    var lastModified = datas["query"]["pages"][titleKey]["revisions"][0]["user"] + "(" + datas["query"]["pages"][titleKey]["revisions"][0]["timestamp"] + ")";
                    box.find(".p-last").text(lastModified);
                },
                async: false
            })
             $(".box-article").append(box);
         }
         }
   
       
        return repo.getDocument("틀:플러그인/setup")
        .then(function(templateRaw){
            var $template = $(templateRaw);
            var allPlugins = uninstalledPlugins.concat(unupdatedPlugins);
            allPlugins.forEach(function(plugin){
                var status = uninstalledPlugins.includes(plugin) ? "설치" : "버전업";
                var pluginCard = getPluginCard($template,plugin, status);
                $template.find(".box-article").append(pluginCard);
            });


        if (isPluginsEmpty(docPlugins) || (isPluginsEmpty(unupdatedPlugins) && isPluginsEmpty(uninstalledPlugins))) return;
            return $template;
        var doc = $("#mw-content-text");
        var setupMeta = api.readDocument("틀:플러그인/setup");
        doc.html(setupMeta);
        forEach(uninstalledPlugins, function(uninstalledPlugin) {
            appendBox(uninstalledPlugin, "설치");
         });
         });
        forEach(unupdatedPlugins, function(unupdatedPlugin) {
            appendBox(unupdatedPlugin, "버전업");
        });
        $(".install-button").on("click", onPluginInstall);
     }
     }


     var showPluginTemplet = (function() {
     // 플러그인 설치 관련 로직
        if (isPluginsEmpty(needPlugins)) {
    (function(){
             return;
        var commonjsPath = "사용자:" + mw.config.get("wgUserName") + "/common.js"; // 사용자의 commonjs의 경로를 획득합니다.
        }
        var docPlugins = getPlugin.document(); // 문서에서 사용하는 플러그인들을 체크합니다.
        if(Object.keys(docPlugins).length === 0) return; // 문서에서 사용하는 플러그인이 없으면 종료합니다.
 
        setSubtitleByPlugins(docPlugins); // 문서의 서브타이틀을 문서에서 사용중인 플러그인들의 이름으로 설정합니다.
 
        repo.getRawDocument(commonjsPath) // 사용자의 commonjs의 문서를 획득합니다.
        .then(function(commonjsRaw){
            var userPlugins = getPlugin.user(commonjsRaw); // 사용자가 가지고 있는 플러그인들을 체크합니다.
 
            var uninstalledPlugins = docPlugins.filter(function(plugin){
                return !userPlugins.some(function(userPlugin){
                    return userPlugin.name === plugin.name;
                });
            }); // 사용자가 가지고 있지 않은 플러그인들을 체크합니다.
 
            var unupdatedPlugins = docPlugins.filter(function(plugin){
                return userPlugins.some(function(userPlugin){
                    return userPlugin.name === plugin.name && userPlugin.version !== plugin.version;
                });
            }); // 사용자가 가지고 있는 플러그인들 중 버전이 다른 플러그인들을 체크합니다.
 
            // 추가 플러그인 설치가 필요하지 않은 경우 로직 종료
            if (isPluginsEmpty(uninstalledPlugins) && isPluginsEmpty(unupdatedPlugins)) return;
 
            // 설치가 필요한 플러그인들의 이름을 문자열로 반환합니다.
            var needPluginNames = uninstalledPlugins.concat(unupdatedPlugins).map(function(plugin){
                return plugin.name;
            }).join(", ");
 
            // 플러그인 개발자/코드/마지막 수정일 등 획득, 설치버튼 클릭 시 즉시 설치 가능하도록 미리 Promise를 생성
            var pluginMetaPromise = Promise.all(
                uninstalledPlugins.concat(unupdatedPlugins).map(function(plugin){
                    return Promise.all([plugin.name, repo.getPluginMetadata(plugin.state)]);
                })
            ).then(function(pluginMeta){
                return Object.fromEntries(pluginMeta);
             }).catch(function(){
                alert("플러그인 정보를 불러오는데 실패했습니다. 페이지를 리로드 해주세요.");
            });
 
            // 플러그인 리스트 수집 끝나면 플러그인 안내 템플릿을 반환하는 Promise를 생성
            var installPagePromise = pluginMetaPromise
            .then(function(pluginMeta){
                return getInstallPagePromise(userPlugins, uninstalledPlugins, unupdatedPlugins, pluginMeta);
            }).catch(function(){
                alert("플러그인 설치 페이지를 불러오는데 실패했습니다. 페이지를 리로드 해주세요.");
            });
 
            // 플러그인 설치 버튼 클릭 이벤트 핸들러
            var setInstallPage = function(pluginMeta){
                return installPagePromise.then(function($installPage){
                    $installPage.find(".install-button").on("click", function() {
                        $(".install-button").text("설치중..");
                        $(".install-button").off("click");
                   
                        var commonjs = getUpdatedCommonjs(uninstalledPlugins.concat(unupdatedPlugins), commonjsRaw, pluginMeta);
                        repo.changeDocument(commonjsPath, commonjs, "플러그인 " + needPluginNames + "설치")
                        .then(function(){
                            location.reload();
                        })
                        .catch(function(e){
                            alert("플러그인 설치에 실패했습니다. 다시 시도해주세요.");
                            console.error(e);
                        });
                    });
 
                    var $document = $("#mw-content-text");
                    $document.html($installPage);
                });
            };


        if ($(".plugin-install").length >= 1) {
            var $loadCodeLink = $(".plugin-install");
            $(".plugin-install").eq(0).closest("table").show();
            // 플러그인 설치 필요 틀이 있는 페이지인 경우
            $(".plugin-name").eq(0).text(needPlugins);
            if ($loadCodeLink.length >= 1) {
            $(".plugin-install").on("click", checkinstalledPlugin);
                $loadCodeLink.eq(0).closest("table").show(); // 플러그인 설치 필요 틀을 보여줍니다.
        } else {
                $(".plugin-name").eq(0).text(needPluginNames); // 필요한 플러그인들의 이름을 표시합니다.
            checkinstalledPlugin();
                $loadCodeLink.on("click", function(){
         }
                    // 클릭한 버튼 이벤트 끄고 로딩중메세지 표시
                        $loadCodeLink.off("click");
                        $loadCodeLink.text("필요한 플러그인 데이터를 가져오고 있습니다...");
                        pluginMetaPromise.then(setInstallPage);
                    }); // 플러그인 설치 버튼을 클릭하면 플러그인 체크 로직을 실행합니다.
            } else {
                pluginMetaPromise.then(setInstallPage);
            }
         });
     })();
     })();
}
});
$(pluginCore)

2023년 6월 4일 (일) 14:21 판

/** [[틀:플러그인]]을 사용 가능하게 해 줍니다. 사용자의 허락을 맡고 사용자의 commonjs 편집을 허가 할 수 있는 문서입니다.
    * 작성자: [[사용자:BANIP|BANIP]]
    * - [[백괴게임:관리자 요청/2018년 1월]]에서 BANIP님 요청으로 퍼왔습니다.
    * - 2023.06.04 2.0.0 전체적인 소스 리팩토링/MediaWikiAPI의 의존성 삭제  --[[사용자:BANIP|BANIP]] ([[사용자토론:BANIP|토론]]) 2023년 6월 4일 (일) 13:52 (KST) 
*/
    // 페이지 로드 후 실행
$(function(){
    // 구현필요
    var repo = {
        api: new mw.Api(),
        getRawDocument: function(title) {
            return new Promise(function (resolve, reject){
                repo.api.get({
                    action: 'query',
                    prop: 'revisions',
                    rvprop: 'content',
                    titles: title,
                    formatversion: 2
                }).then(function(data){
                    var page = data.query.pages[0];
                    if (page.missing) {
                        reject("The page does not exist.");
                    } else {
                        resolve(page.revisions[0].content);
                    }
                }, function(e){
                    reject(e);
                });
            });
        },
    
        changeDocument: function(title, text, summary) {
            return new Promise(function(resolve, reject){
                repo.api.postWithEditToken({
                    action: 'edit',
                    title: title,
                    text: text,
                    summary: summary,
                    nocreate: true
                }).then(function(data){
                    resolve(data);
                }, function(e){
                    reject(e);
                });
            });
        },
        
        getDocument: function(title) {
            return new Promise(function(resolve, reject){
                repo.api.get({
                    action: 'parse',
                    page: title,
                    formatversion: 2
                }).then(function(data){
                    resolve(data.parse.text);
                }, function(e){
                    reject(e);
                });
            });
        },
        getPluginMetadata: function(page) {
            var revisionPromise = repo.api.get({
                action: 'query',
                prop: 'revisions',
                rvlimit: 1,
                rvprop: 'timestamp|user|comment', 
                titles: page,
                formatversion: 2
            });
        
            var parsedPagePromise = repo.api.get({
                action: 'parse',
                page: page,
                formatversion: 2
            });
        
            return Promise.all([revisionPromise, parsedPagePromise])
                .then(function(results) {
                    var revisionResponse = results[0];
                    var parsedPageResponse = results[1];
                    
                    var pages = revisionResponse.query.pages;
                    var pageId = Object.keys(pages)[0];
                    var revision = pages[pageId].revisions[0];
                    
                    var parsedContent = parsedPageResponse.parse.text;
        
                    return {
                        author: revision.user,
                        timestamp: revision.timestamp,
                        comment: revision.comment,
                        code: $(parsedContent).find("pre.script").text()
                    };
                });
        }

    };

    // 플러그인이 비어있는지 확인
    function isPluginsEmpty(plugins) {
        return Object.keys(plugins).length === 0;
    }

    //플러그인의 모든 키 순회
    function forEach(object, callback) {
        for (var key in object) {
            var variable = object[key];
            callback(variable, key);
        }
    }

    var getPlugin = {
        // 문서에서 사용하는 플러그인들을 체크합니다. use-script클래스를 가진 모든 돔 요소를 조사하고
        // 그 돔에 내장된 플러그인 데이터를 docPlugins에 추가합니다.
        document: function(){
            // 현재 문서에 사용되는 플러그인들의 데이터를 docPlugins에 추가합니다.
            return $(".use-script").toArray().map(function(el){
                var $this = $(el);
                return {
                    name: $this.attr("data-name"), // 플러그인 이름
                    descript: $this.attr("data-descript"), // 플러그인 내용
                    version: $this.attr("data-version"), // 플러그인 이름
                    local: $this.attr("data-local") === "true", // 즉시 실행 여부
                    creat: $this.attr("data-creat"), // 플러그인 작성자 이름
                    state: $this.attr("data-state"),
                    link: $this.attr("data-link"),
                    executable: $this.attr("data-executable") === "true",
                };
            });
        },
        user: function(commonjs){
            var userplugins = [];
            // plugins.---가 있는지 체크하는 정규식
            var pluginreg = /JSON \=\> ([\S]+) = (\{.*\})/g;
            var nameMatch = pluginreg.exec(commonjs);
            while (nameMatch) {
                userplugins.push(JSON.parse(nameMatch[2]));
                nameMatch = pluginreg.exec(commonjs);
            }

            return userplugins;
        },
    };

    // 문서의 서브타이틀을 플러그인들의 이름으로 설정합니다.
    function setSubtitleByPlugins(plugins){
        // 서브타이틀에 현재 가동중인 플러그인들의 이름을 추가합니다.
        var pluginNames = plugins.map(function(plugin){
            return plugin.name;
        });
        var subTitle = " " + pluginNames.join(", ") + " 플러그인 가동중";
        $("#siteSub").text(function(i, v) {
            return v + subTitle;
        });
    }

    // 플러그인을 사용자문서에 추가합니다.
    function getUpdatedCommonjs(plugins, commonjs, pluginMeta) {
        function getPluginCode(plugin) {
            function entityDecode(doc) {
            return $('<p></p>').html(doc).text();
            }
        
            function getDocHead(plugin) {
            var docHead = "";
            var toJSONPlugin = Object.assign({}, plugin);
            toJSONPlugin.code = undefined;
            toJSONPlugin.link = undefined;
            docHead += "\n";
            docHead += "\n/** 플러그인 " + plugin.name + "***************************\n";
            docHead += "* " + plugin.descript + "\n";
            docHead += "* 버전 => " + plugin.version + "\n";
            docHead += "* 작성자 : [[사용자:" + plugin.creat + "|" + plugin.creat + "]] \n";
            docHead += "* JSON => " + plugin.name + " = " + JSON.stringify(toJSONPlugin) + "; \n";
            docHead += "*/ \n";
            docHead += "function plugin_" + plugin.name + "(){\n";
            if (plugin.local) docHead += "  if($(\"[data-name='" + plugin.name + "']\").length >= 1){\n";
            return docHead;
            }
        
            function getDocFoot(plugin) {
            var docFoot = "";
            if (plugin.local) docFoot += "\n  }\n";
            docFoot += "\n}\n";
            if (plugin.executable) docFoot += "$( plugin_" + plugin.name + " );\n";
            docFoot += "/* " + plugin.name + " 끝 */\n\n";
            return docFoot;
            }
        
            var docHead = getDocHead(plugin);
            var docFoot = getDocFoot(plugin);
        
            return entityDecode(docHead + pluginMeta[plugin.name].code + docFoot);
        }

        //commonjs에서 특정 플러그인 제거
        function removePluginByDoc(pluginTitle, commonjs) {
            var reg = new RegExp("\\/\\*\\* 플러그인 " + pluginTitle + "([\\s\\S]*)\\/\\* " + pluginTitle + " 끝 \\*\\/", "g");
            commonjs = commonjs.replace(reg, "");
            return commonjs;
        }

        plugins.forEach(function(plugin) {
            commonjs = removePluginByDoc(plugin.name, commonjs);
            commonjs += getPluginCode(plugin);
        });

        return commonjs;
    }

    // 플러그인 안내 템플릿을 반환합니다.
    function getInstallPagePromise(userPlugins, uninstalledPlugins, unupdatedPlugins, pluginMeta) {
        function getPluginCard($template,plugin, status) {
            var pluginName = plugin.name;
            var $box = $template.find(".cloneable.p-box").clone().removeClass("cloneable");
            var code = pluginMeta[pluginName].code;
            $box.find(".p-status").html(status);
            $box.find(".p-code").html(code.replace(/\s{1,}$/, ""));
            $box.find(".p-name").text(pluginName);
            $box.find(".p-descript").text(plugin.descript);
            if (status == "버전업") {
                var prevVersion = userPlugins.find(function(userPlugin) {
                    return userPlugin.name == pluginName;
                }).version;
                $box.find(".p-version").text(prevVersion + " => " + plugin.version);
            } else {
                $box.find(".p-version").text(plugin.version);
            }
            $box.find(".p-local").text(plugin.local == true ? "일부 문서만" : "문서 전체");
            $box.find(".p-creat").text(pluginMeta[pluginName].author);
            $box.find(".p-last").text(pluginMeta[pluginName].timestamp);
            $box.find(".p-comment").text(pluginMeta[pluginName].comment);
            return $box;
        }
    
        
        return repo.getDocument("틀:플러그인/setup")
        .then(function(templateRaw){
            var $template = $(templateRaw);
            var allPlugins = uninstalledPlugins.concat(unupdatedPlugins);
            allPlugins.forEach(function(plugin){
                var status = uninstalledPlugins.includes(plugin) ? "설치" : "버전업";
                var pluginCard = getPluginCard($template,plugin, status);
                $template.find(".box-article").append(pluginCard);
            });

            return $template;
        });
    }

    // 플러그인 설치 관련 로직
    (function(){
        var commonjsPath = "사용자:" + mw.config.get("wgUserName") + "/common.js"; // 사용자의 commonjs의 경로를 획득합니다.
        var docPlugins = getPlugin.document(); // 문서에서 사용하는 플러그인들을 체크합니다.
        if(Object.keys(docPlugins).length === 0) return; // 문서에서 사용하는 플러그인이 없으면 종료합니다.

        setSubtitleByPlugins(docPlugins); // 문서의 서브타이틀을 문서에서 사용중인 플러그인들의 이름으로 설정합니다.

        repo.getRawDocument(commonjsPath) // 사용자의 commonjs의 문서를 획득합니다.
        .then(function(commonjsRaw){
            var userPlugins = getPlugin.user(commonjsRaw); // 사용자가 가지고 있는 플러그인들을 체크합니다.

            var uninstalledPlugins = docPlugins.filter(function(plugin){
                return !userPlugins.some(function(userPlugin){
                    return userPlugin.name === plugin.name;
                });
            }); // 사용자가 가지고 있지 않은 플러그인들을 체크합니다.

            var unupdatedPlugins = docPlugins.filter(function(plugin){
                return userPlugins.some(function(userPlugin){
                    return userPlugin.name === plugin.name && userPlugin.version !== plugin.version;
                });
            }); // 사용자가 가지고 있는 플러그인들 중 버전이 다른 플러그인들을 체크합니다.

            // 추가 플러그인 설치가 필요하지 않은 경우 로직 종료
            if (isPluginsEmpty(uninstalledPlugins) && isPluginsEmpty(unupdatedPlugins)) return;

            // 설치가 필요한 플러그인들의 이름을 문자열로 반환합니다.
            var needPluginNames = uninstalledPlugins.concat(unupdatedPlugins).map(function(plugin){
                return plugin.name;
            }).join(", ");

            // 플러그인 개발자/코드/마지막 수정일 등 획득, 설치버튼 클릭 시 즉시 설치 가능하도록 미리 Promise를 생성
            var pluginMetaPromise = Promise.all(
                uninstalledPlugins.concat(unupdatedPlugins).map(function(plugin){
                    return Promise.all([plugin.name, repo.getPluginMetadata(plugin.state)]);
                })
            ).then(function(pluginMeta){
                return Object.fromEntries(pluginMeta);
            }).catch(function(){
                alert("플러그인 정보를 불러오는데 실패했습니다. 페이지를 리로드 해주세요.");
            });

            // 플러그인 리스트 수집 끝나면 플러그인 안내 템플릿을 반환하는 Promise를 생성
            var installPagePromise = pluginMetaPromise
            .then(function(pluginMeta){
                return getInstallPagePromise(userPlugins, uninstalledPlugins, unupdatedPlugins, pluginMeta);
            }).catch(function(){
                alert("플러그인 설치 페이지를 불러오는데 실패했습니다. 페이지를 리로드 해주세요.");
            });

            // 플러그인 설치 버튼 클릭 이벤트 핸들러
            var setInstallPage = function(pluginMeta){
                return installPagePromise.then(function($installPage){
                    $installPage.find(".install-button").on("click", function() {
                        $(".install-button").text("설치중..");
                        $(".install-button").off("click");
                    
                        var commonjs = getUpdatedCommonjs(uninstalledPlugins.concat(unupdatedPlugins), commonjsRaw, pluginMeta);
                        repo.changeDocument(commonjsPath, commonjs, "플러그인 " + needPluginNames + "설치")
                        .then(function(){
                            location.reload();
                        })
                        .catch(function(e){
                            alert("플러그인 설치에 실패했습니다. 다시 시도해주세요.");
                            console.error(e);
                        });
                    });

                    var $document = $("#mw-content-text");
                    $document.html($installPage);
                });
            };

            var $loadCodeLink = $(".plugin-install");
            // 플러그인 설치 필요 틀이 있는 페이지인 경우
            if ($loadCodeLink.length >= 1) {
                $loadCodeLink.eq(0).closest("table").show(); // 플러그인 설치 필요 틀을 보여줍니다.
                $(".plugin-name").eq(0).text(needPluginNames); // 필요한 플러그인들의 이름을 표시합니다.
                $loadCodeLink.on("click", function(){
                    // 클릭한 버튼 이벤트 끄고 로딩중메세지 표시
                        $loadCodeLink.off("click");
                        $loadCodeLink.text("필요한 플러그인 데이터를 가져오고 있습니다...");
                        pluginMetaPromise.then(setInstallPage);
                    }); // 플러그인 설치 버튼을 클릭하면 플러그인 체크 로직을 실행합니다.
            } else {
                pluginMetaPromise.then(setInstallPage);
            }
        });
    })();
});