PT torrent page waterfall view.
// ==UserScript== // @name PT种子列表无限下拉瀑布流视图 // @name:en PT_waterfall_torrent // @namespace https://github.com/KesaubeEire/PT_TorrentList_Masonry // @version 0.4.16 // @author Kesa // @description PT种子列表无限下拉瀑布流视图(描述不能与名称相同, 乐) // @description:en PT torrent page waterfall view. // @license MIT // @icon https://kamept.com/favicon.ico // @match https://kamept.com/* // @match https://kp.m-team.cc/* // @match https://pterclub.com/* // @exclude */offers.php* // @exclude */index.php* // @exclude */forums.php* // @exclude */viewrequests.php* // @exclude */seek.php* // @grant none // ==/UserScript== (function () { 'use strict'; const _COUNT = { // 外部呼叫函数次数 Call: 0, // 函数实际执行次数 Run: 0 }; function debounce(func, delay) { let timer; return function() { clearTimeout(timer); timer = setTimeout(function() { func.apply(this, arguments); }, delay); }; } function throttle(func, delay) { let timerId; let lastExecTime = 0; return function(...args) { const currentTime = Date.now(); const elapsedTime = currentTime - lastExecTime; if (!timerId && elapsedTime > delay) { func.apply(this, args); lastExecTime = currentTime; } else { clearTimeout(timerId); timerId = setTimeout(() => { func.apply(this, args); lastExecTime = currentTime; timerId = null; }, delay - elapsedTime); } }; } const throttleSort = throttle(doSortMasonry, 1500); function doSortMasonry() { _COUNT.Run++; console.log(`呼叫整理次数: ${_COUNT.Call} 实际整理次数: ${_COUNT.Run}`); masonry.layout(); } function sortMasonry() { _COUNT.Call++; if (masonry) { throttleSort(); } } const CONFIG$2 = { torrentListTable: "table.torrents", TORRENT_LIST_TO_JSON: TORRENT_LIST_TO_JSON$3, RENDER_TORRENT_JSON_IN_MASONRY: RENDER_TORRENT_JSON_IN_MASONRY$3, /**如果站点有自定义的icon, 可以用自定义的 */ ICON: {}, /**如果站点有必要设置自定义的css, 可以用自定义的 */ CSS: css$2, /**如果站点有必要设置分类颜色, 可以用自定义的 */ CATEGORY: {} }; function css$2(variable) { return ` `; } function TORRENT_LIST_TO_JSON$3(torrent_list_Dom, CARD2) { const rows = torrent_list_Dom.querySelectorAll("tbody tr"); const data = []; rows.forEach((row) => { const categoryImg = row.querySelector("td:nth-child(1) > a > img"); const category = categoryImg ? categoryImg.alt : ""; if (!category) return; const categoryLinkDOM = categoryImg.parentNode; const categoryLink = categoryLinkDOM.href; const categoryNumber = categoryLink.slice(-3); const _categoryImg = categoryImg.cloneNode(true); _categoryImg.className = "card-category-img"; const torrentIndex = CARD2.CARD_INDEX++; const torrentNameLink = row.querySelector(".torrentname a"); const torrentName = torrentNameLink ? torrentNameLink.textContent.trim() : ""; const torrentLink = torrentNameLink.href; const pattern = /id=(\d+)&hit/; const match = torrentLink.match(pattern); const torrentId = match ? parseInt(match[1]) : null; const picLink = row.querySelector(".torrentname img").getAttribute("data-src"); const desCell = row.querySelector(".torrentname td:nth-child(2)"); const length = desCell.childNodes.length - 1; const desDom = desCell.childNodes[length]; const description = desDom.nodeName == "#text" ? desDom.textContent.trim() : ""; const place_at_the_top = row.querySelectorAll(".torrentname img.sticky"); const pattMsg = place_at_the_top[0] ? place_at_the_top[0].title : ""; const tempTagDom = row.querySelectorAll(".torrentname font"); const freeTypeImg = row.querySelector('img[class^="pro_"]'); const freeType = freeTypeImg ? "_" + freeTypeImg.alt.replace(/\s+/g, "") : ""; const freeRemainingTimeSpan = freeType ? Array.from(tempTagDom)[tempTagDom.length - 1] : ""; const freeRemainingTime = freeRemainingTimeSpan ? freeRemainingTimeSpan.innerText : ""; const tagSpans = row.querySelectorAll(".torrentname span"); const tagsDOM = Array.from(tagSpans); let tags = tagSpans ? tagsDOM.map((span) => span.textContent.trim()) : []; if (freeRemainingTime != "") { tags.shift(); tagsDOM.shift(); } const raw_tags = tagsDOM.map((el) => el.outerHTML).join(""); const downloadLink = `download.php?id=${torrentId}`; const collectLink = `javascript: bookmark(${torrentId},${torrentIndex});`; const collectDOM = row.querySelector(".torrentname a[id^='bookmark']"); const collectState = collectDOM.children[0].alt; const commentsLink = row.querySelector("td.rowfollow:nth-child(3) a"); const comments = commentsLink ? parseInt(commentsLink.textContent) : 0; const uploadDateSpan = row.querySelector("td:nth-child(4) span"); const uploadDate = uploadDateSpan ? uploadDateSpan.title : ""; const sizeCell = row.querySelector("td:nth-child(5)"); const size = sizeCell ? sizeCell.textContent.trim() : ""; const seedersLink = row.querySelector("td:nth-child(6) a"); const seeders = seedersLink ? parseInt(seedersLink.textContent) : 0; const leechersCell = row.querySelector("td:nth-child(7)"); const leechers = leechersCell ? parseInt(leechersCell.textContent) : 0; const snatchedLink = row.querySelector("td:nth-child(8) a"); const snatched = snatchedLink ? parseInt(snatchedLink.textContent) : 0; const rowData = { torrentIndex, _categoryImg, category, categoryLink, categoryNumber, torrent_name: torrentName, torrentLink, torrentId, picLink, place_at_the_top, pattMsg, downloadLink, collectLink, collectState, tempTagDom, freeTypeImg, free_type: freeType, free_remaining_time: freeRemainingTime, raw_tags, tagsDOM, tags, description, upload_date: uploadDate, comments, size, seeders, leechers, snatched }; data.push(rowData); }); return data; } function RENDER_TORRENT_JSON_IN_MASONRY$3(waterfallNode, torrent_json, isFirst = true, masonry2, CARD2, ICON2 = CONFIG$2.ICON) { const cardTemplate = (data) => { const { torrentIndex, _categoryImg, category, categoryLink, categoryNumber, torrent_name: torrentName, torrentLink, torrentId, picLink, place_at_the_top, pattMsg, downloadLink, collectLink, collectState, tempTagDom, freeTypeImg, free_type: freeType, free_remaining_time: freeRemainingTime, raw_tags, tagsDOM, tags, description, upload_date: uploadDate, comments, size, seeders, leechers, snatched } = data; return ` <div class="card-holder"> <!-- 分区类别 --> <div class="card-category" href="${categoryLink}" <!-- TODO: 颜色这里和龟龟商量怎么搞分类的颜色捏 --> <!-- style="background: ${CONFIG$2.CATEGORY[categoryNumber]};" --> > <!-- TODO: 图片这里先注释了, 和龟龟商量捏 --> <!-- ${_categoryImg.outerHTML} --> ${category} </div> <!-- 标题 & 跳转详情链接 --> <div class="card-title"> <a class="two-lines" src="${torrentLink}" href="${torrentLink}" target="_blank"> ${tempTagDom ? Array.from(tempTagDom).map((e) => e.outerHTML).join(" ") : ""} <b>${torrentName}</b> </a> </div> <!-- 卡片其他信息 --> <div class="card-body"> <div class="card-image" onclick="window.open('${torrentLink}')"> <!-- <img class="card-image--img nexus-lazy-load_Kesa" src="pic/misc/spinner.svg" data-src="${picLink}" alt="${torrentName}" /> --> <!-- NOTE: 加载图片这里换成了logo, 和 MT 一样了捏 --> <img class="card-image--img nexus-lazy-load_Kesa" src="pic/logo2_100.png" data-src="${picLink}" alt="${torrentName}" /> <div class="card-index"> ${torrentIndex + 1} </div> </div> <!-- 置顶 && 免费类型&剩余时间 --> ${freeType || pattMsg ? ` <div class="card-alter"> <div class="top_and_free ${freeType}"> <!-- 置顶等级 --> ${place_at_the_top.length != 0 ? Array.from(place_at_the_top).map((e) => e.outerHTML) + " " : ""} <!-- 免费类型 & 免费剩余时间 --> ${freeTypeImg ? freeTypeImg.outerHTML : ""} <b>${freeRemainingTime ? freeRemainingTime : ""}</b> </div> </div> ` : ``} <!-- 置顶等级 --> <!--${pattMsg ? `<div><b>置顶等级:</b> ${pattMsg}</div>` : ""}--> <!-- 副标题 --> ${description ? `<a class="card-description" href='${torrentLink}'> ${description}</a>` : ""} <!-- 标签 Tags --> <div class="cl-tags"> ${tagsDOM.map((el) => { const _tag = document.createElement("div"); _tag.innerHTML = el.outerHTML; return _tag.outerHTML; }).join("")} <!-- <b>Tags:</b> ${tags.join(", ")} --> </div> <div class="card-details"> <div class="card-line"> <!-- 大小 --> <div class="cl-center"> ${ICON2.SIZE} ${size} </div> <!-- 下载 --> <div class="cl-center"> ${ICON2.DOWNLOAD} <b><a src="${downloadLink}" href="${downloadLink}">下载</a></b> </div> <!-- 收藏 --> <div class="cl-center"> <div class="btnCollet cl-center" id="tI_${torrentIndex}" onclick='COLLET_AND_ICON_CHANGE("${collectLink}", "tI_${torrentIndex}")'> ${collectState == "Unbookmarked" ? ICON2.COLLET : ICON2.COLLETED} <b>收藏</b> </div> </div> </div> <!-- 种子id, 默认不显示 --> <!--<div class="card-line"><b>Torrent ID:</b> ${torrentId}</div> --> <!-- 上传时间 --> <div class="card-line"><b>上传时间:</b> ${uploadDate}</div> <div class="card-line"> ${ICON2.COMMENT} <b>${comments}</b> ${ICON2.SEEDERS} <b>${seeders}</b> ${ICON2.LEECHERS} <b>${leechers}</b> ${ICON2.SNATCHED} <b>${snatched}</b> </div> </div> </div> </div>`; }; for (const rowData of torrent_json) { const card = document.createElement("div"); card.classList.add("card"); card.innerHTML = cardTemplate(rowData); card.style.width = `${CARD2.CARD_WIDTH}px`; card.style.zIndex = 1e4 - rowData.torrentIndex; const card_img = card.querySelector(".card-image--img"); card_img.onload = function() { sortMasonry(); const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === "attributes" && mutation.attributeName === "src") { sortMasonry(); } }); }); const config = { attributes: true, // 监听属性变化 attributeFilter: ["src"] // 只监听 src 属性的变化 }; observer.observe(card_img, config); }; card_img.addEventListener("load", () => { sortMasonry(); }); waterfallNode.appendChild(card); if (!isFirst) { masonry2.appended(card); } } } const CONFIG$1 = { torrentListTable: "table.torrents", TORRENT_LIST_TO_JSON: TORRENT_LIST_TO_JSON$2, RENDER_TORRENT_JSON_IN_MASONRY: RENDER_TORRENT_JSON_IN_MASONRY$2, /**如果站点有自定义的icon, 可以用自定义的 */ ICON: {}, /**如果站点有必要设置自定义的css, 可以用自定义的 */ CSS: css$1, /**如果站点有必要设置分类颜色, 可以用自定义的 */ CATEGORY: { // 成人分类 410: "#f52bcb", // 有码 HD 429: "#f52bcb", // 无码 HD 424: "#db55a9", // 有码 Xvid 430: "#db55a9", // 无码 Xvid 437: "#f77afa", // 有码 DVD 426: "#f77afa", // 无码 DVD 431: "#19a7ec", // 有码 BluRay 432: "#19a7ec", // 无码 BluRay 440: "#f52bcb", // GAY 436: "#bb1e9a", // 0 day 425: "#bb1e9a", // 写真 video 433: "#bb1e9a", // 写真 pic 411: "#f49800", // H-Game 412: "#f49800", // H-Anime 413: "#f49800", // H-Comic // 综合分类 401: "#c74854", // Movie SD 419: "#c01a20", // Movie HD 420: "#c74854", // Movie DVD 421: "#00a0e9", // Movie BluRay 439: "#1b2a51", // Movie Remux 403: "#c74854", // TV SD 402: "#276fb8", // TV HD 435: "#4dbebd ", // TV DVD 438: "#1897d6", // TV BluRay 404: "#23ac38", // 纪录教育 405: "####c34", // Anime 407: "#23ac38", // Sport 422: "#f39800", // Software 423: "#f39800", // Game 427: "#f39800", // EBook 409: "####c34", // Other // 音乐分类 406: "#8a57a1", // MV 408: "#8a57a1", // Music AAC/ALAC 434: "#8a57a1" // Music 无损 } }; function css$1(variable) { return ` /* 卡片种类tag */ .card-category{ height: 24px; padding: 0 6px; border: 1px; background: black; color: white; font-weight: 600; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; display: flex; align-items: center; } /* 卡片种类tag预览图 */ .card-category-img { height: 18px; background-size: 100% 141%; background-position: center top; padding-left: 5%; } `; } function TORRENT_LIST_TO_JSON$2(torrent_list_Dom, CARD2) { const rows = torrent_list_Dom.querySelectorAll("tbody tr"); const data = []; rows.forEach((row) => { const categoryImg = row.querySelector("td:nth-child(1) > a > img"); const category = categoryImg ? categoryImg.title : ""; if (!category) return; const categoryLinkDOM = categoryImg.parentNode; const categoryLink = categoryLinkDOM.href; const categoryNumber = categoryLink.slice(-3); const _categoryImg = categoryImg.cloneNode(true); _categoryImg.className = "card-category-img"; const torrentIndex = CARD2.CARD_INDEX++; const torrentNameLink = row.querySelector(".torrentname a"); const torrentName = torrentNameLink ? torrentNameLink.title.trim() : ""; const torrentLink = torrentNameLink.href; const pattern = /id=(\d+)&hit/; const match = torrentLink.match(pattern); const torrentId = match ? parseInt(match[1]) : null; const imgDom = row.querySelector(".torrentname img"); const _mouseOver = imgDom.getAttribute("onmouseover"); const raw1 = _mouseOver ? _mouseOver.split(",")[2].toString() : ""; const picLink = raw1 ? raw1.slice(raw1.indexOf("'") + 1, raw1.lastIndexOf("'")) : "/pic/nopic.jpg"; const desCell = row.querySelector(".torrentname td:nth-child(2)"); const length = desCell.childNodes.length - 1; const desDom = desCell.childNodes[length]; const description = desDom.nodeName == "#text" ? desDom.textContent.trim() : ""; const place_at_the_top = row.querySelectorAll(".torrentname img.sticky"); const pattMsg = place_at_the_top[0] ? place_at_the_top[0].title : ""; const tempTagDom = row.querySelectorAll(".torrentname font"); const freeTypeImg = row.querySelector('img[class^="pro_"]'); const freeType = freeTypeImg ? "_" + freeTypeImg.alt.replace(/\s+/g, "") : ""; const freeRemainingTimeSpan = row.querySelector(".torrentname td:nth-child(2) span"); const freeRemainingTime = freeRemainingTimeSpan ? freeRemainingTimeSpan.innerText : ""; const tagSpans = row.querySelectorAll(".torrentname img[class^='label_']"); const tagsDOM = Array.from(tagSpans); let tags = tagSpans ? tagsDOM.map((el) => el.title.trim()) : []; const raw_tags = tagsDOM.map((el) => el.outerHTML).join(" "); const downloadLink = `download.php?id=${torrentId}`; const collectLink = `javascript: bookmark(${torrentId},${torrentIndex});`; const collectDOM = row.querySelector(".torrentname a[id^='bookmark']"); const collectState = collectDOM.children[0].alt; const commentsLink = row.querySelector("td.rowfollow:nth-child(3) a"); const comments = commentsLink ? parseInt(commentsLink.textContent) : 0; const uploadDateSpan = row.querySelector("td:nth-child(4) span"); const uploadDate = uploadDateSpan ? uploadDateSpan.title : ""; const sizeCell = row.querySelector("td:nth-child(5)"); const size = sizeCell ? sizeCell.textContent.trim() : ""; const seedersLink = row.querySelector("td:nth-child(6) a"); const seeders = seedersLink ? parseInt(seedersLink.textContent) : 0; const leechersCell = row.querySelector("td:nth-child(7)"); const leechers = leechersCell ? parseInt(leechersCell.textContent) : 0; const snatchedLink = row.querySelector("td:nth-child(8) a"); const snatched = snatchedLink ? parseInt(snatchedLink.textContent) : 0; const rowData = { torrentIndex, _categoryImg, category, categoryLink, categoryNumber, torrent_name: torrentName, torrentLink, torrentId, picLink, place_at_the_top, pattMsg, downloadLink, collectLink, collectState, tempTagDom, freeTypeImg, free_type: freeType, free_remaining_time: freeRemainingTime, raw_tags, tagsDOM, tags, description, comments, upload_date: uploadDate, size, seeders, leechers, snatched }; data.push(rowData); }); return data; } function RENDER_TORRENT_JSON_IN_MASONRY$2(waterfallNode, torrent_json, isFirst = true, masonry2, CARD2, ICON2 = CONFIG$1.ICON) { const cardTemplate = (data) => { const { torrentIndex, _categoryImg, category, categoryLink, categoryNumber, torrent_name: torrentName, torrentLink, torrentId, picLink, place_at_the_top, pattMsg, downloadLink, collectLink, collectState, tempTagDom, freeTypeImg, free_type: freeType, free_remaining_time: freeRemainingTime, raw_tags, tagsDOM, tags, description, comments, upload_date: uploadDate, size, seeders, leechers, snatched } = data; return ` <div class="card-holder"> <!-- 分区类别 --> <div class="card-category" href="${categoryLink}" style="background: ${CONFIG$1.CATEGORY[categoryNumber]};" > ${_categoryImg.outerHTML} ${category} </div> <!-- 标题 & 跳转详情链接 --> <div class="card-title"> <a class="two-lines" src="${torrentLink}" href="${torrentLink}" target="_blank"> ${tempTagDom ? Array.from(tempTagDom).map((e) => e.outerHTML).join(" ") : ""} <b>${torrentName}</b> </a> </div> <!-- 卡片其他信息 --> <div class="card-body"> <div class="card-image" onclick="window.open('${torrentLink}')"> <img class="card-image--img nexus-lazy-load_Kesa" src="logo.png" data-src="${picLink}" alt="${torrentName}"/> <div class="card-index"> ${torrentIndex + 1} </div> </div> <!-- 置顶 && 免费类型&剩余时间 --> ${freeType || pattMsg ? ` <div class="card-alter"> <div class="top_and_free ${freeType}"> <!-- 置顶等级 --> ${place_at_the_top.length != 0 ? Array.from(place_at_the_top).map((e) => e.outerHTML) + " " : ""} <!-- 免费类型 & 免费剩余时间 --> ${freeTypeImg ? freeTypeImg.outerHTML : ""} <b>${freeRemainingTime ? freeRemainingTime : ""}</b> </div> </div> ` : ``} <!-- 置顶等级 --> <!--${pattMsg ? `<div><b>置顶等级:</b> ${pattMsg}</div>` : ""}--> <!-- 副标题 --> ${description ? `<div class="card-description"><a href='${torrentLink}'> ${description}</a></div>` : ""} <!-- 标签 Tags --> <div class="cl-tags"> <!-- ${tempTagDom ? Array.from(tempTagDom).map((e) => e.outerHTML + " ") : ""} --> ${tagsDOM.map((el) => { const _tag = document.createElement("div"); _tag.innerHTML = el.outerHTML; return _tag.outerHTML; }).join("")} <!-- <b>Tags:</b> ${tags.join(" ")} --> </div> <div class="card-details"> <div class="card-line"> <!-- 大小 --> <div class="cl-center"> ${ICON2.SIZE} ${size} </div> <!-- 下载 --> <div class="cl-center"> ${ICON2.DOWNLOAD} <b><a src="${downloadLink}" href="${downloadLink}">下载</a></b> </div> <!-- 收藏 --> <div class="cl-center"> <div class="btnCollet cl-center" id="tI_${torrentIndex}" onclick='COLLET_AND_ICON_CHANGE("${collectLink}", "tI_${torrentIndex}")'> ${collectState == "Unbookmarked" ? ICON2.COLLET : ICON2.COLLETED} <b>收藏</b> </div> </div> </div> <!-- 种子id, 默认不显示 --> <!--<div class="card-line"><b>Torrent ID:</b> ${torrentId}</div> --> <!-- 上传时间 --> <div class="card-line"><b>上传时间:</b> ${uploadDate}</div> <div class="card-line"> ${ICON2.COMMENT} <b>${comments}</b> ${ICON2.SEEDERS} <b>${seeders}</b> ${ICON2.LEECHERS} <b>${leechers}</b> ${ICON2.SNATCHED} <b>${snatched}</b> </div> </div> </div> </div>`; }; for (const rowData of torrent_json) { const card = document.createElement("div"); card.classList.add("card"); card.innerHTML = cardTemplate(rowData); card.style.width = `${CARD2.CARD_WIDTH}px`; card.style.zIndex = 1e4 - rowData.torrentIndex; const card_img = card.querySelector(".card-image--img"); card_img.onload = function() { sortMasonry(); const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === "attributes" && mutation.attributeName === "src") { sortMasonry(); } }); }); const config = { attributes: true, // 监听属性变化 attributeFilter: ["src"] // 只监听 src 属性的变化 }; observer.observe(card_img, config); }; card_img.addEventListener("load", () => { sortMasonry(); }); waterfallNode.appendChild(card); if (!isFirst) { masonry2.appended(card); } } } const CONFIG = { torrentListTable: "table.torrents", TORRENT_LIST_TO_JSON: TORRENT_LIST_TO_JSON$1, RENDER_TORRENT_JSON_IN_MASONRY: RENDER_TORRENT_JSON_IN_MASONRY$1, /**如果站点有必要设置分类颜色, 可以用自定义的 */ LOADING_IMG: "/pic/PTer.Club_Logo_2023.png", /**如果站点有自定义的icon, 可以用自定义的 */ ICON: {}, /**如果站点有必要设置自定义的css, 可以用自定义的 */ CSS: css, /**如果站点有必要设置分类颜色, 可以用自定义的 */ CATEGORY: { // 分类 401: "#ca464b", // Movie 404: "#ed8f3b", // TV Series 403: "#729dbf", // Anime 405: "#d97163", // TV Show 413: "#a1dae7", // MV 406: "#2a4f85", // Music 418: "#61281d", // Real Show 402: "#bb3e6e", // 纪录教育 407: "#275b5c", // Sport 408: "#f6eda2", // EBook 409: "#7a5a5e", // Game 410: "#e5b2af", // Software 411: "#c1aa92", // Learn 412: "#c5c6c8" // Other } }; function css(variable) { return ` /* 卡片种类tag */ .card-category{ height: 24px; padding: 0 6px; border: 1px; background: black; color: white; font-weight: 600; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; display: flex; align-items: center; } /* 卡片种类tag预览图 */ .card-category-img { height: 18px; background-size: 100% 141%; background-position: center top; padding-left: 5%; } `; } function TORRENT_LIST_TO_JSON$1(torrent_list_Dom, CARD2) { const rows = torrent_list_Dom.querySelectorAll("tbody tr"); const data = []; rows.forEach((row) => { const categoryImg = row.querySelector("td:nth-child(1) > a > img"); const category = categoryImg ? categoryImg.alt : ""; if (!category) return; const DOM_LIST = row.querySelectorAll("td.rowfollow"); const categoryLinkDOM = categoryImg.parentNode; const categoryLink = categoryLinkDOM.href; const categoryNumber = categoryLink.slice(-3); const _categoryImg = categoryImg.cloneNode(true); _categoryImg.className = "card-category-img"; const torrentIndex = CARD2.CARD_INDEX++; const torrentNameLink = row.querySelector(".torrentname a"); const torrentName = torrentNameLink ? torrentNameLink.textContent.trim() : ""; const torrentLink = torrentNameLink.href; const pattern = /id=(\d+)/; const match = torrentLink.match(pattern); const torrentId = match ? parseInt(match[1]) : null; const picLink = DOM_LIST[1].querySelector(".torrentname img").getAttribute("data-orig"); const desCell = DOM_LIST[1].querySelector(".torrentname td:nth-child(2) > div > div:nth-child(2)"); const length = desCell ? desCell.childNodes.length - 1 : null; const desDom = desCell ? desCell.childNodes[length] : null; const description = desCell ? desDom.nodeName == "SPAN" ? desDom.textContent.trim() : "" : null; const place_at_the_top = DOM_LIST[1].querySelectorAll(".torrentname img.sticky"); const pattMsg = place_at_the_top[0] ? place_at_the_top[0].title : ""; const tempTagDom = DOM_LIST[1].querySelectorAll(".torrentname td:nth-child(2) font"); const freeTypeImg = DOM_LIST[1].querySelector('img[class^="pro_"]'); const freeType = freeTypeImg ? "_" + freeTypeImg.alt.replace(/\s+/g, "") : ""; const freeRemainingTimeSpan = freeType ? DOM_LIST[1].querySelector(".torrentname td:nth-child(2) > div > div:nth-child(1) span") : ""; const freeRemainingTime = freeRemainingTimeSpan ? freeRemainingTimeSpan.innerText : ""; const tagSpans = DOM_LIST[1].querySelectorAll(".torrentname td:nth-child(2) > div > div:nth-child(2) a"); const tagsDOM = Array.from(tagSpans); let tags = tagSpans ? tagsDOM.map((span) => span.textContent.trim()) : []; if (freeRemainingTime != "") { tags.shift(); tagsDOM.shift(); } const raw_tags = tagsDOM.map((el) => el.outerHTML).join(""); const downloadLink = row.querySelector("td:nth-child(5) a").href; const collectLink = `javascript: bookmark(${torrentId},${torrentIndex});`; const collectDOM = row.querySelector(".torrentname a[id^='bookmark']"); const collectState = collectDOM.children[0].alt; const commentsLink = DOM_LIST[2]; const comments = commentsLink ? parseInt(commentsLink.textContent) : 0; const uploadDateSpan = DOM_LIST[3].childNodes[0]; const uploadDate = uploadDateSpan ? uploadDateSpan.title : ""; const sizeCell = DOM_LIST[4]; const size = sizeCell ? sizeCell.textContent.trim() : ""; const seedersLink = DOM_LIST[5]; const seeders = seedersLink ? parseInt(seedersLink.textContent) : 0; const leechersCell = DOM_LIST[6]; const leechers = leechersCell ? parseInt(leechersCell.textContent) : 0; const snatchedLink = DOM_LIST[7]; const snatched = snatchedLink ? parseInt(snatchedLink.textContent) : 0; const rowData = { torrentIndex, _categoryImg, category, categoryLink, categoryNumber, torrent_name: torrentName, torrentLink, torrentId, picLink, place_at_the_top, pattMsg, downloadLink, collectLink, collectState, tempTagDom, freeTypeImg, free_type: freeType, free_remaining_time: freeRemainingTime, raw_tags, tagsDOM, tags, description, upload_date: uploadDate, comments, size, seeders, leechers, snatched }; data.push(rowData); }); return data; } function RENDER_TORRENT_JSON_IN_MASONRY$1(waterfallNode, torrent_json, isFirst = true, masonry2, CARD2, ICON2 = CONFIG.ICON) { const cardTemplate = (data) => { const { torrentIndex, _categoryImg, category, categoryLink, categoryNumber, torrent_name: torrentName, torrentLink, torrentId, picLink, place_at_the_top, pattMsg, downloadLink, collectLink, collectState, tempTagDom, freeTypeImg, free_type: freeType, free_remaining_time: freeRemainingTime, raw_tags, tagsDOM, tags, description, upload_date: uploadDate, comments, size, seeders, leechers, snatched } = data; return ` <div class="card-holder"> <!-- 分区类别 --> <div class="card-category" href="${categoryLink}" style="background: ${CONFIG.CATEGORY[categoryNumber]};" > <!--${_categoryImg.outerHTML}--> ${category} </div> <!-- 标题 & 跳转详情链接 --> <div class="card-title"> <a class="two-lines" src="${torrentLink}" href="${torrentLink}" target="_blank"> ${tempTagDom ? Array.from(tempTagDom).map((e) => e.outerHTML).join(" ") : ""} <b>${torrentName}</b> </a> </div> <!-- 卡片其他信息 --> <div class="card-body"> <div class="card-image" onclick="window.open('${torrentLink}')"> <!-- <img class="card-image--img nexus-lazy-load_Kesa" src="pic/misc/spinner.svg" data-src="${picLink}" alt="${torrentName}" /> --> <!-- NOTE: 加载图片这里换成了logo, 和 MT 一样了捏 --> <img class="card-image--img nexus-lazy-load_Kesa" src="${CONFIG.LOADING_IMG}" data-src="${picLink}" alt="${torrentName}" /> <div class="card-index"> ${torrentIndex + 1} </div> </div> <!-- 置顶 && 免费类型&剩余时间 --> ${freeType || pattMsg ? ` <div class="card-alter"> <div class="top_and_free ${freeType}"> <!-- 置顶等级 --> ${place_at_the_top.length != 0 ? Array.from(place_at_the_top).map((e) => e.outerHTML) + " " : ""} <!-- 免费类型 & 免费剩余时间 --> ${freeTypeImg ? freeTypeImg.outerHTML : ""} <b>${freeRemainingTime ? " " + freeRemainingTime : ""}</b> </div> </div> ` : ``} <!-- 置顶等级 --> <!--${pattMsg ? `<div><b>置顶等级:</b> ${pattMsg}</div>` : ""}--> <!-- 副标题 --> ${description ? `<a class="card-description" href='${torrentLink}'> ${description}</a>` : ""} <!-- 标签 Tags --> <div class="cl-tags"> ${tagsDOM.map((el) => { const _tag = document.createElement("div"); _tag.innerHTML = el.outerHTML; return _tag.outerHTML; }).join("")} <!-- <b>Tags:</b> ${tags.join(", ")} --> </div> <div class="card-details"> <div class="card-line"> <!-- 大小 --> <div class="cl-center"> ${ICON2.SIZE} ${size} </div> <!-- 下载 --> <div class="cl-center"> ${ICON2.DOWNLOAD} <b><a src="${downloadLink}" href="${downloadLink}">下载</a></b> </div> <!-- 收藏 --> <div class="cl-center"> <div class="btnCollet cl-center" id="tI_${torrentIndex}" onclick='COLLET_AND_ICON_CHANGE("${collectLink}", "tI_${torrentIndex}")'> ${collectState == "Unbookmarked" ? ICON2.COLLET : ICON2.COLLETED} <b>收藏</b> </div> </div> </div> <!-- 种子id, 默认不显示 --> <!--<div class="card-line"><b>Torrent ID:</b> ${torrentId}</div> --> <!-- 上传时间 --> <div class="card-line"><b>上传时间:</b> ${uploadDate}</div> <div class="card-line"> ${ICON2.COMMENT} <b>${comments}</b> ${ICON2.SEEDERS} <b>${seeders}</b> ${ICON2.LEECHERS} <b>${leechers}</b> ${ICON2.SNATCHED} <b>${snatched}</b> </div> </div> </div> </div>`; }; for (const rowData of torrent_json) { const card = document.createElement("div"); card.classList.add("card"); card.innerHTML = cardTemplate(rowData); card.style.width = `${CARD2.CARD_WIDTH}px`; card.style.zIndex = 1e4 - rowData.torrentIndex; const card_img = card.querySelector(".card-image--img"); card_img.onload = function() { sortMasonry(); const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === "attributes" && mutation.attributeName === "src") { sortMasonry(); } }); }); const config = { attributes: true, // 监听属性变化 attributeFilter: ["src"] // 只监听 src 属性的变化 }; observer.observe(card_img, config); }; card_img.addEventListener("load", () => { sortMasonry(); }); waterfallNode.appendChild(card); if (!isFirst) { masonry2.appended(card); } } } const SITE = { "kp.m-team.cc": CONFIG$1, "kamept.com": CONFIG$2, "pterclub.com": CONFIG }; function GET_CURRENT_PT_DOMAIN() { const domainName = window.location.hostname; console.log("当前站点: ", domainName); return domainName; } const SITE_DOMAIN = GET_CURRENT_PT_DOMAIN(); const CARD = { /** 瀑布流卡片宽度 */ CARD_WIDTH: 200, /** 瀑布流卡片边框宽度 -> 这个2是真值, 但是边框好像是会随着分辨率和缩放变化, 给高有利大分辨率, 给低有利于小分辨率 */ CARD_BORDER: 3, /** 瀑布流卡片索引 */ CARD_INDEX: 0 }; const PAGE = { /** 翻页: 底部检测时间间隔 */ GAP: 900, /** 翻页: 底部检测视点与底部距离 */ DISTANCE: 300, /** 翻页: 是否为初始跳转页面 */ IS_ORIGIN: true, /** 翻页: 当前页数 */ PAGE_CURRENT: 0, /** 翻页: 下一页数 */ PAGE_NEXT: 0, /** 翻页: 下一页的链接 */ NEXT_URL: "", /** 翻页: 下一页的加载方式: Button | Slip */ SWITCH_MODE: "Button" }; const ICON = { /** 大小图标 */ SIZE: '<img class="size" src="pic/trans.gif" style=" transform: translateY(-0.4px);" alt="size" title="大小">', /** 评论图标 */ COMMENT: '<img class="comments" src="pic/trans.gif" alt="comments" title="评论数">', /** 上传人数图标 */ SEEDERS: '<img class="seeders" src="pic/trans.gif" alt="seeders" title="种子数">', /** 下载人数图标 */ LEECHERS: '<img class="leechers" src="pic/trans.gif" alt="leechers" title="下载数">', /** 已完成人数图标 */ SNATCHED: '<img class="snatched" src="pic/trans.gif" alt="snatched" title="完成数">', /** 下载图标 */ DOWNLOAD: '<img class="download" src="pic/trans.gif" style=" transform: translateY(1px);" alt="download" title="下载本种">', /** 未收藏图标 */ COLLET: '<img class="delbookmark" src="pic/trans.gif" alt="Unbookmarked" title="收藏">', /** 已收藏图标 */ COLLETED: '<img class="bookmark" src="pic/trans.gif" alt="Bookmarked">' }; function GET_TORRENT_LIST_DOM_FROM_DOMAIN() { const selector = SITE[SITE_DOMAIN].torrentListTable; return document.querySelector(selector); } function TORRENT_LIST_TO_JSON(torrent_list_Dom) { return SITE[SITE_DOMAIN].TORRENT_LIST_TO_JSON(torrent_list_Dom, CARD); } function RENDER_TORRENT_JSON_IN_MASONRY(waterfallNode, torrent_json, isFirst = true, masonry2) { return SITE[SITE_DOMAIN].RENDER_TORRENT_JSON_IN_MASONRY( waterfallNode, torrent_json, isFirst, masonry2, CARD, ICON ); } function PUT_TORRENT_INTO_MASONRY(torrent_list_Dom, waterfallNode, isFirst = true, masonry2) { const data = TORRENT_LIST_TO_JSON(torrent_list_Dom); console.log(`渲染行数: ${data.length}`); RENDER_TORRENT_JSON_IN_MASONRY(waterfallNode, data, isFirst, masonry2); NEXUS_TOOLS(); } function GET_CARD_GUTTER(containerDom, card_width) { const _width = containerDom.clientWidth; const card_real_width = card_width + CARD.CARD_BORDER; const columns = Math.floor(_width / card_real_width); const gutter = (_width - columns * card_real_width) / (columns - 1); return Math.floor(gutter); } function CHANGE_CARD_WIDTH(targetWidth, containerDom, masonry2) { for (const card of containerDom.childNodes) { card.style.width = `${targetWidth}px`; } masonry2.options.gutter = GET_CARD_GUTTER(containerDom, targetWidth); sortMasonry(); } function COLLET_AND_ICON_CHANGE(jsCodeLink, card_id) { try { window.location.href = jsCodeLink; const btn = document.querySelector(`div#${card_id}`); const img = btn.children[0]; img.className = img.className == "delbookmark" ? "bookmark" : "delbookmark"; console.log(`执行脚本${jsCodeLink}成功, 已经收藏或者取消~`); } catch (error) { console.error(error); } } window.COLLET_AND_ICON_CHANGE = COLLET_AND_ICON_CHANGE; function ADD_SITE_EXCLUSIVE_CSS() { if (SITE[SITE_DOMAIN].CSS) return SITE[SITE_DOMAIN].CSS(); else console.log("本站点无自定义CSS~"); } function NEXUS_TOOLS() { jQuery(document).ready(function() { function getImgPosition(event, imgEle2) { let imgWidth = imgEle2.prop("naturalWidth"); let imgHeight = imgEle2.prop("naturalHeight"); let ratio = imgWidth / imgHeight; let offsetX = 0; let offsetY = 0; let width = window.innerWidth - event.clientX; let height = window.innerHeight - event.clientY; let changeOffsetY = 0; let changeOffsetX = false; if (event.clientX > window.innerWidth / 2 && event.clientX + imgWidth > window.innerWidth) { changeOffsetX = true; width = event.clientX; } if (event.clientY > window.innerHeight / 2) { if (event.clientY + imgHeight / 2 > window.innerHeight) { changeOffsetY = 1; height = event.clientY; } else if (event.clientY + imgHeight > window.innerHeight) { changeOffsetY = 2; height = event.clientY; } } if (imgWidth > width) { imgWidth = width; imgHeight = imgWidth / ratio; } if (imgHeight > height) { imgHeight = height; imgWidth = imgHeight * ratio; } if (changeOffsetX) { offsetX = -imgWidth; } if (changeOffsetY == 1) { offsetY = -(imgHeight - (window.innerHeight - event.clientY)); } else if (changeOffsetY == 2) { offsetY = -imgHeight / 2; } return { imgWidth, imgHeight, offsetX, offsetY }; } function getMinRatio(pic, container) { return Math.min(container.width / pic.width, container.height / pic.height); } function previewPosition_Kesa(event, imgEle2) { let imgWidth = imgEle2.prop("naturalWidth") ?? 0; let imgHeight = imgEle2.prop("naturalHeight") ?? 0; const mouseX = event.clientX; const mouseY = event.clientY; const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const distanceToTop = mouseY; const distanceToBottom = viewportHeight - mouseY; const distanceToLeft = mouseX; const distanceToRight = viewportWidth - mouseX; const picSize = { width: imgWidth, height: imgHeight }; const containerSize = { bot: { width: viewportWidth, height: distanceToBottom }, top: { width: viewportWidth, height: distanceToTop }, right: { width: distanceToRight, height: viewportHeight }, left: { width: distanceToLeft, height: viewportHeight } }; let maxRatio = 0; let maxPosition = ""; for (const key in containerSize) { if (Object.hasOwnProperty.call(containerSize, key)) { const element = containerSize[key]; if (getMinRatio(picSize, element) > maxRatio) { maxRatio = getMinRatio(picSize, element); maxPosition = key; } } } const r###lt = { top: { left: 0, top: 0, width: viewportWidth, height: distanceToTop }, bot: { left: 0, top: distanceToTop, width: viewportWidth, height: distanceToBottom }, left: { left: 0, top: 0, width: distanceToLeft, height: viewportHeight }, right: { left: distanceToLeft, top: 0, width: distanceToRight, height: viewportHeight }, default: { left: 0, top: 0, width: 0, height: 0 } }; const container = maxPosition != "" ? r###lt[maxPosition] : r###lt["default"]; return container; } function getPosition(event, position) { return { left: event.pageX + position.offsetX, top: event.pageY + position.offsetY, width: position.imgWidth, height: position.imgHeight }; } const selector = "img.preview_Kesa"; let imgEle; let imgPosition; if (!jQuery("#nexus-preview").length) { const _previewDom = document.body.appendChild(document.createElement("img")); _previewDom.id = "nexus-preview"; } jQuery("#nexus-preview"); function createKesaPreview(color) { const parent = jQuery("<div>", { id: "kp_container", css: { backgroundColor: color, opacity: 1, position: "fixed", zIndex: 2e4, pointerEvents: "none", transition: "all .3s" } }); parent.append(jQuery("<img>", { class: "kp_img", css: { position: "absolute", zIndex: 20002, pointerEvents: "none", width: "100%", height: "100%", objectFit: "contain" } })); parent.append(jQuery("<img>", { class: "kp_img", css: { position: "absolute", zIndex: 20001, pointerEvents: "none", width: "100%", height: "100%", objectFit: "cover", filter: `blur(8px)` } })); return parent; } const kesa_preview = jQuery("#kp_container").length > 0 ? jQuery("#kp_container") : createKesaPreview(""); jQuery("body").append(kesa_preview); jQuery("body").on("mouseover", selector, function(e) { imgEle = jQuery(this); imgPosition = getImgPosition(e, imgEle); getPosition(e, imgPosition); let src = imgEle.attr("src"); if (src) { if (kesa_preview) kesa_preview.find(".kp_img").attr("src", src); } kesa_preview.css(previewPosition_Kesa(e, imgEle)).show(); }).on("mouseout", selector, function(e) { kesa_preview.hide(); }).on("mousemove", selector, function(e) { imgPosition = getImgPosition(e, imgEle); getPosition(e, imgPosition); kesa_preview.css(previewPosition_Kesa(e, imgEle)); }); if ("IntersectionObserver" in window) { let imgList = [...document.querySelectorAll(".nexus-lazy-load_Kesa")]; const io = new IntersectionObserver((entries) => { entries.forEach((entry) => { const el = entry.target; entry.intersectionRatio; el._entry = entry; if (entry.isIntersecting && !el.classList.contains("preview_Kesa")) { const source = el.dataset.src; el.src = source; el.classList.add("preview_Kesa"); sortMasonry(); } }); }); imgList.forEach((img) => io.observe(img)); } }); } /*! * Masonry PACKAGED v4.2.2 * Cascading grid layout library * https://masonry.desandro.com * MIT License * by David DeSandro */ (function(window2, factory8) { if (typeof define == "function" && define.amd) { define("jquery-bridget/jquery-bridget", ["jquery"], function(jQuery2) { return factory8(window2, jQuery2); }); } else { window2.jQueryBridget = factory8( window2, window2.jQuery ); } })(window, function factory(window2, jQuery2) { var arraySlice = Array.prototype.slice; var console2 = window2.console; var logError = typeof console2 == "undefined" ? function() { } : function(message) { console2.error(message); }; function jQueryBridget(namespace, PluginClass, $) { $ = $ || jQuery2 || window2.jQuery; if (!$) { return; } if (!PluginClass.prototype.option) { PluginClass.prototype.option = function(opts) { if (!$.isPlainObject(opts)) { return; } this.options = $.extend(true, this.options, opts); }; } $.fn[namespace] = function(arg0) { if (typeof arg0 == "string") { var args = arraySlice.call(arguments, 1); return methodCall(this, arg0, args); } plainCall(this, arg0); return this; }; function methodCall($elems, methodName, args) { var returnValue; var pluginMethodStr = "$()." + namespace + '("' + methodName + '")'; $elems.each(function(i, elem) { var instance = $.data(elem, namespace); if (!instance) { logError(namespace + " not initialized. Cannot call methods, i.e. " + pluginMethodStr); return; } var method = instance[methodName]; if (!method || methodName.charAt(0) == "_") { logError(pluginMethodStr + " is not a valid method"); return; } var value = method.apply(instance, args); returnValue = returnValue === void 0 ? value : returnValue; }); return returnValue !== void 0 ? returnValue : $elems; } function plainCall($elems, options) { $elems.each(function(i, elem) { var instance = $.data(elem, namespace); if (instance) { instance.option(options); instance._init(); } else { instance = new PluginClass(elem, options); $.data(elem, namespace, instance); } }); } updateJQuery($); } function updateJQuery($) { if (!$ || $ && $.bridget) { return; } $.bridget = jQueryBridget; } updateJQuery(jQuery2 || window2.jQuery); return jQueryBridget; }); (function(global, factory8) { if (typeof define == "function" && define.amd) { define("ev-emitter/ev-emitter", factory8); } else { global.EvEmitter = factory8(); } })(typeof window != "undefined" ? window : globalThis, function() { function EvEmitter() { } var proto = EvEmitter.prototype; proto.on = function(eventName, listener) { if (!eventName || !listener) { return; } var events = this._events = this._events || {}; var listeners = events[eventName] = events[eventName] || []; if (listeners.indexOf(listener) == -1) { listeners.push(listener); } return this; }; proto.once = function(eventName, listener) { if (!eventName || !listener) { return; } this.on(eventName, listener); var onceEvents = this._onceEvents = this._onceEvents || {}; var onceListeners = onceEvents[eventName] = onceEvents[eventName] || {}; onceListeners[listener] = true; return this; }; proto.off = function(eventName, listener) { var listeners = this._events && this._events[eventName]; if (!listeners || !listeners.length) { return; } var index = listeners.indexOf(listener); if (index != -1) { listeners.splice(index, 1); } return this; }; proto.emitEvent = function(eventName, args) { var listeners = this._events && this._events[eventName]; if (!listeners || !listeners.length) { return; } listeners = listeners.slice(0); args = args || []; var onceListeners = this._onceEvents && this._onceEvents[eventName]; for (var i = 0; i < listeners.length; i++) { var listener = listeners[i]; var isOnce = onceListeners && onceListeners[listener]; if (isOnce) { this.off(eventName, listener); delete onceListeners[listener]; } listener.apply(this, args); } return this; }; proto.allOff = function() { delete this._events; delete this._onceEvents; }; return EvEmitter; }); /*! * getSize v2.0.3 * measure size of elements * MIT license */ (function(window2, factory8) { if (typeof define == "function" && define.amd) { define("get-size/get-size", factory8); } else { window2.getSize = factory8(); } })(window, function factory2() { function getStyleSize(value) { var num = parseFloat(value); var isValid = value.indexOf("%") == -1 && !isNaN(num); return isValid && num; } function noop() { } var logError = typeof console == "undefined" ? noop : function(message) { console.error(message); }; var measurements = [ "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "marginLeft", "marginRight", "marginTop", "marginBottom", "borderLeftWidth", "borderRightWidth", "borderTopWidth", "borderBottomWidth" ]; var measurementsLength = measurements.length; function getZeroSize() { var size = { width: 0, height: 0, innerWidth: 0, innerHeight: 0, outerWidth: 0, outerHeight: 0 }; for (var i = 0; i < measurementsLength; i++) { var measurement = measurements[i]; size[measurement] = 0; } return size; } function getStyle(elem) { var style = getComputedStyle(elem); if (!style) { logError("Style returned " + style + ". Are you running this code in a hidden iframe on Firefox? See https://bit.ly/getsizebug1"); } return style; } var isSetup = false; var isBoxSizeOuter; function setup() { if (isSetup) { return; } isSetup = true; var div = document.createElement("div"); div.style.width = "200px"; div.style.padding = "1px 2px 3px 4px"; div.style.borderStyle = "solid"; div.style.borderWidth = "1px 2px 3px 4px"; div.style.boxSizing = "border-box"; var body = document.body || document.documentElement; body.appendChild(div); var style = getStyle(div); isBoxSizeOuter = Math.round(getStyleSize(style.width)) == 200; getSize.isBoxSizeOuter = isBoxSizeOuter; body.removeChild(div); } function getSize(elem) { setup(); if (typeof elem == "string") { elem = document.querySelector(elem); } if (!elem || typeof elem != "object" || !elem.nodeType) { return; } var style = getStyle(elem); if (style.display == "none") { return getZeroSize(); } var size = {}; size.width = elem.offsetWidth; size.height = elem.offsetHeight; var isBorderBox = size.isBorderBox = style.boxSizing == "border-box"; for (var i = 0; i < measurementsLength; i++) { var measurement = measurements[i]; var value = style[measurement]; var num = parseFloat(value); size[measurement] = !isNaN(num) ? num : 0; } var paddingWidth = size.paddingLeft + size.paddingRight; var paddingHeight = size.paddingTop + size.paddingBottom; var marginWidth = size.marginLeft + size.marginRight; var marginHeight = size.marginTop + size.marginBottom; var borderWidth = size.borderLeftWidth + size.borderRightWidth; var borderHeight = size.borderTopWidth + size.borderBottomWidth; var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter; var styleWidth = getStyleSize(style.width); if (styleWidth !== false) { size.width = styleWidth + // add padding and border unless it's already including it (isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth); } var styleHeight = getStyleSize(style.height); if (styleHeight !== false) { size.height = styleHeight + // add padding and border unless it's already including it (isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight); } size.innerWidth = size.width - (paddingWidth + borderWidth); size.innerHeight = size.height - (paddingHeight + borderHeight); size.outerWidth = size.width + marginWidth; size.outerHeight = size.height + marginHeight; return size; } return getSize; }); (function(window2, factory8) { if (typeof define == "function" && define.amd) { define("desandro-matches-selector/matches-selector", factory8); } else { window2.matchesSelector = factory8(); } })(window, function factory3() { var matchesMethod = function() { var ElemProto = window.Element.prototype; if (ElemProto.matches) { return "matches"; } if (ElemProto.matchesSelector) { return "matchesSelector"; } var prefixes = ["webkit", "moz", "ms", "o"]; for (var i = 0; i < prefixes.length; i++) { var prefix = prefixes[i]; var method = prefix + "MatchesSelector"; if (ElemProto[method]) { return method; } } }(); return function matchesSelector(elem, selector) { return elem[matchesMethod](selector); }; }); (function(window2, factory8) { if (typeof define == "function" && define.amd) { define("fizzy-ui-utils/utils", [ "desandro-matches-selector/matches-selector" ], function(matchesSelector) { return factory8(window2, matchesSelector); }); } else { window2.fizzyUIUtils = factory8( window2, window2.matchesSelector ); } })(window, function factory4(window2, matchesSelector) { var utils = {}; utils.extend = function(a, b) { for (var prop in b) { a[prop] = b[prop]; } return a; }; utils.modulo = function(num, div) { return (num % div + div) % div; }; var arraySlice = Array.prototype.slice; utils.makeArray = function(obj) { if (Array.isArray(obj)) { return obj; } if (obj === null || obj === void 0) { return []; } var isArrayLike = typeof obj == "object" && typeof obj.length == "number"; if (isArrayLike) { return arraySlice.call(obj); } return [obj]; }; utils.removeFrom = function(ary, obj) { var index = ary.indexOf(obj); if (index != -1) { ary.splice(index, 1); } }; utils.getParent = function(elem, selector) { while (elem.parentNode && elem != document.body) { elem = elem.parentNode; if (matchesSelector(elem, selector)) { return elem; } } }; utils.getQueryElement = function(elem) { if (typeof elem == "string") { return document.querySelector(elem); } return elem; }; utils.handleEvent = function(event) { var method = "on" + event.type; if (this[method]) { this[method](event); } }; utils.filterFindElements = function(elems, selector) { elems = utils.makeArray(elems); var ffElems = []; elems.forEach(function(elem) { if (!(elem instanceof HTMLElement)) { return; } if (!selector) { ffElems.push(elem); return; } if (matchesSelector(elem, selector)) { ffElems.push(elem); } var childElems = elem.querySelectorAll(selector); for (var i = 0; i < childElems.length; i++) { ffElems.push(childElems[i]); } }); return ffElems; }; utils.debounceMethod = function(_class, methodName, threshold) { threshold = threshold || 100; var method = _class.prototype[methodName]; var timeoutName = methodName + "Timeout"; _class.prototype[methodName] = function() { var timeout = this[timeoutName]; clearTimeout(timeout); var args = arguments; var _this = this; this[timeoutName] = setTimeout(function() { method.apply(_this, args); delete _this[timeoutName]; }, threshold); }; }; utils.docReady = function(callback) { var readyState = document.readyState; if (readyState == "complete" || readyState == "interactive") { setTimeout(callback); } else { document.addEventListener("DOMContentLoaded", callback); } }; utils.toDashed = function(str) { return str.replace(/(.)([A-Z])/g, function(match, $1, $2) { return $1 + "-" + $2; }).toLowerCase(); }; var console2 = window2.console; utils.htmlInit = function(WidgetClass, namespace) { utils.docReady(function() { var dashedNamespace = utils.toDashed(namespace); var dataAttr = "data-" + dashedNamespace; var dataAttrElems = document.querySelectorAll("[" + dataAttr + "]"); var jsDashElems = document.querySelectorAll(".js-" + dashedNamespace); var elems = utils.makeArray(dataAttrElems).concat(utils.makeArray(jsDashElems)); var dataOptionsAttr = dataAttr + "-options"; var jQuery2 = window2.jQuery; elems.forEach(function(elem) { var attr = elem.getAttribute(dataAttr) || elem.getAttribute(dataOptionsAttr); var options; try { options = attr && JSON.parse(attr); } catch (error) { if (console2) { console2.error("Error parsing " + dataAttr + " on " + elem.className + ": " + error); } return; } var instance = new WidgetClass(elem, options); if (jQuery2) { jQuery2.data(elem, namespace, instance); } }); }); }; return utils; }); (function(window2, factory8) { if (typeof define == "function" && define.amd) { define( "outlayer/item", [ "ev-emitter/ev-emitter", "get-size/get-size" ], factory8 ); } else { window2.Outlayer = {}; window2.Outlayer.Item = factory8( window2.EvEmitter, window2.getSize ); } })(window, function factory5(EvEmitter, getSize) { function isEmptyObj(obj) { for (var prop in obj) { return false; } prop = null; return true; } var docElemStyle = document.documentElement.style; var transitionProperty = typeof docElemStyle.transition == "string" ? "transition" : "WebkitTransition"; var transformProperty = typeof docElemStyle.transform == "string" ? "transform" : "WebkitTransform"; var transitionEndEvent = { WebkitTransition: "webkitTransitionEnd", transition: "transitionend" }[transitionProperty]; var vendorProperties = { transform: transformProperty, transition: transitionProperty, transitionDuration: transitionProperty + "Duration", transitionProperty: transitionProperty + "Property", transitionDelay: transitionProperty + "Delay" }; function Item(element, layout) { if (!element) { return; } this.element = element; this.layout = layout; this.position = { x: 0, y: 0 }; this._create(); } var proto = Item.prototype = Object.create(EvEmitter.prototype); proto.constructor = Item; proto._create = function() { this._transn = { ingProperties: {}, clean: {}, onEnd: {} }; this.css({ position: "absolute" }); }; proto.handleEvent = function(event) { var method = "on" + event.type; if (this[method]) { this[method](event); } }; proto.getSize = function() { this.size = getSize(this.element); }; proto.css = function(style) { var elemStyle = this.element.style; for (var prop in style) { var supportedProp = vendorProperties[prop] || prop; elemStyle[supportedProp] = style[prop]; } }; proto.getPosition = function() { var style = getComputedStyle(this.element); var isOriginLeft = this.layout._getOption("originLeft"); var isOriginTop = this.layout._getOption("originTop"); var xValue = style[isOriginLeft ? "left" : "right"]; var yValue = style[isOriginTop ? "top" : "bottom"]; var x = parseFloat(xValue); var y = parseFloat(yValue); var layoutSize = this.layout.size; if (xValue.indexOf("%") != -1) { x = x / 100 * layoutSize.width; } if (yValue.indexOf("%") != -1) { y = y / 100 * layoutSize.height; } x = isNaN(x) ? 0 : x; y = isNaN(y) ? 0 : y; x -= isOriginLeft ? layoutSize.paddingLeft : layoutSize.paddingRight; y -= isOriginTop ? layoutSize.paddingTop : layoutSize.paddingBottom; this.position.x = x; this.position.y = y; }; proto.layoutPosition = function() { var layoutSize = this.layout.size; var style = {}; var isOriginLeft = this.layout._getOption("originLeft"); var isOriginTop = this.layout._getOption("originTop"); var xPadding = isOriginLeft ? "paddingLeft" : "paddingRight"; var xProperty = isOriginLeft ? "left" : "right"; var xResetProperty = isOriginLeft ? "right" : "left"; var x = this.position.x + layoutSize[xPadding]; style[xProperty] = this.getXValue(x); style[xResetProperty] = ""; var yPadding = isOriginTop ? "paddingTop" : "paddingBottom"; var yProperty = isOriginTop ? "top" : "bottom"; var yResetProperty = isOriginTop ? "bottom" : "top"; var y = this.position.y + layoutSize[yPadding]; style[yProperty] = this.getYValue(y); style[yResetProperty] = ""; this.css(style); this.emitEvent("layout", [this]); }; proto.getXValue = function(x) { var isHorizontal = this.layout._getOption("horizontal"); return this.layout.options.percentPosition && !isHorizontal ? x / this.layout.size.width * 100 + "%" : x + "px"; }; proto.getYValue = function(y) { var isHorizontal = this.layout._getOption("horizontal"); return this.layout.options.percentPosition && isHorizontal ? y / this.layout.size.height * 100 + "%" : y + "px"; }; proto._transitionTo = function(x, y) { this.getPosition(); var curX = this.position.x; var curY = this.position.y; var didNotMove = x == this.position.x && y == this.position.y; this.setPosition(x, y); if (didNotMove && !this.isTransitioning) { this.layoutPosition(); return; } var transX = x - curX; var transY = y - curY; var transitionStyle = {}; transitionStyle.transform = this.getTranslate(transX, transY); this.transition({ to: transitionStyle, onTransitionEnd: { transform: this.layoutPosition }, isCleaning: true }); }; proto.getTranslate = function(x, y) { var isOriginLeft = this.layout._getOption("originLeft"); var isOriginTop = this.layout._getOption("originTop"); x = isOriginLeft ? x : -x; y = isOriginTop ? y : -y; return "translate3d(" + x + "px, " + y + "px, 0)"; }; proto.goTo = function(x, y) { this.setPosition(x, y); this.layoutPosition(); }; proto.moveTo = proto._transitionTo; proto.setPosition = function(x, y) { this.position.x = parseFloat(x); this.position.y = parseFloat(y); }; proto._nonTransition = function(args) { this.css(args.to); if (args.isCleaning) { this._removeStyles(args.to); } for (var prop in args.onTransitionEnd) { args.onTransitionEnd[prop].call(this); } }; proto.transition = function(args) { if (!parseFloat(this.layout.options.transitionDuration)) { this._nonTransition(args); return; } var _transition = this._transn; for (var prop in args.onTransitionEnd) { _transition.onEnd[prop] = args.onTransitionEnd[prop]; } for (prop in args.to) { _transition.ingProperties[prop] = true; if (args.isCleaning) { _transition.clean[prop] = true; } } if (args.from) { this.css(args.from); this.element.offsetHeight; } this.enableTransition(args.to); this.css(args.to); this.isTransitioning = true; }; function toDashedAll(str) { return str.replace(/([A-Z])/g, function($1) { return "-" + $1.toLowerCase(); }); } var transitionProps = "opacity," + toDashedAll(transformProperty); proto.enableTransition = function() { if (this.isTransitioning) { return; } var duration = this.layout.options.transitionDuration; duration = typeof duration == "number" ? duration + "ms" : duration; this.css({ transitionProperty: transitionProps, transitionDuration: duration, transitionDelay: this.staggerDelay || 0 }); this.element.addEventListener(transitionEndEvent, this, false); }; proto.onwebkitTransitionEnd = function(event) { this.ontransitionend(event); }; proto.onotransitionend = function(event) { this.ontransitionend(event); }; var dashedVendorProperties = { "-webkit-transform": "transform" }; proto.ontransitionend = function(event) { if (event.target !== this.element) { return; } var _transition = this._transn; var propertyName = dashedVendorProperties[event.propertyName] || event.propertyName; delete _transition.ingProperties[propertyName]; if (isEmptyObj(_transition.ingProperties)) { this.disableTransition(); } if (propertyName in _transition.clean) { this.element.style[event.propertyName] = ""; delete _transition.clean[propertyName]; } if (propertyName in _transition.onEnd) { var onTransitionEnd = _transition.onEnd[propertyName]; onTransitionEnd.call(this); delete _transition.onEnd[propertyName]; } this.emitEvent("transitionEnd", [this]); }; proto.disableTransition = function() { this.removeTransitionStyles(); this.element.removeEventListener(transitionEndEvent, this, false); this.isTransitioning = false; }; proto._removeStyles = function(style) { var cleanStyle = {}; for (var prop in style) { cleanStyle[prop] = ""; } this.css(cleanStyle); }; var cleanTransitionStyle = { transitionProperty: "", transitionDuration: "", transitionDelay: "" }; proto.removeTransitionStyles = function() { this.css(cleanTransitionStyle); }; proto.stagger = function(delay) { delay = isNaN(delay) ? 0 : delay; this.staggerDelay = delay + "ms"; }; proto.removeElem = function() { this.element.parentNode.removeChild(this.element); this.css({ display: "" }); this.emitEvent("remove", [this]); }; proto.remove = function() { if (!transitionProperty || !parseFloat(this.layout.options.transitionDuration)) { this.removeElem(); return; } this.once("transitionEnd", function() { this.removeElem(); }); this.hide(); }; proto.reveal = function() { delete this.isHidden; this.css({ display: "" }); var options = this.layout.options; var onTransitionEnd = {}; var transitionEndProperty = this.getHideRevealTransitionEndProperty("visibleStyle"); onTransitionEnd[transitionEndProperty] = this.onRevealTransitionEnd; this.transition({ from: options.hiddenStyle, to: options.visibleStyle, isCleaning: true, onTransitionEnd }); }; proto.onRevealTransitionEnd = function() { if (!this.isHidden) { this.emitEvent("reveal"); } }; proto.getHideRevealTransitionEndProperty = function(styleProperty) { var optionStyle = this.layout.options[styleProperty]; if (optionStyle.opacity) { return "opacity"; } for (var prop in optionStyle) { return prop; } }; proto.hide = function() { this.isHidden = true; this.css({ display: "" }); var options = this.layout.options; var onTransitionEnd = {}; var transitionEndProperty = this.getHideRevealTransitionEndProperty("hiddenStyle"); onTransitionEnd[transitionEndProperty] = this.onHideTransitionEnd; this.transition({ from: options.visibleStyle, to: options.hiddenStyle, // keep hidden stuff hidden isCleaning: true, onTransitionEnd }); }; proto.onHideTransitionEnd = function() { if (this.isHidden) { this.css({ display: "none" }); this.emitEvent("hide"); } }; proto.destroy = function() { this.css({ position: "", left: "", right: "", top: "", bottom: "", transition: "", transform: "" }); }; return Item; }); /*! * Outlayer v2.1.1 * the brains and guts of a layout library * MIT license */ (function(window2, factory8) { if (typeof define == "function" && define.amd) { define( "outlayer/outlayer", [ "ev-emitter/ev-emitter", "get-size/get-size", "fizzy-ui-utils/utils", "./item" ], function(EvEmitter, getSize, utils, Item) { return factory8(window2, EvEmitter, getSize, utils, Item); } ); } else { window2.Outlayer = factory8( window2, window2.EvEmitter, window2.getSize, window2.fizzyUIUtils, window2.Outlayer.Item ); } })(window, function factory6(window2, EvEmitter, getSize, utils, Item) { var console2 = window2.console; var jQuery2 = window2.jQuery; var noop = function() { }; var GUID = 0; var instances = {}; function Outlayer(element, options) { var queryElement = utils.getQueryElement(element); if (!queryElement) { if (console2) { console2.error("Bad element for " + this.constructor.namespace + ": " + (queryElement || element)); } return; } this.element = queryElement; if (jQuery2) { this.$element = jQuery2(this.element); } this.options = utils.extend({}, this.constructor.defaults); this.option(options); var id = ++GUID; this.element.outlayerGUID = id; instances[id] = this; this._create(); var isInitLayout = this._getOption("initLayout"); if (isInitLayout) { this.layout(); } } Outlayer.namespace = "outlayer"; Outlayer.Item = Item; Outlayer.defaults = { containerStyle: { position: "relative" }, initLayout: true, originLeft: true, originTop: true, resize: true, resizeContainer: true, // item options transitionDuration: "0.4s", hiddenStyle: { opacity: 0, transform: "scale(0.001)" }, visibleStyle: { opacity: 1, transform: "scale(1)" } }; var proto = Outlayer.prototype; utils.extend(proto, EvEmitter.prototype); proto.option = function(opts) { utils.extend(this.options, opts); }; proto._getOption = function(option) { var oldOption = this.constructor.compatOptions[option]; return oldOption && this.options[oldOption] !== void 0 ? this.options[oldOption] : this.options[option]; }; Outlayer.compatOptions = { // currentName: oldName initLayout: "isInitLayout", horizontal: "isHorizontal", layoutInstant: "isLayoutInstant", originLeft: "isOriginLeft", originTop: "isOriginTop", resize: "isResizeBound", resizeContainer: "isResizingContainer" }; proto._create = function() { this.reloadItems(); this.stamps = []; this.stamp(this.options.stamp); utils.extend(this.element.style, this.options.containerStyle); var canBindResize = this._getOption("resize"); if (canBindResize) { this.bindResize(); } }; proto.reloadItems = function() { this.items = this._itemize(this.element.children); }; proto._itemize = function(elems) { var itemElems = this._filterFindItemElements(elems); var Item2 = this.constructor.Item; var items = []; for (var i = 0; i < itemElems.length; i++) { var elem = itemElems[i]; var item = new Item2(elem, this); items.push(item); } return items; }; proto._filterFindItemElements = function(elems) { return utils.filterFindElements(elems, this.options.itemSelector); }; proto.getItemElements = function() { return this.items.map(function(item) { return item.element; }); }; proto.layout = function() { this._resetLayout(); this._manageStamps(); var layoutInstant = this._getOption("layoutInstant"); var isInstant = layoutInstant !== void 0 ? layoutInstant : !this._isLayoutInited; this.layoutItems(this.items, isInstant); this._isLayoutInited = true; }; proto._init = proto.layout; proto._resetLayout = function() { this.getSize(); }; proto.getSize = function() { this.size = getSize(this.element); }; proto._getMeasurement = function(measurement, size) { var option = this.options[measurement]; var elem; if (!option) { this[measurement] = 0; } else { if (typeof option == "string") { elem = this.element.querySelector(option); } else if (option instanceof HTMLElement) { elem = option; } this[measurement] = elem ? getSize(elem)[size] : option; } }; proto.layoutItems = function(items, isInstant) { items = this._getItemsForLayout(items); this._layoutItems(items, isInstant); this._postLayout(); }; proto._getItemsForLayout = function(items) { return items.filter(function(item) { return !item.isIgnored; }); }; proto._layoutItems = function(items, isInstant) { this._emitCompleteOnItems("layout", items); if (!items || !items.length) { return; } var queue = []; items.forEach(function(item) { var position = this._getItemLayoutPosition(item); position.item = item; position.isInstant = isInstant || item.isLayoutInstant; queue.push(position); }, this); this._processLayoutQueue(queue); }; proto._getItemLayoutPosition = function() { return { x: 0, y: 0 }; }; proto._processLayoutQueue = function(queue) { this.updateStagger(); queue.forEach(function(obj, i) { this._positionItem(obj.item, obj.x, obj.y, obj.isInstant, i); }, this); }; proto.updateStagger = function() { var stagger = this.options.stagger; if (stagger === null || stagger === void 0) { this.stagger = 0; return; } this.stagger = getMilliseconds(stagger); return this.stagger; }; proto._positionItem = function(item, x, y, isInstant, i) { if (isInstant) { item.goTo(x, y); } else { item.stagger(i * this.stagger); item.moveTo(x, y); } }; proto._postLayout = function() { this.resizeContainer(); }; proto.resizeContainer = function() { var isResizingContainer = this._getOption("resizeContainer"); if (!isResizingContainer) { return; } var size = this._getContainerSize(); if (size) { this._setContainerMeasure(size.width, true); this._setContainerMeasure(size.height, false); } }; proto._getContainerSize = noop; proto._setContainerMeasure = function(measure, isWidth) { if (measure === void 0) { return; } var elemSize = this.size; if (elemSize.isBorderBox) { measure += isWidth ? elemSize.paddingLeft + elemSize.paddingRight + elemSize.borderLeftWidth + elemSize.borderRightWidth : elemSize.paddingBottom + elemSize.paddingTop + elemSize.borderTopWidth + elemSize.borderBottomWidth; } measure = Math.max(measure, 0); this.element.style[isWidth ? "width" : "height"] = measure + "px"; }; proto._emitCompleteOnItems = function(eventName, items) { var _this = this; function onComplete() { _this.dispatchEvent(eventName + "Complete", null, [items]); } var count = items.length; if (!items || !count) { onComplete(); return; } var doneCount = 0; function tick() { doneCount++; if (doneCount == count) { onComplete(); } } items.forEach(function(item) { item.once(eventName, tick); }); }; proto.dispatchEvent = function(type, event, args) { var emitArgs = event ? [event].concat(args) : args; this.emitEvent(type, emitArgs); if (jQuery2) { this.$element = this.$element || jQuery2(this.element); if (event) { var $event = jQuery2.Event(event); $event.type = type; this.$element.trigger($event, args); } else { this.$element.trigger(type, args); } } }; proto.ignore = function(elem) { var item = this.getItem(elem); if (item) { item.isIgnored = true; } }; proto.unignore = function(elem) { var item = this.getItem(elem); if (item) { delete item.isIgnored; } }; proto.stamp = function(elems) { elems = this._find(elems); if (!elems) { return; } this.stamps = this.stamps.concat(elems); elems.forEach(this.ignore, this); }; proto.unstamp = function(elems) { elems = this._find(elems); if (!elems) { return; } elems.forEach(function(elem) { utils.removeFrom(this.stamps, elem); this.unignore(elem); }, this); }; proto._find = function(elems) { if (!elems) { return; } if (typeof elems == "string") { elems = this.element.querySelectorAll(elems); } elems = utils.makeArray(elems); return elems; }; proto._manageStamps = function() { if (!this.stamps || !this.stamps.length) { return; } this._getBoundingRect(); this.stamps.forEach(this._manageStamp, this); }; proto._getBoundingRect = function() { var boundingRect = this.element.getBoundingClientRect(); var size = this.size; this._boundingRect = { left: boundingRect.left + size.paddingLeft + size.borderLeftWidth, top: boundingRect.top + size.paddingTop + size.borderTopWidth, right: boundingRect.right - (size.paddingRight + size.borderRightWidth), bottom: boundingRect.bottom - (size.paddingBottom + size.borderBottomWidth) }; }; proto._manageStamp = noop; proto._getElementOffset = function(elem) { var boundingRect = elem.getBoundingClientRect(); var thisRect = this._boundingRect; var size = getSize(elem); var offset = { left: boundingRect.left - thisRect.left - size.marginLeft, top: boundingRect.top - thisRect.top - size.marginTop, right: thisRect.right - boundingRect.right - size.marginRight, bottom: thisRect.bottom - boundingRect.bottom - size.marginBottom }; return offset; }; proto.handleEvent = utils.handleEvent; proto.bindResize = function() { window2.addEventListener("resize", this); this.isResizeBound = true; }; proto.unbindResize = function() { window2.removeEventListener("resize", this); this.isResizeBound = false; }; proto.onresize = function() { this.resize(); }; utils.debounceMethod(Outlayer, "onresize", 100); proto.resize = function() { if (!this.isResizeBound || !this.needsResizeLayout()) { return; } this.layout(); }; proto.needsResizeLayout = function() { var size = getSize(this.element); var hasSizes = this.size && size; return hasSizes && size.innerWidth !== this.size.innerWidth; }; proto.addItems = function(elems) { var items = this._itemize(elems); if (items.length) { this.items = this.items.concat(items); } return items; }; proto.appended = function(elems) { var items = this.addItems(elems); if (!items.length) { return; } this.layoutItems(items, true); this.reveal(items); }; proto.prepended = function(elems) { var items = this._itemize(elems); if (!items.length) { return; } var previousItems = this.items.slice(0); this.items = items.concat(previousItems); this._resetLayout(); this._manageStamps(); this.layoutItems(items, true); this.reveal(items); this.layoutItems(previousItems); }; proto.reveal = function(items) { this._emitCompleteOnItems("reveal", items); if (!items || !items.length) { return; } var stagger = this.updateStagger(); items.forEach(function(item, i) { item.stagger(i * stagger); item.reveal(); }); }; proto.hide = function(items) { this._emitCompleteOnItems("hide", items); if (!items || !items.length) { return; } var stagger = this.updateStagger(); items.forEach(function(item, i) { item.stagger(i * stagger); item.hide(); }); }; proto.revealItemElements = function(elems) { var items = this.getItems(elems); this.reveal(items); }; proto.hideItemElements = function(elems) { var items = this.getItems(elems); this.hide(items); }; proto.getItem = function(elem) { for (var i = 0; i < this.items.length; i++) { var item = this.items[i]; if (item.element == elem) { return item; } } }; proto.getItems = function(elems) { elems = utils.makeArray(elems); var items = []; elems.forEach(function(elem) { var item = this.getItem(elem); if (item) { items.push(item); } }, this); return items; }; proto.remove = function(elems) { var removeItems = this.getItems(elems); this._emitCompleteOnItems("remove", removeItems); if (!removeItems || !removeItems.length) { return; } removeItems.forEach(function(item) { item.remove(); utils.removeFrom(this.items, item); }, this); }; proto.destroy = function() { var style = this.element.style; style.height = ""; style.position = ""; style.width = ""; this.items.forEach(function(item) { item.destroy(); }); this.unbindResize(); var id = this.element.outlayerGUID; delete instances[id]; delete this.element.outlayerGUID; if (jQuery2) { jQuery2.removeData(this.element, this.constructor.namespace); } }; Outlayer.data = function(elem) { elem = utils.getQueryElement(elem); var id = elem && elem.outlayerGUID; return id && instances[id]; }; Outlayer.create = function(namespace, options) { var Layout = subclass(Outlayer); Layout.defaults = utils.extend({}, Outlayer.defaults); utils.extend(Layout.defaults, options); Layout.compatOptions = utils.extend({}, Outlayer.compatOptions); Layout.namespace = namespace; Layout.data = Outlayer.data; Layout.Item = subclass(Item); utils.htmlInit(Layout, namespace); if (jQuery2 && jQuery2.bridget) { jQuery2.bridget(namespace, Layout); } return Layout; }; function subclass(Parent) { function SubClass() { Parent.apply(this, arguments); } SubClass.prototype = Object.create(Parent.prototype); SubClass.prototype.constructor = SubClass; return SubClass; } var msUnits = { ms: 1, s: 1e3 }; function getMilliseconds(time) { if (typeof time == "number") { return time; } var matches = time.match(/(^\d*\.?\d*)(\w*)/); var num = matches && matches[1]; var unit = matches && matches[2]; if (!num.length) { return 0; } num = parseFloat(num); var mult = msUnits[unit] || 1; return num * mult; } Outlayer.Item = Item; return Outlayer; }); /*! * Masonry v4.2.2 * Cascading grid layout library * https://masonry.desandro.com * MIT License * by David DeSandro */ (function(window2, factory8) { if (typeof define == "function" && define.amd) { define( [ "outlayer/outlayer", "get-size/get-size" ], factory8 ); } else { window2.Masonry = factory8( window2.Outlayer, window2.getSize ); } })(window, function factory7(Outlayer, getSize) { var Masonry2 = Outlayer.create("masonry"); Masonry2.compatOptions.fitWidth = "isFitWidth"; var proto = Masonry2.prototype; proto._resetLayout = function() { this.getSize(); this._getMeasurement("columnWidth", "outerWidth"); this._getMeasurement("gutter", "outerWidth"); this.measureColumns(); this.colYs = []; for (var i = 0; i < this.cols; i++) { this.colYs.push(0); } this.maxY = 0; this.horizontalColIndex = 0; }; proto.measureColumns = function() { this.getContainerWidth(); if (!this.columnWidth) { var firstItem = this.items[0]; var firstItemElem = firstItem && firstItem.element; this.columnWidth = firstItemElem && getSize(firstItemElem).outerWidth || // if first elem has no width, default to size of container this.containerWidth; } var columnWidth = this.columnWidth += this.gutter; var containerWidth = this.containerWidth + this.gutter; var cols = containerWidth / columnWidth; var excess = columnWidth - containerWidth % columnWidth; var mathMethod = excess && excess < 1 ? "round" : "floor"; cols = Math[mathMethod](cols); this.cols = Math.max(cols, 1); }; proto.getContainerWidth = function() { var isFitWidth = this._getOption("fitWidth"); var container = isFitWidth ? this.element.parentNode : this.element; var size = getSize(container); this.containerWidth = size && size.innerWidth; }; proto._getItemLayoutPosition = function(item) { item.getSize(); var remainder = item.size.outerWidth % this.columnWidth; var mathMethod = remainder && remainder < 1 ? "round" : "ceil"; var colSpan = Math[mathMethod](item.size.outerWidth / this.columnWidth); colSpan = Math.min(colSpan, this.cols); var colPosMethod = this.options.horizontalOrder ? "_getHorizontalColPosition" : "_getTopColPosition"; var colPosition = this[colPosMethod](colSpan, item); var position = { x: this.columnWidth * colPosition.col, y: colPosition.y }; var setHeight = colPosition.y + item.size.outerHeight; var setMax = colSpan + colPosition.col; for (var i = colPosition.col; i < setMax; i++) { this.colYs[i] = setHeight; } return position; }; proto._getTopColPosition = function(colSpan) { var colGroup = this._getTopColGroup(colSpan); var minimumY = Math.min.apply(Math, colGroup); return { col: colGroup.indexOf(minimumY), y: minimumY }; }; proto._getTopColGroup = function(colSpan) { if (colSpan < 2) { return this.colYs; } var colGroup = []; var groupCount = this.cols + 1 - colSpan; for (var i = 0; i < groupCount; i++) { colGroup[i] = this._getColGroupY(i, colSpan); } return colGroup; }; proto._getColGroupY = function(col, colSpan) { if (colSpan < 2) { return this.colYs[col]; } var groupColYs = this.colYs.slice(col, col + colSpan); return Math.max.apply(Math, groupColYs); }; proto._getHorizontalColPosition = function(colSpan, item) { var col = this.horizontalColIndex % this.cols; var isOver = colSpan > 1 && col + colSpan > this.cols; col = isOver ? 0 : col; var hasSize = item.size.outerWidth && item.size.outerHeight; this.horizontalColIndex = hasSize ? col + colSpan : this.horizontalColIndex; return { col, y: this._getColGroupY(col, colSpan) }; }; proto._manageStamp = function(stamp) { var stampSize = getSize(stamp); var offset = this._getElementOffset(stamp); var isOriginLeft = this._getOption("originLeft"); var firstX = isOriginLeft ? offset.left : offset.right; var lastX = firstX + stampSize.outerWidth; var firstCol = Math.floor(firstX / this.columnWidth); firstCol = Math.max(0, firstCol); var lastCol = Math.floor(lastX / this.columnWidth); lastCol -= lastX % this.columnWidth ? 0 : 1; lastCol = Math.min(this.cols - 1, lastCol); var isOriginTop = this._getOption("originTop"); var stampMaxY = (isOriginTop ? offset.top : offset.bottom) + stampSize.outerHeight; for (var i = firstCol; i <= lastCol; i++) { this.colYs[i] = Math.max(stampMaxY, this.colYs[i]); } }; proto._getContainerSize = function() { this.maxY = Math.max.apply(Math, this.colYs); var size = { height: this.maxY }; if (this._getOption("fitWidth")) { size.width = this._getContainerFitWidth(); } return size; }; proto._getContainerFitWidth = function() { var unusedCols = 0; var i = this.cols; while (--i) { if (this.colYs[i] !== 0) { break; } unusedCols++; } return (this.cols - unusedCols) * this.columnWidth - this.gutter; }; proto.needsResizeLayout = function() { var previousWidth = this.containerWidth; this.getContainerWidth(); return previousWidth != this.containerWidth; }; return Masonry2; }); console.log("________PT-TorrentList-Masonry 已启动!________"); const _ORIGIN_TL_Node = GET_TORRENT_LIST_DOM_FROM_DOMAIN(); if (!_ORIGIN_TL_Node) { console.log("未识别到种子列表捏~"); } else { let scan_and_launch = function() { const scrollHeight = document.body.scrollHeight; const clientHeight = document.documentElement.clientHeight; const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; if (scrollTop + clientHeight >= scrollHeight - PAGE.DISTANCE) { if (PAGE.SWITCH_MODE != "Button") debounceLoad(); sortMasonry(); } }, loadNextPage = function() { console.log("到页面底部啦!!! Scrolled to bottom!"); const urlSearchParams = new URLSearchParams(window.location.search); PAGE.PAGE_CURRENT = PAGE.IS_ORIGIN ? urlSearchParams.get("page") : PAGE.PAGE_CURRENT; if (!PAGE.PAGE_CURRENT) { console.log(`网页链接没有page参数, 无法跳转下一页, 生成PAGE.PAGE_CURRENT为0`); PAGE.PAGE_CURRENT = 0; } else { console.log("当前页数: " + PAGE.PAGE_CURRENT); } PAGE.PAGE_NEXT = parseInt(PAGE.PAGE_CURRENT) + 1; urlSearchParams.set("page", PAGE.PAGE_NEXT); PAGE.NEXT_URL = window.location.origin + window.location.pathname + "?" + urlSearchParams.toString(); console.log("New URL:", PAGE.NEXT_URL); fetch(PAGE.NEXT_URL).then((response) => response.text()).then((html) => { const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); const table = doc.querySelector("table.torrents"); PUT_TORRENT_INTO_MASONRY(table, waterfallNode, false, masonry2); CHANGE_CARD_WIDTH(CARD.CARD_WIDTH, waterfallNode, masonry2); PAGE.IS_ORIGIN = false; PAGE.PAGE_CURRENT = PAGE.PAGE_NEXT; }).catch((error) => { console.warn("获取不到下页信息, 可能到头了"); console.warn(error); }); btnTurnPageDOM.disabled = false; btnTurnPageDOM.textContent = "点击加载下一页"; }; _ORIGIN_TL_Node.style.display = "none"; while (!Masonry) { console.log("等待初始化......"); } let masonry2; window.masonry = masonry2; const mainOuterDOM = document.querySelector("table.mainouter"); const themeColor = window.getComputedStyle(mainOuterDOM)["background-color"]; console.log("背景颜色:", themeColor); const parentNode = _ORIGIN_TL_Node.parentNode; const waterfallNode = document.createElement("div"); waterfallNode.classList.add("waterfall"); parentNode.insertBefore(waterfallNode, _ORIGIN_TL_Node.nextSibling); waterfallNode.addEventListener("click", () => { if (masonry2) masonry2.layout(); console.log("Masonry 已整理~"); }); document.getElementById("btnViewOrigin"); const toggleBtn = document.createElement("button"); toggleBtn.classList.add("debug"); toggleBtn.setAttribute("id", "toggle_oldTable"); toggleBtn.innerText = "显示原种子表格"; toggleBtn.style.zIndex = 10001; toggleBtn.addEventListener("click", function() { if (_ORIGIN_TL_Node.style.display === "none") { _ORIGIN_TL_Node.style.display = "block"; toggleBtn.innerText = "隐藏原种子表格"; } else { _ORIGIN_TL_Node.style.display = "none"; toggleBtn.innerText = "显示原种子表格"; } }); document.body.appendChild(toggleBtn); document.getElementById("btnReLayout"); const reLayoutBtn = document.createElement("button"); reLayoutBtn.classList.add("debug"); reLayoutBtn.setAttribute("id", "btnReLayout"); reLayoutBtn.innerText = "单列宽度切换(200/300)"; reLayoutBtn.style.zIndex = 10002; reLayoutBtn.addEventListener("click", function() { sortMasonry(); CARD.CARD_WIDTH = CARD.CARD_WIDTH == 200 ? 300 : 200; CHANGE_CARD_WIDTH(CARD.CARD_WIDTH, waterfallNode, masonry2); sortMasonry(); }); document.body.appendChild(reLayoutBtn); const btnTurnPageDOM = document.createElement("button"); waterfallNode.insertAdjacentElement("afterend", btnTurnPageDOM); btnTurnPageDOM.classList.add("turnPage"); btnTurnPageDOM.setAttribute("id", "turnPage"); btnTurnPageDOM.innerText = "点击加载下一页"; btnTurnPageDOM.addEventListener("click", function(event) { event.preventDefault(); btnTurnPageDOM.disabled = true; btnTurnPageDOM.textContent = "正在加载中..."; debounceLoad(); }); document.getElementById("btnSwitchMode"); const switchModeBtn = document.createElement("button"); switchModeBtn.classList.add("debug"); switchModeBtn.setAttribute("id", "btnSwitchMode"); switchModeBtn.innerText = "当前加载方式: 按钮"; switchModeBtn.style.zIndex = 10003; switchModeBtn.addEventListener("click", function() { if (switchModeBtn.innerText == "当前加载方式: 按钮") { switchModeBtn.innerText = "当前加载方式: 滑动"; PAGE.SWITCH_MODE = "Slip"; btnTurnPageDOM.style.display = "none"; scan_and_launch(); } else { switchModeBtn.innerText = "当前加载方式: 按钮"; PAGE.SWITCH_MODE = "Button"; btnTurnPageDOM.style.display = "block"; } }); document.body.appendChild(switchModeBtn); const sortMasonryBtn = document.createElement("button"); sortMasonryBtn.classList.add("debug"); sortMasonryBtn.setAttribute("id", "sort_masonry"); sortMasonryBtn.innerText = "手动整理布局"; sortMasonryBtn.style.zIndex = 10004; sortMasonryBtn.addEventListener("click", function() { if (masonry2) masonry2.layout(); }); document.body.appendChild(sortMasonryBtn); PUT_TORRENT_INTO_MASONRY(_ORIGIN_TL_Node, waterfallNode, true, masonry2); const css2 = ` /* 瀑布流主容器 */ div.waterfall{ width: 100%; padding-top: 20px; padding-bottom: 20px; border-radius: 20px; height: 100%; /* margin: 0 auto; */ margin: 20px auto; transition: height 0.3s; } /* 调试按键统一样式 */ button.debug { position: fixed; top: 10px; right: 10px; padding: 4px; background-color: #333; color: #fff; border: none; border-radius: 5px; cursor: pointer; } /* 调试按键1: 显示隐藏原种子列表 */ button#toggle_oldTable { top: 10px; } /* 调试按键2: Masonry 切换卡片宽度 */ button#btnReLayout { top: 40px; } /* 调试按键3: 切换下一页加载方式 */ button#btnSwitchMode { top: 70px; } /* 调试按键4: Masonry 重新排列 */ button#sort_masonry { top: 100px; } /* 卡片 */ .card { width: ${CARD.CARD_WIDTH}px; border: 1px solid rgba(255, 255, 255, 0.5); border-radius: 16px; background-color: ${themeColor}; /* box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3); */ /* margin: 10px; */ margin: 6px 0; overflow: hidden; cursor: pointer; box-shadow: rgba(0, 0, 0, 0.3) 3px 3px 0px, rgba(0, 0, 0, 0.1) -1px -1px 0px; } } .card:hover { } /* 卡片标题 */ .card-title{ padding: 2px 0; } /* 卡片内部容器 */ .card-holder{ background-color: rgba(255, 255, 255, 0.5); background: linear-gradient(to bottom, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0)); padding-bottom: 6px; } /* 卡片行默认样式 */ .card-line{ margin-bottom: 1px; display: flex; justify-content: space-evenly; align-items: center; height: 20px; } /* 卡片标题: 默认两行 */ .two-lines { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; transition: color 0.3s; } /* 卡片标题: hover时变为正常 */ .two-lines:hover { -webkit-line-clamp: 100; } /* 卡片信息: flex 居中 */ .cl-center{ display: flex; justify-content: space-evenly; align-items: center; } /* 卡片信息行: 标签行 */ .cl-tags{ display: flex; justify-content: left; align-items: center; flex-wrap: wrap; gap: 2px; transform: translateX(4px); } /* 卡片简介总容器 */ .card-details{ display: flex; justify-content: center; align-items: center; flex-direction: column; padding-top: 2px; } /* 卡片图像div */ .card-image { height: 100%; position: relative; margin-bottom: 2px; } /* 卡片图像div -> img标签 */ .card-image img { width: 100%; object-fit: cover; } /* 卡片可选信息 */ .card-alter{ text-align: center; height: 20px; display: flex; justify-content: center; align-items: center; } /* 置顶 && 免费类型&剩余时间 */ .top_and_free{ padding: 2px; border-radius: 4px; margin-bottom: 2px; display: flex; justify-content: center; align-items: center; line-height: 11px; height: 11px; font-size: 10px; } ._Free{ color: blue; /* background-color: #00e6 */ } ._2XFree{ color: green; /* background-color: #0e0 */ } /* 卡片索引 */ .card-description{ padding-left: 4px; padding-right: 4px; } /* 卡片索引 */ .card-index{ position: absolute; top: 0; left: 0; padding-right: 9px; padding-left: 2px; margin: 0; height: 20px; line-height: 16px; font-size: 16px; background-color: rgba(0,0,0); color: yellow; border-top-right-radius: 100px; border-bottom-right-radius: 100px; display: flex; align-items: center; pointer-events: none; } /* 卡片: 收藏按钮 */ .btnCollet{ padding: 1px 2px; cursor: pointer; } /* 卡片: 收藏按钮 */ #turnPage{ width: 100%; height: 32px; border-radius: 16px; line-height: 20px; font-size: 14px; } /* 上面是我自己脚本的css */ /* --------------------------------------- */ /* 下面是改进原有的css */ /* 卡片索引 */ #nexus-preview{ z-index: 20000; position: absolute; display: none; pointer-events: none; } /* 临时标签_热门 */ .hot{ padding: 0 2px; border-radius: 8px; background: white; margin: 2px; } /* 临时标签_新 */ .new{ padding: 0 2px; border-radius: 8px; background: white; margin: 2px; } `; const style = document.createElement("style"); style.textContent = css2 + ADD_SITE_EXCLUSIVE_CSS(); document.head.appendChild(style); masonry2 = new Masonry(waterfallNode, { itemSelector: ".card", columnWidth: ".card", gutter: GET_CARD_GUTTER(waterfallNode, CARD.CARD_WIDTH) }); window.addEventListener("resize", function() { masonry2.options.gutter = GET_CARD_GUTTER(waterfallNode, CARD.CARD_WIDTH); sortMasonry(); }); sortMasonry(); window.masonry = masonry2; let debounceLoad; window.addEventListener("scroll", function() { scan_and_launch(); }); debounceLoad = debounce(loadNextPage, PAGE.GAP); } })();