틀:빠른이동/app.js
(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 = $("
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 = $("#firstHeading > span.mw-page-title-namespace").val() || "";
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;
// #bodyContent에 로딩창 추가$("
.css({ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", background: "#fff", opacity: 0.8, zIndex: 9999, display: "none" }) .attr("id", "loading").append($("
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%,-50%)"
})
.append($("로딩중")))
.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); // 이벤트 등록
})();
})();