미디어위키:Common.js 문서 원본 보기
←
미디어위키:Common.js
둘러보기로 이동
검색으로 이동
문서 편집 권한이 없습니다. 다음 이유를 확인해주세요:
요청한 명령은 다음 권한을 가진 사용자에게 제한됩니다:
사용자
.
이 문서는 소프트웨어 인터페이스에 쓰이는 문서로, 부정 행위를 막기 위해 보호되어 있습니다.
이 문서는
관리자
만이 수정할 수 있습니다. 내용의 변경이 필요하다면
관리자 요청
에 편집 요청을 남겨 주세요.
모든 방문자에게 영향을 미칠 수 있기 때문에 이 자바스크립트 문서의 편집 권한이 없습니다.
문서의 원본을 보거나 복사할 수 있습니다.
/* 이 자바스크립트 설정은 모든 문서, 모든 사용자에게 적용됩니다. */ /*<source lang="javascript"><nowiki>*/ /** [[틀:USERNAME]]에서 사용하는 바꿔치기 함수 * 작성자: [[사용자:Peremen]] */ function UserNameReplace() { if (typeof(disableUsernameReplace) != 'undefined' && disableUsernameReplace) return; if (!document.getElementById('pt-userpage')) return; $("span.insertusername").each(function(i) { $(this).text(mw.config.get("wgUserName")) }) }; $(UserNameReplace); /** 플러그인이 작동하지 않을 때 나타나는 내용 * js: noPlugin('(이름)'); * html: class="noplugin-(이름)" * 작성자: [[사용자:Gustmd7410|Gustmd7410]] */ function noPlugin(name) { $('.noplugin-' + name).each(function() { $(this).remove(); }); } // [[미디어위키:common.js]] 미작동시: class="noplugin" $('.noplugin').each(function() { $(this).remove(); }); /* window.location.search를 객체 형태로 반환 @param {object} location 획득할 로케이션 객체, 비어있으면 window.location을 참조 원작성자: [[사용자:BANIP|BANIP]] */ function geturlSearch(location) { location = location || window.location; var result = {}; if(location.search) decodeURIComponent(location.search) .substring(1) .split("&") .map(function(v) { return v.split("=") }) .forEach(function(v) { result[v[0]] = v[1] }); return result;url.hash = encodeURIComponent(hash); } /* 객체형태로 된 SearchParam을 받아 string형태로 반환 원작성자: [[사용자:BANIP|BANIP]] */ function searchParamToString(searchParamsObj){ return "?" + Object.entries(searchParamsObj).map(function(v){ return v.map(function(v) { return encodeURIComponent(v); }).join("="); }).join("&"); } /** [[백괴게임:연습장]] 문서 내용 비우기 * 작성자: [[사용자:*devunt]] */ $(function() { if (mw.config.get("wgPageName") != "리버티게임:연습장") return; if (document.URL.lastIndexOf("action=edit") != -1) { if (document.URL.lastIndexOf("fakeaction=clean") == -1) return; var dbindex = document.editform.wpTextbox1; dbindex.value = "<!-- 이 줄은 지우지 마세요 -->{{리버티게임:연습장/안내문}}[["+"분류:리버티게임 도움말]]"; document.editform.wpSummary.value = "연습장 비움"; document.editform.wpSave.click(); return; } }); /** [[틀:제목]]에서 사용하는 제목 바꿔치기 함수 * 작성자: [[사용자:Peremen|Peremen]] */ /* function rewriteTitle() { if (typeof(disableTitleRewrite) != 'undefined' && disableTitleRewrite) return; if (!document.getElementById('title-meta')) return; $('h1.firstHeading').each(function(i) { $(this).html($("#title-meta").html()).css('text-align', $("#title-align").text()); }); } $(rewriteTitle); */ /* from en: */ var hasClass = (function() { var reCache = {}; return function(element, className) { return (reCache[className] ? reCache[className] : (reCache[className] = new RegExp("(?:\\s|^)" + className + "(?:\\s|$)"))).test(element.className); }; })(); /* ([[위키백과:관리자 요청/2007년 5월#스크립트 추가 요청]]) */ /** Collapsible tables ********************************************************* * * Description: Allows tables to be collapsed, showing only the header. See * Wikipedia:NavFrame. * Maintainers: User:R. Koot */ //var autoCollapse = 2; //var collapseCaption = "hide"; //var expandCaption = "show"; function collapseTable(tableIndex) { var Button = document.getElementById("collapseButton" + tableIndex); var Table = document.getElementById("collapsibleTable" + tableIndex); if (!Table || !Button) { return false; } var Rows = Table.getElementsByTagName("tr"); if (Button.firstChild.data == collapseCaption) { for (var i = 1; i < Rows.length; i++) { Rows[i].style.display = "none"; } Button.firstChild.data = expandCaption; } else { for (var i = 1; i < Rows.length; i++) { Rows[i].style.display = Rows[0].style.display; } Button.firstChild.data = collapseCaption; } } function createCollapseButtons() { var tableIndex = 0; var NavigationBoxes = new Object(); var Tables = document.getElementsByTagName("table"); for (var i = 0; i < Tables.length; i++) { if (hasClass(Tables[i], "collapsible")) { NavigationBoxes[tableIndex] = Tables[i]; Tables[i].setAttribute("id", "collapsibleTable" + tableIndex); var Button = document.createElement("span"); var ButtonLink = document.createElement("a"); var ButtonText = document.createTextNode(collapseCaption); Button.style.styleFloat = "right"; Button.style.cssFloat = "right"; Button.style.fontWeight = "normal"; Button.style.textAlign = "right"; Button.style.width = "6em"; ButtonLink.setAttribute("id", "collapseButton" + tableIndex); ButtonLink.setAttribute("href", "javascript:collapseTable(" + tableIndex + ");"); ButtonLink.appendChild(ButtonText); Button.appendChild(document.createTextNode("[")); Button.appendChild(ButtonLink); Button.appendChild(document.createTextNode("]")); var Header = Tables[i].getElementsByTagName("tr")[0].getElementsByTagName("th")[0]; /* only add button and increment count if there is a header row to work with */ if (Header) { Header.insertBefore(Button, Header.childNodes[0]); tableIndex++; } } } for (var i = 0; i < tableIndex; i++) { if (hasClass(NavigationBoxes[i], "collapsed") || (tableIndex >= autoCollapse && hasClass(NavigationBoxes[i], "autocollapse"))) { collapseTable(i); } } } $(createCollapseButtons); /** ([[위키백과:관리자 요청/2007년 5월#스크립트 추가 요청]]) * 이 스크립트는 위키백과 펌이므로, 위키백과 라이선스인 CCL BY-SA가 적용됩니다. */ /** Dynamic Navigation Bars (experimental) ************************************* * * Description: See [[:en:Wikipedia:NavFrame]]. * Maintainers: UNMAINTAINED */ // set up the words in your language var autoCollapse = 2; var collapseCaption = "숨기기"; var expandCaption = "보이기"; var NavigationBarHide = '[' + collapseCaption + ']'; var NavigationBarShow = '[' + expandCaption + ']'; // set up max count of Navigation Bars on page, // if there are more, all will be hidden // NavigationBarShowDefault = 0; // all bars will be hidden // NavigationBarShowDefault = 1; // on pages with more than 1 bar all bars will be hidden var NavigationBarShowDefault = autoCollapse; // shows and hides content and picture (if available) of navigation bars // Parameters: // indexNavigationBar: the index of navigation bar to be toggled function toggleNavigationBar(indexNavigationBar) { var NavToggle = document.getElementById("NavToggle" + indexNavigationBar); var NavFrame = document.getElementById("NavFrame" + indexNavigationBar); if (!NavFrame || !NavToggle) { return false; } // if shown now if (NavToggle.firstChild.data == NavigationBarHide) { for ( var NavChild = NavFrame.firstChild; NavChild != null; NavChild = NavChild.nextSibling ) { if (hasClass(NavChild, 'NavPic')) { NavChild.style.display = 'none'; } if (hasClass(NavChild, 'NavContent')) { NavChild.style.display = 'none'; } } NavToggle.firstChild.data = NavigationBarShow; // if hidden now } else if (NavToggle.firstChild.data == NavigationBarShow) { for ( var NavChild = NavFrame.firstChild; NavChild != null; NavChild = NavChild.nextSibling ) { if (hasClass(NavChild, 'NavPic')) { NavChild.style.display = 'block'; } if (hasClass(NavChild, 'NavContent')) { NavChild.style.display = 'block'; } } NavToggle.firstChild.data = NavigationBarHide; } } // adds show/hide-button to navigation bars function createNavigationBarToggleButton() { var indexNavigationBar = 0; // iterate over all < div >-elements var divs = document.getElementsByTagName("div"); for (var i = 0; NavFrame = divs[i]; i++) { // if found a navigation bar if (hasClass(NavFrame, "NavFrame")) { indexNavigationBar++; var NavToggle = document.createElement("a"); NavToggle.className = 'NavToggle'; NavToggle.setAttribute('id', 'NavToggle' + indexNavigationBar); NavToggle.setAttribute('href', 'javascript:toggleNavigationBar(' + indexNavigationBar + ');'); var NavToggleText = document.createTextNode(NavigationBarHide); NavToggle.appendChild(NavToggleText); // Find the NavHead and attach the toggle link (Must be this complicated because Moz's firstChild handling is borked) for ( var j = 0; j < NavFrame.childNodes.length; j++ ) { if (hasClass(NavFrame.childNodes[j], "NavHead")) { NavFrame.childNodes[j].appendChild(NavToggle); } } NavFrame.setAttribute('id', 'NavFrame' + indexNavigationBar); } } // if more Navigation Bars found than Default: hide all if (NavigationBarShowDefault < indexNavigationBar) { for ( var i = 1; i <= indexNavigationBar; i++ ) { toggleNavigationBar(i); } } } $(createNavigationBarToggleButton); /** 대문의 탭을 "프로젝트"->"대문"으로 바꾸는 함수. * 작성자: [[사용자:Peremen|Peremen]] */ function MainPageRenameNamespaceTab() { var hasMainPageTab = document.getElementsByClassName("hasMainPageTab"); if (hasMainPageTab[0] !== undefined) { $('#ca-nstab-project a').text("대문"); } var title = mw.config.get('wgPageName'); var hasGameListTab = (title == '백괴게임:게임_목록' || title == '백괴게임토론:게임_목록'); if (hasGameListTab) { $('#ca-nstab-project a').text("게임 목록"); } $('#ca-nstab-main a').text("게임"); } $(MainPageRenameNamespaceTab); /** 임베드 플래시 무비 * 문서에 플래시 파일을 넣을 수 있게 합니다. [[틀:플래시]]를 참고하십시오. * 원작성자: [[:en:User:Olipro|Olipro]] */ // 플래시 사용 중지에 따라 이 기능은 비활성화됩니다. /* function useFlash() { var flashOk; var contentTempHolder; $(embedFlashCheck); } function embedFlashMovie(flashOk) { mainbody = document.getElementById('bodyContent'); mainbody.innerHTML = contentTempHolder; spancheck = document.getElementsByTagName('span'); for (i = 0; i < spancheck.length; i++) { if (spancheck[i].getAttribute('id') != 'embedFlashDoc') { continue; } obj = spancheck[i].innerHTML.split('@'); flwidth = obj[0]; flheight = obj[1]; flfile = obj[2].replace('fullurl://', 'https://'); showFlash = ' '; if (flashOk) { showFlash = '<object width="' + flwidth + '" height="' + flheight + '"'; showFlash += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"'; showFlash += 'codebase="http://fpdownload.macromedia.com/pub/'; showFlash += 'shockwave/cabs/flash/swflash.cab#version=8,0,0,0">'; showFlash += '<param name="movie" value="' + flfile + '" />'; showFlash += '<embed src="' + flfile + '" width="' + flwidth + '" height='; showFlash += '"' + flheight + '" type="application/x-shockwave-flash" '; showFlash += 'pluginspage="http://www.macromedia.com/go/getflashplayer" />'; showFlash += '</object>'; } else { showFlash = '<a class="plainlinks" href="javascript:embedFlashMovie(true)" onClick="embedFlashMovie(true)">' + flfile + '</a> (신뢰하는 경우 보려면 클릭하십시오.)'; } spancheck[i].innerHTML = showFlash; spancheck[i].style.display = 'inline'; } } function embedFlashCheck() { if (!document.getElementById('embedFlashDoc')) { return; } mainbody = document.getElementById('bodyContent'); contentTempHolder = mainbody.innerHTML; if (typeof displayFlashOverride != 'undefined') { embedFlashMovie(displayFlashOverride); return; } askmessage = '<div align="center" id="askflash">이 게임에는 '; askmessage += '<a href="/wiki/백괴:플래시" class="plainlinks">플래시</a>가 들어있습니다.<br />'; askmessage += '컴퓨터에 해를 끼칠 수 있으므로 신뢰하는 경우에만 여십시오.<br />'; askmessage += '플래시가 포함된 게임을 보시겠습니까?<br><b> '; askmessage += '<a href="javascript:embedFlashMovie(true)" '; askmessage += 'onClick="embedFlashMovie(true)">예</a> | <a '; askmessage += 'href="https://libertygame.miraheze.org/">아니오</a>'; mainbody.innerHTML = askmessage; } $(useFlash); */ /* userAgent */ /** source: http://www.gregoryvarghese.com/how-to-get-browser-name-and-version-via-javascript/ **/ function get_browser_info() { var ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; if (/trident/i.test(M[1])) { tem = /\brv[ :]+(\d+)/g.exec(ua) || []; return { name: 'IE ', version: (tem[1] || '') }; } if (M[1] === 'Chrome') { tem = ua.match(/\bOPR\/(\d+)/) if (tem != null) { return { name: 'Opera', version: tem[1] }; } } M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?']; if ((tem = ua.match(/version\/(\d+)/i)) != null) { M.splice(1, 1, tem[1]); } return { name: M[0], version: M[1] }; } /** DB기능 초기화 * [[틀:DB]]를 사용 가능하게 해 줍니다. * 원작성자: [[사용자:BANIP|BANIP]] */ function enableLocalStorage() { // 문서 미리보기 상태일 시 종료 var searchParams = geturlSearch(location); var isNormalMode = searchParams.action === undefined && searchParams.curid === undefined && searchParams.oldid === undefined && searchParams.direction === undefined && searchParams.printable === undefined && searchParams.diff === undefined ; if (!(isNormalMode)) return ""; //플러그인의 모든 키 순회 function forEach(object, callback) { for (var key in object) { var variable = object[key]; callback(variable, key); } } function setLocalStorage(parentKey, datas) { var stringKeys = JSON.stringify(Object.keys(datas)); localStorage.setItem(parentKey + "/__keys__", stringKeys); forEach(datas, function(v, i) { if (i === "action") return; localStorage.setItem(parentKey + "/" + i, v); }); } function getLocalStorage(parentKey) { var datas = {}; var stringKeys = localStorage.getItem(parentKey + "/__keys__"); if (stringKeys === null) return null; var keys = JSON.parse(stringKeys); keys.forEach(function(key) { datas[key] = localStorage.getItem(parentKey + "/" + key); }) return datas; } function clearLocalStorage(parentKey) { var datas = {}; var stringKeys = localStorage.getItem(parentKey + "/__keys__"); if (stringKeys === null) return null; var keys = JSON.parse(stringKeys); keys.forEach(function(key) { datas[key] = localStorage.removeItem(parentKey + "/" + key); }) } function getSearch() { return geturlSearch(); } function searchParamToString(searchParamsObj){ var params = "?" + Object.entries(searchParamsObj) .map(function(v){ return v.join("=") }) .join("&"); return encodeURI(params); } function setSearch(datas) { var searchParams = getSearch(); forEach(datas, function(v, i) { searchParams[i] = v; }); location.search = searchParamToString(searchParams); } function hasSearch() { return !(location.search === ""); } $(".controlLocalStorage").each(function() { var parentKey = $(this).data("key"); var behavior = $(this).data("behavior"); switch (behavior) { case "세이브": if (!hasSearch()) return; var datas = getSearch(); clearLocalStorage(parentKey); setLocalStorage(parentKey, datas); break; case "로드": if (hasSearch()) return; var datas = getLocalStorage(parentKey, datas); if (datas === null) return; setSearch(datas); break; case "초기화": clearLocalStorage(parentKey); break; } }) } $(enableLocalStorage); /* DB기능 초기화 끝 */ /** [[틀:CGI2]] 변수 구문 해석기 * 작성자: [[사용자:Gustmd7410]] **/ var CGI2Parser = (function() { function CGI2Parser(actions) { // new 키워드 미사용시 if(!(this instanceof CGI2Parser)) { console.warn("CGI2Parser 객체를 new 키워드 없이 생성했습니다"); return new CGI2Parser(actions); } if(typeof actions === "object") this.actions = actions; else if(actions === undefined) this.actions = {}; else throw new TypeError("생성자 CGI2Parser의 첫번째 변수가 객체가 아닙니다"); } CGI2Parser.prototype.parse = function parse(origin, params, useStrict) { var actions = this.actions; JSON.parse(params.replace(/\,\s*\]/, "]")).forEach(function(command, useStrict) { var wrapper = Object.entries(command)[0]; var action = actions[wrapper[0]]; var arg = wrapper[1]; if(typeof action === "function") { origin = actions[wrapper[0]].apply(origin, Array.isArray(arg)? arg : [arg]) || origin; } else if(!useStrict) { return; } else if(action === undefined) { origin = new TypeError('동작 "' + wrapper[0] + '"이(가) 존재하지 않습니다'); origin.name = "ActionNoExistError"; throw origin; } else { origin = new TypeError('동작 "' + wrapper[0] + '"은(는) 유효하지 않는 타입(' + typeof action + ') 입니다'); origin.name = "ActionWrongTypeError"; throw origin; } }); return origin; }; return CGI2Parser; })(); /* [[틀:CGI2]] 변수 구문 해석기 끝 */ /** [[틀:CGI2|CGI2]]를 사용 가능하게 만드는 jquery 구문 * 원작성자: [[사용자:BANIP|BANIP]] * 2차 작성자: [[사용자:Gustmd7410|Gustmd7410]] **/ function useCGI2() { $(".cgilink").each(function() { // CGI2의 page속성과 data속성을 가져옴 var page = $(this).data("page").split('#'); var variables = $(this).data("var"); // 가져온 page데이터 속성을 조합해 url인스턴스 생성 var pagename = page.shift() || mw.config.get('wgPageName'); var hash = page.join('#'); var urlstr = window.location.origin; var url = new URL(urlstr); var searchParam = geturlSearch(); // 엄격모드 사용여부 결정 if(typeof useCGI2.useStrict !== "boolean") { useCGI2.useStrict = (searchParam.debug === "true" || searchParam.debug == 1)? true : false; } // 가져온 var속성으로 url 인스턴스의 searchparams를 씹고 뜯고 맛보고 즐기고 var enabledKey = null; try { searchParam = new CGI2Parser({ set: function(key, value) { this[key.trim()] = value.trim(); }, "default": function(key, value) { key = key.trim(); this[key] = this[key] || value.trim(); }, del: function(key) { delete this[key.trim()]; }, restrict: function(key) { if(!enabledKey) enabledKey = []; enabledKey.push(key); } }).parse(searchParam, variables, useCGI2.useStrict); if(enabledKey) Object.keys(searchParam).forEach(function(key) { if(!enabledKey.includes(key)) delete searchParam[key]; }); } catch(error) { $(this).html($("<span class='error' />").text(error.message)); return; } // url 경로 조정 if(Object.keys(searchParam).length > 0) { url.pathname = "/w/index.php"; searchParam.title = pagename; } else url.pathname = "/wiki/" + mw.util.wikiUrlencode(pagename); url.search = searchParamToString(searchParam); url.hash = mw.util.escapeIdForLink(hash); // cgilink에 a태그 추가 var children = $(this).html(); $(this).html("<a href='" + url.href.replace("'", "'") + "'>" + children + "</a>"); }); } /* true = 항상 엄격모드 null = 디버그 모드 (debug=true) 에 엄격모드 자동 전환 false = 엄격모드 사용 안함 */ useCGI2.useStrict = null; $(useCGI2); /* [[틀:CGI2|CGI2]]를 사용 가능하게 만드는 jquery 구문 끝 */ /** mediawiki api를 통해 간단하게 문서들을 수집, 변경하는 함수. * 플러그인 코어에서 사용하기 위해 만들었습니다. * 원작성자: [[사용자:BANIP|BANIP]], 수정자: [[사용자:Bd3076|Bd3076]] * 사용방법 *: var api = MediaWikiAPI(); .changeDocument() *: api.changeDocument(변경할 문서의 타이틀, 편집 요약, 변경할 문서의 내용) => 문서의 내용을 변경합니다. *: api.addDocument(변경할 문서의 타이틀, 편집 요약, 추가할 문서의 내용) => 문서에 새로운 내용을 추가합니다. *: api.getDocument(가져올 문서의 타이틀) => 문서의 모든 텍스트를 읽어옵니다. *: api.readDocument(가져올 문서의 타이틀) => 문서의 모든 텍스트를 jquery 객체로 읽어옵니다. * 영 좋지 않은 목적으로 사용 하면 안드로메다 경찰관이 잡아간다! */ function MediaWikiAPI() { var token; var getToken = function() { if (token !== undefined) return token; $.ajax({ url: "/w/api.php?action=query&meta=tokens", success: function(v, i) { var datas = JSON.parse($(v).find("pre").text()); token = datas["query"]["tokens"]["csrftoken"]; }, async: false }); return token; }; function changeDocument(title, summary, content, isUnReload, minor) { mw.loader.using( ['mediawiki.util','mediawiki.Title'] ).then( function () { $.ajax({ url: mw.util.wikiScript("api"), data: { format: 'json', action: 'edit', title: title, summary: summary, text: content, minor: minor, token: getToken(), }, dataType: 'json', type: 'POST', success: function(data) { if (data && data.edit && data.edit.result == 'Success') { if (!isUnReload) window.location.reload(); // reload page if edit was successful } else if (data && data.error) { alert('Error: API returned error code "' + data.error.code + '": ' + data.error.info); } else { alert('Error: Unknown result from API.'); } }, error: function(xhr) { alert('Error: Request failed.'); } }); } ); } function addDocument(title, summary, content, isUnReload, minor) { originContent = getDocument(title); changeDocument(title, summary, originContent + content, isUnReload, minor); } function getDocument(title) { function entityDecode(doc) { return $('<p></p>').html(doc).text(); } var originContent; $.ajax({ url: "/w/index.php?title=" + title + "&action=edit", success: function ajaxSucess(data) { originContent = $(data).find("textarea").html(); }, async: false }); return entityDecode(originContent); } function readDocument(title) { var doc; $.ajax({ url: "/wiki/" + title, success: function ajaxSucess(data) { doc = $(data).find("#mw-content-text"); }, async: false }); return doc; } return { getToken: getToken, changeDocument: changeDocument, addDocument: addDocument, getDocument: getDocument, readDocument: readDocument, }; } /* DB 기능 초기화 끝 */ /** [[틀:플러그인]]을 사용 가능하게 해 줍니다. 사용자의 허락을 맡고 사용자의 commonjs 편집을 허가 할 수 있는 문서입니다. * 작성자: [[사용자:BANIP|BANIP]] * [[백괴게임:관리자 요청/2018년 1월]]에서 BANIP님 요청으로 퍼왔습니다. */ function pluginCore() { // 사용자의 commonjs의 문서 이름을 획득 var commonjs = "사용자:" + mw.config.get("wgUserName") + "/common.js"; var plugins = {}, docPlugins = {}, uninstalledPlugins = {}, unupdatedPlugins = {}; var preloadedplugins = [], needPlugins = []; var api = MediaWikiAPI(); var jsdoc = api.getDocument(commonjs); //commonjs에서 특정 플러그인 제거 function removePluginByDoc(pluginTitle, doc) { var reg = new RegExp("\\/\\*\\* 플러그인 " + pluginTitle + "([\\s\\S]*)\\/\\* " + pluginTitle + " 끝 \\*\\/", "g"); doc = doc.replace(reg, ""); return doc } // 플러그인이 비어있는지 확인 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); } } // 문서에서 사용하는 플러그인들을 체크합니다. use-script클래스를 가진 모든 돔 요소를 조사하고 // 그 돔에 내장된 플러그인 데이터를 docPlugins에 추가합니다. var checkDocPlugin = (function(docPlugins) { function getPluginData($this) { return { name: $this.attr("data-name"), // 플러그인 이름 descript: $this.attr("data-descript"), // 플러그인 내용 version: $this.attr("data-version"), // 플러그인 이름 local: ($this.attr("data-local") === "true") ? true : false, creat: $this.attr("data-creat"), state: $this.attr("data-state"), link: $this.attr("data-link"), executable: $this.attr("data-executable") === "true" ? true : false, }; } function getAllPluginsName(plugins) { var pluginNames = []; forEach(plugins, function(plugin) { var pluginName = plugin.name; pluginNames.push(pluginName); }) return pluginNames; } var loadAllPlugins = (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 = []; // plugins.---가 있는지 체크하는 정규식 var pluginreg = /JSON \=\> ([\S]+) = (\{.*\})/g; var nameMatch = pluginreg.exec(jsdoc); while (nameMatch) { userplugins.push(JSON.parse(nameMatch[2])); nameMatch = pluginreg.exec(jsdoc); } return userplugins; } function isSamePlugin(pluginFirst, pluginSecond) { return pluginFirst.name == pluginSecond.name } function isSameVersionPlugin(pluginFirst, plugiSecond) { return pluginFirst.version == plugiSecond.version } function addNeedPlugins(targetPlugins) { //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; } }); addNeedPlugins(uninstalledPlugins); addNeedPlugins(unupdatedPlugins); } })(); function onPluginInstall() { function getPluginCode(plugin) { // html로 구성된 코드를 텍스트로 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), docFoot = getDocFoot(plugin); return entityDecode(docHead + plugin["code"] + docFoot); } $(".install-button").text("설치중.."); $(".install-button").off("click"); var doc = ""; 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) { var pluginName = plugin.name; var box = $(".cloneable.p-box").clone().removeClass("cloneable"); var code = api.readDocument(plugin.state).find("pre.script").html(); docPlugins[pluginName].code = 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 thisVersion; for (var key in preloadedplugins) { if (preloadedplugins[key].name == plugin["name"]) { thisVersion = preloadedplugins[key].version; } } box.find(".p-version").text(thisVersion + " => " + plugin["version"]); } else { box.find(".p-version").text(plugin["version"]); } box.find(".p-local").text(plugin["local"] == true ? "일부 문서만" : "문서 전체"); box.find(".p-creat").text(plugin["creat"]); $.ajax({ url: "/w/api.php?action=query&prop=revisions&rvdir=older&titles=" + plugin.state, success: function(v, i) { 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); } if (isPluginsEmpty(docPlugins) || (isPluginsEmpty(unupdatedPlugins) && isPluginsEmpty(uninstalledPlugins))) return; 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)) { return; } if ($(".plugin-install").length >= 1) { $(".plugin-install").eq(0).closest("table").show(); $(".plugin-name").eq(0).text(needPlugins); $(".plugin-install").on("click", checkinstalledPlugin); } else { checkinstalledPlugin(); } })(); } $(pluginCore) /** paramtest 함수 * [[틀:CGI2|CGI2]] 등에 적용되는 searchParams 메소드 지원 여부를 판단하는 함수 * 작성자: [[사용자:Manymaster|manymaster]] */ function paramtest() { var test_code; var Params = new URL(document.location).searchParams; if (Params === undefined) { test_code = 0; } else { test_code = 1; } return test_code; } /** paraminfo 함수 * paramtest의 부속 함수로 searchParams 메소드 지원시 지정한 메시지를 숨길 수 있게 해주는 함수 * 메시지는 searchparam_test 클래스로 지정하여 숨길 수 있습니다. */ function paraminfo() { var test_code = paramtest(); if (test_code == 0) { return; } else { for (var i = 0; i < document.getElementsByClassName("searchparam_test").length; i++) { document.getElementsByClassName("searchparam_test")[i].style.display = "none"; } } } $(paraminfo); /* paramtest 및 부속 함수 끝 */ /** [[틀:편집불가]] 재 리뉴얼용 함수 * [[틀:편집불가]]의 브라우저 및 모니터 차별, 3대 원칙 위반 가능 요소 포함 등으로 리뉴얼을 했으나, 리뉴얼한 틀을 소화 못하는 게임이 많아보이는 관계로 재 리뉴얼 합니다. * 작성자: [[사용자:Manymaster|manymaster]] */ function editprevent() { var caprevent = document.getElementsByClassName("edit-prevent"); var cahisprevent = document.getElementsByClassName("history-prevent"); if (caprevent[0] === undefined || caprevent[0] === null) { return; } else { if (cahisprevent[0] === undefined || cahisprevent[0] === null) { cahisprevent = document.getElementsByClassName("edit-prevent"); } var caedit = document.getElementById('ca-edit'); var cahistory = document.getElementById('ca-history'); var caviewsource = document.getElementById('ca-viewsource'); if (caedit !== null && caedit !== undefined){ caedit.innerHTML = caprevent[0].innerHTML; cahistory.innerHTML = cahisprevent[0].innerHTML; } else { caviewsource.innerHTML = caprevent[0].innerHTML; cahistory.innerHTML = cahisprevent[0].innerHTML; } } return; } $( editprevent ); /* [[틀:편집불가]] 재 리뉴얼용 함수 끝 */ /** 스크립트의 함수를 실행시키는 링크 만들기 * $('(객체)').each(function() { scriptLink(this); $(this).click(function () { (함수) }); }); * 작성자: [[사용자:Gustmd7410|Gustmd7410]] */ function scriptLink(element) { $(element).html('<a>' + $(element).html() + '</a>'); } /** [[틀:고급 넘겨주기]]에서 사용하는 넘겨주기 함수 * id="advredirect" data-url="(문서명)" * 작성자: [[사용자:Gustmd7410|Gustmd7410]] */ $('#advredirect').each(function() { location.replace(location.origin + $(this).data('url')); }); /** [[틀:비밀링크]]에서 사용하는 리다이렉트 링크 함수 * class="clearlink" data-url="(문서명)" * 작성자: [[사용자:Gustmd7410|Gustmd7410]] */ $('.clearlink').each(function() { scriptLink(this); $(this).click(function() { location.replace(location.origin + $(this).data('url')); }); }); /** [[틀:뒤로]], [[틀:새로고침]], [[틀:앞으로]]에서 사용하는 방문기록 이동 함수 * class="historygo" data-url="(문서명)" * 작성자: [[사용자:Gustmd7410|Gustmd7410]] */ $('.historygo').each(function() { scriptLink(this); $(this).click(function() { history.go($(this).data('page')); }); }); /** [[특수:올리기]]에 파일 정보 틀 자동 로드 * [[w:특수:고유링크/20640234]] 이 스크립트는 위키백과 펌이므로, 위키백과 라이선스인 CCL BY-SA가 적용됩니다. */ function uploadPreload() { // 새 버전 올리기일 때 동작 안 함 if (mw.util.getParamValue('wpForReUpload') !== null) return; // 설명란이 있고 비어있을 때 내용 변경 var desc = document.getElementById('wpUploadDescription'); if(desc !== null && desc.value === '' ) { desc.value = '{\{파일 정보\n|설명 = \n|출처 = \n|날짜 = \n}}\n'; } } jQuery( document ).ready(uploadPreload); /* [[특수:올리기]]에 파일 정보 틀 자동 로드 끝 */ /** [[틀:로고 바꾸기]]용 함수 V 1.1 * 작성자: [[사용자:Manymaster|manymaster]], * 1.1 업데이트: [[사용자:BANIP|BANIP]] */ function logochange() { // 허용 확장자 및 url 패턴 var allwedURLPattern = ["i.uncyclopedia.kr/game/","i.uncyclopedia.kr/pedia/","upload.wikimedia.org/wikipedia/commons/"] var allowedExt = ["gif","png","jpg","jpeg","svg"] const logoElement = document.getElementsByClassName("changelogo123"); /* .changelogo 요소가 없으면 종료 */ if (logoElement.length === 0) { return; } var logoTest = logoElement[0].innerHTML.toLowerCase(); //이미지 url이 주어진 조건에 맞는지 검사 var isSafeURL = allwedURLPattern.some( function(middleURL){ var reg = new RegExp("//" + middleURL) return ( logoTest.match(reg) != null) }) var isSafeExt = allowedExt.some( function(ext){ var reg = new RegExp("\." + ext + "$") return ( logoTest.match(reg) != null) }) // URL패턴과 확장자의 조건이 맞지 않으면 종료 if ( !(isSafeURL && isSafeExt) ) { return; } /* 로고 교체 */ var orglogoElement = document.querySelector("#p-logo a"); orglogoElement.style.backgroundImage = "url('" + logoElement[0].innerHTML + "')"; /* 사이즈 교체 */ var widthElement = document.querySelector(".changelogowidth"); widthElement.innerHTML = widthElement === null ? "160px" : widthElement.innerHTML; var heightElement = document.querySelector(".changelogoheight"); heightElement.innerHTML = heightElement === null ? "160px" : heightElement.innerHTML; orglogoElement.style.backgroundSize = widthElement.innerHTML + " " + heightElement.innerHTML; } $(logochange); /* [[틀:로고 바꾸기]]용 함수 끝 */ /** * [[백괴게임:댸문]]문서의 일부 링크를 낚시문서로 바꿔치기 * 원작성자: [[사용자:BANIP|BANIP]] */ $(function(){ var gate = document.getElementsByClassName("fakegate"); if (gate[0] === undefined || gate[0] === null) { return; } else{ var targetLink = "https://libertygame.miraheze.org/wiki/"+gate[0].innerHTML; $("#bodyContent a").attr("href",targetLink); } }) /* [[백괴게임:댸문]]문서의 일부 링크를 낚시문서로 바꿔치기 끝 */ /** * pluginX Core * pluginX 시스템이 제대로 돌아가게 해 줍니다. * 작성자: [[사용자:Bd3076|Bd3076]] */ function pluginXCore() { var api = MediaWikiAPI(); var ipUser = false; if(mw.config.get("wgUserName") === null) ipUser = true; var script_list = []; var pluginExist = false; var neededPluginExist = false; var html_before; var jsonDoc = "사용자:" + mw.config.get("wgUserName") + "/pluginX.json"; var jsonData = JSON.parse(api.getDocument(jsonDoc)); /// 플러그인의 정보가 담긴 데이터를 만듭니다. var generateScriptData = function(_url, _name, _creator, _doc, _script, _revid){ return { url: _url, name: _name, creator: _creator, doc: _doc, script: _script, revid: _revid }; }; /// 플러그인 목록을 만들어서 script_list[]에 담습니다. var getPluginList = function(){ $(".c_pluginX").each(function() { pluginExist = true; var url = $(this).attr('data-url'); var name = $(this).attr('data-name'); var creator = $(this).attr('data-creator'); var doc = $(this).attr('data-doc'); var revid = $(this).attr('data-revid'); var script = api.getDocument(doc); script_list.push(generateScriptData(url, name, creator, doc, script, revid)); if(jsonData[doc] === undefined || jsonData[doc] !== revid){ console.log(name); neededPluginExist = true; } }); }; /// 플러그인을 실행합니다. var executePlugins = function(){ script_list.forEach(function(data){ var link = data.url; var revid = data.revid; var doc = data.doc; if(neededPluginExist){ jsonData[doc] = revid; } $.getScript(link); }); ///자동 인증된 사용자가 플러그인을 실행할 경우 json을 갱신합니다. var userGroups = mw.config.get('wgUserGroups'); var autocheck = 0; if (userGroups) { for (var i = 0; i < userGroups.length; i++) { if (userGroups[i] === 'autoconfirmed') { autocheck++; } } } if(neededPluginExist && !(autocheck == 0) ){ api.changeDocument(jsonDoc, "pluginX - 새로운 플러그인", JSON.stringify(jsonData)); } }; /// 플러그인을 실행하지 않습니다. var doNotExecutePlugins = function(){ document.getElementById('mw-content-text').innerHTML = html_before; }; /// 알림 창을 만듭니다. var showWindow = function(){ html_before = document.getElementById('mw-content-text').innerHTML; $('#mw-content-text').html(api.readDocument("틀:PluginX/setup")); var script = ""; script_list.forEach(function(data){ var addingCode; addingCode = '<div class="px-code"><pre>'; addingCode = addingCode.concat(((data.script.replace(/&/g, '&')).replace(/</g, '<')).replace(/>/g, '>')); addingCode = addingCode.concat('</pre> </div> <div class="px-codeinfo"> <ul> <li> 플러그인 이름: '); addingCode = addingCode.concat(data.name); addingCode = addingCode.concat('</li> <li> 플러그인 제작자: '); addingCode = addingCode.concat(data.creator); addingCode = addingCode.concat('</li> </ul> </div>'); // 위험 코드 탐지 시작 var pattern; var nscode = data.script.replace(/\r?\n|\r/g, ' '); // 패턴 1. document.innerHTML 사용 (위험도: 심각) pattern = new RegExp("^.*document *\. *innerHTML.*$"); if(pattern.test(nscode)){ addingCode = addingCode.concat('<div class="px-critical">이 플러그인은 문서 열람 시 지장을 줄 수 있습니다.<br>플러그인 제작자를 신뢰할 수 있는 경우에만 실행하시기 바랍니다.</div>'); } // 패턴 2. document.write 사용 (위험도: 심각) pattern = new RegExp("^.*document *\. *write.*$"); if(pattern.test(nscode)){ addingCode = addingCode.concat('<div class="px-critical">이 플러그인은 문서 내용을 왜곡시킬 수 있습니다.<br>플러그인 제작자를 신뢰할 수 있는 경우에만 실행하시기 바랍니다.</div>'); } // 패턴 3. "wgUserName" 사용 (위험도: 안내) pattern = new RegExp('^.*"wgUserName".*$'); if(pattern.test(nscode)){ addingCode = addingCode.concat('<div class="px-notice">이 플러그인은 당신의 사용자 이름을 수집합니다.<br>이를 원치 않으시면 플러그인을 실행하지 마시기 바랍니다.</div>'); } //패턴 4. MediaWikiAPI.changeDocument 사용(위험도: 경고) pattern = new RegExp('^.*MediaWikiAPI.*changeDocument.*$'); if(pattern.test(nscode)){ addingCode = addingCode.concat('<div class="px-warning">이 플러그인은 다른 문서를 편집합니다.<br>이 게임이 계정 생성형 게임일 가능성이 높습니다.<br>플러그인 제작자를 신뢰할 수 있는 경우에만 실행하시기 바랍니다.</div>'); } script += addingCode; }); document.getElementById('px-script').innerHTML = script; $('#px-button').on('click', executePlugins); $('#px-button2').on('click', doNotExecutePlugins); }; getPluginList(); if(pluginExist === false) return; if(neededPluginExist === false){ executePlugins(); return; } else{ showWindow(); return; } } $(pluginXCore); /* pluginX Core 끝 */ /** 플러그인 inputform * 완전한 입력기를 구현합니다. (베타) * 작성자 : [[사용자:Gustmd7410|Gustmd7410]] */ function plugin_inputform(){ // 이부분에 코드 입력 // mw.loader.using('oojs-ui-core').done(function () { function toBool(value) { switch (value) { case undefined: return false; case '': return true; } } function toArray(value) { if (value) return value.split(' '); else return undefined; } function InpTable(table) { this["case"] = {}; this["default"] = Object.assign({ prefix: '', value: undefined, suffix: '', replace: [], sub: [0] }, table["default"]); if (table["case"]) { for (var value in table["case"]) { this["case"][value] = Object.assign({}, this["default"], { value: this["default"].value || value }, table["case"][value]); } } } $('.input-form').each(function () { var container = this; $(this).html(new $('<form />', { "class": $(this).data('class'), id: $(this).data('id'), style: $(this).data('style'), 'accept-charset': 'UTF-8', autocomplete: toBool($(this).data('autocomplete')), novalidate: toBool($(this).data('novalidate')), html: $(this).html() })); $(this).children('form').submit(function (event) { event.preventDefault(); var action = new URL('/w/index.php', location); if (toBool($(container).data('pass'))) action.search = location.search; if ($(container).data('get')) new URLSearchParams($(container).data('get')).forEach(function (value, key) { action.searchParams.set(key, value); }); action.searchParams.set('title', $(container).data('title')); $(this).children('.input-field').each(function () { if (typeof ($(this).data('table')) == 'object') { var name = $(this).data('name'); var rawval = $(this).find('input').val(); var table = new InpTable($(this).data('table')); var cvttbl = table["case"][rawval] || table["default"]; var cvtval = (cvttbl.value || rawval).slice(cvttbl.sub[0], cvttbl.sub[1]); cvttbl.replace.forEach(function (reparr) { var regex = reparr[0].substr(1).split('/'); regex.pop(); regex = regex.join('/'); var flag = reparr[0].substr(1).split('/').reverse()[0]; cvtval = cvtval.replace(new RegExp(regex, flag), reparr[1]); }); action.searchParams.set(name, cvttbl.prefix + cvtval + cvttbl.suffix); } else action.searchParams.set($(this).data('name'), $(this).find('input').val()); }); location.href = action.href; }); }); $('.input-field').each(function () { $(this).html(new OO.ui.TextInputWidget({ accessKey: $(this).data('accessKey'), autocomplete: toBool($(this).data('autocomplete')), autofocus: toBool($(this).data('autofocus')), classes: toArray($(this).data('classes')), disabled: toBool($(this).data('disabled')), flags: toArray($(this).data('flags')), icon: $(this).data('icon'), iconTitle: $(this).data('iconTitle'), id: $(this).data('id'), indicator: $(this).data('indicator'), indicatorTitle: $(this).data('indicatorTitle'), inputId: $(this).data('inputId'), maxLength: Number($(this).data('maxLength')), name: $(this).data('name'), placeholder: $(this).data('placeholder'), readOnly: toBool($(this).data('readOnly')), required: toBool($(this).data('required')), spellcheck: toBool($(this).data('spellcheck')), tabIndex: Number($(this).data('tabIndex')), text: $(this).data('text'), title: $(this).data('title'), type: $(this).data('type'), validate: (function(container) { if ($(container).data('validatetype') == 'RegExp') { var value = $(container).data('validate').substr(1).split('/'); return RegExp(value[0], value[1]); } else return $(container).data('validate'); })(this), value: this.dataset.value || '' }).$element); if(toBool($(this).data('inline'))) $(this).find('*').css({ display: 'inline', width: 'auto' }); }); $('.input-button').each(function () { $(this).html(new OO.ui.ButtonInputWidget({ accessKey: $(this).data('accessKey'), classes: toArray($(this).data('classes')), disabled: toBool($(this).data('disabled')), flags: toArray($(this).data('flags')), framed: toBool($(this).data('framed')), icon: $(this).data('icon'), iconTitle: $(this).data('iconTitle'), id: $(this).data('id'), indicator: $(this).data('indicator'), indicatorTitle: $(this).data('indicatorTitle'), inputId: $(this).data('inputId'), label: $(this).data('label'), name: $(this).data('name'), tabIndex: Number($(this).data('tabIndex')), text: $(this).data('text'), title: $(this).data('title'), type: $(this).data('type'), value: $(this).data('value') }).$element); }); noPlugin('input'); }); } $( plugin_inputform ); /* inputform 끝 */ /** [[틀:SCGI]]에서 사용하는 링크 리다이렉트 함수 * class="scgi-use" * 작성자: [[사용자:Gustmd7410|Gustmd7410]] **/ $(function useSCGI() { if(!URLSearchParams) return; $('.scgi-use a').each(function() { var href = new URL(this.href); var params = new URLSearchParams(location.search); if( location.host === href.host && ( location.pathname === '/w/index.php' && params.get('title') || location.pathname.startsWith('/wiki/') && location.pathname.replace('/wiki/', '') ) === ( href.pathname === '/w/index.php' && params.searchParams.get('title') || href.pathname.startsWith('/wiki/') && href.pathname.replace('/wiki/', '') ) && params.get('action') === href.searchParams.get('action') ) $(this).click(function(event) { event.preventDefault(); location.replace(event.currentTarget.href); }); }); }); /* [[틀:SCGI]]에서 사용하는 링크 리다이렉트 함수 끝 */ /** 편집 저장시 다른 문서나 CGI로 넘겨주기 * ?action=edit&redirectquery=(params) * 작성자: [[사용자:Gustmd7410|Gustmd7410]] **/ $(function() { // 현재 URL 파라미터 var params; // URLSearchParams 호환시 if(URLSearchParams) params = new URLSearchParams(location.search); // 비호환시 geturlSearch() 사용 else { // Map 호환시 if(Map) params = new Map(Object.entries(geturlSearch())); // 비호환시 mw.Map 사용 else { params = new mw.Map(); params.values = geturlSearch(); params.has = params.exists; } } // 편집창에서 redirectquery 파라미터가 있을때 if(params.has('redirectquery')) { // 편집 폼에 wpExtraQueryRedirect 파라미터 input 요소 추가 $('#editform').append($('<input />', { type: 'hidden', name: 'wpExtraQueryRedirect', value: params.get('redirectquery') })); } }); /* 편집 저장시 다른 문서나 CGI로 넘겨주기 끝 */ /** JSON 편집을 편리하게 해 주는 기능. * [[틀:JSON수정]]을 위한 플러그인입니다. * 작성자: [[사:Bd3076|Bd3076]] */ function uncy_jsonEdit(){ var mp = new Map(); var api = MediaWikiAPI(); $('.uncy-jsonedit').each(function() { var jsonDoc = $(this).attr("data-title"); if (jsonDoc.indexOf("/pluginX.json") !== -1) { console.log("사용자의 pluginX JSON은 안돼요!"); return; } var jsonData; if(mp.get(jsonDoc) === undefined){ jsonData = JSON.parse(api.getDocument(jsonDoc)); } else{ jsonData = JSON.parse(mp.get(jsonDoc)); } var variable = $(this).attr("data-var"); var value = $(this).attr("data-val"); var reset = $(this).attr("data-reset"); if(reset === "1") jsonData = {}; else jsonData[variable] = value; mp.set(jsonDoc, JSON.stringify(jsonData)); console.log(jsonDoc); console.log(JSON.stringify(jsonData)); }); mp.forEach(function(value, key) { if(key === undefined){ console.log("error: document name is undefined"); return; } if(value === undefined){ console.log("error: variable or value is wrong"); return; } api.changeDocument(key, "JSON 데이터 수정", value, 1, 1); mw.loader.using( ['mediawiki.notify','mediawiki.Title'] ).then( function () { mw.notify('JSON 데이터가 수정되었습니다.'); }); }); } $(uncy_jsonEdit); /* JSON 편집을 편리하게 해 주는 기능 끝 */ /** 기여 확인용 플러그인 * 작성자: [[사:Bd3076|Bd3076]] */ function enoughEdit(){ function compareEditCount(number, id1, id2){ var editCount = mw.config.get('wgUserEditCount'); if(editCount < number){ document.getElementById(id1).style.display = "block"; } else{ document.getElementById(id2).style.display = "block"; } } $('.editCompare').each(function(){ var num = $(this).attr("data-num"); var enough = $(this).attr("data-id1"); var nenough = $(this).attr("data-id2"); compareEditCount(num, enough, nenough); }); } $(enoughEdit); /* 기여 확인용 플러그인 끝 */ /** 상위 문서 링크 바꿔치기 * id="setsub" * 작성자: [[사용자:Gustmd7410|Gustmd7410]] **/ function changeContentSub() { var newsub = document.getElementById('setsub'); if(newsub) { document.querySelector('#contentSub').innerHTML = newsub.innerHTML; newsub.remove(); } } $(changeContentSub); /* 상위 문서 링크 바꿔치기 끝 */ /** [[틀:자동저장]]용 문서 미리 비워놓기 * 원출처: [[틀:자동저장/플러그인]] * 비 자동 인증된 사용자가 자동저장 겸용 문서에서 복붙을 할 때 * 문서를 덮어씌우지 않고 밑에 복붙을 하는 상황을 막기 위한 플러그인입니다. */ function nonautosaveready() { /* autosave 편집모드가 아닐 경우 플러그인 종료 */ var searchParams = geturlSearch(location); var isEditMode = searchParams.action === "edit"; var isAutosaveMode = searchParams.autosave === "1"; if (!(isEditMode && isAutosaveMode)) return ""; /* 자동 저장하기에 안전한 네임스페이스가 아닌 경우 플러그인 종료 */ var safeNameSpace = [""]; var thisNamespaceNumber = mw.config.get("wgNamespaceNumber"); var nameSpaceIds = mw.config.get("wgNamespaceIds"); var isSafeNameSpace = safeNameSpace .map(function (namespace) { return nameSpaceIds[namespace]; }) .some(function (nsNumber) { return nsNumber == thisNamespaceNumber; }); if (!(isSafeNameSpace)) return ""; /* 자동 인증된 사용자가 일 경우 플러그인 종료 */ var userGroups = mw.config.get('wgUserGroups'); var autocheck = 0; if (userGroups) { for (var i = 0; i < userGroups.length; i++) { if (userGroups[i] === 'autoconfirmed') { autocheck++; } } } if (autocheck != 0) return ""; /* 미리 비워놓기 */ var savetemp = document.editform.wpTextbox1; savetemp.value = ""; return; } $(nonautosaveready); /* [[틀:자동저장]]용 문서 미리 비워놓기 끝 */ /** 진동 구현 * 작성자: 사용자:아라 */ /* function Vibration(duration) { if (!document.getElementById('vibrate')) return; if (!duration) duration = 100; if (duration > 1000) duration = 1000; navigator.vibrate(duration); } $(function() { Vibration(100); }); */ /* 진동 구현 끝 */ /** 게임 컨트롤러 진동 구현 * 작성자: 사용자:아라 (사용자 공대여자 소스 참조) */ /* function GamepadVibration(idx, duration) { if (!document.getElementById('vibrate')) return; var gamepads = navigator.getGamepads(); if (!idx) idx = 0; if (!gamepads[idx]) return; var pad = gamepads[idx]; if (!duration) duration = 100; if (duration > 1000) duration = 1000; pad.vibrationActuator.playEffect(pad.vibrationActuator.type, { startDelay: 0, duration: duration, weakMagnitude: 0.5, strongMagnitude: 1 }); } $(function() { GamepadVibration(0, 100); }); */ /* 게임 컨트롤러 진동 구현 끝 */ /** TTS 지원 * class="speech" (lang="") (data-pitch="") (data-rate="") (data-volume="") * 제작자: [[사용자:hsl0]] **/ /* var voiceSettings = {}; voiceSettings.default = { voice: null, pitch: 1, rate: 1, volume: 1 }; function speakSpeech() { var ssu, setting; if(!('notts' in geturlSearch() || window.disableTTS)) Array.from(document.getElementsByClassName('speech')).forEach(function(elem) { ssu = new SpeechSynthesisUtterance(elem.innerText); ssu.lang = elem.lang; setting = Object.assign({}, voiceSettings.default, voiceSettings[ssu.lang]); if(setting.voice) ssu.voice = setting.voice; if(!isNaN(elem.dataset.pitch)) ssu.pitch = setting.pitch * elem.dataset.pitch; if(!isNaN(elem.dataset.rate)) ssu.rate = setting.rate * elem.dataset.rate; if(!isNaN(elem.dataset.volume)) ssu.volume = setting.volume * elem.dataset.volume; speechSynthesis.speak(ssu); }); } $(speakSpeech); */ /* TTS 끝 */ /** * punycode.js - 퓨니코드 처리 라이브러리 * DB2에서 미디어위키 옵션 저장소 키의 문자열 제한 우회를 위해 사용중 * 제작자: bestiejs - https://github.com/bestiejs/punycode.js * 라이선스: MIT 라이선스 * 출처: CDNJS - https://cdnjs.cloudflare.com/ajax/libs/punycode/1.4.1/punycode.min.js **/ !function(e){function o(e){throw new RangeError(T[e])}function n(e,o){for(var n=e.length,r=[];n--;)r[n]=o(e[n]);return r}function r(e,o){var r=e.split("@"),t="";r.length>1&&(t=r[0]+"@",e=r[1]),e=e.replace(S,".");var u=e.split("."),i=n(u,o).join(".");return t+i}function t(e){for(var o,n,r=[],t=0,u=e.length;u>t;)o=e.charCodeAt(t++),o>=55296&&56319>=o&&u>t?(n=e.charCodeAt(t++),56320==(64512&n)?r.push(((1023&o)<<10)+(1023&n)+65536):(r.push(o),t--)):r.push(o);return r}function u(e){return n(e,function(e){var o="";return e>65535&&(e-=65536,o+=P(e>>>10&1023|55296),e=56320|1023&e),o+=P(e)}).join("")}function i(e){return 10>e-48?e-22:26>e-65?e-65:26>e-97?e-97:b}function f(e,o){return e+22+75*(26>e)-((0!=o)<<5)}function c(e,o,n){var r=0;for(e=n?M(e/j):e>>1,e+=M(e/o);e>L*C>>1;r+=b)e=M(e/L);return M(r+(L+1)*e/(e+m))}function l(e){var n,r,t,f,l,s,d,a,p,h,v=[],g=e.length,w=0,m=I,j=A;for(r=e.lastIndexOf(E),0>r&&(r=0),t=0;r>t;++t)e.charCodeAt(t)>=128&&o("not-basic"),v.push(e.charCodeAt(t));for(f=r>0?r+1:0;g>f;){for(l=w,s=1,d=b;f>=g&&o("invalid-input"),a=i(e.charCodeAt(f++)),(a>=b||a>M((x-w)/s))&&o("overflow"),w+=a*s,p=j>=d?y:d>=j+C?C:d-j,!(p>a);d+=b)h=b-p,s>M(x/h)&&o("overflow"),s*=h;n=v.length+1,j=c(w-l,n,0==l),M(w/n)>x-m&&o("overflow"),m+=M(w/n),w%=n,v.splice(w++,0,m)}return u(v)}function s(e){var n,r,u,i,l,s,d,a,p,h,v,g,w,m,j,F=[];for(e=t(e),g=e.length,n=I,r=0,l=A,s=0;g>s;++s)v=e[s],128>v&&F.push(P(v));for(u=i=F.length,i&&F.push(E);g>u;){for(d=x,s=0;g>s;++s)v=e[s],v>=n&&d>v&&(d=v);for(w=u+1,d-n>M((x-r)/w)&&o("overflow"),r+=(d-n)*w,n=d,s=0;g>s;++s)if(v=e[s],n>v&&++r>x&&o("overflow"),v==n){for(a=r,p=b;h=l>=p?y:p>=l+C?C:p-l,!(h>a);p+=b)j=a-h,m=b-h,F.push(P(f(h+j%m,0))),a=M(j/m);F.push(P(f(a,0))),l=c(r,w,u==i),r=0,++u}++r,++n}return F.join("")}function d(e){return r(e,function(e){return F.test(e)?l(e.slice(4).toLowerCase()):e})}function a(e){return r(e,function(e){return O.test(e)?"xn--"+s(e):e})}var p="object"==typeof exports&&exports&&!exports.nodeType&&exports,h="object"==typeof module&&module&&!module.nodeType&&module,v="object"==typeof global&&global;v.global!==v&&v.window!==v&&v.self!==v||(e=v);var g,w,x=2147483647,b=36,y=1,C=26,m=38,j=700,A=72,I=128,E="-",F=/^xn--/,O=/[^\x20-\x7E]/,S=/[\x2E\u3002\uFF0E\uFF61]/g,T={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},L=b-y,M=Math.floor,P=String.fromCharCode;if(g={version:"1.4.1",ucs2:{decode:t,encode:u},decode:l,encode:s,toASCII:a,toUnicode:d},"function"==typeof define&&"object"==typeof define.amd&&define.amd)define("punycode",function(){return g});else if(p&&h)if(module.exports==p)h.exports=g;else for(w in g)g.hasOwnProperty(w)&&(p[w]=g[w]);else e.punycode=g}(this); //# sourceMappingURL=./punycode.min.js.map /* punycode.js 끝 */ /** @function fetchScript * 체크섬을 활용해 보다 안전하게 외부 스크립트를 가져올 수 있음 * @author hsl0 **/ var fetchScript = fetch? function fetchScript(url, integrity) { return fetch(url, { header: { Accept: [ 'application/javascript', 'application/ecmascript', 'text/javascript', 'application/x-javascript', '*/*' ] }, integrity: integrity }).then(function(res) { return res.text().then(function(text) { new Function(text)(); return new $.Deferred().resolve(text, res.statusText, res).promise(); }); }); } : $.getScript; /** [[틀:DB2]] * 제작자: [[사용자:hsl0]] **/ mw.loader.using([ 'mediawiki.notification', 'oojs-ui-core' ]); function enableDB2() { var title = mw.config.get('wgPageName').split('/')[0].split(':'); title = title[0].replace(/talk|토론/gi, '') + ':' + title[1]; var noti; var currentUrl = new mw.Title(mw.config.get('wgPageName')).getUrl(geturlSearch()); /* option key 인코딩 퓨니코드 + url인코딩 % = _ _ = __ */ function encode(key) { return encodeURIComponent(punycode.toASCII(key)) .replace(/\./g, '%2E') .replace(/!/g, '%21') .replace(/~/g, '%7E') .replace(/\*/g, '%2A') .replace(/'/g, '%27') .replace(/\(/g, '%28') .replace(/\)/g, '%29') .replace(/_/g, '__') .replace(/%/g, '_'); } function decode(key) { return punycode.toUnicode(decodeURIComponent(key.replace(/_(?=[a-zA-Z0-9]{2})/g, '%').replace(/__/g, '_'))); } function parseJSON(json) { try { return JSON.parse(json); } catch(e) { return; } } // hybridStorage 서브셋 생성 window.setStoragePrefix = function(storage, prefix, except, needEncode) { if(mw.user.isAnon()) needEncode = false; var spp = new Proxy(storage, { get: function(target, prop, receiver) { function keys() { return Object.keys(target).filter(function(key) { return key.startsWith(prefix); }); } return Reflect.get(Object.assign({ length: (prop === 'length') && keys().length, getItem: function getItem(key) { return target.getItem(prefix + (needEncode? encode(key) : key)); }, key: function key(index) { return decode(keys()[index].slice(prefix.length)); }, removeItem: function removeItem(key) { return target.removeItem(prefix + (needEncode? encode(key) : key)).then(function() { return spp; }); }, setItem: function setItem(key, value) { return target.setItem(prefix + (needEncode? encode(key) : key), value).then(function() { return spp; }); } }, except), prop, receiver) || target.getItem(prefix + (needEncode? encode(prop) : prop)); }, set: function(target, prop, value) { return target.setItem(prefix + (needEncode? encode(prop) : prop), value); }, deleteProperty: function(target, prop) { return target.removeItem(prefix + (needEncode? encode(prop) : prop)); }, has: function(target, prop) { Reflect.has(target, prefix + (needEncode? encode(prop) : prop)); }, ownKeys: function(target) { return Reflect.ownKeys(target).filter(function(key) { return key.startsWith(prefix); }).map(function(key) { return decode(key.slice(prefix.length)); }); } }); return spp; }; /* anon = localStorage[*] user = mw.user.options[userjs-*] */ window.hybridStorage = (function() { var action; var api = new mw.Api(); var storage, action; var saveOption = (function() { var options = {}; var deferred = new $.Deferred(); var timeout = null; return function saveOption(key, value) { if(timeout) clearTimeout(timeout); options[key] = value; timeout = setTimeout(function() { api.saveOptions(options).then(deferred.resolve, deferred.reject); options = {}; timeout = null; deferred = new $.Deferred(); }, 100); return deferred.promise(); }; })(); if(mw.user.isAnon()) { storage = new Proxy(localStorage, { get: function(target, prop, receiver) { var value = Reflect.get({ length: target.length, getItem: target.getItem, key: target.key, removeItem: function removeItem(key) { return Promise.resolve(target.removeItem(key)); }, setItem: function setItem(key, value) { return Promise.resolve(target.setItem(key, value)); }, refresh: function refresh() { return Promise.resolve(storage); }, needRefresh: false }, prop, receiver) || target.getItem(prop); return (value === null)? undefined : value; }, set: function(target, prop, value, receiver) { return Promise.resolve(target.setItem(prop, value)); }, deleteProperty: function(target, prop) { return Promise.resolve(target.removeItem(prop)); } }); } else { action = { removeItem: function removeItem(key) { if(key) return saveOption("userjs-" + key, null); else throw new TypeError("Failed to execute 'removeItem' on 'Storage': 1 argument required, but only 0 present."); }, setItem: function setItem(key, value) { if(key) return saveOption("userjs-" + key, value); else throw new TypeError("Failed to execute 'removeItem' on 'Storage': 1 argument required, but only 0 present."); }, refresh: (function() { var deferred = new $.Deferred(); var timeout = null; return function refresh() { if(timeout) clearTimeout(timeout); timeout = setTimeout(function() { api.get({ action: 'query', meta: 'userinfo', uiprop: 'options' }, { cache: false }).then(function(response) { mw.user.options.values = response.query.userinfo.options; return storage; }).then(deferred.resolve, deferred.reject); timeout = null; deferred = new $.Deferred(); }, 100); return deferred.promise(); }; })() }; storage = new Proxy(mw.user.options, { get: function(target, prop, receiver) { function keys() { return Object.keys(target.values).filter(function(key) { return key.startsWith('userjs-'); }); } return Reflect.get({ length: prop === 'length' && keys().length, getItem: function getItem(key) { if(key) return target.get("userjs-" + key); else throw new TypeError("Failed to execute 'getItem' on 'Storage': 1 argument required, but only 0 present."); }, key: function key(index) { return keys()[index].slice(7); }, removeItem: function removeItem(key) { if(!target.exists("userjs-" + key)) return Promise.resolve(); return action.removeItem(key).then(function() { Reflect.deleteProperty(target.values, "userjs-" + key); }); }, setItem: function setItem(key, value) { if(target.get("userjs-" + key) === value) return Promise.resolve(); return action.setItem(key, value).then(function() { target.set("userjs-" + key, value); }); }, refresh: action.refresh, needRefresh: true }, prop, receiver) || Reflect.get(target.values, "userjs-" + prop, receiver); }, set: function(target, prop, value, receiver) { if(Reflect.get(target.values, "userjs-" + prop, receiver) === value) return Promise.resolve(); return action.setItem(prop, value).then(function() { Reflect.set(target.values, "userjs-" + prop, value, receiver); }); }, deleteProperty: function(target, prop) { if(!Reflect.has(target.values, "userjs-" + prop)) return Promise.resolve(); return action.removeItem(prop).then(function() { Reflect.deleteProperty(target.values, "userjs-" + prop); }); }, has: function(target, prop) { return Reflect.has(target.values, "userjs-" + prop); }, ownKeys: function(target) { return Reflect.ownKeys(target.values).filter(function(key) { return key.startsWith("userjs-"); }).map(function(key) { return key.slice(7); }); } }); } return storage; })(); // local + global 슈퍼셋 (hybridStorage[gamedb-*]) var rootGameDB = setStoragePrefix(hybridStorage, 'gamedb-', { refresh: function() { return hybridStorage.refresh().then(function() { return rootGameDB; }); }, needRefresh: hybridStorage.needRefresh }, true); // 게임별로 할당되는 영역 (rootGameDB[{게임}/*]) window.localGameDB = setStoragePrefix(rootGameDB, title + '/', { refresh: function() { return rootGameDB.refresh().then(function() { return localGameDB; }); }, needRefresh: rootGameDB.needRefresh }); // 모든 게임이 공유하는 영역 (rootGameDB[#*]) window.globalGameDB = setStoragePrefix(rootGameDB, '#', { refresh: function() { return rootGameDB.refresh().then(function() { return globalGameDB; }); }, needRefresh: rootGameDB.needRefresh }); function DataChange(href) { this.params = geturlSearch(new URL(href, location)); this.local = {}; this.global = {}; this.root = {}; this.refresh = false; this.paramChanged = false; } DataChange.prototype.control = function control(element) { var base, key, storage, params, val, paramChanged; var data = element.dataset; /* 저장할 키 local: 키 지정 global: 전역 키 지정 local global: 키 지정 (없음): 기본 키 */ if('local' in data && 'global' in data) { throw new TypeError('전역키와 일반키가 동시에 지정되었습니다'); } else if('local' in data) { storage = localGameDB; base = this.local; key = data.local; } else if('global' in data) { storage = globalGameDB; base = this.global; key = data.global; } else { storage = rootGameDB; base = this.root; key = title; } switch(data.action) { // 호환 case '저장': case 'save': /* create: 새 키를 생성할 때만 저장 */ if(!('create' in data && key in base) && location.search) base[key] = JSON.stringify(geturlSearch()); break; case '로드': case 'load': /* safe: 파라미터가 있으면 불러오지 않음 fill: 없는 파라미터만 불러옴 */ if(!(location.search && 'safe' in data)) { Object.assign(this.params, parseJSON(storage.getItem(key)), 'fill' in data && this.params); this.paramChanged = true; } break; // 기본 case '호출': case 'get': /* else: 저장된 데이터가 없을 때의 대체 텍스트 */ if(key in storage || data.else) { this.params[data.arg] = storage.getItem(key) || data.else; this.paramChanged = true; } break; case '수정': case 'set': /* create: 새 키를 생성할 때만 저장 */ if(!('create' in data && key in base)) base[key] = data.arg; break; case '삭제': case '제거': case 'del': base[key] = null; break; // JSON case 'JSON': case 'json': /* reset: 데이터 초기화 */ params = this.params; val = JSON.stringify(new CGI2Parser({ get: function(args) { var base = this; if(typeof args === 'object') Object.pairs(args).forEach(function(pair) { if(base[pair[1]]) { params[pair[0]] = base[pair[1]]; paramChanged = true; } }); else Array.from(arguments).forEach(function(key) { if(base[key]) { params[key] = base[key]; paramChanged = true; } }); }, set: function(args) { var base = this; Object.pairs(args).forEach(function(pair) { base[pair[0]] = pair[1]; }); }, del: function() { var base = this; Array.from(arguments).forEach(function(key) { delete base[key]; }); }, def: function(args) { var base = this; Object.pairs(args).forEach(function(pair) { if(!(pair[0] in base)) base[pair[0]] = pair[1]; }); }, sav: function(args) { var base = this; if(typeof args === 'object') Object.pairs(args).forEach(function(pair) { base[pair[0]] = params[pair[1]]; }); else Array.from(arguments).forEach(function(key) { base[key] = params[key]; }); } }).parse(parseJSON('reset' in data? '' : storage.getItem(key)) || {}, '[' + data.arg + ']')); if(val.length > 2) base[key] = val; Object.assign(this.params, params); if(paramChanged) this.paramChanged = true; break; default: $(element).addClass('error').text(data.action? "'" + data.action + "'은(는) 올바른 동작이 아닙니다" : '올바른 동작을 입력하지 않았습니다'); } }; /** * DB 저장 * @function * @param {DataChange} change */ DataChange.prototype.save = function() { var key, promise; var yet = true; var promises = []; if(title in this.root) { promises.push(rootGameDB.setItem(title, this.root[title])); } for(key in this.local) { promises.push(localGameDB.setItem(key, this.local[key])); } for(key in this.global) { promises.push(globalGameDB.setItem(key, this.global[key])); } promise = $.when.apply(null, promises); promise.then(function() { yet = false; }); if(promises.length && !mw.user.isAnon()) setTimeout(function() { if(yet) noti = mw.notification.notify('데이터를 저장하는 중입니다...', { autoHide: false, tag: 'gameDB', type: 'pending' }); }, 10); return promise; } function handleError(code, object) { mw.notification.notify( code === 'http'? $('<span />') .append($('<p />', {class: 'gameDB-noti-errmsg'}).text(object.xhr.responseText)) .append($('<code />', {class: 'gameDB-noti-errcode'}).text('HTTP ' + object.xhr.status)) : $('<span />') .append($('<p />', {class: 'gameDB-noti-errmsg'}).text(object.error.info)) .append($('<code />', {class: 'gameDB-noti-errcode'}).text(code)), { title: '데이터 저장에 실패하였습니다.', type: 'error', tag: 'gameDB' } ); } // 즉시 (function() { var instant = new DataChange(location.href); $('.gameDB-control').each(function() { instant.control(this); }); instant.save().then(function() { var url = new mw.Title(instant.params.title || mw.config.get('wgPageName')).getUrl(instant.params) + location.hash; if(currentUrl !== url && instant.paramChanged) location.href = url; else if(noti) noti.close(); }, handleError); })(); // 링크 (function() { function process(link, controllers, change) { controllers.each(function() { change.control(this); }); var url = new mw.Title(change.params.title || mw.config.get('wgPageName')).getUrl(change.params) + new URL(link.href).hash; if(change.paramChanged) link.href = url; else if(link.href === location.href) link.href = ""; } $('.gameDB-container').not(':has(a, .gameDB-container)').each(function() { var href; /* clear: 기존 파라미터 넘겨주지 않음 */ if('clear' in this.dataset) { href = new URL(location.href); href.search = ''; href = href.href; } else href = location.href; $(this).html($('<a />', { href: href }).text(this.innerText)); }); $('.gameDB-container a').each(function() { var change = new DataChange(this.href); var controllers = $($(this).parents('.gameDB-container').get().reverse()); process(this, controllers, change); $(this).data('change', change); $(this).addClass('gameDB-link'); }); $('.gameDB-link').on('click', function handler(event) { var link = this; if(!('done' in this.dataset)) { event.preventDefault(); event.stopImmediatePropagation(); } $(this).data('change').save().then(function() { if(link.href !== location.href) { link.dataset.done = ''; $(link).off('click', handler)[0].click(); } else mw.notification.notify('데이터가 저장되었습니다.', { tag: 'gameDB' }); }, handleError); }); })(); } $(function() { mw.loader.using([ 'mediawiki.notification', 'oojs-ui-core' ]).then(enableDB2)['catch'](console.error); }) /* [[틀:DB2]] 끝 */
이 문서에서 사용한 틀:
리버티게임:연습장/안내문
(
원본 보기
)
미디어위키:Common.js
문서로 돌아갑니다.
둘러보기 메뉴
개인 도구
계정 만들기
로그인
어두운 모드
이름공간
메시지
토론
English
보기
읽기
원본 보기
역사 보기
더 보기
새로 고침
검색
둘러보기
대문
최근 바뀜
게임 목록
임의의 게임으로
커뮤니티
리버티게임 본진
도구
여기를 가리키는 문서
가리키는 글의 최근 바뀜
특수 문서 목록
문서 정보
축약된 URL 얻기