diff --git a/docs/app-min.js b/docs/app-min.js index 249dad7..4969367 100644 --- a/docs/app-min.js +++ b/docs/app-min.js @@ -1 +1 @@ -class TableManager{constructor(t,e,n){this.container=document.getElementById(t),this.searchInput=document.getElementById(e),this.clearIcon=document.getElementById(n),this.setupSearchFunctionality()}generateTable(t){if(!t||0===Object.keys(t).length)return void(this.container.innerHTML="

No data available.

");const e=Object.keys(t[Object.keys(t)[0]]),n=`\n \n \n \n \n ${e.map((t=>``)).join("")}\n \n \n \n ${Object.entries(t).map((([t,n])=>`\n \n \n ${e.map((t=>`\n \n `)).join("")}\n \n `)).join("")}\n \n
Service Name${t}
${t}\n ${"yes"===n[t]?"✅":"❌"}\n
\n `;this.container.innerHTML=n}setupSearchFunctionality(){this.searchInput?.addEventListener("input",(()=>{const t=this.searchInput.value.toLowerCase(),e=this.container.querySelectorAll("table tbody tr");this.clearIcon.style.display=t?"block":"none",e.forEach((e=>{const n=e.querySelector("td:first-child")?.textContent.toLowerCase();e.style.display=n?.includes(t)?"":"none"}))})),this.clearIcon?.addEventListener("click",(()=>{this.searchInput.value="",this.clearIcon.style.display="none",this.searchInput.dispatchEvent(new Event("input"))}))}}class ComparisonManager{constructor(t,e,n,a){this.container=document.getElementById(t),this.select1=document.getElementById(e),this.select2=document.getElementById(n),this.data=a,this.setupEventListeners(),this.populateDropdowns()}populateDropdowns(){const t=Object.keys(this.data[Object.keys(this.data)[0]]);[this.select1,this.select2].forEach((e=>{t.forEach((t=>{const n=document.createElement("option");n.value=t,n.textContent=t,e.appendChild(n)}))}))}generateCompareTable(){const t=this.select1.value,e=this.select2.value;if(!t||!e)return;const n=`\n \n \n \n \n \n \n \n \n \n \n ${Object.entries(this.data).map((([n,a])=>`\n \n \n \n \n \n `)).join("")}\n \n
Service Name${t}${e}
${n}${"yes"===a[t]?"✅":"❌"}${"yes"===a[e]?"✅":"❌"}
\n `;this.container.innerHTML=n,this.container.style.display="block",this.setupCloseButton()}setupEventListeners(){[this.select1,this.select2].forEach((t=>{t?.addEventListener("change",(()=>this.generateCompareTable()))}))}setupCloseButton(){document.getElementById("close-compare")?.addEventListener("click",(()=>{this.container.style.display="none"}))}}class PricingManager{constructor(t){this.container=document.getElementById(t)}generatePricingTable(t){if(!t?.plans?.length)return void(this.container.innerHTML="

Error: Invalid pricing data structure.

");const e=Object.keys(t.plans[0]).filter((t=>"name"!==t)),n=`\n \n \n \n \n ${e.map((t=>``)).join("")}\n \n \n \n ${t.plans.map((t=>`\n \n \n ${e.map((e=>``)).join("")}\n \n `)).join("")}\n \n
Plans${t}
${t.name}${t[e]}
\n `;this.container.innerHTML=n}}document.addEventListener("DOMContentLoaded",(async()=>{const t=new TableManager("file-hosts-table-container","host-search-input","clear-host-search"),e=new TableManager("adult-hosts-table-container","adult-host-search-input","clear-adult-host-search"),n=new PricingManager("pricing");try{const[a,s,i]=await Promise.all([fetch("./json/file-hosts.json").then((t=>t.json())),fetch("./json/adult-hosts.json").then((t=>t.json())),fetch("./json/pricing.json").then((t=>t.json()))]);t.generateTable(a),e.generateTable(s),n.generatePricingTable(i),new ComparisonManager("compare-table-container","provider1","provider2",a)}catch(a){const s="

Error loading data. Please try again later.

";t.container.innerHTML=s,e.container.innerHTML=s,n.container.innerHTML=s}})); \ No newline at end of file +const debounce=(e,t=300)=>{let n;return(...a)=>{clearTimeout(n),n=setTimeout((()=>e(...a)),t)}};class TableManager{constructor(e,t,n){this.container=document.querySelector(`#${e}`),this.searchInput=document.querySelector(`#${t}`),this.clearIcon=document.querySelector(`#${n}`),this._setupSearchFunctionality()}generateTable(e={}){if(!Object.keys(e).length)return void(this.container.innerHTML="

No data available.

");const t=Object.keys(e[Object.keys(e)[0]]);this.container.innerHTML=`\n \n \n \n \n ${t.map((e=>``)).join("")}\n \n \n \n ${Object.entries(e).map((([e,n])=>`\n \n \n ${t.map((e=>`\n \n `)).join("")}\n \n `)).join("")}\n \n
Service Name${e}
${e}\n ${"yes"===n[e]?"✅":"❌"}\n
\n `}_setupSearchFunctionality(){const e=debounce((()=>{const e=this.searchInput.value.toLowerCase(),t=this.container.querySelectorAll("table tbody tr");this.clearIcon.style.display=e?"block":"none",t.forEach((t=>{const n=t.querySelector("td:first-child")?.textContent.toLowerCase();t.style.display=n?.includes(e)?"":"none"}))}));this.searchInput?.addEventListener("input",e),this.clearIcon?.addEventListener("click",(()=>{this.searchInput.value="",this.clearIcon.style.display="none",e()}))}}class ComparisonManager{constructor(e,t,n,a){this.container=document.querySelector(`#${e}`),this.select1=document.querySelector(`#${t}`),this.select2=document.querySelector(`#${n}`),this.data=a,this._populateDropdowns(),this._setupEventListeners()}_populateDropdowns(){const e=Object.keys(this.data[Object.keys(this.data)[0]]);[this.select1,this.select2].forEach((t=>{e.forEach((e=>{const n=document.createElement("option");n.value=e,n.textContent=e,t.appendChild(n)}))}))}_generateCompareTable(){const e=this.select1.value,t=this.select2.value;e&&t&&(this.container.innerHTML=`\n \n \n \n \n \n \n \n \n \n \n ${Object.entries(this.data).map((([n,a])=>`\n \n \n \n \n \n `)).join("")}\n \n
Service Name${e}${t}
${n}${"yes"===a[e]?"✅":"❌"}${"yes"===a[t]?"✅":"❌"}
\n `,this.container.style.display="block",this._setupCloseButton())}_setupEventListeners(){[this.select1,this.select2].forEach((e=>{e?.addEventListener("change",(()=>this._generateCompareTable()))}))}_setupCloseButton(){this.container.querySelector("#close-compare")?.addEventListener("click",(()=>{this.container.style.display="none"}))}}class PricingManager{constructor(e){this.container=document.querySelector(`#${e}-table-container`)}generatePricingTable(e={}){if(!e.plans?.length)return void(this.container.innerHTML="

Error: Invalid pricing data structure.

");const t=Object.keys(e.plans[0]).filter((e=>"name"!==e));this.container.innerHTML=`\n \n \n \n \n ${t.map((e=>``)).join("")}\n \n \n \n ${e.plans.map((e=>`\n \n \n ${t.map((t=>`\n \n `)).join("")}\n \n `)).join("")}\n \n
Plans${e}
${e.name}${e[t]}
\n `}}document.addEventListener("DOMContentLoaded",(async()=>{const e=new TableManager("file-hosts-table-container","host-search-input","clear-host-search"),t=new TableManager("adult-hosts-table-container","adult-host-search-input","clear-adult-host-search"),n=new PricingManager("pricing");try{const[a,r,o]=await Promise.all([fetch("./json/file-hosts.json").then((e=>e.json())),fetch("./json/adult-hosts.json").then((e=>e.json())),fetch("./json/pricing.json").then((e=>e.json()))]);e.generateTable(a),t.generateTable(r),n.generatePricingTable(o),new ComparisonManager("compare-table-container","provider1","provider2",a)}catch(a){const r="

Error loading data. Please try again later.

";e.container.innerHTML=r,t.container.innerHTML=r,n.container.innerHTML=r}})),document.addEventListener("DOMContentLoaded",(()=>{const e=document.getElementById("darkModeToggle"),t=localStorage.getItem("theme");t&&document.documentElement.setAttribute("data-theme",t),e.addEventListener("click",(()=>{const e="dark"===document.documentElement.getAttribute("data-theme")?"light":"dark";document.documentElement.setAttribute("data-theme",e),localStorage.setItem("theme",e)}))})); \ No newline at end of file diff --git a/docs/app.js b/docs/app.js index c93efcd..282d536 100644 --- a/docs/app.js +++ b/docs/app.js @@ -3,261 +3,225 @@ * Handles data fetching, table generation, search functionality, and comparison features */ -// Class to handle table-related operations +// Utility to debounce frequent events +const debounce = (func, delay = 300) => { + let timeout; + return (...args) => { + clearTimeout(timeout); + timeout = setTimeout(() => func(...args), delay); + }; +}; + +// Class to manage table operations class TableManager { - /** - * @param {string} containerId - ID of the table container element - * @param {string} searchInputId - ID of the search input element - * @param {string} clearIconId - ID of the clear search icon element - */ - constructor(containerId, searchInputId, clearIconId) { - this.container = document.getElementById(containerId); - this.searchInput = document.getElementById(searchInputId); - this.clearIcon = document.getElementById(clearIconId); - this.setupSearchFunctionality(); + constructor(containerId, searchInputId, clearIconId) { + this.container = document.querySelector(`#${containerId}`); + this.searchInput = document.querySelector(`#${searchInputId}`); + this.clearIcon = document.querySelector(`#${clearIconId}`); + this._setupSearchFunctionality(); + } + + generateTable(data = {}) { + if (!Object.keys(data).length) { + this.container.innerHTML = '

No data available.

'; + return; } - - /** - * Generates a table from provided data - * @param {Object} data - Table data object - * @returns {void} - */ - generateTable(data) { - if (!data || Object.keys(data).length === 0) { - this.container.innerHTML = '

No data available.

'; - return; - } - - const providers = Object.keys(data[Object.keys(data)[0]]); - const tableHTML = ` - - + + const providers = Object.keys(data[Object.keys(data)[0]]); + this.container.innerHTML = ` +
+ + + + ${providers.map(provider => ``).join('')} + + + + ${Object.entries(data).map(([host, providerData]) => ` - - ${providers.map(provider => ``).join('')} + + ${providers.map(provider => ` + + `).join('')} - - - ${Object.entries(data).map(([host, providerData]) => ` - - - ${providers.map(provider => ` - - `).join('')} - - `).join('')} - -
Service Name${provider}
Service Name${provider}${host} + ${providerData[provider] === 'yes' ? '✅' : '❌'} +
${host} - ${providerData[provider] === 'yes' ? '✅' : '❌'} -
- `; - - this.container.innerHTML = tableHTML; - } - - /** - * Sets up search functionality for the table - * @private - */ - setupSearchFunctionality() { - this.searchInput?.addEventListener('input', () => { - const searchTerm = this.searchInput.value.toLowerCase(); - const rows = this.container.querySelectorAll('table tbody tr'); - - this.clearIcon.style.display = searchTerm ? 'block' : 'none'; - - rows.forEach(row => { - const serviceName = row.querySelector('td:first-child')?.textContent.toLowerCase(); - row.style.display = serviceName?.includes(searchTerm) ? '' : 'none'; - }); + `).join('')} + + + `; + } + + _setupSearchFunctionality() { + const handleSearch = debounce(() => { + const searchTerm = this.searchInput.value.toLowerCase(); + const rows = this.container.querySelectorAll('table tbody tr'); + this.clearIcon.style.display = searchTerm ? 'block' : 'none'; + + rows.forEach(row => { + const serviceName = row.querySelector('td:first-child')?.textContent.toLowerCase(); + row.style.display = serviceName?.includes(searchTerm) ? '' : 'none'; }); - - this.clearIcon?.addEventListener('click', () => { - this.searchInput.value = ''; - this.clearIcon.style.display = 'none'; - this.searchInput.dispatchEvent(new Event('input')); + }); + + this.searchInput?.addEventListener('input', handleSearch); + this.clearIcon?.addEventListener('click', () => { + this.searchInput.value = ''; + this.clearIcon.style.display = 'none'; + handleSearch(); + }); + } +} + +// Class to manage comparison operations +class ComparisonManager { + constructor(containerId, select1Id, select2Id, data) { + this.container = document.querySelector(`#${containerId}`); + this.select1 = document.querySelector(`#${select1Id}`); + this.select2 = document.querySelector(`#${select2Id}`); + this.data = data; + + this._populateDropdowns(); + this._setupEventListeners(); + } + + _populateDropdowns() { + const providers = Object.keys(this.data[Object.keys(this.data)[0]]); + [this.select1, this.select2].forEach(select => { + providers.forEach(provider => { + const option = document.createElement('option'); + option.value = provider; + option.textContent = provider; + select.appendChild(option); }); + }); + } + + _generateCompareTable() { + const provider1 = this.select1.value; + const provider2 = this.select2.value; + + if (!provider1 || !provider2) return; + + this.container.innerHTML = ` + + + + + + + + + + + ${Object.entries(this.data).map(([host, providerData]) => ` + + + + + + `).join('')} + +
Service Name${provider1}${provider2}
${host}${providerData[provider1] === 'yes' ? '✅' : '❌'}${providerData[provider2] === 'yes' ? '✅' : '❌'}
+ `; + this.container.style.display = 'block'; + this._setupCloseButton(); + } + + _setupEventListeners() { + [this.select1, this.select2].forEach(select => { + select?.addEventListener('change', () => this._generateCompareTable()); + }); + } + + _setupCloseButton() { + this.container.querySelector('#close-compare')?.addEventListener('click', () => { + this.container.style.display = 'none'; + }); + } +} + +// Class to manage pricing operations +class PricingManager { + constructor(containerId) { + this.container = document.querySelector(`#${containerId}-table-container`); + } + + generatePricingTable(data = {}) { + if (!data.plans?.length) { + this.container.innerHTML = '

Error: Invalid pricing data structure.

'; + return; } + + const services = Object.keys(data.plans[0]).filter(key => key !== 'name'); + this.container.innerHTML = ` + + + + + ${services.map(service => ``).join('')} + + + + ${data.plans.map(plan => ` + + + ${services.map(service => ` + + `).join('')} + + `).join('')} + +
Plans${service}
${plan.name}${plan[service]}
+ `; + } +} + +// Main application initialization +document.addEventListener('DOMContentLoaded', async () => { + const fileHostsTable = new TableManager('file-hosts-table-container', 'host-search-input', 'clear-host-search'); + const adultHostsTable = new TableManager('adult-hosts-table-container', 'adult-host-search-input', 'clear-adult-host-search'); + const pricingManager = new PricingManager('pricing'); + + try { + const [fileHostsData, adultHostsData, pricingData] = await Promise.all([ + fetch('./json/file-hosts.json').then(res => res.json()), + fetch('./json/adult-hosts.json').then(res => res.json()), + fetch('./json/pricing.json').then(res => res.json()) + ]); + + fileHostsTable.generateTable(fileHostsData); + adultHostsTable.generateTable(adultHostsData); + pricingManager.generatePricingTable(pricingData); + + new ComparisonManager('compare-table-container', 'provider1', 'provider2', fileHostsData); + } catch (error) { + console.error('Error initializing application:', error); + const errorMessage = '

Error loading data. Please try again later.

'; + fileHostsTable.container.innerHTML = errorMessage; + adultHostsTable.container.innerHTML = errorMessage; + pricingManager.container.innerHTML = errorMessage; + } +}); +document.addEventListener('DOMContentLoaded', () => { + const darkModeToggle = document.getElementById('darkModeToggle'); + + // Check for saved theme preference + const savedTheme = localStorage.getItem('theme'); + if (savedTheme) { + document.documentElement.setAttribute('data-theme', savedTheme); } - // Class to handle comparison functionality - class ComparisonManager { - /** - * @param {string} containerId - ID of the comparison container - * @param {string} select1Id - ID of the first provider select element - * @param {string} select2Id - ID of the second provider select element - * @param {Object} data - Comparison data object - */ - constructor(containerId, select1Id, select2Id, data) { - this.container = document.getElementById(containerId); - this.select1 = document.getElementById(select1Id); - this.select2 = document.getElementById(select2Id); - this.data = data; - this.setupEventListeners(); - this.populateDropdowns(); - } - - /** - * Populates provider dropdowns with available options - * @private - */ - populateDropdowns() { - const providers = Object.keys(this.data[Object.keys(this.data)[0]]); + darkModeToggle.addEventListener('click', () => { + const currentTheme = document.documentElement.getAttribute('data-theme'); + const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; - [this.select1, this.select2].forEach(select => { - providers.forEach(provider => { - const option = document.createElement('option'); - option.value = provider; - option.textContent = provider; - select.appendChild(option); - }); - }); - } - - /** - * Generates comparison table for selected providers - * @private - */ - generateCompareTable() { - const provider1 = this.select1.value; - const provider2 = this.select2.value; - - if (!provider1 || !provider2) return; - - const tableHTML = ` - - - - - - - - - - - ${Object.entries(this.data).map(([host, providerData]) => ` - - - - - - `).join('')} - -
Service Name${provider1}${provider2}
${host}${providerData[provider1] === 'yes' ? '✅' : '❌'}${providerData[provider2] === 'yes' ? '✅' : '❌'}
- `; - - this.container.innerHTML = tableHTML; - this.container.style.display = 'block'; - this.setupCloseButton(); - } - - /** - * Sets up event listeners for comparison functionality - * @private - */ - setupEventListeners() { - [this.select1, this.select2].forEach(select => { - select?.addEventListener('change', () => this.generateCompareTable()); - }); - } - - /** - * Sets up close button functionality - * @private - */ - setupCloseButton() { - document.getElementById('close-compare')?.addEventListener('click', () => { - this.container.style.display = 'none'; - }); - } - } - - // Class to handle pricing table generation - class PricingManager { - /** - * @param {string} containerId - ID of the pricing section container - */ - constructor(containerId) { - this.container = document.getElementById(containerId); - } - - /** - * Generates pricing table from provided data - * @param {Object} data - Pricing data object - */ - generatePricingTable(data) { - if (!data?.plans?.length) { - this.container.innerHTML = '

Error: Invalid pricing data structure.

'; - return; - } - - const services = Object.keys(data.plans[0]).filter(key => key !== 'name'); - const tableHTML = ` - - - - - ${services.map(service => ``).join('')} - - - - ${data.plans.map(plan => ` - - - ${services.map(service => ``).join('')} - - `).join('')} - -
Plans${service}
${plan.name}${plan[service]}
- `; - - this.container.innerHTML = tableHTML; - } - } - - // Main application initialization - document.addEventListener('DOMContentLoaded', async () => { - // Initialize table managers - const fileHostsTable = new TableManager( - 'file-hosts-table-container', - 'host-search-input', - 'clear-host-search' - ); - - const adultHostsTable = new TableManager( - 'adult-hosts-table-container', - 'adult-host-search-input', - 'clear-adult-host-search' - ); - - const pricingManager = new PricingManager('pricing'); - - try { - // Fetch all required data concurrently - const [fileHostsData, adultHostsData, pricingData] = await Promise.all([ - fetch('./json/file-hosts.json').then(res => res.json()), - fetch('./json/adult-hosts.json').then(res => res.json()), - fetch('./json/pricing.json').then(res => res.json()) - ]); - - // Generate tables and initialize comparison - fileHostsTable.generateTable(fileHostsData); - adultHostsTable.generateTable(adultHostsData); - pricingManager.generatePricingTable(pricingData); - - // Initialize comparison manager - new ComparisonManager( - 'compare-table-container', - 'provider1', - 'provider2', - fileHostsData - ); - - } catch (error) { - console.error('Error initializing application:', error); - const errorMessage = '

Error loading data. Please try again later.

'; - fileHostsTable.container.innerHTML = errorMessage; - adultHostsTable.container.innerHTML = errorMessage; - pricingManager.container.innerHTML = errorMessage; - } - }); \ No newline at end of file + document.documentElement.setAttribute('data-theme', newTheme); + localStorage.setItem('theme', newTheme); + }); +}); \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index bc3d2de..abebc70 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,11 +4,11 @@ - - - + + + Debrid Services Comparison - + -
-

Debrid Services Comparison

- + -
-

A comparison of available hosts and pricing for AllDebrid, Real-Debrid, LinkSnappy, - Premiumize, Debrid-Link, TorBox and Mega-Debrid. -

-

Contents:

-
-