From d35389cc9aeb64e7ab9c4b863eb7a0b5726808a4 Mon Sep 17 00:00:00 2001 From: foxster-mp4 Date: Thu, 27 Apr 2023 20:27:37 -0700 Subject: [PATCH] Initial commit --- .gitignore | 13 ++ app.html | 105 +++++++++++++ apps.html | 24 +++ css/app.css | 156 +++++++++++++++++++ css/apps.css | 27 ++++ css/index.css | 45 ++++++ css/main.css | 389 +++++++++++++++++++++++++++++++++++++++++++++++ css/news.css | 19 +++ css/uibanner.css | 68 +++++++++ home.html | 48 ++++++ index.html | 61 ++++++++ js/app.js | 216 ++++++++++++++++++++++++++ js/apps.js | 37 +++++ js/home.js | 72 +++++++++ js/index.js | 16 ++ js/main.js | 132 ++++++++++++++++ js/news.js | 16 ++ news.html | 24 +++ 18 files changed, 1468 insertions(+) create mode 100644 .gitignore create mode 100644 app.html create mode 100644 apps.html create mode 100644 css/app.css create mode 100644 css/apps.css create mode 100644 css/index.css create mode 100644 css/main.css create mode 100644 css/news.css create mode 100644 css/uibanner.css create mode 100644 home.html create mode 100644 index.html create mode 100644 js/app.js create mode 100644 js/apps.js create mode 100644 js/home.js create mode 100644 js/index.js create mode 100644 js/main.js create mode 100644 js/news.js create mode 100644 news.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2871f87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Temporary files +*~ +~$*.doc* +~$*.xls* +~$*.ppt* +*.xlk +*.pdf + +# .DS_Store files +*.DS_Store + +# Preference files +.vscode/ \ No newline at end of file diff --git a/app.html b/app.html new file mode 100644 index 0000000..b264b0d --- /dev/null +++ b/app.html @@ -0,0 +1,105 @@ + + + + + + + + + + + +
+ loading +

Loading

+
+
+
+ + +
+ +
+
+
+ +
+
+

AltSource

+

therealFoxster

+
+ +
+
+
+
+
+
+

The quick brown fox jumps over the lazy dog.

+
+

Preview

+
+
+

+ The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. +

+
+
+
+

What's New

+

Apr 10, 2023

+
+
+

Version 2.0

+

0 KB

+
+

+ The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. + The quick brown fox jumps over the lazy dog. +

+
+
+
+

Permissions

+
+
+ +
+

None

+

The developer has not specified any permission required by this app.

+
+
+
+
+ + + + \ No newline at end of file diff --git a/apps.html b/apps.html new file mode 100644 index 0000000..26d8537 --- /dev/null +++ b/apps.html @@ -0,0 +1,24 @@ + + + + + + + + + + + +
+ loading +

Loading

+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/css/app.css b/css/app.css new file mode 100644 index 0000000..dffc97e --- /dev/null +++ b/css/app.css @@ -0,0 +1,156 @@ +a { + color: var(--app-tint-color) +} + +/* Main */ + +#main { + padding: unset; + overflow: hidden; +} + +#main .item { + margin-top: 30%; + margin-bottom: 0; + padding: 1em; +} + +#nav-bar #title, +#nav-bar .install { + transition: opacity 0.25s ease-in-out; +} + +/* IPA install & download */ + +.ipa { + display: flex; + flex-direction: column; + align-items: center; +} + +.ipa #download { + position: absolute; + margin-top: 36px; /* 32px button height + 4px gap */ + font-size: 0.65em; + font-weight: 450; +} + +/* Preview */ + +#preview { + position: relative; +} + +#preview #subtitle { + text-align: center; + font-size: 0.9em; + margin: 0 1em; +} + +#preview .header { + margin: 1em 0; +} + +#preview #screenshots { + display: flex; + flex-direction: row; + overflow: scroll; + padding-right: 1em; +} + +#preview #screenshots>img { + max-width: 70%; + margin-left: 1em; + border-radius: 0.25em; +} + +#preview #description { + margin: 1em; + -webkit-line-clamp: 5; + line-clamp: 5; + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + position: relative; + word-wrap: break-word +} + +#more button { + min-width: 0 !important; + margin-left: 2px !important; + padding: 0; + font-size: 15px; + font-weight: 500; + text-transform: none; + border-radius: 4px; + border: none; + background: unset; + color: var(--accent-color); + cursor: pointer; + position: absolute; + bottom: 0; + right: 0; + padding-left: 36px; + background: linear-gradient(to right, transparent, var(--color-bg) 35%); +} + +@media (hover:hover) { + #more button:hover { + opacity: 0.75; + } +} + +/* What's new */ + +#whats-new, +#permissions { + padding: 1em; +} + +#whats-new .header, +#permissions .header { + margin: 0.35em 0; + padding: 0; +} + +#whats-new .header>p:first-of-type { + opacity: 0.5; +} + +#whats-new .header>p { + opacity: 0.35; +} + +#whats-new #version-description { + -webkit-line-clamp: 3; + line-clamp: 3; + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + position: relative; + word-wrap: break-word +} + +/* Permissions */ + +#permissions .permission { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.75em; + margin: 0.5em 0; +} + +#permissions .permission i { + font-size: 1.55em; + color: var(--app-tint-color); +} + +#permissions .permission .title { + font-weight: 550; +} + +#permissions .permission .description { + font-size: 0.9em; + opacity: 0.5; +} \ No newline at end of file diff --git a/css/apps.css b/css/apps.css new file mode 100644 index 0000000..0a7b488 --- /dev/null +++ b/css/apps.css @@ -0,0 +1,27 @@ +#main { + padding-top: 7rem; +} + +#apps .app-container { + margin: 1em 1rem 3.25em 1rem; +} + +#apps .app-container:last-of-type { + margin-bottom: 1em; +} + +.screenshots { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-top: 1em; +} + +.screenshots:last-of-type { + margin-bottom: 1em; +} + +.screenshot { + max-width: 48%; + border-radius: 4px; +} \ No newline at end of file diff --git a/css/index.css b/css/index.css new file mode 100644 index 0000000..6a34c29 --- /dev/null +++ b/css/index.css @@ -0,0 +1,45 @@ +#main { + padding-top: 2rem; +} + +#main #title { + margin-bottom: 0.35rem; + padding: 0 1rem; +} + +#main input { + width: 95%; + font-size: 1em; + padding: 0.5rem 0.8rem; + margin-bottom: 1rem; + border: none; + border-radius: 10px; + background-color: rgba(0, 0, 0, 0.07); +} + +.suggestion { + padding: 1rem 1rem 1rem 0; + margin-left: 1rem; + font-size: 1.1rem; + border-bottom: 0.1px solid var(--color-separator); +} + +.suggestion .bi { + margin-right: 4px; +} + +@media (prefers-color-scheme: dark) { + #main input { + background-color: rgba(255, 255, 255, 0.07); + } + .suggestion { + border-bottom: 0.1px solid var(--color-separator-dark) !important; + } +} + +#main .textfield { + display: flex; + align-items: center; + justify-content: center; + padding: 0 1rem; +} \ No newline at end of file diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..1a0ec25 --- /dev/null +++ b/css/main.css @@ -0,0 +1,389 @@ +@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.css"); + +:root { + --accent-color: #018084; + --app-tint-color: #018084; + + --color-bg: rgba(255, 255, 255, 255); + --color-separator: rgba(0, 0, 0, 0.15); + --color-primary: rgba(0, 122, 254, 255); + --color-transparent: rgba(255, 255, 255, 0.75); + + --color-bg-dark: rgb(26, 25, 27); + --color-separator-dark: rgba(255, 255, 255, 0.15); + --color-primary-dark: rgba(11, 132, 254, 255); + --color-transparent-dark: rgba(26, 25, 27, 0.25); +} + +* { + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + margin: 0; +} + +/* Dark appearance */ +@media (prefers-color-scheme: dark) { + + body, + .uibanner, + #loading { + background-color: var(--color-bg-dark) !important; + } + + p, + h1, + h2 { + color: white; + } + + #nav-bar { + background-color: var(--color-transparent-dark) !important; + } + + .uibanner, + #nav-bar { + border-color: var(--color-separator-dark) !important; + } + + .app-header .background { + opacity: 0.35 !important; + } + + #more button { + background: linear-gradient(to right, transparent, var(--color-bg-dark) 35%) !important; + } +} + +body.loading { + overflow: hidden; +} + +#loading { + position: fixed; + left: 0; + right: 0; + height: 100vh; + margin: 0 auto; + z-index: 4; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 4px; + background: var(--color-bg); +} + +#loading img { + width: 24px; + opacity: 0.5; +} + +#loading p { + font-size: 0.75em; + font-weight: 500; + text-transform: uppercase; + opacity: 0.35; +} + +/* h1#title { + padding-left: 1rem; +} */ + +body { + width: 100%; + padding-bottom: 20px; +} + +@media screen and (min-device-width: 767px) { + body { + margin: 0 auto; + max-width: 414px; + } +} + +.hidden { + opacity: 0; + pointer-events: none; +} + +#top { + position: fixed; + z-index: 5; + left: 0; + right: 0; +} + +#title { + margin-bottom: 0.35em; +} + +#main>p:first-of-type { + text-transform: uppercase; + font-weight: 500; + font-size: 0.9em; + opacity: 0.5; + padding-left: 1rem; + margin-top: 1rem; +} + +/* Navigation bar */ + +#nav-bar { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + border-bottom: 0.1px solid var(--color-separator); + background-color: var(--color-transparent); + -webkit-backdrop-filter: saturate(100%) blur(30px); + backdrop-filter: saturate(100%) blur(20px); + max-width: 414px; + margin: 0 auto; + padding: 0.25em 0.75em; + z-index: 2; +} + +#nav-bar #title { + display: flex; + flex-direction: row; + align-items: center; + gap: 6px; + min-height: 2.5em; + margin: 0; +} + +#nav-bar #title>p { + font-weight: 600; +} + +#nav-bar #title>img { + max-height: 2em; + border-radius: 0.4em; +} + +#nav-bar #back { + cursor: pointer; +} + +#nav-bar #back .bi { + margin-left: -8px; + -webkit-text-stroke: 1px; + font-size: 1.3em; +} + +/* Main */ + +#main { + /* padding: 1em; */ + padding-top: 3.55rem; +} + +.item { + margin: 1rem 0; +} + +/* Section header */ + +.header { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 0 1rem; +} + +/* News */ + +#news-items { + display: grid; + height: 100%; + gap: 1em; + grid-auto-columns: 95%; + grid-auto-flow: column; + overflow-x: scroll; + padding: 1rem; +} + +#news-items.one { + grid-auto-columns: 100% !important; +} + +#news .item { + display: flex; + flex-direction: column; + justify-content: center; + border-radius: 1.5em; + overflow: hidden; + min-width: 100%; + height: 100%; + margin: 0; +} + +#news .item>.text { + margin: 1.5em; + color: #fff; +} + +#news .item>.text>p { + opacity: 0.75; + font-size: 0.9em; +} + +#news .item .image-wrapper { + max-height: 15.15em; + overflow: hidden; +} + +#news img { + width: 100%; + height: auto; + display: block; +} + +/* App header */ + +#apps.section .item { + padding: 0 1rem; +} + +.app-header { + position: relative; + display: flex; +} + +.app-header>.content { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + padding: 1rem; +} + +.app-header img { + display: block; + border-radius: 13.5px; + max-width: 64px; +} + +.app-header .background { + position: absolute; + background-color: var(--accent-color); + opacity: 0.2; + border-radius: 1.5em; + width: 100%; + height: 100%; + z-index: -1; +} + +.app-header>.content>.right { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 100%; + margin-left: 0.65em; +} + +.app-header>.content .text>.title { + font-weight: 600; + font-size: 1rem; +} + +.app-header>.content .text>.subtitle { + opacity: 0.5; + font-size: 0.85em; +} + +/* About */ + +#about a:visited { + color: var(--accent-color) +} + +#about.section .item { + padding: 0 1rem; +} + +/* Buttons */ + +button.uibutton { + min-width: 78px; + height: 32px; + padding: 0 12px; + font-size: 15px; + font-weight: 700; + text-transform: uppercase; + border: none; + border-radius: 20px; + color: white; + background-color: var(--accent-color); + cursor: pointer; +} + +button.uibutton:active { + opacity: 0.8; +} + +/* Links */ + +a { + cursor: pointer; + text-decoration: none; + color: var(--accent-color) +} + +a>button { + cursor: pointer; +} + +@media (hover:hover) { + a:hover { + opacity: 0.75; + } +} + +.header a, +#nav-bar #back { + display: flex; + align-items: center; + font-size: 1.05em; + font-weight: 450; + border: none; + background: unset; + padding: 0; + color: var(--accent-color); +} + +/* Badges */ + +.badge { + margin-left: 0.05rem !important; + margin-right: 0.05rem !important; + padding: 0.5px 6.5px; + font-size: 10px; + line-height: 1.3333733333; + font-weight: 700; + border-radius: 12px; + border-width: 1px; + border-style: solid; + box-sizing: border-box; + white-space: nowrap; + display: inline-block; + background: transparent; + text-transform: uppercase; + vertical-align: middle; + color: white; + border-color: transparent; +} + +.beta.badge { + margin-bottom: 2px; + background-color: #f5a10d; +} + +.beta.badge::before { + content: "Beta"; +} + +.small.badge { + font-size: 8px; +} \ No newline at end of file diff --git a/css/news.css b/css/news.css new file mode 100644 index 0000000..c632027 --- /dev/null +++ b/css/news.css @@ -0,0 +1,19 @@ +#main { + padding-top: 8rem; +} + +#news { + padding: 0 1rem; +} + +.news-item-wrapper { + margin-bottom: 3em; +} + +.news-item-wrapper:last-of-type { + margin-bottom: 1em; +} + +.news-item-wrapper .item { + margin-bottom: 1rem !important; +} \ No newline at end of file diff --git a/css/uibanner.css b/css/uibanner.css new file mode 100644 index 0000000..3bc2e09 --- /dev/null +++ b/css/uibanner.css @@ -0,0 +1,68 @@ +.uibanner .icon { + max-width: 30px; + border-radius: 7px; + border: 0.75px solid rgba(0, 0, 0, 0.1); +} + +.uibanner { + z-index: 5; + display: flex; + align-items: center; + justify-content: center; + padding-bottom: 1px; + border-bottom: 0.1px solid var(--color-separator); +} + +.uibanner { + background: white; +} + +.uibanner>.icon { + margin-left: 14px; + display: flex; +} + +.uibanner>.content { + min-height: 28px; + width: 100%; + margin-left: 8px; + padding: 8px 16px 8px 0px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.uibanner>.content>.text-container>.title-text { + padding-bottom: 0 !important; + font-weight: 600; + font-size: 12.95px !important; +} + +.uibanner>.content>.text-container>.detail-text { + padding-top: 0 !important; + font-size: 11px !important; + font-weight: 400; + line-height: 12px !important; + opacity: 0.55; +} + +.uibanner>.content button { + min-width: 68px !important; + padding: 5px 13px; + font-size: 15px; + font-weight: 600; + text-transform: uppercase; + border: none; + border-radius: 20px; + color: white; + background-color: rgb(0, 122, 254); + cursor: pointer; +} + +.uibanner>.content a { + text-decoration: none; + color: unset; + font-weight: 600; + + color: white; +} \ No newline at end of file diff --git a/home.html b/home.html new file mode 100644 index 0000000..6d003f2 --- /dev/null +++ b/home.html @@ -0,0 +1,48 @@ + + + + + + + + + + + + +
+ loading +

Loading

+
+ +
+
+

Now viewing

+
+

altsource-v2

+ +
+
+
+

News

+ View All +
+
+
+
+
+

Featured Apps

+ View All Apps +
+
+
+
+

About

+
+
+
+ + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..15a0cf4 --- /dev/null +++ b/index.html @@ -0,0 +1,61 @@ + + + + + + + + + + + +
+

AltSource Viewer

+
+ +
+ +
+ + + \ No newline at end of file diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..f7fab0a --- /dev/null +++ b/js/app.js @@ -0,0 +1,216 @@ +const bundleId = urlSearchParams.get('id'); +if (!urlSearchParams.has('id') || !bundleId) nope(); + +// Hide/show navigation bar title & install button +let hidden = false; +window.onscroll = function (e) { + const appName = document.querySelector(".app-header .text>.title"); + const title = document.getElementById("title"); + const button = document.querySelector("#nav-bar .install"); + + if (hidden && appName.getBoundingClientRect().y >= 72) { // App name not visible + hidden = false; + title.classList.add("hidden"); + button.classList.add("hidden"); + button.disaled = true; + } else if (!hidden && appName.getBoundingClientRect().y < 72) { + hidden = true; + title.classList.remove("hidden"); + button.classList.remove("hidden"); + button.disaled = false; + } +} + +fetch(sourceURL) + .then(response => response.json()) + .then(json => { + 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}`; + + const tintColor = `#${app.tintColor}`; + + if (tintColor) + document.querySelector(':root').style.setProperty("--app-tint-color", `${tintColor}`); + + // Tint back button + const backButton = document.getElementById("back"); + backButton.style.color = tintColor; + + 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", ``); + }); + + let localizedDescription = app.localizedDescription; + + const perviewDescriptionURLs = [...new Set(localizedDescription.match(urlRegex))]; // Creating set from array to remove duplicates + + perviewDescriptionURLs.forEach(url => { + localizedDescription = localizedDescription.replaceAll(url, `${url}`) + }); + + + previewDescription.innerHTML = localizedDescription.replaceAll("\n", "
"); + + const more = ` + + + `; + + 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 + var appVersionDescription = app.versionDescription; + const urls = [...new Set(appVersionDescription.match(urlRegex))]; // Creating set from array to remove duplicates + + urls.forEach(url => + appVersionDescription = appVersionDescription.replaceAll(url, `${url}`) + ); + + versionDescription.innerHTML = appVersionDescription.replaceAll("\n", "
"); + 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 = ` +
+ +
+

${permissionType}

+

${permission.usageDescription ?? "No description provided."}

+
+
`; + + permissions.insertAdjacentHTML("beforeend", html); + }); + + waitForAllImagesToLoad(); + }); + +function 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) +} + +function nope() { + window.location.replace("index.html"); +} \ No newline at end of file diff --git a/js/apps.js b/js/apps.js new file mode 100644 index 0000000..ca9d10e --- /dev/null +++ b/js/apps.js @@ -0,0 +1,37 @@ +addNavigationBar("All Apps"); + +fetch(sourceURL) + .then(response => response.json()) + .then(json => { + if (json.tintColor) setTintColor(json.tintColor) + + document.title = `Apps - ${json.name}`; + + json.apps.sort((a, b) => (new Date(b.versionDate)).valueOf() - (new Date(a.versionDate)).valueOf()); + json.apps.forEach(app => { + if (app.beta) return; // Ignore beta apps + + const urls = app.screenshotURLs; + + let html = ` +
`; + html += + appHeaderHTML(app); + html += ` +

${app.subtitle ?? ""}

`; + if (urls) { + html += ` +
`; + for (let i = 0; i < urls.length, i < 2; i++) html += ` + `; + html += ` +
`; + } + html += ` +
`; + + document.getElementById("apps").insertAdjacentHTML("beforeend", html); + }); + + waitForAllImagesToLoad(); + }); \ No newline at end of file diff --git a/js/home.js b/js/home.js new file mode 100644 index 0000000..c1da505 --- /dev/null +++ b/js/home.js @@ -0,0 +1,72 @@ +fetch(sourceURL) + .then(response => response.json()) + .then(json => { + document.querySelector("#news a").href = `news.html?source=${sourceURL}`; + document.querySelector("#apps a").href = `apps.html?source=${sourceURL}`; + + if (json.tintColor) setTintColor(json.tintColor) + + document.title = json.name; + document.getElementById("title").innerText = json.name; + + // Sort apps in descending order + json.apps.sort((a, b) => { + // If b < a + return (new Date(b.versionDate)).valueOf() - (new Date(a.versionDate)).valueOf(); + }); + + if (json.news && json.news.length >= 1) { + // Sort news in decending order (latest first) + json.news.sort((a, b) => (new Date(b.date)).valueOf() - (new Date(a.date)).valueOf()); + + // News + if (json.news.length == 1) { + 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 + let count = 1; + json.apps.forEach(app => { + // Max: 3 featured apps if not specified + if (count > 3) return; + + // Ignore beta apps + if (app.beta) return; + + // If there are featured apps, ignore non-featured apps + if (json.featuredApps && !json.featuredApps.includes(app.bundleIdentifier)) return; + + document.getElementById("apps").insertAdjacentHTML("beforeend", appHeaderHTML(app)); + + count++; + }); + + var description = json.description; + + if (description) { + const urls = [...new Set(description.match(urlRegex))]; // Creating set from array to remove duplicates + + urls.forEach(url => + description = description.replaceAll(url, `${url}`) + ); + + document.getElementById("about").insertAdjacentHTML("beforeend", ` +
+

${description.replaceAll("\n", "
")}

+
+ `); + } else { + document.getElementById("about").remove(); + } + + + waitForAllImagesToLoad(); + }); diff --git a/js/index.js b/js/index.js new file mode 100644 index 0000000..32338d4 --- /dev/null +++ b/js/index.js @@ -0,0 +1,16 @@ +const textField = document.querySelector("input"); + +textField.addEventListener("keypress", function (event) { + // If the user presses the "Enter" key on the keyboard + if (event.key === "Enter") { + event.preventDefault(); + + const url = textField.value; + const urlRegex = /(https?:\/\/[^ ]*)/g; + + if (!url.match(urlRegex)) + alert("Invalid URL."); + else + window.location.replace(`home.html?source=${url}`); + } +}); \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..a30c5c8 --- /dev/null +++ b/js/main.js @@ -0,0 +1,132 @@ +const urlSearchParams = new URLSearchParams(window.location.search); +const sourceURL = urlSearchParams.get('source'); + +// If no source +if (!urlSearchParams.has('source') || !sourceURL) { + alert(`No source provided.`); + window.location.replace("index.html"); +} + +// https://stackoverflow.com/a/31760088 +const urlRegex = /(https?:\/\/[^ ]*)/g; // "g": global flag; without this, match() returns only the first matching result + +// 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", ` +
+ altstore-icon +
+
+

AltStore

+

+ Add this source to AltStore to receive app updates (requires AltStore beta) +

+
+ + + +
+
`); + +function waitForAllImagesToLoad() { + const allImages = document.querySelectorAll("img"); + var count = 0; + + allImages.forEach(image => { + // New img element that won't be rendered to the DOM + var newImage = document.createElement("img"); + // Attach load listener + newImage.addEventListener("load", imageLoaded); + // Set src + newImage.src = image.src; + }) + + function imageLoaded() { + if (++count == allImages.length) { + document.querySelector("body").classList.remove("loading"); + document.getElementById("loading").remove(); + } + } +} + +function setTintColor(color) { + document.querySelector(':root').style.setProperty("--accent-color", `#${color}`); +} + +function addNavigationBar(title) { + document.getElementById("top").insertAdjacentHTML("beforeend", ` + `); + document.getElementById("back")?.addEventListener("click", () => history.back(1)); +} + +function newsItemHTML(news, apps, minimal) { + let html = ` +
`; + + if (news.url) html += ` + `; + html += ` +
+
+

${news.title}

+

${news.caption}

+
`; + if (news.imageURL && !minimal) html += ` +
+ +
`; + html += ` +
`; + if (news.url) html += + "
"; + + if (news.appID && !minimal) { + const app = apps.find(app => app.bundleIdentifier == news.appID); + if (app) html += appHeaderHTML(app); + } + + html += "
"; + + return html; +} + +function appHeaderHTML(app) { + return ` +
+
+
+ +
+
+

${app.name}

+

${app.developerName}

+
+ + + +
+
+
+
+
`; +} \ No newline at end of file diff --git a/js/news.js b/js/news.js new file mode 100644 index 0000000..fb3462b --- /dev/null +++ b/js/news.js @@ -0,0 +1,16 @@ +addNavigationBar("All News"); + +fetch(sourceURL) + .then(response => response.json()) + .then(json => { + if (json.tintColor) setTintColor(json.tintColor) + + document.title = `News - ${json.name}`; + + json.news.sort((a, b) => (new Date(b.date)).valueOf() - (new Date(a.date)).valueOf()); + json.news.forEach(news => + document.getElementById("news").insertAdjacentHTML("beforeend", newsItemHTML(news, json.apps)) + ); + + waitForAllImagesToLoad(); + }); \ No newline at end of file diff --git a/news.html b/news.html new file mode 100644 index 0000000..d2c2903 --- /dev/null +++ b/news.html @@ -0,0 +1,24 @@ + + + + + + + + + + + +
+ loading +

Loading

+
+ +
+
+
+
+ + + + \ No newline at end of file