mirror of
https://github.com/driftywinds/driftywinds.github.io.git
synced 2025-12-19 11:03:32 +00:00
Refactor
This commit is contained in:
5
app.html
5
app.html
@@ -4,9 +4,9 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="css/main.css">
|
<link rel="stylesheet" href="css/shared.css">
|
||||||
<link rel="stylesheet" href="css/app.css">
|
|
||||||
<link rel="stylesheet" href="css/uibanner.css">
|
<link rel="stylesheet" href="css/uibanner.css">
|
||||||
|
<link rel="stylesheet" href="css/app.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="loading">
|
<body class="loading">
|
||||||
<div id="loading">
|
<div id="loading">
|
||||||
@@ -99,6 +99,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="js/shared.js"></script>
|
||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
<script src="js/app.js"></script>
|
<script src="js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="css/main.css">
|
<link rel="stylesheet" href="css/shared.css">
|
||||||
<link rel="stylesheet" href="css/apps.css">
|
|
||||||
<link rel="stylesheet" href="css/uibanner.css">
|
<link rel="stylesheet" href="css/uibanner.css">
|
||||||
|
<link rel="stylesheet" href="css/apps.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="loading">
|
<body class="loading">
|
||||||
<div id="loading">
|
<div id="loading">
|
||||||
@@ -18,7 +18,8 @@
|
|||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="apps"></div>
|
<div id="apps"></div>
|
||||||
</div>
|
</div>
|
||||||
<script src="js/main.js"></script>
|
<script src="js/shared.js"></script>
|
||||||
<script src="js/apps.js"></script>
|
<script src="js/apps.js"></script>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="css/shared.css">
|
||||||
<link rel="stylesheet" href="css/uibanner.css">
|
<link rel="stylesheet" href="css/uibanner.css">
|
||||||
<link rel="stylesheet" href="css/main.css">
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="loading">
|
<body class="loading">
|
||||||
<div id="loading">
|
<div id="loading">
|
||||||
<img src="img/loading.gif" alt="loading">
|
<img src="img/loading.gif" alt="loading">
|
||||||
@@ -41,8 +39,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="js/main.js"></script>
|
<script src="js/shared.js"></script>
|
||||||
<script src="js/home.js"></script>
|
<script src="js/home.js"></script>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="css/shared.css">
|
||||||
<link rel="stylesheet" href="css/uibanner.css">
|
<link rel="stylesheet" href="css/uibanner.css">
|
||||||
<link rel="stylesheet" href="css/main.css">
|
|
||||||
<link rel="stylesheet" href="css/index.css">
|
<link rel="stylesheet" href="css/index.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="js/shared.js"></script>
|
||||||
<script src="js/index.js"></script>
|
<script src="js/index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
355
js/app.js
355
js/app.js
@@ -1,200 +1,183 @@
|
|||||||
|
if (!urlSearchParams.has('id')) exit();
|
||||||
const bundleId = urlSearchParams.get('id');
|
const bundleId = urlSearchParams.get('id');
|
||||||
if (!urlSearchParams.has('id') || !bundleId) nope();
|
|
||||||
|
|
||||||
// Hide/show navigation bar title & install button
|
(function () {
|
||||||
let hidden = false;
|
// Hide/show navigation bar title & install button
|
||||||
window.onscroll = function (e) {
|
let hidden = false;
|
||||||
const appName = document.querySelector(".app-header .text>.title");
|
window.onscroll = function (e) {
|
||||||
const title = document.getElementById("title");
|
const appName = document.querySelector(".app-header .text>.title");
|
||||||
const button = document.querySelector("#nav-bar .install");
|
const title = document.getElementById("title");
|
||||||
|
const button = document.querySelector("#nav-bar .install");
|
||||||
|
|
||||||
if (hidden && appName.getBoundingClientRect().y >= 72) { // App name not visible
|
if (hidden && appName.getBoundingClientRect().y >= 72) { // App name not visible
|
||||||
hidden = false;
|
hidden = false;
|
||||||
title.classList.add("hidden");
|
title.classList.add("hidden");
|
||||||
button.classList.add("hidden");
|
button.classList.add("hidden");
|
||||||
button.disaled = true;
|
button.disaled = true;
|
||||||
} else if (!hidden && appName.getBoundingClientRect().y < 72) {
|
} else if (!hidden && appName.getBoundingClientRect().y < 72) {
|
||||||
hidden = true;
|
hidden = true;
|
||||||
title.classList.remove("hidden");
|
title.classList.remove("hidden");
|
||||||
button.classList.remove("hidden");
|
button.classList.remove("hidden");
|
||||||
button.disaled = false;
|
button.disaled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})();
|
||||||
|
|
||||||
fetch(sourceURL)
|
function main(json) {
|
||||||
.then(response => response.json())
|
const app = getAppWithBundleId(bundleId);
|
||||||
.then(json => {
|
if (!app) exit();
|
||||||
const apps = json.apps.filter(app => app.bundleIdentifier === bundleId);
|
|
||||||
const app = apps[0];
|
|
||||||
if (!app) {
|
|
||||||
alert(`Unable to find app matching bundle identifier "${bundleId}".\nYou will now be redirected to the home page.`);
|
|
||||||
nope();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.title = `${app.name} - ${json.name}`;
|
// Set tab title
|
||||||
|
document.title = `${app.name} - ${json.name}`;
|
||||||
|
|
||||||
const tintColor = `#${app.tintColor}`;
|
const tintColor = `#${app.tintColor}`;
|
||||||
|
// Set tint color
|
||||||
|
if (tintColor) document.querySelector(':root').style.setProperty("--app-tint-color", `${tintColor}`);
|
||||||
|
|
||||||
if (tintColor)
|
// Tint back button
|
||||||
document.querySelector(':root').style.setProperty("--app-tint-color", `${tintColor}`);
|
document.getElementById("back").style.color = tintColor;
|
||||||
|
|
||||||
// Tint back button
|
// Set up install buttons
|
||||||
const backButton = document.getElementById("back");
|
document.querySelectorAll("a.install").forEach(button => {
|
||||||
backButton.style.color = tintColor;
|
button.href = `altstore://install?url=${app.downloadURL}`;
|
||||||
|
|
||||||
const installButtons = document.querySelectorAll("a.install");
|
|
||||||
installButtons.forEach(button => {
|
|
||||||
button.href = `altstore://install?url=${app.downloadURL}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const downloadButton = document.getElementById("download");
|
|
||||||
downloadButton.href = app.downloadURL;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Navigation bar
|
|
||||||
//
|
|
||||||
const navBar = document.getElementById("nav-bar");
|
|
||||||
const navBarIcon = navBar.querySelector("#title>img");
|
|
||||||
const navBarTitle = navBar.querySelector("#title>p");
|
|
||||||
const navBarInstallButton = navBar.querySelector(".uibutton");
|
|
||||||
|
|
||||||
navBarTitle.textContent = app.name;
|
|
||||||
navBarIcon.src = app.iconURL;
|
|
||||||
navBarInstallButton.style.backgroundColor = `${tintColor}`;
|
|
||||||
|
|
||||||
//
|
|
||||||
// App header
|
|
||||||
//
|
|
||||||
const appHeader = document.querySelector("#main .app-header");
|
|
||||||
const appHeaderIcon = appHeader.querySelector("img");
|
|
||||||
const appHeaderTitle = appHeader.querySelector(".title");
|
|
||||||
const appHeaderSubtitle = appHeader.querySelector(".subtitle");
|
|
||||||
const appHeaderInstallButton = appHeader.querySelector(".uibutton");
|
|
||||||
const appHeaderBackground = appHeader.querySelector(".background");
|
|
||||||
|
|
||||||
appHeaderIcon.src = app.iconURL;
|
|
||||||
appHeaderTitle.textContent = app.name;
|
|
||||||
appHeaderSubtitle.textContent = app.developerName;
|
|
||||||
appHeaderInstallButton.style.backgroundColor = tintColor;
|
|
||||||
appHeaderBackground.style.backgroundColor = tintColor;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Preview
|
|
||||||
//
|
|
||||||
const preview = document.getElementById("preview");
|
|
||||||
const previewSubtitle = preview.querySelector("#subtitle");
|
|
||||||
const previewScreenshots = preview.querySelector("#screenshots");
|
|
||||||
const previewDescription = preview.querySelector("#description");
|
|
||||||
|
|
||||||
previewSubtitle.textContent = app.subtitle;
|
|
||||||
app.screenshotURLs.forEach(url => {
|
|
||||||
previewScreenshots.insertAdjacentHTML("beforeend", `<img src="${url}" alt="">`);
|
|
||||||
});
|
|
||||||
|
|
||||||
previewDescription.innerHTML = formatString(app.localizedDescription);
|
|
||||||
|
|
||||||
const more = `
|
|
||||||
<a id="more" onclick="revealTruncatedText(this);">
|
|
||||||
<button style="color: ${tintColor};">more</button>
|
|
||||||
</a>`;
|
|
||||||
|
|
||||||
if (previewDescription.scrollHeight > previewDescription.clientHeight)
|
|
||||||
previewDescription.insertAdjacentHTML("beforeend", more);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Version info
|
|
||||||
//
|
|
||||||
const versionDate = document.getElementById("version-date");
|
|
||||||
const version = document.getElementById("version");
|
|
||||||
const versionSize = document.getElementById("version-size");
|
|
||||||
const versionDescription = document.getElementById("version-description");
|
|
||||||
|
|
||||||
// Version date
|
|
||||||
const versionDateObject = new Date(app.versionDate),
|
|
||||||
month = versionDateObject.toUTCString().split(" ")[2],
|
|
||||||
date = versionDateObject.getDate(),
|
|
||||||
dateString = `${month} ${date}, ${versionDateObject.getFullYear()}`;
|
|
||||||
const today = new Date();
|
|
||||||
const msPerDay = 60 * 60 * 24 * 1000;
|
|
||||||
const msDifference = today.valueOf() - versionDateObject.valueOf();
|
|
||||||
versionDate.textContent = dateString;
|
|
||||||
if (msDifference <= msPerDay) // Today
|
|
||||||
versionDate.textContent = "Today";
|
|
||||||
else if (msDifference <= msPerDay * 2) // Yesterday
|
|
||||||
versionDate.textContent = "Yesterday";
|
|
||||||
|
|
||||||
// Version number
|
|
||||||
version.textContent = `Version ${app.version}`;
|
|
||||||
|
|
||||||
// Version size
|
|
||||||
const units = ["B", "KB", "MB", "GB"];
|
|
||||||
var appSize = app.size, c = 0;
|
|
||||||
while (appSize > 1024) {
|
|
||||||
appSize = parseFloat(appSize / 1024).toFixed(1);
|
|
||||||
c++;
|
|
||||||
}
|
|
||||||
versionSize.textContent = `${appSize} ${units[c]}`;
|
|
||||||
|
|
||||||
// Version description
|
|
||||||
versionDescription.innerHTML = formatString(app.versionDescription);
|
|
||||||
if (versionDescription.scrollHeight > versionDescription.clientHeight)
|
|
||||||
versionDescription.insertAdjacentHTML("beforeend", more);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Permissions
|
|
||||||
//
|
|
||||||
const permissions = document.getElementById("permissions");
|
|
||||||
|
|
||||||
if (app.permissions)
|
|
||||||
permissions.querySelector(".permission").remove();
|
|
||||||
|
|
||||||
app.permissions?.forEach(permission => {
|
|
||||||
let permissionType = "Unknown", icon = "gear-wide-connected";
|
|
||||||
switch (permission.type) {
|
|
||||||
case "background-audio":
|
|
||||||
permissionType = "Background Audio";
|
|
||||||
// icon = "audio";
|
|
||||||
icon = "volume-up-fill";
|
|
||||||
break;
|
|
||||||
case "background-fetch":
|
|
||||||
permissionType = "Background Fetch";
|
|
||||||
// icon = "fetch";
|
|
||||||
icon = "arrow-repeat"
|
|
||||||
break;
|
|
||||||
case "photos":
|
|
||||||
permissionType = "Photos"
|
|
||||||
// icon = "photos";
|
|
||||||
icon = "image-fill";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<div class="permission">
|
|
||||||
<i class="bi-${icon}" style="color: ${tintColor};"></i>
|
|
||||||
<div class="text">
|
|
||||||
<p class="title">${permissionType}</p>
|
|
||||||
<p class="description">${permission.usageDescription ?? "No description provided."}</p>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
permissions.insertAdjacentHTML("beforeend", html);
|
|
||||||
});
|
|
||||||
|
|
||||||
waitForAllImagesToLoad();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function revealTruncatedText(moreButton) {
|
// Set up download button
|
||||||
const textId = moreButton.parentNode.id;
|
document.getElementById("download").href = app.downloadURL;
|
||||||
const text = document.getElementById(textId);
|
|
||||||
text.style.display = "block";
|
//
|
||||||
text.style.overflow = "auto";
|
// Navigation bar
|
||||||
text.style.webkitLineClamp = "none";
|
const navigationBar = document.getElementById("nav-bar");
|
||||||
text.style.lineClamp = "none";
|
// Title
|
||||||
text.removeChild(moreButton)
|
navigationBar.querySelector("#title>p").textContent = app.name;
|
||||||
|
// App icon
|
||||||
|
navigationBar.querySelector("#title>img").src = app.iconURL;
|
||||||
|
// Install button
|
||||||
|
navigationBar.querySelector(".uibutton").style.backgroundColor = `${tintColor}`;
|
||||||
|
|
||||||
|
//
|
||||||
|
// App header
|
||||||
|
const appHeader = document.querySelector("#main .app-header");
|
||||||
|
// Icon
|
||||||
|
appHeader.querySelector("img").src = app.iconURL;
|
||||||
|
// App name
|
||||||
|
appHeader.querySelector(".title").textContent = app.name;
|
||||||
|
// Developer name
|
||||||
|
appHeader.querySelector(".subtitle").textContent = app.developerName;
|
||||||
|
// Install button
|
||||||
|
appHeader.querySelector(".uibutton").style.backgroundColor = tintColor;
|
||||||
|
// Background
|
||||||
|
appHeader.querySelector(".background").style.backgroundColor = tintColor;
|
||||||
|
|
||||||
|
const more = `
|
||||||
|
<a id="more" onclick="revealTruncatedText(this);">
|
||||||
|
<button style="color: ${tintColor};">more</button>
|
||||||
|
</a>`;
|
||||||
|
|
||||||
|
this.revealTruncatedText = moreButton => {
|
||||||
|
const textId = moreButton.parentNode.id;
|
||||||
|
const text = document.getElementById(textId);
|
||||||
|
text.style.display = "block";
|
||||||
|
text.style.overflow = "auto";
|
||||||
|
text.style.webkitLineClamp = "none";
|
||||||
|
text.style.lineClamp = "none";
|
||||||
|
text.removeChild(moreButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Preview
|
||||||
|
const preview = document.getElementById("preview");
|
||||||
|
// Subtitle
|
||||||
|
preview.querySelector("#subtitle").textContent = app.subtitle;
|
||||||
|
// Screenshots
|
||||||
|
app.screenshotURLs.forEach(url => {
|
||||||
|
preview.querySelector("#screenshots").insertAdjacentHTML("beforeend", `<img src="${url}" alt="">`);
|
||||||
|
});
|
||||||
|
// Description
|
||||||
|
const previewDescription = preview.querySelector("#description");
|
||||||
|
previewDescription.innerHTML = formatString(app.localizedDescription);
|
||||||
|
if (previewDescription.scrollHeight > previewDescription.clientHeight)
|
||||||
|
previewDescription.insertAdjacentHTML("beforeend", more);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Version info
|
||||||
|
const versionDateElement = document.getElementById("version-date");
|
||||||
|
const versionNumberElement = document.getElementById("version");
|
||||||
|
const versionSizeElement = document.getElementById("version-size");
|
||||||
|
const versionDescriptionElement = document.getElementById("version-description");
|
||||||
|
|
||||||
|
const versionDate = new Date(app.versionDate),
|
||||||
|
month = versionDate.toUTCString().split(" ")[2],
|
||||||
|
date = versionDate.getDate();
|
||||||
|
const today = new Date();
|
||||||
|
const msPerDay = 60 * 60 * 24 * 1000;
|
||||||
|
const msDifference = today.valueOf() - versionDate.valueOf();
|
||||||
|
|
||||||
|
// Version date
|
||||||
|
versionDateElement.textContent = `${month} ${date}, ${versionDate.getFullYear()}`;
|
||||||
|
if (msDifference <= msPerDay)
|
||||||
|
versionDateElement.textContent = "Today";
|
||||||
|
else if (msDifference <= msPerDay * 2)
|
||||||
|
versionDateElement.textContent = "Yesterday";
|
||||||
|
|
||||||
|
// Version number
|
||||||
|
versionNumberElement.textContent = `Version ${app.version}`;
|
||||||
|
|
||||||
|
// Version size
|
||||||
|
const units = ["B", "KB", "MB", "GB"];
|
||||||
|
var appSize = app.size, i = 0;
|
||||||
|
while (appSize > 1024) { i++;
|
||||||
|
appSize = parseFloat(appSize / 1024).toFixed(1);
|
||||||
|
}
|
||||||
|
versionSizeElement.textContent = `${appSize} ${units[i]}`;
|
||||||
|
|
||||||
|
// Version description
|
||||||
|
versionDescriptionElement.innerHTML = formatString(app.versionDescription);
|
||||||
|
if (versionDescriptionElement.scrollHeight > versionDescriptionElement.clientHeight)
|
||||||
|
versionDescriptionElement.insertAdjacentHTML("beforeend", more);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Permissions
|
||||||
|
const permissions = document.getElementById("permissions");
|
||||||
|
|
||||||
|
// If permissions specified
|
||||||
|
if (app.permissions)
|
||||||
|
// Remove placeholder permission
|
||||||
|
permissions.querySelector(".permission").remove();
|
||||||
|
else return;
|
||||||
|
|
||||||
|
app.permissions?.forEach(permission => {
|
||||||
|
var permissionType, icon;
|
||||||
|
switch (permission.type) {
|
||||||
|
case "background-audio":
|
||||||
|
permissionType = "Background Audio";
|
||||||
|
icon = "volume-up-fill";
|
||||||
|
break;
|
||||||
|
case "background-fetch":
|
||||||
|
permissionType = "Background Fetch";
|
||||||
|
icon = "arrow-repeat"
|
||||||
|
break;
|
||||||
|
case "photos":
|
||||||
|
permissionType = "Photos"
|
||||||
|
icon = "image-fill";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
permissionType = permission.type.replaceAll("-", " ");
|
||||||
|
icon = "gear-wide-connected";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions.insertAdjacentHTML("beforeend", `
|
||||||
|
<div class="permission">
|
||||||
|
<i class="bi-${icon}" style="color: ${tintColor};"></i>
|
||||||
|
<div class="text">
|
||||||
|
<p class="title">${permissionType}</p>
|
||||||
|
<p class="description">${permission.usageDescription ?? "No description provided."}</p>
|
||||||
|
</div>
|
||||||
|
</div>`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function nope() {
|
function exit() {
|
||||||
window.location.replace("index.html");
|
window.location.replace(`home.html?source=${sourceURL}`);
|
||||||
}
|
}
|
||||||
52
js/apps.js
52
js/apps.js
@@ -1,37 +1,31 @@
|
|||||||
addNavigationBar("All Apps");
|
insertNavigationBar("All Apps");
|
||||||
|
|
||||||
fetch(sourceURL)
|
function main(json) {
|
||||||
.then(response => response.json())
|
// Set tab title
|
||||||
.then(json => {
|
document.title = `Apps - ${json.name}`;
|
||||||
if (json.tintColor) setTintColor(json.tintColor)
|
|
||||||
|
|
||||||
document.title = `Apps - ${json.name}`;
|
// Sort apps in decending order of version date (newest first)
|
||||||
|
json.apps.sort((a, b) => (new Date(b.versionDate)).valueOf() - (new Date(a.versionDate)).valueOf());
|
||||||
|
|
||||||
json.apps.sort((a, b) => (new Date(b.versionDate)).valueOf() - (new Date(a.versionDate)).valueOf());
|
// Create & insert app items
|
||||||
json.apps.forEach(app => {
|
json.apps.forEach(app => {
|
||||||
if (app.beta) return; // Ignore beta apps
|
if (app.beta) return; // Ignore beta apps
|
||||||
|
|
||||||
const urls = app.screenshotURLs;
|
let html = `
|
||||||
|
<div class="app-container">
|
||||||
let html = `
|
${appHeaderHTML(app) }
|
||||||
<div class="app-container">`;
|
<p style="text-align: center; font-size: 0.9em;">${app.subtitle ?? ""}</p>`;
|
||||||
html +=
|
if (app.screenshotURLs) {
|
||||||
appHeaderHTML(app);
|
|
||||||
html += `
|
html += `
|
||||||
<p style="text-align: center; font-size: 0.9em;">${app.subtitle ?? ""}</p>`;
|
<div class="screenshots">`;
|
||||||
if (urls) {
|
for (let i = 0; i < app.screenshotURLs.length, i < 2; i++) html += `
|
||||||
html += `
|
<img src="${app.screenshotURLs[i]}" class="screenshot">`;
|
||||||
<div class="screenshots">`;
|
|
||||||
for (let i = 0; i < urls.length, i < 2; i++) html += `
|
|
||||||
<img src="${urls[i]}" class="screenshot">`;
|
|
||||||
html += `
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
html += `
|
html += `
|
||||||
</div>`;
|
</div>`;
|
||||||
|
}
|
||||||
|
html += `
|
||||||
|
</div>`;
|
||||||
|
|
||||||
document.getElementById("apps").insertAdjacentHTML("beforeend", html);
|
document.getElementById("apps").insertAdjacentHTML("beforeend", html);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
waitForAllImagesToLoad();
|
|
||||||
});
|
|
||||||
102
js/home.js
102
js/home.js
@@ -1,64 +1,56 @@
|
|||||||
fetch(sourceURL)
|
function main(json) {
|
||||||
.then(response => response.json())
|
// Set "View All News" link
|
||||||
.then(json => {
|
document.querySelector("#news a").href = `news.html?source=${sourceURL}`;
|
||||||
document.querySelector("#news a").href = `news.html?source=${sourceURL}`;
|
// Set "View All Apps" link
|
||||||
document.querySelector("#apps a").href = `apps.html?source=${sourceURL}`;
|
document.querySelector("#apps a").href = `apps.html?source=${sourceURL}`;
|
||||||
|
|
||||||
if (json.tintColor) setTintColor(json.tintColor)
|
// Set tab title
|
||||||
|
document.title = json.name;
|
||||||
|
// Set page title
|
||||||
|
document.getElementById("title").innerText = json.name;
|
||||||
|
|
||||||
document.title = json.name;
|
//
|
||||||
document.getElementById("title").innerText = json.name;
|
// News
|
||||||
|
if (json.news && json.news.length >= 1) {
|
||||||
|
// Sort news in decending order of date (latest first)
|
||||||
|
json.news.sort((a, b) => // If b < a
|
||||||
|
(new Date(b.date)).valueOf() - (new Date(a.date)).valueOf());
|
||||||
|
|
||||||
// Sort apps in descending order
|
if (json.news.length == 1) {
|
||||||
json.apps.sort((a, b) => {
|
document.getElementById("news-items").insertAdjacentHTML("beforeend", newsItemHTML(json.news[0], true));
|
||||||
// If b < a
|
document.getElementById("news-items").classList.add("one");
|
||||||
return (new Date(b.versionDate)).valueOf() - (new Date(a.versionDate)).valueOf();
|
} else for (let i = 0; i < 5 && i < json.news.length; i++)
|
||||||
});
|
document.getElementById("news-items").insertAdjacentHTML("beforeend", newsItemHTML(json.news[i], true));
|
||||||
|
} else document.getElementById("news").remove();
|
||||||
|
|
||||||
if (json.news && json.news.length >= 1) {
|
// Sort apps in descending order of version date
|
||||||
// Sort news in decending order (latest first)
|
json.apps.sort((a, b) => (new Date(b.versionDate)).valueOf() - (new Date(a.versionDate)).valueOf());
|
||||||
json.news.sort((a, b) => (new Date(b.date)).valueOf() - (new Date(a.date)).valueOf());
|
|
||||||
|
//
|
||||||
|
// Featured apps
|
||||||
|
let count = 1;
|
||||||
|
json.apps.forEach(app => {
|
||||||
|
// Max: 3 featured apps if not specified
|
||||||
|
if (count > 3) return;
|
||||||
|
|
||||||
// News
|
// Ignore beta apps
|
||||||
if (json.news.length == 1) {
|
if (app.beta) return;
|
||||||
document.getElementById("news-items").insertAdjacentHTML("beforeend", newsItemHTML(json.news[0], json.apps, true));
|
|
||||||
document.getElementById("news-items").classList.add("one");
|
|
||||||
} else if (json.news.length > 1) {
|
|
||||||
for (let i = 0; i < 5 && i < json.news.length; i++) {
|
|
||||||
document.getElementById("news-items").insertAdjacentHTML("beforeend", newsItemHTML(json.news[i], json.apps, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document.getElementById("news").remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apps
|
// If there are featured apps, ignore non-featured apps
|
||||||
let count = 1;
|
if (json.featuredApps && !json.featuredApps.includes(app.bundleIdentifier)) return;
|
||||||
json.apps.forEach(app => {
|
|
||||||
// Max: 3 featured apps if not specified
|
|
||||||
if (count > 3) return;
|
|
||||||
|
|
||||||
// Ignore beta apps
|
document.getElementById("apps").insertAdjacentHTML("beforeend", appHeaderHTML(app));
|
||||||
if (app.beta) return;
|
|
||||||
|
|
||||||
// If there are featured apps, ignore non-featured apps
|
count++;
|
||||||
if (json.featuredApps && !json.featuredApps.includes(app.bundleIdentifier)) return;
|
|
||||||
|
|
||||||
document.getElementById("apps").insertAdjacentHTML("beforeend", appHeaderHTML(app));
|
|
||||||
|
|
||||||
count++;
|
|
||||||
});
|
|
||||||
|
|
||||||
var description = formatString(json.description);
|
|
||||||
if (description) {
|
|
||||||
document.getElementById("about").insertAdjacentHTML("beforeend", `
|
|
||||||
<div class="item">
|
|
||||||
<p>${description}</p>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
} else {
|
|
||||||
document.getElementById("about").remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForAllImagesToLoad();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// About
|
||||||
|
var description = formatString(json.description);
|
||||||
|
if (description) document.getElementById("about").insertAdjacentHTML("beforeend", `
|
||||||
|
<div class="item">
|
||||||
|
<p>${description}</p>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
else document.getElementById("about").remove();
|
||||||
|
}
|
||||||
29
js/index.js
29
js/index.js
@@ -1,16 +1,17 @@
|
|||||||
const textField = document.querySelector("input");
|
(function main() {
|
||||||
|
// If source specified, go straight to home page
|
||||||
|
if (urlSearchParams.has('source') && sourceURL.match(urlRegex))
|
||||||
|
window.location.replace(`home.html?source=${sourceURL}`);
|
||||||
|
|
||||||
textField.addEventListener("keypress", function (event) {
|
const textField = document.querySelector("input");
|
||||||
// If the user presses the "Enter" key on the keyboard
|
textField.addEventListener("keypress", function (event) {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const url = textField.value;
|
const url = textField.value;
|
||||||
const urlRegex = /(https?:\/\/[^ ]*)/g;
|
if (!url.match(urlRegex))
|
||||||
|
alert("Invalid URL.");
|
||||||
if (!url.match(urlRegex))
|
else window.location.replace(`home.html?source=${url}`);
|
||||||
alert("Invalid URL.");
|
}
|
||||||
else
|
});
|
||||||
window.location.replace(`home.html?source=${url}`);
|
})();
|
||||||
}
|
|
||||||
});
|
|
||||||
143
js/main.js
143
js/main.js
@@ -1,50 +1,23 @@
|
|||||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
(function () {
|
||||||
const sourceURL = urlSearchParams.get('source');
|
// If no source or source is not a URL
|
||||||
|
if (!urlSearchParams.has('source') || !sourceURL.match(urlRegex))
|
||||||
|
window.location.replace("index.html");
|
||||||
|
insertAddToAltStoreBanner();
|
||||||
|
})()
|
||||||
|
|
||||||
// If no source
|
fetch(sourceURL, {
|
||||||
if (!urlSearchParams.has('source') || !sourceURL) {
|
cache: "force-cache"
|
||||||
alert(`No source provided.`);
|
})
|
||||||
window.location.replace("index.html");
|
.then(response => response.json())
|
||||||
}
|
.then(json => {
|
||||||
|
// Set tint color
|
||||||
|
if (json.tintColor) setTintColor(json.tintColor);
|
||||||
|
|
||||||
const urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; // https://stackoverflow.com/a/8943487
|
setApps(json.apps);
|
||||||
function formatString(string) {
|
main(json);
|
||||||
if (!string) return undefined;
|
waitForAllImagesToLoad();
|
||||||
|
})
|
||||||
// URLs
|
.catch(error => console.error("An error occurred.", error));
|
||||||
const urlArray = string.match(urlRegex);
|
|
||||||
const urlSet = [...new Set(urlArray)]; // Converting to set to remove duplicates
|
|
||||||
urlSet.forEach(url => string = string.replaceAll(url, `<a href="${url}">${url}</a>`));
|
|
||||||
|
|
||||||
// New lines
|
|
||||||
return string.replaceAll("\n", "<br>");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If source is not a URL
|
|
||||||
if (!sourceURL.match(urlRegex)) {
|
|
||||||
alert("Invalid URL.");
|
|
||||||
window.location.replace("index.html");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Back button
|
|
||||||
document.getElementById("back")?.addEventListener("click", () => history.back(1));
|
|
||||||
|
|
||||||
// Add to AltStore banner
|
|
||||||
document.getElementById("top")?.insertAdjacentHTML("afterbegin", `
|
|
||||||
<div class="uibanner">
|
|
||||||
<img src="https://user-images.githubusercontent.com/705880/65270980-1eb96f80-dad1-11e9-9367-78ccd25ceb02.png" alt="altstore-icon" class="icon">
|
|
||||||
<div class="content">
|
|
||||||
<div class="text-container">
|
|
||||||
<p class="title-text">AltStore <span class="small beta badge"></span></p>
|
|
||||||
<p class="detail-text">
|
|
||||||
Add this source to AltStore to receive app updates (requires AltStore beta)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<a href="altstore://source?url=${sourceURL}">
|
|
||||||
<button>Add</button>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>`);
|
|
||||||
|
|
||||||
function waitForAllImagesToLoad() {
|
function waitForAllImagesToLoad() {
|
||||||
const allImages = document.querySelectorAll("img");
|
const allImages = document.querySelectorAll("img");
|
||||||
@@ -54,89 +27,15 @@ function waitForAllImagesToLoad() {
|
|||||||
// New img element that won't be rendered to the DOM
|
// New img element that won't be rendered to the DOM
|
||||||
var newImage = document.createElement("img");
|
var newImage = document.createElement("img");
|
||||||
// Attach load listener
|
// Attach load listener
|
||||||
newImage.addEventListener("load", imageLoaded);
|
newImage.addEventListener("load", loaded);
|
||||||
// Set src
|
// Set src
|
||||||
newImage.src = image.src;
|
newImage.src = image.src;
|
||||||
})
|
});
|
||||||
|
|
||||||
function imageLoaded() {
|
function loaded() {
|
||||||
if (++count == allImages.length) {
|
if (++count == allImages.length) {
|
||||||
document.querySelector("body").classList.remove("loading");
|
document.querySelector("body").classList.remove("loading");
|
||||||
document.getElementById("loading").remove();
|
document.getElementById("loading").remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function setTintColor(color) {
|
|
||||||
document.querySelector(':root').style.setProperty("--accent-color", `#${color}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addNavigationBar(title) {
|
|
||||||
document.getElementById("top").insertAdjacentHTML("beforeend", `
|
|
||||||
<div id="nav-bar">
|
|
||||||
<button id="back" type="button">
|
|
||||||
<i class="bi bi-chevron-left"></i>
|
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
<div id="title">
|
|
||||||
<p>${title ?? ""}</p>
|
|
||||||
</div>
|
|
||||||
<button id="back" class="hidden">
|
|
||||||
<i class="bi bi-chevron-left"></i>
|
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
</div>`);
|
|
||||||
document.getElementById("back")?.addEventListener("click", () => history.back(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
function newsItemHTML(news, apps, minimal) {
|
|
||||||
let html = `
|
|
||||||
<div class="news-item-wrapper">`;
|
|
||||||
|
|
||||||
if (news.url) html += `
|
|
||||||
<a href="${news.url}">`;
|
|
||||||
html += `
|
|
||||||
<div class="item" style="background-color: #${news.tintColor};">
|
|
||||||
<div class="text">
|
|
||||||
<h3>${news.title}</h3>
|
|
||||||
<p>${news.caption}</p>
|
|
||||||
</div>`;
|
|
||||||
if (news.imageURL && !minimal) html += `
|
|
||||||
<div class="image-wrapper">
|
|
||||||
<img src="${news.imageURL}">
|
|
||||||
</div>`;
|
|
||||||
html += `
|
|
||||||
</div>`;
|
|
||||||
if (news.url) html +=
|
|
||||||
"</a>";
|
|
||||||
|
|
||||||
if (news.appID && !minimal) {
|
|
||||||
const app = apps.find(app => app.bundleIdentifier == news.appID);
|
|
||||||
if (app) html += appHeaderHTML(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
html += "</div>";
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function appHeaderHTML(app) {
|
|
||||||
return `
|
|
||||||
<div class="item">
|
|
||||||
<div class="app-header">
|
|
||||||
<div class="content">
|
|
||||||
<img src="${app.iconURL}" alt="">
|
|
||||||
<div class="right">
|
|
||||||
<div class="text">
|
|
||||||
<p class="title">${app.name}</p>
|
|
||||||
<p class="subtitle">${app.developerName}</p>
|
|
||||||
</div>
|
|
||||||
<a href="app.html?source=${sourceURL}&id=${app.bundleIdentifier}">
|
|
||||||
<button class="uibutton" style="background-color: #${app.tintColor};">View</button>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="background" style="background-color: #${app.tintColor};"></div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
22
js/news.js
22
js/news.js
@@ -1,16 +1,12 @@
|
|||||||
addNavigationBar("All News");
|
insertNavigationBar("All News");
|
||||||
|
|
||||||
fetch(sourceURL)
|
function main(json) {
|
||||||
.then(response => response.json())
|
// Set tab title
|
||||||
.then(json => {
|
document.title = `News - ${json.name}`;
|
||||||
if (json.tintColor) setTintColor(json.tintColor)
|
|
||||||
|
|
||||||
document.title = `News - ${json.name}`;
|
// Sort news by latest
|
||||||
|
json.news.sort((a, b) => (new Date(b.date)).valueOf() - (new Date(a.date)).valueOf());
|
||||||
|
|
||||||
json.news.sort((a, b) => (new Date(b.date)).valueOf() - (new Date(a.date)).valueOf());
|
// Create & insert news items
|
||||||
json.news.forEach(news =>
|
json.news.forEach(news => document.getElementById("news").insertAdjacentHTML("beforeend", newsItemHTML(news)));
|
||||||
document.getElementById("news").insertAdjacentHTML("beforeend", newsItemHTML(news, json.apps))
|
}
|
||||||
);
|
|
||||||
|
|
||||||
waitForAllImagesToLoad();
|
|
||||||
});
|
|
||||||
109
js/shared.js
Normal file
109
js/shared.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||||
|
const sourceURL = urlSearchParams.get('source');
|
||||||
|
// https://stackoverflow.com/a/8943487
|
||||||
|
const urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
||||||
|
|
||||||
|
(function (global) {
|
||||||
|
var apps;
|
||||||
|
global.setApps = array =>
|
||||||
|
apps = array;
|
||||||
|
global.getAppWithBundleId = bundleId =>
|
||||||
|
apps?.find(app => app.bundleIdentifier == bundleId) ?? undefined;
|
||||||
|
|
||||||
|
setUpBackButton();
|
||||||
|
})(this);
|
||||||
|
|
||||||
|
const newsItemHTML = (news, minimal = false) => `
|
||||||
|
<div class="news-item-wrapper"> ${news.url ?
|
||||||
|
"<a href='" + news.url + "'>" : ""}
|
||||||
|
<div class="item" style="background-color: #${news.tintColor};">
|
||||||
|
<div class="text">
|
||||||
|
<h3>${news.title}</h3>
|
||||||
|
<p>${news.caption}</p>
|
||||||
|
</div>${news.imageURL && !minimal ?
|
||||||
|
"<div class='image-wrapper'>" +
|
||||||
|
"<img src='" + news.imageURL + "'>" +
|
||||||
|
"</div>" : ""}
|
||||||
|
</div> ${news.url ?
|
||||||
|
"</a>" : ""} ${news.appID && !minimal ?
|
||||||
|
appHeaderHTML(getAppWithBundleId(news.appID)) ?? "" : ""}
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
const appHeaderHTML = app => app ? `
|
||||||
|
<div class="item">
|
||||||
|
<div class="app-header">
|
||||||
|
<div class="content">
|
||||||
|
<img src="${app.iconURL}" alt="">
|
||||||
|
<div class="right">
|
||||||
|
<div class="text">
|
||||||
|
<p class="title">${app.name}</p>
|
||||||
|
<p class="subtitle">${app.developerName}</p>
|
||||||
|
</div>
|
||||||
|
<a href="app.html?source=${sourceURL}&id=${app.bundleIdentifier}">
|
||||||
|
<button class="uibutton" style="background-color: #${app.tintColor};">View</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="background" style="background-color: #${app.tintColor};"></div>
|
||||||
|
</div>
|
||||||
|
</div>` : undefined;
|
||||||
|
|
||||||
|
function formatString(string) {
|
||||||
|
if (!string) return undefined;
|
||||||
|
|
||||||
|
// URLs
|
||||||
|
const urlArray = string.match(urlRegex);
|
||||||
|
const urlSet = [...new Set(urlArray)]; // Converting to set to remove duplicates
|
||||||
|
urlSet.forEach(url => string = string.replaceAll(url, `<a href="${url}">${url}</a>`));
|
||||||
|
|
||||||
|
// New lines
|
||||||
|
return string.replaceAll("\n", "<br>");
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertAddToAltStoreBanner() {
|
||||||
|
document.getElementById("top")?.insertAdjacentHTML("afterbegin", `
|
||||||
|
<div class="uibanner">
|
||||||
|
<img src="https://user-images.githubusercontent.com/705880/65270980-1eb96f80-dad1-11e9-9367-78ccd25ceb02.png" alt="altstore-icon" class="icon">
|
||||||
|
<div class="content">
|
||||||
|
<div class="text-container">
|
||||||
|
<p class="title-text">AltStore <span class="small beta badge"></span></p>
|
||||||
|
<p class="detail-text">
|
||||||
|
Add this source to AltStore to receive app updates (requires AltStore beta)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a href="altstore://source?url=${sourceURL}">
|
||||||
|
<button>Add</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertNavigationBar(title) {
|
||||||
|
document.getElementById("top")?.insertAdjacentHTML("beforeend", `
|
||||||
|
<div id="nav-bar">
|
||||||
|
<button id="back" type="button">
|
||||||
|
<i class="bi bi-chevron-left"></i>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
<div id="title">
|
||||||
|
<p>${title ?? ""}</p>
|
||||||
|
</div>
|
||||||
|
<button id="back" class="hidden">
|
||||||
|
<i class="bi bi-chevron-left"></i>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</div>`);
|
||||||
|
setUpBackButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTintColor(color) {
|
||||||
|
document.querySelector(':root')?.style.setProperty("--accent-color", `#${color}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUpBackButton() {
|
||||||
|
document.getElementById("back")?.addEventListener("click", () => history.back(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
const $ = selector => selector.startsWith("#") && !selector.includes(".") && !selector.includes(" ")
|
||||||
|
? document.getElementById(selector.substring(1))
|
||||||
|
: document.querySelectorAll(selector);
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="css/main.css">
|
<link rel="stylesheet" href="css/shared.css">
|
||||||
<link rel="stylesheet" href="css/news.css">
|
|
||||||
<link rel="stylesheet" href="css/uibanner.css">
|
<link rel="stylesheet" href="css/uibanner.css">
|
||||||
|
<link rel="stylesheet" href="css/news.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="loading">
|
<body class="loading">
|
||||||
<div id="loading">
|
<div id="loading">
|
||||||
@@ -18,7 +18,8 @@
|
|||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="news"></div>
|
<div id="news"></div>
|
||||||
</div>
|
</div>
|
||||||
<script src="js/main.js"></script>
|
<script src="js/shared.js"></script>
|
||||||
<script src="js/news.js"></script>
|
<script src="js/news.js"></script>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user