(function(){
// 프리로딩 가능한 링크 최고 갯수
var previewLimit = 5;
// 링크로 해당 문서 컨텐츠와 타이틀을 가져오는 프로미스 획득
var getParsedDocumentPromise = (function(){
var mwApi = new mw.Api();
return function(option){
var {href, fullPagename} = option;
var searchParams = new URL(href).searchParams;
var searchParamsObject = {};
searchParams.forEach(function(value, key){
// title 파라미터는 제외
if(key === "title") return;
searchParamsObject[key] = value;
});
// href에서 searchParams 추출
return new Promise(function(resolve, reject){
var apiParams = {
action: 'parse',
page: fullPagename,
formatversion: 2,
};
var requestOption = Object.assign(apiParams,searchParamsObject);
mwApi.get(requestOption).then(function(data){
var title = data.parse.title;
var text = data.parse.text;
//text내에 #title-meta 요소가 포함되어 있으면 해당 컨텐츠를 title로 변경(제목틀 호환)
var $titleMeta = $(text).find("#title-meta");
if( $titleMeta.length > 0 ){
title = $titleMeta.html();
}
resolve({ title:title, text:text });
}, function(e){
reject(e);
});
});
};
})();
var setLoading = (function(){
// 로딩창 표시 여부 플래그
var timers = [];
return function(isSetShow){
// 이미 로딩 완료된 작업의 경우 로딩창을 표시하지 않기 위해
// 로딩창 표시 작업시 예약 플래그를 설정하고 약간의 텀을 두기.
if(isSetShow){
setLoading.showFlag = true;
var timer = setTimeout(function(){
if(setLoading.showFlag){
$("#loading").fadeIn(200);
// 타이머에서 제거
timers.splice(timers.indexOf(timer), 1);
}
}, 50);
timers.push(timer);
} else {
// 로딩창 삭제 작업시 표시작업이 예약되어있으면 같이 삭제
setLoading.showFlag = false;
$("#loading").fadeOut(200);
timers.forEach(function(timer){
clearTimeout(timer);
});
timers = [];
}
};
})();
var toast = function(message){
// 화면 오른쪽 하단에서 메세지 표시, 2초 후 사라짐
var $toast = $("<div>").addClass("toast").css({
position:"fixed",
bottom: "20px",
right: "20px",
padding: "10px 20px",
background: "#000",
color: "#fff",
opacity: 0.8,
boxShadow: "0 0 10px rgba(0,0,0,0.3)",
zIndex: 9999,
display: "none"
});
$toast.text(message).appendTo("body").fadeIn(200);
setTimeout(function(){
$toast.fadeOut(200, function(){
$toast.remove();
});
}, 2000);
};
// 선행로딩된 페이지들
var documentPromiseMap = {};
var namespace = mw.config.get('wgFormattedNamespaces')[ mw.config.get('wgNamespaceNumber')] || "";
function getPagenameFromUrl(rawUrl){
var url = new URL(rawUrl, location.origin);
// url의 searchParams에 title 파라미터가 있을 경우 해당 파라미터로 docname 지정
var fullPagename = url.searchParams.get("title") || "";
if(!fullPagename){
var matchedUrl = decodeURI(url.pathname).match(/[^\/]+\/(.*)/);
if(matchedUrl){
// 문서명 파싱 성공시 문서명 지정
fullPagename = matchedUrl[1];
} else {
// 문서명 파싱 실패시 모든 링크명 지정
fullPagename = decodeURI(url.pathname)
}
}
// fullPagename에 ":"이 없을 경우 스트링 처음위치에 삽입
fullPagename = fullPagename.indexOf(":") === -1 ? `:${fullPagename}` : fullPagename;
// :를 기준으로 네임스페이스와 문서명 분리
var [namespace, pagename] = fullPagename.split(":");
return {
namespace,
pagename,
fullPagename,
}
}
function preloadDocument($doc){
// 해당 링크의 action, title, oldid 파라미터가 없는 링크만 가져오기
var targetLinkItems = $doc.find("a").not(".new")
// 대상이 되는 링크들만 필터링
.filter(function() {
var href = this.href;
if(href === "" || href.match(/^\#/) !== null) return false; // href가 없는 경우 제외
// 링크에서 가져온 url
var url = new URL(href);
var searchParams = url.searchParams;
// 현재 url
var nowUrl = new URL(location.href);
// 동일한 도메인이 아닌 경우 제외
if(url.origin !== nowUrl.origin) return false;
// 수정/리비전 확인 링크는 제외
if( ["action", "oldid"].some(function(key){
return searchParams.has(key);
})) return false;
return true;
})
// 필터링된 링크에 문서명 정보를 추가
.toArray().map(function(el) {
var { namespace, pagename, fullPagename } = getPagenameFromUrl(el.href);
return {
el, href: el.href, namespace, pagename, fullPagename
}
})
// 동일한 네임스페이스만 이동되게 필터링
.filter(({namespace:thisNamespace}) => thisNamespace === namespace )
// 필터된게 previewLimit 갯수 이상이면 동작 x, 프로미스맵에 클릭 시 로딩 필요함을 명시
if(targetLinkItems.length > previewLimit){
targetLinkItems.forEach(({href}) => {
documentPromiseMap[href] ||= null
});
} else {
// 필터링된게 프로미스맵에 없는 경우 사전 프리로딩 프로미스 추가
targetLinkItems.forEach(({href, pagename, namespace}) => {
documentPromiseMap[href] ||= getParsedDocumentPromise({href, fullPagename: `${namespace}:${pagename}`})
});
}
// 가져온 링크로 전체 작업
targetLinkItems.forEach(function(linkItem){
// 문서명 파싱 실패시 해당 링크 제외
if(!linkItem) return;
$(linkItem.el)
.click(function(e){
e.preventDefault();
replaceBodyContent(this.href);
});
})
};
async function replaceBodyContent(url, {urlChange = true} = {}){
var fullPagename = getPagenameFromUrl(url).fullPagename;
// 프리로드 불필요한 경우(네임스페이스가 다를때, 편집/리비전확인 링크일때) url 영구이동
if(documentPromiseMap[url] === undefined){
return location.href = url;
}
// 프리로드되지 않은 이동 가능한 링크면 로딩창 표시 후 해당 페이지 로딩
if(documentPromiseMap[url] === null){
documentPromiseMap[url] = getParsedDocumentPromise({href:url, fullPagename});
}
// 프로미스가 아직 안끝났으면 로딩창 표시
setLoading(true);
// 링크 프로미스에 문제가 있을 시 다시 내용 확인, 성공시 문서 프로미스에 저장, 오류시 에러메세지
documentPromiseMap[url].then(function({title, text}){
// 프리로드 프로미스가 정상적으로 불러왔을 경우
let $thisDoc = $(text)
// 사전로딩된 내용에서 프리로드 분석 재실행
preloadDocument( $thisDoc );
$("#bodyContent").fadeOut(100, function(){
// 문서내용 교체 후 페이드 인
$("#firstHeading").html(title);
// .vector-article-toolbar 내용물 링크 변경 필요
$(".mw-article-toolbar-container").find("a").each(function(){
try{
var $this = $(this);
var href = $this.attr("href");
var url = new URL(href);
url.searchParams.set("title", title);
$this.attr("href", url.href);
} catch (e){ }
})
// 현재 url 변경
if(urlChange){
history.pushState(null, null, url);
}
$("#bodyContent").html($thisDoc);
$("#bodyContent").fadeIn(100);
})
}).catch(function(e){
console.error(e)
// 에러 발생 시 실패한 페이지 다시 프로미스에 저장 시도
documentPromiseMap[url] = null;
// 에러메세지 표시
toast('페이지 로딩에 실패했습니다. 다시 시도해주세요.');
}).finally(function(){
// 로딩창 삭제
setLoading(false);
});
}
(function main(){
// #enable-preload div 없으면 동작 x
if(!$(".mw-parser-output .enable-preload").length) return;
//enable-preload의 data-preload-limit가 있으면 previewLimit에 설정
let preloadLimit = $(".mw-parser-output .enable-preload").data("preload-limit");
// preloadLimit가 있고 0-10개일시만 설정
if(preloadLimit && preloadLimit >= 0 && preloadLimit <= 10){
previewLimit = preloadLimit;
}
// #bodyContent에 로딩창 추가
$("<div>").addClass("content-loading")
.css({
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
background: "#fff",
opacity: 0.8,
zIndex: 9999,
display: "none"
})
.attr("id", "loading")
.append($("<div>").css({
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%,-50%)"
})
.append($("<span>로딩중</span>")))
.appendTo("#bodyContent");
// 현재 페이지 프리로드, 함수 내부에서 재귀적으로 프리로드 실행
preloadDocument($("#mw-content-text > div.mw-parser-output"));
// url 변경 시(일반적으로 뒤로가기) 프리로드된 문서가 있으면 해당 문서로 이동( 2023년 7월 9일 (일) 14:00 (KST)기준 iOS/파폭에서 동작하지 않음 )
function handleNavigate(e){
// 사용자가 url 변경시에만 동작
if(e.navigationType === "traverse"){
replaceBodyContent(e.destination.url, {urlChange:false});
}
}
navigation.removeEventListener("navigate", handleNavigate); // 이벤트 중복 등록 방지
navigation.addEventListener("navigate", handleNavigate); // 이벤트 등록
})();
})();