diff --git a/.gitignore b/.gitignore index a77fa96..8e8ab40 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ cover.png .vs/tranga-website/FileContentIndex/91a465d3-1190-42e0-95eb-fa3694744e58.vsidx .vs/tranga-website/v17/.wsuo .vs/VSWorkspaceState.json +/node_modules/ +/.vite/ diff --git a/README.md b/README.md index fed1b32..3df7b89 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,9 @@ This repo makes HTTP-requests to the [Tranga-API](https://github.com/C9Glax/tran ### Built With - nginx -- HTML, CSS, and barebones Javascript +- vite +- react +- typescript - 💙 Blåhaj 🦈 <p align="right">(<a href="#readme-top">back to top</a>)</p> @@ -73,16 +75,6 @@ There is a single release: Download [docker-compose.yaml](https://github.com/C9Glax/tranga-website/blob/cuttingedge/docker-compose.yaml) and configure to your needs. The `docker-compose` also includes [Tranga](https://github.com/C9Glax/tranga) as backend. For its configuration refer to the repo README. -<!-- ROADMAP --> -## Roadmap - -- [ ] ❓ - -See the [open issues](https://github.com/C9Glax/tranga-website/issues) for a full list of proposed features (and known issues). - -<p align="right">(<a href="#readme-top">back to top</a>)</p> - - <!-- CONTRIBUTING --> ## Contributing diff --git a/Screenshots/Screenshot 2023-09-08 at 20-03-13 Tranga.png b/Screenshots/Screenshot 2023-09-08 at 20-03-13 Tranga.png deleted file mode 100644 index c30b3a8..0000000 Binary files a/Screenshots/Screenshot 2023-09-08 at 20-03-13 Tranga.png and /dev/null differ diff --git a/Screenshots/Screenshot 2023-09-08 at 20-03-37 Tranga.png b/Screenshots/Screenshot 2023-09-08 at 20-03-37 Tranga.png deleted file mode 100644 index 670743c..0000000 Binary files a/Screenshots/Screenshot 2023-09-08 at 20-03-37 Tranga.png and /dev/null differ diff --git a/Screenshots/Screenshot 2023-09-08 at 20-03-45 Tranga.png b/Screenshots/Screenshot 2023-09-08 at 20-03-45 Tranga.png deleted file mode 100644 index 2f38769..0000000 Binary files a/Screenshots/Screenshot 2023-09-08 at 20-03-45 Tranga.png and /dev/null differ diff --git a/Screenshots/Screenshot 2023-09-08 at 20-03-58 Tranga.png b/Screenshots/Screenshot 2023-09-08 at 20-03-58 Tranga.png deleted file mode 100644 index a287f41..0000000 Binary files a/Screenshots/Screenshot 2023-09-08 at 20-03-58 Tranga.png and /dev/null differ diff --git a/Screenshots/Screenshot 2023-09-08 at 20-04-41 Tranga.png b/Screenshots/Screenshot 2023-09-08 at 20-04-41 Tranga.png deleted file mode 100644 index 68c191f..0000000 Binary files a/Screenshots/Screenshot 2023-09-08 at 20-04-41 Tranga.png and /dev/null differ diff --git a/Website/App.tsx b/Website/App.tsx new file mode 100644 index 0000000..1788095 --- /dev/null +++ b/Website/App.tsx @@ -0,0 +1,132 @@ +import React, {useEffect, useState} from 'react'; +import Footer from "./modules/Footer"; +import Search from "./modules/Search"; +import Header from "./modules/Header"; +import MonitorJobsList from "./modules/MonitorJobsList"; +import './styles/index.css' +import IFrontendSettings, {LoadFrontendSettings} from "./modules/interfaces/IFrontendSettings"; +import {useCookies} from "react-cookie"; + +export default function App(){ + const [, setCookie] = useCookies(['apiUri', 'jobInterval']); + const [connected, setConnected] = React.useState(false); + const [showSearch, setShowSearch] = React.useState(false); + const [frontendSettings, setFrontendSettings] = useState<IFrontendSettings>(LoadFrontendSettings()); + const [updateInterval, setUpdateInterval] = React.useState<number>(); + const [updateMonitorList, setUpdateMonitorList] = React.useState<Date>(new Date()); + + const apiUri = frontendSettings.apiUri; + + useEffect(() => { + checkConnection(apiUri).then(res => setConnected(res)).catch(() => setConnected(false)); + if(updateInterval === undefined){ + setUpdateInterval(setInterval(() => { + checkConnection(apiUri).then(res => setConnected(res)).catch(() => setConnected(false)); + }, 500)); + }else{ + clearInterval(updateInterval); + setUpdateInterval(undefined); + } + }, [frontendSettings]); + + function ChangeSettings(settings: IFrontendSettings) { + setFrontendSettings(settings); + setCookie('apiUri', settings.apiUri); + setCookie('jobInterval', settings.jobInterval); + } + + const UpdateList = () => {setUpdateMonitorList(new Date())} + + return(<div> + <Header apiUri={apiUri} backendConnected={connected} settings={frontendSettings} changeSettings={ChangeSettings}/> + {connected + ? <> + {showSearch + ? <> + <Search apiUri={apiUri} jobInterval={frontendSettings.jobInterval} onJobsChanged={UpdateList} closeSearch={() => setShowSearch(false)} /> + <hr/> + </> + : <></>} + <MonitorJobsList updateList={updateMonitorList} apiUri={apiUri} onStartSearch={() => setShowSearch(true)} onJobsChanged={UpdateList} connectedToBackend={connected} /> + </> + : <> + <h1>No connection to the Backend.</h1> + <h3>Check the Settings ApiUri.</h3> + </>} + <Footer apiUri={apiUri} connectedToBackend={connected} /> + </div>) +} + +export function getData(uri: string) : Promise<object> { + return fetch(uri, + { + method: 'GET', + headers : { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }) + .then(function(response){ + if(!response.ok) throw new Error("Could not fetch data"); + return response.json(); + }) + .catch(function(err){ + console.error(`Error GETting Data ${uri}\n${err}`); + return Promise.reject(); + }); +} + +export function postData(uri: string, content: object) : Promise<object> { + return fetch(uri, + { + method: 'POST', + headers : { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify(content) + }) + .then(function(response){ + if(!response.ok) + throw new Error("Could not fetch data"); + let json = response.json(); + return json.then((json) => json).catch(() => null); + }) + .catch(function(err){ + console.error(`Error POSTing Data ${uri}\n${err}`); + return Promise.reject(); + }); +} + +export function deleteData(uri: string) : Promise<void> { + return fetch(uri, + { + method: 'DELETE', + headers : { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }) + .then(() =>{ + return Promise.resolve(); + }) + .catch(function(err){ + console.error(`Error DELETEing Data ${uri}\n${err}`); + return Promise.reject(); + }); +} + +export function isValidUri(uri: string) : boolean{ + try { + new URL(uri); + return true; + } catch (err) { + return false; + } +} + +export const checkConnection = async (apiUri: string): Promise<boolean> =>{ + return getData(`${apiUri}/v2/Ping`).then((result) => { + return result != null; + }).catch(() => Promise.reject()); +} \ No newline at end of file diff --git a/Website/apiConnector.js b/Website/apiConnector.js deleted file mode 100644 index 64dca93..0000000 --- a/Website/apiConnector.js +++ /dev/null @@ -1,346 +0,0 @@ -let apiUri = `${window.location.protocol}//${window.location.host.split(':')[0]}:6531` - -if(getCookie("apiUri") != ""){ - apiUri = getCookie("apiUri"); -} -setCookie("apiUri", apiUri); - -function setCookie(cname, cvalue) { - const d = new Date(); - d.setTime(d.getTime() + (365*24*60*60*1000)); - let expires = "expires="+ d.toUTCString(); - document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;samesite=strict"; -} - -function getCookie(cname) { - let name = cname + "="; - let decodedCookie = decodeURIComponent(document.cookie); - let ca = decodedCookie.split(';'); - for(let i = 0; i < ca.length; i++) { - let c = ca[i]; - while (c.charAt(0) == ' ') { - c = c.substring(1); - } - if (c.indexOf(name) == 0) { - return c.substring(name.length, c.length); - } - } - return ""; -} - -async function GetData(uri){ - let request = await fetch(uri, { - method: 'GET', - headers: { - 'Accept': 'application/json' - } - }); - let json = await request.json(); - return json; -} - -async function PostData(uri){ - let request = await fetch(uri, { - method: 'POST' - }); - //console.log(request); -} - -function DeleteData(uri){ - fetch(uri, { - method: 'DELETE' - }); -} - -async function Ping(){ - let ret = await GetData(`${apiUri}/Ping`); - return ret; -} - -async function GetAvailableControllers(){ - var uri = apiUri + "/Connectors"; - let json = await GetData(uri); - return json; -} - -async function GetPublicationFromConnector(connector, title){ - var uri; - if(title.includes("http")){ - uri = `${apiUri}/Manga/FromConnector?connector=${connector}&url=${title}`; - }else{ - uri = `${apiUri}/Manga/FromConnector?connector=${connector}&title=${title}`; - } - let json = await GetData(uri); - return json; -} - -async function GetChapters(connector, internalId, language){ - var uri = `${apiUri}/Manga/Chapters?connector=${connector}&internalId=${internalId}&translatedLanguage=${language}`; - let json = await GetData(uri); - return json; -} - -function GetCoverUrl(internalId){ - return `${apiUri}/Manga/Cover?internalId=${internalId}`; -} - -async function GetAllJobs(){ - var uri = `${apiUri}/Jobs`; - let json = await GetData(uri); - return json; -} - -async function GetRunningJobs(){ - var uri = `${apiUri}/Jobs/Running`; - let json = await GetData(uri); - return json; -} - -async function GetWaitingJobs(){ - var uri = `${apiUri}/Jobs/Waiting`; - let json = await GetData(uri); - return json; -} - -async function GetMonitorJobs(){ - var uri = `${apiUri}/Jobs/MonitorJobs`; - let json = await GetData(uri); - return json; -} - -async function GetProgress(jobId){ - var uri = `${apiUri}/Jobs/Progress?jobId=${jobId}`; - let json = await GetData(uri); - return json; -} - -async function GetSettings(){ - var uri = `${apiUri}/Settings`; - let json = await GetData(uri); - return json; -} - -async function GetAvailableNotificationConnectors(){ - var uri = `${apiUri}/NotificationConnectors/Types`; - let json = await GetData(uri); - return json; -} - -async function GetNotificationConnectors(){ - var uri = `${apiUri}/NotificationConnectors`; - let json = await GetData(uri); - return json; -} - -async function GetAvailableLibraryConnectors(){ - var uri = `${apiUri}/LibraryConnectors/Types`; - let json = await GetData(uri); - return json; -} - -async function GetLibraryConnectors(){ - var uri = `${apiUri}/LibraryConnectors`; - let json = await GetData(uri); - return json; -} - -async function GetRateLimits() { - var uri = `${apiUri}/Settings/customRequestLimit` - let json = await GetData(uri); - return json; -} - -function CreateMonitorJob(connector, internalId, language){ - var uri = `${apiUri}/Jobs/MonitorManga?connector=${connector}&internalId=${internalId}&interval=03:00:00&translatedLanguage=${language}`; - PostData(uri); -} - -function CreateDownloadNewChaptersJob(connector, internalId, language){ - var uri = `${apiUri}/Jobs/DownloadNewChapters?connector=${connector}&internalId=${internalId}&translatedLanguage=${language}`; - PostData(uri); -} - -function StartJob(jobId){ - var uri = `${apiUri}/Jobs/StartNow?jobId=${jobId}`; - PostData(uri); -} - -function UpdateDownloadLocation(downloadLocation){ - var uri = `${apiUri}/Settings/UpdateDownloadLocation?downloadLocation=${downloadLocation}`; - PostData(uri); -} - -function RefreshLibraryMetadata() { - var uri = `${apiUri}/Jobs/UpdateMetadata`; - PostData(uri); -} - -async function DownloadLogs() { - var uri = `${apiUri}/LogFile`; - - //Below taken from https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream - fetch(uri) - .then((response) => response.body) - .then((rb) => { - const reader = rb.getReader(); - - return new ReadableStream({ - start(controller) { - // The following function handles each data chunk - function push() { - // "done" is a Boolean and value a "Uint8Array" - reader.read().then(({ done, value }) => { - // If there is no more data to read - if (done) { - console.log("done", done); - controller.close(); - return; - } - // Get the data and send it to the browser via the controller - controller.enqueue(value); - // Check chunks by logging to the console - console.log(done, value); - push(); - }); - } - - push(); - }, - }); - }) - .then((stream) => - // Respond with our stream - new Response(stream, { headers: { "Content-Type": "text/html" } }).text(), - ) - .then((result) => { - // Do things with result - //console.log(result); - - //Below download taken from https://stackoverflow.com/questions/3665115/how-to-create-a-file-in-memory-for-user-to-download-but-not-through-server - var element = document.createElement('a'); - element.setAttribute('href', 'data:text/plain;charset-utf-8,' + encodeURIComponent(result)); - var newDate = new Date(); - var filename = "Tranga_Logs_" + newDate.today() + "_" + newDate.timeNow() + ".log"; - element.setAttribute('download', filename); - element.click(); - }); -} - -//Following date-time code taken from: https://stackoverflow.com/questions/10211145/getting-current-date-and-time-in-javascript -// For todays date; -Date.prototype.today = function () { - return ((this.getDate() < 10)?"0":"") + this.getDate() +"/"+(((this.getMonth()+1) < 10)?"0":"") + (this.getMonth()+1) +"/"+ this.getFullYear(); -} - -// For the time now -Date.prototype.timeNow = function () { - return ((this.getHours() < 10)?"0":"") + this.getHours() +"_"+ ((this.getMinutes() < 10)?"0":"") + this.getMinutes() +"_"+ ((this.getSeconds() < 10)?"0":"") + this.getSeconds(); -} - -//Komga -function UpdateKomga(komgaUrl, komgaAuth){ - var uri = `${apiUri}/LibraryConnectors/Update?libraryConnector=Komga&komgaUrl=${komgaUrl}&komgaAuth=${komgaAuth}`; - PostData(uri); -} - -function ResetKomga(){ - var uri = `${apiUri}/LibraryConnectors/Reset?libraryConnector=Komga`; - PostData(uri); -} - -function TestKomga(komgaUrl, komgaAuth){ - var uri = `${apiUri}/LibraryConnectors/Test?libraryConnector=Komga&komgaUrl=${komgaUrl}&komgaAuth=${komgaAuth}`; - PostData(uri); -} - - -//Kavita -function UpdateKavita(kavitaUrl, kavitaUsername, kavitaPassword){ - var uri = `${apiUri}/LibraryConnectors/Update?libraryConnector=Kavita&kavitaUrl=${kavitaUrl}&kavitaUsername=${kavitaUsername}&kavitaPassword=${kavitaPassword}`; - PostData(uri); -} - -function ResetKavita(){ - var uri = `${apiUri}/LibraryConnectors/Reset?libraryConnector=Kavita`; - PostData(uri); -} - -function TestKavita(kavitaUrl, kavitaUsername, kavitaPassword){ - var uri = `${apiUri}/LibraryConnectors/Test?libraryConnector=Kavita&kavitaUrl=${kavitaUrl}&kavitaUsername=${kavitaUsername}&kavitaPassword=${kavitaPassword}`; - PostData(uri); -} - -//Gotify -function UpdateGotify(gotifyUrl, gotifyAppToken){ - var uri = `${apiUri}/NotificationConnectors/Update?notificationConnector=Gotify&gotifyUrl=${gotifyUrl}&gotifyAppToken=${gotifyAppToken}`; - PostData(uri); -} - -function ResetGotify(){ - var uri = `${apiUri}/NotificationConnectors/Reset?notificationConnector=Gotify`; - PostData(uri); -} - -function TestGotify(gotifyUrl, gotifyAppToken){ - var uri = `${apiUri}/NotificationConnectors/Test?notificationConnector=Gotify&gotifyUrl=${gotifyUrl}&gotifyAppToken=${gotifyAppToken}`; - PostData(uri); -} - -//LunaSea -function UpdateLunaSea(lunaseaWebhook){ - var uri = `${apiUri}/NotificationConnectors/Update?notificationConnector=LunaSea&lunaseaWebhook=${lunaseaWebhook}`; - PostData(uri); -} - -function ResetLunaSea(){ - var uri = `${apiUri}/NotificationConnectors/Reset?notificationConnector=LunaSea`; - PostData(uri); -} - -function TestLunaSea(lunaseaWebhook){ - var uri = `${apiUri}/NotificationConnectors/Test?notificationConnector=LunaSea&lunaseaWebhook=${lunaseaWebhook}`; - PostData(uri); -} - -//Ntfy -function UpdateNtfy(ntfyEndpoint, ntfyAuth){ - var uri = `${apiUri}/NotificationConnectors/Update?notificationConnector=Ntfy&ntfyUrl=${ntfyEndpoint}&ntfyAuth=${ntfyAuth}`; - PostData(uri); -} - -function ResetNtfy(){ - var uri = `${apiUri}/NotificationConnectors/Reset?notificationConnector=Ntfy`; - PostData(uri); -} - -function TestNtfy(ntfyEndpoint, ntfyAuth){ - var uri = `${apiUri}/NotificationConnectors/Test?notificationConnector=Ntfy&ntfyUrl=${ntfyEndpoint}&ntfyAuth=${ntfyAuth}`; - PostData(uri); -} - -function UpdateUserAgent(userAgent){ - var uri = `${apiUri}/Settings/userAgent?userAgent=${userAgent}`; - PostData(uri); -} - -function UpdateRateLimit(byteValue, rateLimit) { - var uri = `${apiUri}/Settings/customRequestLimit?requestType=${byteValue}&requestsPerMinute=${rateLimit}`; - PostData(uri); -} - -function RemoveJob(jobId){ - var uri = `${apiUri}/Jobs?jobId=${jobId}`; - DeleteData(uri); -} - -function CancelJob(jobId){ - var uri = `${apiUri}/Jobs/Cancel?jobId=${jobId}`; - PostData(uri); -} - -async function GetLogmessages(count){ - var uri = `${apiUri}/LogMessages?count=${count}`; - let json = await GetData(uri); - console.log(json); - return json; -} \ No newline at end of file diff --git a/Website/index.html b/Website/index.html index 1bb8c4c..d0145cc 100644 --- a/Website/index.html +++ b/Website/index.html @@ -1,301 +1,13 @@ -<!DOCTYPE html> +<!DOCTYPE html> <html lang="en"> <head> - <meta charset="UTF-8"> - <title>Tranga</title> - <link id='basestyle' rel="stylesheet" href="styles/base.css"> - <link id='librarystyle' rel="stylesheet" href="styles/style_default.css"> - <link rel="icon" type="image/x-icon" href="favicon.ico"> + <meta charset="UTF-8"> + <title>Tranga</title> + <link rel="icon" type="image/x-icon" href="favicon.ico"> + <link rel="stylesheet" href="styles/index.css"> </head> <body> - <wrapper> - - <topbar> - <titlebox> - <img alt="website image is Blahaj" src="media/blahaj.png"> - <span>Tranga</span> - </titlebox> - <spacer></spacer> - <img id="filterFunnel" src="media/filter-funnel.svg" height="50%" alt="filterFunnel"> - <img id="settingscog" src="media/settings-cogwheel.svg" height="100%" alt="settingscog"> - </topbar> - - <filter-box id="filterBox"> - <border-bar> - <popup-title>Filter by: </popup-title> - <popup-close onclick="filterBox.classList.toggle('animate')" >×</popup-close> - </border-bar> - <popup-content id="filterContent"> - <div class="popup-section"> - NAME: - <div class="section-content"> - <label for="searchbox"></label><input id="searchbox" placeholder="Title" type="text"> - </div> - </div> - <div class = "popup-section"> - CONNECTOR: - <div class="section-content" id="connectorFilterBox"> - </div> - </div> - <div class = "popup-section"> - STATUS: - <div class="section-content" id="statusFilterBox"> - </div> - </div> - </popup-content> - <border-bar-button onclick="ClearFilter()" class="clearFilter">Clear Filter</border-bar-button> - </filter-box> - - - <viewport> - <div id="loaderdiv"> - <blur-background></blur-background> - <div id="loader"></div> - <p id="loaderText">Check your Settings > API-URI</p> - </div> - <content> - <div id="addPublication"> - <p>+</p> - </div> - <publication onclick="ShowNewMangaSearch()"> - <img alt="cover" src="media/cover.jpg"> - <publication-information> - <connector-name class="pill">Sample</connector-name> - <publication-name>Best Manga there is</publication-name> - </publication-information> - </publication> - </content> - - <popup id="newMangaPopup"> - <blur-background id="blurBackgroundNewMangaPopup" onclick="newMangaPopup.style.display = 'none';"></blur-background> - <div id="newMangaPopupSelector"> - <select id="newMangaConnector" /> - <input type="text" placeholder="Title" id="newMangaTitle" /> - <select id="newMangaTranslatedLanguage"> - <option selected="selected">en</option> - <option>it</option> - <option>de</option> - </select> - </div> - <div id="newMangaResult"></div> - </popup> - - <popup id="settingsPopup"> - <blur-background id="blurBackgroundSettingsPopup" onclick="settingsPopup.style.display = 'none';"></blur-background> - <popup-window> - <border-bar> - <popup-title>Settings</popup-title> - <popup-close onclick="settingsPopup.style.display = 'none'">×</popup-close> - </border-bar> - <popup-content> - - <div class="popup-section"> - TRANGA - <div class="section-content"> - <div class="section-item dyn-height"> - <span class="title">API Settings</span> - <row><label for="settingApiUri">API URI:</label><input placeholder="https://" type="text" id="settingApiUri"></row> - <row><label for="userAgent">User Agent:</label><input placeholder="UserAgent" id="userAgent" type="text"></row> - <row> - <border-bar-button class="section" onclick="RefreshLibraryMetadata()">Refresh Library Metadata</border-bar-button> - <border-bar-button class="section" onclick="DownloadLogs()">Download Logs</border-bar-button> - </row> - </div> - <div class="section-item dyn-height"> - <span class="title">Rate Limits</span> - <row><label for="DefaultRL">Default:</label><input id="defaultRL" type="text" ></row> - <row><label for="CoverRL">Manga Covers:</label><input id="coverRL" type="text"></row> - <row><label for="ImageRL">Manga Images:</label><input id="imageRL" type="text"></row> - <row><label for="InfoRL">Manga Info:</label><input id="infoRL" type="text"></row> - </div> - <div class="section-item dyn-height"> - <span class="title">Appearance</span> - <row><label for="cssStyle">Library Style:</label><select id="cssStyle"> - <option id="card_compact" value="card_compact">Cards (Compact)</option> - <option id="card_hover" value="card_hover">Cards (Hover)</option> - </select></row> - </div> - </div> - </div> - - <div class="popup-section"> - MANGA SOURCES - <div class="section-content"> - <!-- <div class="section-item dyn-height"> - <span class="title"><img src="connector-icons/manganato.png"><a href="https://manganato.com">MangaNato</a></span> - - </div> --> - <!-- <div class="section-item dyn-height"> - <span class="title"><img src="connector-icons/mangasee.png"><a href="https://mangasee123.com">MangaSee</a></span> - - </div> --> - <div class="section-item dyn-height"> - <span class="title"><img src="connector-icons/mangadex-logo.svg"><a href="https://mangadex.org">MangaDex</a></span> - <row><label for="mDexAuthorRL">Author Rate Limit:</label><input id="mDexAuthorRL" type="text"></row> - <row><label for="mDexFeedRL">Feed Rate Limit:</label><input id="mDexFeedRL" type="text"></row> - <row><label for="mDexImageRL">Image Rate Limit:</label><input id="mDexImageRL" type="text"></row> - </div> - <!-- <div class="section-item dyn-height"> - <span class="title"><img src="connector-icons/mangakatana.png"><a href="https://mangakatana.com">MangaKatana</a></span> - - </div> --> - <!-- <div class="section-item dyn-height"> - <span class="title"><img src="connector-icons/mangaworld.png"><a href="https://www.mangaworld.ac">MangaWorld</a></span> - - </div> --> - <!-- <div class="section-item dyn-height"> - <span class="title"><img src="connector-icons/bato.ico"><a href="https://bato.to">Bato</a></span> - - </div> --> - <!-- <div class="section-item dyn-height"> - <span class="title"><img src="connector-icons/mangalife.png"><a href="https://www.manga4life.com">MangaLife</a></span> - - </div> --> - </div> - - </div> - - <div class="popup-section"> - LIBRARY CONNECTORS - <div class="section-content"> - <div class="section-item"> - <span class="title"><img src='connector-icons/komga.svg'>Komga<connector-configured id="komgaConfigured"></connector-configured></span> - <label for="komgaUrl"></label><input placeholder="URL" id="komgaUrl" type="text"> - <label for="komgaUsername"></label><input placeholder="Username" id="komgaUsername" type="text"> - <label for="komgaPassword"></label><input placeholder="Password" id="komgaPassword" type="password"> - <div class="section-buttons-container"> - <span onclick="TestKomga(komgaUrl.value, utf8_to_b64(`${komgaUsername.value}:${komgaPassword.value}`))" class='section-button' id="test-connector">Test</span> - <span onclick="ClearKomga()" class='section-button' id="reset">Reset</span> - <span onclick="UpdateKomga(komgaUrl.value, utf8_to_b64(`${komgaUsername.value}:${komgaPassword.value}`))" class='section-button'>Apply</span> - </div> - </div> - <div class="section-item"> - <span class="title"><img src='connector-icons/kavita.png'>Kavita<connector-configured id="kavitaConfigured"></connector-configured></span> - <label for="kavitaUrl"></label><input placeholder="URL" id="kavitaUrl" type="text"> - <label for="kavitaUsername"></label><input placeholder="Username" id="kavitaUsername" type="text"> - <label for="kavitaPassword"></label><input placeholder="Password" id="kavitaPassword" type="password"> - <div class="section-buttons-container"> - <span onclick="TestKavita(kavitaUrl.value, kavitaUsername.value, kavitaPassword.value)" class='section-button' id="test-connector">Test</span> - <span onclick="ClearKavita()" class='section-button' id="reset">Reset</span> - <span onclick="UpdateKavita(kavitaUrl.value, kavitaUsername.value, kavitaPassword.value)" class='section-button'>Apply</span> - </div> - </div> - </div> - </div> - - <div class="popup-section"> - NOTIFICATION CONNECTORS - <div class="section-content"> - <div class="section-item"> - <span class="title"><img src='connector-icons/gotify-logo.png'>Gotify<connector-configured id="gotifyConfigured"></connector-configured></span> - <label for="gotifyUrl"></label><input placeholder="URL" id="gotifyUrl" type="text"> - <label for="gotifyAppToken"></label><input placeholder="App-Token" id="gotifyAppToken" type="text"> - <div class="section-buttons-container"> - <span onclick="TestGotify(gotifyUrl.value, gotifyAppToken.value)" class='section-button' id="test-connector">Test</span> - <span onclick="ClearGotify()" class='section-button' id="reset">Reset</span> - <span onclick="UpdateGotify(gotifyUrl.value, gotifyAppToken.value)" class='section-button'>Apply</span> - </div> - </div> - <div class="section-item"> - <span class="title"><img src='connector-icons/lunasea.png'>LunaSea<connector-configured id="lunaseaConfigured"></connector-configured></span> - <label for="lunaseaWebhook"></label><input placeholder="device/:id or user/:id" id="lunaseaWebhook" type="text"> - <div class="section-buttons-container"> - <span onclick="TestLunaSea(lunaseaWebhook.value);" class='section-button' id="test-connector">Test</span> - <span onclick="ClearLunasea()" class='section-button' id="reset">Reset</span> - <span onclick="UpdateLunaSea(lunaseaWebhook.value);" class='section-button'>Apply</span> - </div> - </div> - <div class="section-item"> - <span class="title"><img src='connector-icons/ntfy.svg'>Ntfy<connector-configured id="ntfyConfigured"></connector-configured></span> - <label for="ntfyEndpoint"></label><input placeholder="URL" id="ntfyEndpoint" type="text"> - <label for="ntfyAuth"></label><input placeholder="Auth" id="ntfyAuth" type="text"> - <div class="section-buttons-container"> - <span onclick="TestNtfy(ntfyEndpoint.value, ntfyAuth.value);" class='section-button' id="test-connector">Test</span> - <span onclick="ClearNtfy()" class='section-button' id="reset">Reset</span> - <span onclick="UpdateNtfy(ntfyEndpoint.value, ntfyAuth.value);" class='section-button'>Apply</span> - </div> - </div> - </div> - </div> - </popup-content> - - <border-bar> - <div class="button-container"> - <border-bar-button class="primary" onclick="UpdateSettings()">Apply Settings</border-bar-button> - </div> - </border-bar> - - </popup-window> - </popup> - - <popup id="publicationViewerPopup"> - <blur-background id="blurBackgroundPublicationPopup" onclick="publicationViewerPopup.style.display= 'none';"></blur-background> - <publication-viewer> - <img id="pubviewcover" src="media/cover.jpg" alt="cover"> - <publication-details> - <publication-name id="publicationViewerName">Best Manga there is</publication-name> - <publication-tags id="publicationViewerTags">A Manga</publication-tags> - <publication-author id="publicationViewerAuthor">Glax</publication-author> - <publication-description id="publicationViewerDescription"> - An interesting description. The description is very intriguing, yet wholesome. - </publication-description> - <publication-interactions> - <publication-starttask id="startJobButton">Start Job ▶️</publication-starttask> - <publication-canceltask id="cancelJobButton">Cancel Job ❌</publication-canceltask> - <publication-delete id="deleteJobButton">Delete Job 🗑️</publication-delete> - <publication-add id="createMonitorJobButton">Monitor ➕</publication-add> - <publication-add id="createDownloadChapterJobButton">Download Chapter 📥</publication-add> - </publication-interactions> - </publication-details> - </publication-viewer> - </popup> - - <popup id="jobStatusView"> - <blur-background id="blurBackgroundSettingsPopup" onclick="jobStatusView.style.display = 'none';"></blur-background> - <popup-window> - <border-bar> - <popup-title>Jobs</popup-title> - <popup-close onclick="jobStatusView.style.display = 'none'">×</popup-close> - </border-bar> - <popup-content> - - <div class="popup-section"> - RUNNING JOBS - <div class="section-content" id="jobStatusRunning"> - - </div> - </div> - - <div class="popup-section"> - QUEUED JOBS - <div class="section-content" id="jobStatusWaiting"> - - </div> - </div> - - </popup-content> - - <border-bar> - <!-- <div class="button-container"> - <border-bar-button class="primary" onclick="UpdateSettings()">Apply Settings</border-bar-button> - </div> --> - </border-bar> - - </popup-window> - </viewport> - - <footer> - <div onclick="ShowJobQueue();"> - <img src="media/running.svg" alt="running"><div id="jobsRunningTag">0</div> - </div> - <div onclick="ShowJobQueue();"> - <img src="media/queue.svg" alt="queue"><div id="jobsQueuedTag">0</div> - </div> - <p id="madeWith">Made with Blåhaj 🦈</p> - </footer> - </wrapper> - - <script src="apiConnector.js"></script> - <script src="interaction.js"></script> + <div id="app"></div> + <script type="module" src="index.jsx"></script> </body> </html> \ No newline at end of file diff --git a/Website/index.jsx b/Website/index.jsx new file mode 100644 index 0000000..de60bc4 --- /dev/null +++ b/Website/index.jsx @@ -0,0 +1,7 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.tsx'; + +const domNode = document.getElementById('app'); +const root = createRoot(domNode); +root.render(<App />); \ No newline at end of file diff --git a/Website/interaction.js b/Website/interaction.js deleted file mode 100644 index 889ade9..0000000 --- a/Website/interaction.js +++ /dev/null @@ -1,885 +0,0 @@ -let monitoringJobsCount = 0; -let runningJobs = []; -let waitingJobs = []; -let notificationConnectorTypes = []; -let libraryConnectorTypes = []; -let selectedManga; -let selectedJob; -let searchMatch; - -let connectorMatch = []; -let connectorNameMatch; -let statusMatch = []; -let statusNameMatch = []; - -const searchBox = document.querySelector("#searchbox"); -const settingsPopup = document.querySelector("#settingsPopup"); -const filterBox = document.querySelector("#filterBox"); -const settingsCog = document.querySelector("#settingscog"); -const filterFunnel = document.querySelector("#filterFunnel"); -const tasksContent = document.querySelector("content"); -const createMonitorTaskButton = document.querySelector("#createMonitoJobButton"); -const createDownloadChapterTaskButton = document.querySelector("#createDownloadChapterJobButton"); -const startJobButton = document.querySelector("#startJobButton"); -const cancelJobButton = document.querySelector("#cancelJobButton"); -const deleteJobButton = document.querySelector("#deleteJobButton"); - -//Manga viewer popup -const mangaViewerPopup = document.querySelector("#publicationViewerPopup"); -const mangaViewerWindow = document.querySelector("publication-viewer"); -const mangaViewerDescription = document.querySelector("#publicationViewerDescription"); -const mangaViewerName = document.querySelector("#publicationViewerName"); -const mangaViewerTags = document.querySelector("#publicationViewerTags"); -const mangaViewerAuthor = document.querySelector("#publicationViewerAuthor"); -const mangaViewCover = document.querySelector("#pubviewcover"); - -//General Rate Limits -const defaultRL = document.querySelector("#defaultRL"); -const coverRL = document.querySelector("#coverRL"); -const imageRL = document.querySelector("#imageRL"); -const infoRL = document.querySelector("#infoRL"); - -//MangaDex Rate Limits -const mDexAuthorRL = document.querySelector("#mDexAuthorRL"); -const mDexFeedRL = document.querySelector("#mDexFeedRL"); -const mDexImageRL = document.querySelector("#mDexImageRL"); - -//Komga -const settingKomgaUrl = document.querySelector("#komgaUrl"); -const settingKomgaUser = document.querySelector("#komgaUsername"); -const settingKomgaPass = document.querySelector("#komgaPassword"); - -//Kavita -const settingKavitaUrl = document.querySelector("#kavitaUrl"); -const settingKavitaUser = document.querySelector("#kavitaUsername"); -const settingKavitaPass = document.querySelector("#kavitaPassword"); - -//Gotify -const settingGotifyUrl = document.querySelector("#gotifyUrl"); -const settingGotifyAppToken = document.querySelector("#gotifyAppToken"); - -//Lunasea -const settingLunaseaWebhook = document.querySelector("#lunaseaWebhook"); - -//Ntfy -const settingNtfyEndpoint = document.querySelector("#ntfyEndpoint"); -const settingNtfyAuth = document.querySelector("#ntfyAuth"); - -//Connector Configured -const settingKomgaConfigured = document.querySelector("#komgaConfigured"); -const settingKavitaConfigured = document.querySelector("#kavitaConfigured"); -const settingGotifyConfigured = document.querySelector("#gotifyConfigured"); -const settingLunaseaConfigured = document.querySelector("#lunaseaConfigured"); -const settingNtfyConfigured = document.querySelector("#ntfyConfigured"); - -const settingUserAgent = document.querySelector("#userAgent"); -const settingApiUri = document.querySelector("#settingApiUri"); -const settingCSSStyle = document.querySelector('#cssStyle'); -const newMangaPopup = document.querySelector("#newMangaPopup"); -const newMangaConnector = document.querySelector("#newMangaConnector"); -const newMangaTitle = document.querySelector("#newMangaTitle"); -const newMangaResult = document.querySelector("#newMangaResult"); -const newMangaTranslatedLanguage = document.querySelector("#newMangaTranslatedLanguage"); - -//Jobs -const jobsRunningTag = document.querySelector("#jobsRunningTag"); -const jobsQueuedTag = document.querySelector("#jobsQueuedTag"); -const loaderdiv = document.querySelector('#loaderdiv'); -const jobStatusView = document.querySelector("#jobStatusView"); -const jobStatusRunning = document.querySelector("#jobStatusRunning"); -const jobStatusWaiting = document.querySelector("#jobStatusWaiting"); - -function Setup(){ - Ping().then((ret) => { - loaderdiv.style.display = 'none'; - - GetAvailableNotificationConnectors().then((json) => { - //console.log(json); - json.forEach(connector => { - notificationConnectorTypes[connector.Key] = connector.Value; - }); - }); - - GetAvailableLibraryConnectors().then((json) => { - //console.log(json); - json.forEach(connector => { - libraryConnectorTypes[connector.Key] = connector.Value; - }); - }); - - GetAvailableControllers().then((json) => { - //console.log(json); - newMangaConnector.replaceChildren(); - connectorFilterBox = document.querySelector("#connectorFilterBox"); - connectorFilterBox.replaceChildren(); - json.forEach(connector => { - //Add the connector to the New Manga dropdown - var option = document.createElement('option'); - option.value = connector; - option.innerText = connector; - newMangaConnector.appendChild(option); - - //Add the connector to the filter box - connectorFilter = document.createElement('connector-name'); - connectorFilter.innerText = connector; - connectorFilter.className = "pill"; - connectorFilter.style.backgroundColor = stringToColour(connector); - - connectorFilter.addEventListener("click", (event) => { - ToggleFilterConnector(connector, event); - }); - connectorFilterBox.appendChild(connectorFilter); - }); - }); - - //Add the publication status options to the filter bar - publicationStatusOptions = ["Ongoing", "Completed", "On Hiatus", "Cancelled", "Upcoming", "Status Unavailable"]; - statusFilterBox = document.querySelector("#statusFilterBox"); - statusFilterBox.replaceChildren(); - publicationStatusOptions.forEach(publicationStatus => { - var releaseStatus = document.createElement('status-filter'); - releaseStatus.innerText = publicationStatus; - releaseStatus.setAttribute("release-status", publicationStatus); - releaseStatus.addEventListener("click", (event) => { - ToggleFilterStatus(publicationStatus, event); - }); - - statusFilterBox.appendChild(releaseStatus); - }); - - ResetContent(); - UpdateJobs(); - GetSettings().then((json) => { - //console.log(json); - settingApiUri.placeholder = apiUri; - }); - GetRateLimits().then((json) => { - defaultRL.placeholder = json.Default + ' Requests/Minute'; - coverRL.placeholder = json.MangaCover + ' Requests/Minute'; - imageRL.placeholder = json.MangaImage + ' Requests/Minute'; - infoRL.placeholder = json.MangaInfo + ' Requests/Minute'; - mDexAuthorRL.placeholder = json.MangaDexAuthor + ' Requests/Minute'; - mDexFeedRL.placeholder = json.MangaDexFeed + ' Requests/Minute'; - mDexImageRL.placeholder = json.MangaDexImage + ' Requests/Minute'; - }); - - //If the cssStyle key isn't in the local storage of the browser, then set the css style to the default and load the page - //Otherwise get the style key from storage and set it. - if (!localStorage.getItem('cssStyle')) { - localStorage.setItem('cssStyle', 'card_compact'); - document.getElementById('librarystyle').setAttribute('href', 'styles/' + localStorage.getItem('cssStyle') + '.css'); - document.getElementById('card_compact').selected = true; - } else { - css_style = localStorage.getItem('cssStyle'); - document.getElementById('librarystyle').setAttribute('href', 'styles/' + css_style + '.css'); - document.getElementById(css_style).selected = true; - } - setInterval(() => { - UpdateJobs(); - }, 1000); - }); - //Clear the previous values if any exist. - searchBox.value = ""; - connectorMatch.length = 0; - statusMatch.length = 0; -} -Setup(); - -function ToggleFilterConnector(connector, event) { - //console.log("Initial Array:"); - //console.log(connectorMatch); - if (connectorMatch.includes(connector)) { - idx = connectorMatch.indexOf(connector); - connectorMatch.splice(idx, 1); - event.target.style.outline = 'none'; - event.target.style.outlineOffset = "0px"; - } else { - connectorMatch.push(connector); - event.target.style.outline = '4px solid var(--secondary-color)'; - event.target.style.outlineOffset = '3px'; - } - //console.log("Final Array"); - //console.log(connectorMatch); - FilterResults(); -} - -function ToggleFilterStatus(status, event) { - //console.log("Initial Array:"); - //console.log(statusMatch); - if (statusMatch.includes(status)) { - idx = statusMatch.indexOf(status); - statusMatch.splice(idx, 1); - event.target.style.outline = 'none'; - event.target.style.outlineOffset = "0px"; - } else { - statusMatch.push(status); - event.target.style.outline = '4px solid var(--secondary-color)'; - event.target.style.outlineOffset = '3px'; - } - //console.log("Final Array"); - //console.log(statusMatch); - FilterResults(); -} - -function ClearFilter() { - searchBox.value = ""; - statusMatch.length = 0; - connectorMatch.length = 0; - FilterResults(); - - //Get rid of the outlines - connectorFilterBox = document.querySelector("#connectorFilterBox"); - connectorFilterBox.childNodes.forEach(connector => { - if (connector.nodeName.toLowerCase() == 'connector-name') { - connector.style.outline = 'none'; - connector.style.outlineOffset = "0px"; - } - }); - - statusFilterBox = document.querySelector("#statusFilterBox"); - statusFilterBox.childNodes.forEach(publicationStatus => { - if (publicationStatus.nodeName.toLowerCase() == 'status-filter') { - publicationStatus.style.outline = 'none'; - publicationStatus.style.outlineOffset = "0px"; - } - }); -} - -settingCSSStyle.addEventListener("change", (event) => { - localStorage.setItem('cssStyle', settingCSSStyle.value); - document.getElementById('librarystyle').setAttribute('href', 'styles/' + localStorage.getItem('cssStyle') + '.css'); -}); - -function ResetContent(){ - //Delete everything - tasksContent.replaceChildren(); - - //Add "Add new Task" Button - var add = document.createElement("div"); - add.setAttribute("id", "addPublication") - var plus = document.createElement("p"); - plus.innerText = "+"; - add.appendChild(plus); - add.addEventListener("click", () => ShowNewMangaSearch()); - tasksContent.appendChild(add); - - //Populate with the monitored mangas - GetMonitorJobs().then((json) => { - //console.log(json); - json.forEach(job => { - var mangaView = CreateManga(job.manga, job.mangaConnector.name); - mangaView.addEventListener("click", (event) => { - ShowMangaWindow(job, job.manga, event, false); - }); - tasksContent.appendChild(mangaView); - }); - monitoringJobsCount = json.length; - }); -} - -function ShowNewMangaSearch(){ - newMangaTitle.value = ""; - newMangaPopup.style.display = "block"; - newMangaResult.replaceChildren(); -} - -newMangaTitle.addEventListener("keypress", (event) => { if(event.key === "Enter") GetNewMangaItems();}); - - - - -function GetNewMangaItems(){ - if(newMangaTitle.value.length < 4) - return; - - newMangaResult.replaceChildren(); - newMangaConnector.disabled = true; - newMangaTitle.disabled = true; - newMangaTranslatedLanguage.disabled = true; - GetPublicationFromConnector(newMangaConnector.value, newMangaTitle.value).then((json) => { - //console.log(json); - if(json.length > 0) - newMangaResult.style.display = "flex"; - json.forEach(result => { - var mangaElement = CreateManga(result, newMangaConnector.value) - newMangaResult.appendChild(mangaElement); - mangaElement.addEventListener("click", (event) => { - ShowMangaWindow(null, result, event, true); - }); - }); - - newMangaConnector.disabled = false; - newMangaTitle.disabled = false; - newMangaTranslatedLanguage.disabled = false; - }); -} - -//Returns a new "Publication" Item to display in the jobs section -function CreateManga(manga, connector){ - //Create a new publication and set an internal ID - var mangaElement = document.createElement('publication'); - mangaElement.id = GetValidSelector(manga.internalId); - - //Append the cover image to the publication - var mangaImage = document.createElement('img'); - mangaImage.src = GetCoverUrl(manga.internalId); - mangaElement.appendChild(mangaImage); - -//Append the publication information to the publication - //console.log(manga); - var info = document.createElement('publication-information'); - var connectorName = document.createElement('connector-name'); - connectorName.innerText = connector; - connectorName.className = "pill"; - connectorName.style.backgroundColor = stringToColour(connector); - info.appendChild(connectorName); - - var mangaName = document.createElement('publication-name'); - mangaName.innerText = manga.sortName; - - //Create the publication status indicator - var releaseStatus = document.createElement('publication-status'); - releaseStatus.setAttribute("release-status", manga.releaseStatus); - switch(manga.releaseStatus){ - case 0: - releaseStatus.setAttribute("release-status", "Ongoing"); - break; - case 1: - releaseStatus.setAttribute("release-status", "Completed"); - break; - case 2: - releaseStatus.setAttribute("release-status", "On Hiatus"); - break; - case 3: - releaseStatus.setAttribute("release-status", "Cancelled"); - break; - case 4: - releaseStatus.setAttribute("release-status", "Upcoming"); - break; - default: - releaseStatus.setAttribute("release-status", "Status Unavailable"); - break; - } - - info.appendChild(mangaName); - mangaElement.appendChild(info); - mangaElement.appendChild(releaseStatus); //Append the release status indicator to the publication element - return mangaElement; -} - -createMonitorJobButton.addEventListener("click", () => { - CreateMonitorJob(newMangaConnector.value, selectedManga.internalId, newMangaTranslatedLanguage.value); - UpdateJobs(); - mangaViewerPopup.style.display = "none"; -}); -startJobButton.addEventListener("click", () => { - StartJob(selectedJob.id); - mangaViewerPopup.style.display = "none"; -}); -cancelJobButton.addEventListener("click", () => { - CancelJob(selectedJob.id); - mangaViewerPopup.style.display = "none"; -}); -deleteJobButton.addEventListener("click", () => { - RemoveJob(selectedJob.id); - UpdateJobs(); - mangaViewerPopup.style.display = "none"; -}); - -function ShowMangaWindow(job, manga, event, add){ - selectedManga = manga; - selectedJob = job; - //Show popup - mangaViewerPopup.style.display = "block"; - - //Set position to mouse-position - if(event.clientY < window.innerHeight - mangaViewerWindow.offsetHeight) - mangaViewerWindow.style.top = `${event.clientY}px`; - else - mangaViewerWindow.style.top = `${event.clientY - mangaViewerWindow.offsetHeight}px`; - - if(event.clientX < window.innerWidth - mangaViewerWindow.offsetWidth) - mangaViewerWindow.style.left = `${event.clientX}px`; - else - mangaViewerWindow.style.left = `${event.clientX - mangaViewerWindow.offsetWidth}px`; - - //Edit information inside the window - mangaViewerName.innerText = manga.sortName; - mangaViewerTags.innerText = manga.tags.join(", "); - mangaViewerDescription.innerText = manga.description; - mangaViewerAuthor.innerText = manga.authors.join(','); - mangaViewCover.src = GetCoverUrl(manga.internalId); - toEditId = manga.internalId; - - //Check what action should be listed - if(add){ - createMonitorJobButton.style.display = "initial"; - createDownloadChapterJobButton.style.display = "none"; - cancelJobButton.style.display = "none"; - startJobButton.style.display = "none"; - deleteJobButton.style.display = "none"; - } - else{ - createMonitorJobButton.style.display = "none"; - createDownloadChapterJobButton.style.display = "none"; - cancelJobButton.style.display = "initial"; - startJobButton.style.display = "initial"; - deleteJobButton.style.display = "initial"; - } -} - -function HidePublicationPopup(){ - publicationViewerPopup.style.display = "none"; -} - -searchBox.addEventListener("keyup", () => FilterResults()); -//Filter shown jobs -function FilterResults(){ - //For each publication - tasksContent.childNodes.forEach(publication => { - //If the search box isn't empty check that the title contains the searchbox content. If it does then - //'searchMatch' is true and the manga is shown. If the search box is empty, then consider this field - //to be true anyways. - if (searchBox.value.length > 0) { - publication.childNodes.forEach(item => { - if (item.nodeName.toLowerCase() == "publication-information"){ - item.childNodes.forEach(information => { - if (information.nodeName.toLowerCase() == "publication-name") { - if (information.textContent.toLowerCase().includes(searchBox.value.toLowerCase())){ - searchMatch = 1; - } else { - searchMatch = 0; - } - } - }); - } - }); - } else { - searchMatch = 1; - } - - //If the array connectorMatch isn't empty then check that the connector matches one of the ones - //in the array - if (connectorMatch.length > 0) { - publication.childNodes.forEach(item => { - if (item.nodeName.toLowerCase() == "publication-information"){ - item.childNodes.forEach(information => { - if (information.nodeName.toLowerCase() == "connector-name") { - if (connectorMatch.includes(information.textContent)){ - connectorNameMatch = 1; - } else { - connectorNameMatch = 0; - } - } - }); - } - }); - } else { - connectorNameMatch = 1; - } - - //If the array statusMatch isn't empty then check that the status matches one of the ones - //in the array - if (statusMatch.length > 0) { - publication.childNodes.forEach(item => { - if (item.nodeName.toLowerCase() == "publication-status"){ - if (statusMatch.includes(item.getAttribute('release-status'))) { - statusNameMatch = 1; - } else { - statusNameMatch = 0; - } - } - }); - } else { - statusNameMatch = 1; - } - - //If all of the filtering conditions are met then show the manga, otherwise hide it. - if (searchMatch && connectorNameMatch && statusNameMatch) { - publication.style.display = 'initial'; - } else { - publication.style.display = 'none'; - } - }); -} - -settingsCog.addEventListener("click", () => { - OpenSettings(); - settingsPopup.style.display = "flex"; -}); - -filterFunnel.addEventListener("click", () => { - filterBox.classList.toggle("animate"); -}); - -settingKomgaUrl.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); -settingKomgaUser.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); -settingKomgaPass.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); -settingKavitaUrl.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); -settingKavitaUser.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); -settingKavitaPass.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); -settingGotifyUrl.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); -settingGotifyAppToken.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); -settingLunaseaWebhook.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); -settingNtfyEndpoint.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); -settingNtfyAuth.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); -settingUserAgent.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); -settingApiUri.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); }); - -defaultRL.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings();}); -coverRL.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings();}); -imageRL.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings();}); -infoRL.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings();}); -mDexAuthorRL.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings();}); -mDexFeedRL.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings();}); -mDexImageRL.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings();}); - - -function OpenSettings(){ - settingGotifyConfigured.setAttribute("configuration", "Not Configured"); - settingLunaseaConfigured.setAttribute("configuration", "Not Configured"); - settingNtfyConfigured.setAttribute("configuration", "Not Configured"); - settingKavitaConfigured.setAttribute("configuration", "Not Configured"); - settingKomgaConfigured.setAttribute("configuration", "Not Configured"); - settingKomgaUrl.value = ""; - settingKomgaUser.value = ""; - settingKomgaPass.value = ""; - settingKavitaUrl.value = ""; - settingKavitaUser.value = ""; - settingKavitaPass.value = ""; - settingGotifyUrl.value = ""; - settingGotifyAppToken.value = ""; - settingLunaseaWebhook.value = ""; - settingNtfyAuth.value = ""; - settingNtfyEndpoint.value = ""; - settingUserAgent.value = ""; - settingApiUri.value = ""; - defaultRL.value = ""; - coverRL.value = ""; - imageRL.value = ""; - infoRL.value = ""; - mDexAuthorRL.value = ""; - mDexFeedRL.value = ""; - mDexImageRL.value = ""; - - GetSettings().then((json) => { - //console.log(json); - settingApiUri.value = apiUri; - settingUserAgent.value = json.userAgent; - //console.log(json.styleSheet); - }); - GetRateLimits().then((json) => { - defaultRL.placeholder = json.Default + ' Requests/Minute'; - coverRL.placeholder = json.MangaCover + ' Requests/Minute'; - imageRL.placeholder = json.MangaImage + ' Requests/Minute'; - infoRL.placeholder = json.MangaInfo + ' Requests/Minute'; - mDexAuthorRL.placeholder = json.MangaDexAuthor + ' Requests/Minute'; - mDexFeedRL.placeholder = json.MangaDexFeed + ' Requests/Minute'; - mDexImageRL.placeholder = json.MangaDexImage + ' Requests/Minute'; - }); - GetLibraryConnectors().then((json) => { - //console.log(json); - json.forEach(connector => { - switch(libraryConnectorTypes[connector.libraryType]){ - case "Kavita": - settingKavitaConfigured.setAttribute("configuration", "Active"); - settingKavitaUrl.value = connector.baseUrl; - settingKavitaUser.value = "***"; - settingKavitaPass.value = "***"; - break; - case "Komga": - settingKomgaConfigured.setAttribute("configuration", "Active"); - settingKomgaUrl.value = connector.baseUrl; - settingKomgaUser.value = "***"; - settingKomgaPass.value = "***"; - break; - default: - console.log("Unknown type"); - console.log(connector); - break; - } - }); - }); - GetNotificationConnectors().then((json) => { - json.forEach(connector => { - switch(notificationConnectorTypes[connector.notificationConnectorType]){ - case "Gotify": - settingGotifyUrl.value = connector.endpoint; - settingGotifyAppToken.value = "***"; - settingGotifyConfigured.setAttribute("configuration", "Active"); - break; - case "LunaSea": - settingLunaseaConfigured.setAttribute("configuration", "Active"); - settingLunaseaWebhook.value = connector.id; - break; - case "Ntfy": - settingNtfyConfigured.setAttribute("configuration", "Active"); - settingNtfyEndpoint.value = connector.endpoint; - settingNtfyAuth.value = "***"; - break; - default: - console.log("Unknown type"); - console.log(connector); - break; - } - }); - }); -} - -//Functions for clearing/resetting connectors in the settings pop-up -function ClearKomga(){ - settingKomgaUrl.value = ""; - settingKomgaUser.value = ""; - settingKomgaPass.value = ""; - settingKomgaConfigured.setAttribute("configuration", "Not Configured"); - ResetKomga(); -} - -function ClearKavita(){ - settingKavitaUrl.value = ""; - settingKavitaUser.value = ""; - settingKavitaPass.value = ""; - settingKavitaConfigured.setAttribute("configuration", "Not Configured"); - ResetKavita(); -} - -function ClearGotify(){ - settingGotifyUrl.value = ""; - settingGotifyAppToken.value = "" - settingGotifyConfigured.setAttribute("configuration", "Not Configured"); - ResetGotify(); -} - -function ClearLunasea(){ - settingLunaseaWebhook.value = ""; - settingLunaseaConfigured.setAttribute("configuration", "Not Configured"); - ResetLunaSea(); -} - -function ClearNtfy(){ - settingNtfyEndpoint.value = ""; - settingNtfyAuth.value = ""; - settingNtfyConfigured.setAttribute("configuration", "Not Configured"); - ResetNtfy(); -} - -function UpdateSettings(){ - if(settingApiUri.value != ""){ - apiUri = settingApiUri.value; - setCookie("apiUri", apiUri); - Setup(); - } - - if(settingKomgaUrl.value != "" && - settingKomgaUser.value != "" && - settingKomgaPass.value != ""){ - UpdateKomga(settingKomgaUrl.value, utf8_to_b64(`${settingKomgaUser.value}:${settingKomgaPass.value}`)); - } - - if(settingKavitaUrl.value != "" && - settingKavitaUser.value != "" && - settingKavitaPass.value != ""){ - UpdateKavita(settingKavitaUrl.value, settingKavitaUser.value, settingKavitaPass.value); - } - - if(settingGotifyUrl.value != "" && - settingGotifyAppToken.value != ""){ - UpdateGotify(settingGotifyUrl.value, settingGotifyAppToken.value); - } - - if(settingLunaseaWebhook.value != ""){ - UpdateLunaSea(settingLunaseaWebhook.value); - } - - if(settingNtfyEndpoint.value != "" && - settingNtfyAuth.value != ""){ - UpdateNtfy(settingNtfyEndpoint.value, settingNtfyAuth.value); - } - - if(settingUserAgent.value != ""){ - UpdateUserAgent(settingUserAgent.value); - } - - if (defaultRL.value != "") { - UpdateRateLimit(0, defaultRL.value); - } - - if (coverRL.value != "") { - UpdateRateLimit(3, coverRL.value); - } - - if (imageRL.value != "") { - UpdateRateLimit(2, imageRL.value); - } - - if (infoRL.value != "") { - UpdateRateLimit(6, infoRL.value); - } - - if (mDexAuthorRL.value != "") { - UpdateRateLimit(5, mDexAuthorRL.value); - } - - if (mDexFeedRL.value != "") { - UpdateRateLimit(1, mDexFeedRL.value); - } - - if (mDexImageRL.value != "") { - UpdateRateLimit(5, mDexImageRL.value); - } - - setTimeout(() => { - OpenSettings(); - Setup(); - }, 100) -} - -function utf8_to_b64(str) { - return window.btoa(unescape(encodeURIComponent( str ))); -} - -function UpdateJobs(){ - - GetMonitorJobs().then((json) => { - if(monitoringJobsCount != json.length){ - ResetContent(); - monitoringJobsCount = json.length; - } - }); - - //Get the jobs that are waiting in the queue - GetWaitingJobs().then((json) => { - jobsQueuedTag.innerText = json.length; - - var nowWaitingJobs = []; - - json.forEach(job => { - if(!waitingJobs.includes(GetValidSelector(job.id))){ - var jobDom = createJob(job); - jobStatusWaiting.appendChild(jobDom); - } - nowWaitingJobs.push(GetValidSelector(job.id)); - }); - waitingJobs = nowWaitingJobs; - }); - - jobStatusWaiting.childNodes.forEach(child => { - if(!waitingJobs.includes(child.id)) - jobStatusWaiting.removeChild(child); - }); - - //Get currently running jobs - GetRunningJobs().then((json) => { - jobsRunningTag.innerText = json.length; - - var nowRunningJobs = []; - - json.forEach(job => { - if(!runningJobs.includes(GetValidSelector(job.id))){ - var jobDom = createJob(job); - jobStatusRunning.appendChild(jobDom); - } - nowRunningJobs.push(GetValidSelector(job.id)); - UpdateJobProgress(job.id); - }); - - runningJobs = nowRunningJobs; - }); - - jobStatusRunning.childNodes.forEach(child => { - if(!runningJobs.includes(child.id)) - jobStatusRunning.removeChild(child); - }); -} - -function createJob(jobjson){ - var manga; - if(jobjson.chapter != null) - manga = jobjson.chapter.parentManga; - else if(jobjson.manga != null) - manga = jobjson.manga; - else return null; - - - var wrapper = document.createElement("div"); - wrapper.className = "section-item"; - wrapper.id = GetValidSelector(jobjson.id); - - var image = document.createElement("img"); - image.className = "jobImage"; - image.src = GetCoverUrl(manga.internalId); - wrapper.appendChild(image); - - var details = document.createElement("div"); - details.className = 'jobDetails'; - - var title = document.createElement("span"); - title.className = "jobTitle"; - if(jobjson.chapter != null) - title.innerText = `${manga.sortName} - ${jobjson.chapter.fileName}`; - else if(jobjson.manga != null) - title.innerText = manga.sortName; - details.appendChild(title); - - var progressBar = document.createElement("progress"); - progressBar.className = "jobProgressBar"; - progressBar.id = `jobProgressBar${GetValidSelector(jobjson.id)}`; - details.appendChild(progressBar); - - var progressSpan = document.createElement("span"); - progressSpan.className = "jobProgressSpan"; - progressSpan.id = `jobProgressSpan${GetValidSelector(jobjson.id)}`; - progressSpan.innerText = "Pending..."; - details.appendChild(progressSpan); - - var cancelSpan = document.createElement("span"); - cancelSpan.className = "jobCancel"; - cancelSpan.innerText = "Cancel"; - cancelSpan.addEventListener("click", () => CancelJob(jobjson.id)); - details.appendChild(cancelSpan); - - wrapper.appendChild(details); - - return wrapper; -} - -function ShowJobQueue(){ - jobStatusView.style.display = "initial"; -} - -function UpdateJobProgress(jobId){ - GetProgress(jobId).then((json) => { - var progressBar = document.querySelector(`#jobProgressBar${GetValidSelector(jobId)}`); - var progressSpan = document.querySelector(`#jobProgressSpan${GetValidSelector(jobId)}`); - if(progressBar != null && json.progress != 0){ - progressBar.value = json.progress; - } - if(progressSpan != null){ - var percentageStr = "0%"; - var timeleftStr = "00:00:00"; - if(json.progress != 0){ - percentageStr = Intl.NumberFormat("en-US", { style: "percent"}).format(json.progress); - timeleftStr = json.timeRemaining.split('.')[0]; - } - progressSpan.innerText = `${percentageStr} ${timeleftStr}`; - } - }); -} - -function GetValidSelector(str){ - var clean = [...str.matchAll(/[a-zA-Z0-9]*-*_*/g)]; - return clean.join(''); -} - -const stringToColour = (str) => { - let hash = 0; - str.split('').forEach(char => { - hash = char.charCodeAt(0) + ((hash << 5) - hash) - }) - let colour = '#' - for (let i = 0; i < 3; i++) { - const value = (hash >> (i * 8)) & 0xff - colour += value.toString(16).padStart(2, '0') - } - return colour -} \ No newline at end of file diff --git a/Website/media/connector-icons/bato.ico b/Website/media/connector-icons/bato.ico new file mode 100644 index 0000000..42c201a Binary files /dev/null and b/Website/media/connector-icons/bato.ico differ diff --git a/Website/media/connector-icons/gotify-logo.png b/Website/media/connector-icons/gotify-logo.png new file mode 100644 index 0000000..5d29004 Binary files /dev/null and b/Website/media/connector-icons/gotify-logo.png differ diff --git a/Website/media/connector-icons/kavita.png b/Website/media/connector-icons/kavita.png new file mode 100644 index 0000000..00a4ba7 Binary files /dev/null and b/Website/media/connector-icons/kavita.png differ diff --git a/Website/media/connector-icons/komga.svg b/Website/media/connector-icons/komga.svg new file mode 100644 index 0000000..e8b3f56 --- /dev/null +++ b/Website/media/connector-icons/komga.svg @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns="http://www.w3.org/2000/svg" + height="512pt" + viewBox="0 0 512 512" + width="512pt" + version="1.1" + id="svg4586"> + <path + d="m512 256c0 141.386719-114.613281 256-256 256s-256-114.613281-256-256 114.613281-256 256-256 256 114.613281 256 256zm0 0" + fill="#005ed3" + id="path4556"/> + <path + d="m 512,256 c 0,-11.71094 -0.80469,-23.23047 -2.32422,-34.52344 L 382.48047,94.28125 320.52344,121.85938 256,56.933594 212.69531,131.30469 129.51953,94.28125 141.86719,178.42187 49.949219,193.81641 114.32031,256 l -64.371091,62.18359 82.121091,82.16016 -2.55078,17.375 91.95703,91.95703 C 232.76953,511.19531 244.28906,512 256,512 397.38672,512 512,397.38672 512,256 Z" + id="path4558" + style="fill:#00459f"/> + <path + d="m256 86.742188 37.109375 63.738281 70.574219-31.414063-10.527344 71.71875 77.078125 12.910156-54.144531 52.304688 54.144531 52.304688-77.078125 12.910156 10.527344 71.71875-70.574219-31.414063-37.109375 63.738281-37.109375-63.738281-70.574219 31.414063 10.527344-71.71875-77.078125-12.910156 54.144531-52.304688-54.144531-52.304688 77.078125-12.910156-10.527344-71.71875 70.574219 31.414063zm0 0" + fill="#ff0335" + id="path4560"/> + <path + d="m430.230469 308.300781-77.070313 12.910157 10.519532 71.71875-70.570313-31.410157-37.109375 63.742188v-338.523438l37.109375 63.742188 70.570313-31.410157-6.757813 46.101563-3.761719 25.617187 58.800782 9.851563 18.269531 3.058594-13.390625 12.929687-40.75 39.371094 11.378906 10.988281zm0 0" + fill="#c2001b" + id="path4562"/> + <path + d="m256 455.066406-43.304688-74.371094-83.175781 37.023438 12.347657-84.140625-91.917969-15.394531 64.371093-62.183594-64.371093-62.183594 91.917969-15.394531-12.347657-84.140625 83.179688 37.023438 43.300781-74.371094 43.304688 74.371094 83.175781-37.023438-12.347657 84.140625 91.917969 15.394531-64.371093 62.183594 64.371093 62.183594-91.917969 15.398437 12.347657 84.136719-83.175781-37.023438zm-30.917969-112.722656 30.917969 53.101562 30.917969-53.101562 57.964843 25.800781-8.703124-59.292969 62.238281-10.425781-43.917969-42.425781 43.917969-42.425781-62.238281-10.425781 8.703124-59.292969-57.964843 25.800781-30.917969-53.101562-30.917969 53.101562-57.964843-25.800781 8.703124 59.292969-62.238281 10.425781 43.917969 42.425781-43.917969 42.425781 62.238281 10.425781-8.703124 59.292969zm0 0" + fill="#ffdf47" + id="path4564"/> + <path + d="m403.308594 261.441406-5.628906-5.441406 25.160156-24.300781 39.210937-37.878907-55.75-9.339843-36.171875-6.058594 2.800782-19.09375 9.550781-65.046875-83.179688 37.019531-43.300781-74.371093v59.621093l30.921875 53.109375 57.957031-25.808594-3.910156 26.667969-2.546875 17.378907-2.242187 15.25 2.480468.421874 59.761719 10.007813-43.921875 42.421875 16.96875 16.390625 26.953125 26.03125-62.242187 10.429687 8.699218 59.296876-57.957031-25.808594-30.921875 53.109375v59.621093l43.300781-74.371093 83.179688 37.019531-12.351563-84.140625 91.921875-15.398437zm0 0" + fill="#fec000" + id="path4566"/> + <g + aria-label="K" + transform="matrix(1.1590846,-0.34467221,0.22789693,0.794981,0,0)" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:296.55969238px;line-height:125%;font-family:Impact;-inkscape-font-specification:Impact;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.54528999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="text4596"> + <path + d="m 220.91497,266.9035 -34.89789,105.85211 38.2284,128.58643 H 161.2555 L 136.63873,400.84769 V 501.34204 H 75.676021 V 266.9035 h 60.962709 v 91.08205 l 27.07845,-91.08205 z" + style="font-size:296.55969238px;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.54528999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path824"/> + </g> +</svg> diff --git a/Website/media/connector-icons/lunasea.png b/Website/media/connector-icons/lunasea.png new file mode 100644 index 0000000..868e849 Binary files /dev/null and b/Website/media/connector-icons/lunasea.png differ diff --git a/Website/media/connector-icons/mangadex-logo.svg b/Website/media/connector-icons/mangadex-logo.svg new file mode 100644 index 0000000..696dd8b --- /dev/null +++ b/Website/media/connector-icons/mangadex-logo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="40" height="34" fill="none" viewBox="0 0 40 34"><path fill="#ff6740" d="M34.542 21.669H8.216a.585.585 0 0 1 0-1.17h26.326a.585.585 0 0 1 0 1.17Zm0 2.218H8.216a.585.585 0 1 1 0-1.17h26.326a.585.585 0 0 1 0 1.17Z"/><path fill="#ff6740" d="M30.233 23.229v3.912l1.675-.97 1.676.97V23.23h-3.35Z"/><path fill="#272B30" d="M35.715 10.866h-4.403v2.441h4.403v-2.44Z"/><path fill="#F1F1F1" d="M38.08 13.928c-.021-.02-.043-.037-.065-.056a5.998 5.998 0 0 1-1.266-1.536l-.009-.018h-.001a5.91 5.91 0 0 1-.72-2.15c-.004-.047-.01-.093-.015-.14v-.009a1.514 1.514 0 0 0-.327-.725l-.048-.054a10.43 10.43 0 0 0-5.301-3.198 6.574 6.574 0 0 1 .81-3.484.376.376 0 0 0-.332-.557h-.026a13.705 13.705 0 0 0-6.965 3.33 13.792 13.792 0 0 0-2.85 3.56 12.616 12.616 0 0 0-1.722-.764 12.859 12.859 0 0 0-5.536-.697 12.765 12.765 0 0 0-4.165 1.105C5.379 10.437 2.405 14.49 2.039 19.27c-.026.33-.039.665-.039 1v3.233c0 .56.083 1.098.237 1.605a5.55 5.55 0 0 0 2.596 3.226c.76.427 1.63.68 2.56.704h8.212a1.623 1.623 0 0 1 .243 0c.013 0 .026 0 .04.003.021.001.042.002.064.005a1.941 1.941 0 0 1 1.706 1.702 1.291 1.291 0 0 1 .014.23l-.001.036v.004a1.95 1.95 0 0 0 .918 1.61 1.94 1.94 0 0 0 2.955-1.514.943.943 0 0 0 .004-.077v-.108c0-.662-.11-1.3-.313-1.891a5.816 5.816 0 0 0-1.421-2.254 5.815 5.815 0 0 0-4.087-1.676H8.88c-.016.002-.033.002-.05.002-.016 0-.033 0-.05-.002a2.917 2.917 0 0 1-2.87-2.918 2.92 2.92 0 0 1 2.92-2.921h26.106a1.52 1.52 0 0 0 1.443-1.05l.02-.07c.091-.294.27-.55.506-.737l.004-.002a.638.638 0 0 1 .058-.044.934.934 0 0 1 .082-.055 2.985 2.985 0 0 0 1.032-3.385Zm-3.487-1.165a.377.377 0 0 1-.175-.044 2.037 2.037 0 0 0-1.423-.234c-.212.04-.412.114-.595.215l-.008.006c-.012.004-.022.011-.032.018a.369.369 0 0 1-.37-.634l.036-.022a2.794 2.794 0 0 1 2.735 0l.036.022a.367.367 0 0 1-.204.673Z"/><path fill="#E6E6E6" d="M38.08 13.928c-.021-.02-.043-.037-.065-.056a5.998 5.998 0 0 1-1.266-1.536l-.009-.018h-.001a5.91 5.91 0 0 1-.72-2.15c-.004-.047-.01-.093-.015-.14v-.009a1.514 1.514 0 0 0-.327-.725l-.048-.054a10.43 10.43 0 0 0-5.301-3.198 6.574 6.574 0 0 1 .81-3.484.376.376 0 0 0-.332-.557h-.026a13.705 13.705 0 0 0-6.965 3.33 13.792 13.792 0 0 0-2.85 3.56 12.616 12.616 0 0 0-1.722-.764 12.859 12.859 0 0 0-5.536-.697 12.765 12.765 0 0 0-4.165 1.105C5.379 10.437 2.405 14.49 2.039 19.27c-.026.33-.039.665-.039 1v3.233c0 .56.083 1.098.237 1.605a5.55 5.55 0 0 0 2.596 3.226c.76.427 1.63.68 2.56.704h8.212a1.623 1.623 0 0 1 .243 0c.013 0 .026 0 .04.003.021.001.042.002.064.005a1.941 1.941 0 0 1 1.706 1.702 1.291 1.291 0 0 1 .014.23l-.001.036v.004a1.95 1.95 0 0 0 .918 1.61 1.94 1.94 0 0 0 2.955-1.514.943.943 0 0 0 .004-.077v-.108c0-.662-.11-1.3-.313-1.891a5.816 5.816 0 0 0-1.421-2.254 5.815 5.815 0 0 0-4.087-1.676H8.88c-.016.002-.033.002-.05.002-.016 0-.033 0-.05-.002a2.917 2.917 0 0 1-2.87-2.918 2.92 2.92 0 0 1 2.92-2.921h26.106a1.52 1.52 0 0 0 1.443-1.05l.02-.07c.091-.294.27-.55.506-.737l.004-.002a.638.638 0 0 1 .058-.044.934.934 0 0 1 .082-.055 2.985 2.985 0 0 0 1.032-3.385Zm-3.487-1.165a.377.377 0 0 1-.175-.044 2.037 2.037 0 0 0-1.423-.234c-.212.04-.412.114-.595.215l-.008.006c-.012.004-.022.011-.032.018a.369.369 0 0 1-.37-.634l.036-.022a2.794 2.794 0 0 1 2.735 0l.036.022a.367.367 0 0 1-.204.673Z"/><path fill="#ff6740" d="M21.005 8.824c.703 3.38 3.635 5.915 7.147 5.915 2.067 0 3.933-.88 5.262-2.292l-.021-.001c-.137 0-.27.013-.398.04a1.942 1.942 0 0 0-.595.214l-.008.006a.11.11 0 0 0-.033.019.369.369 0 0 1-.532-.33c0-.128.064-.239.162-.305l.037-.022a2.789 2.789 0 0 1 1.952-.294 7.519 7.519 0 0 0 1.309-2.898 10.415 10.415 0 0 0-4.96-2.834 6.573 6.573 0 0 1 .81-3.485.376.376 0 0 0-.332-.557c-.008 0-.017 0-.026.002a13.7 13.7 0 0 0-6.965 3.33 13.793 13.793 0 0 0-2.81 3.492Z"/><path fill="#272B30" d="M36.145 15.807a13.641 13.641 0 0 0-.955-.26 16.819 16.819 0 0 0-2.907-.459c-.652-.054-1.306-.027-1.956.023l-.488.054c-.162.023-.32.062-.482.09-.328.04-.636.16-.962.228a7.178 7.178 0 0 1 1.906-.572 8.967 8.967 0 0 1 1.998-.083 11.15 11.15 0 0 1 1.972.313 9.682 9.682 0 0 1 1.874.666Zm-.287.6a13.093 13.093 0 0 0-.983-.115 16.794 16.794 0 0 0-2.944-.022c-.653.043-1.295.167-1.93.313l-.475.126c-.156.047-.308.108-.463.16-.319.089-.605.252-.917.369a7.175 7.175 0 0 1 1.8-.849 8.965 8.965 0 0 1 1.963-.38c.665-.05 1.335-.05 1.997.018a9.73 9.73 0 0 1 1.952.38Zm-.045.699c-.33.026-.659.051-.985.1a16.838 16.838 0 0 0-2.878.614c-.628.183-1.228.442-1.818.723l-.435.225c-.143.08-.278.172-.418.257-.293.155-.537.376-.817.557a7.174 7.174 0 0 1 1.575-1.217 8.939 8.939 0 0 1 1.835-.794 11.13 11.13 0 0 1 1.953-.415 9.683 9.683 0 0 1 1.988-.05Z"/><path fill="#F27BAA" d="M38.015 13.872a6.04 6.04 0 0 1-.32-.3.968.968 0 0 0 .5 1.796h.015c.006-.041.016-.08.02-.122a2.95 2.95 0 0 0-.15-1.319l-.065-.055Z"/><path fill="#fff" d="M29.993 6.54c0 .046 0 .092.002.138a10.408 10.408 0 0 0-3.166 2.017 5.556 5.556 0 0 1 4.345-6.23 7.584 7.584 0 0 0-1.18 4.075Z"/><path fill="#ff6740" d="M19.814 26.785a5.844 5.844 0 0 0-.404-.36 4.512 4.512 0 0 0-3.245 2.66 1.942 1.942 0 0 1 1.507 1.893l-.001.037v.004a1.955 1.955 0 0 0 .918 1.611 1.94 1.94 0 0 0 2.959-1.592v-.108c0-.662-.11-1.3-.313-1.891a5.831 5.831 0 0 0-1.421-2.255Z"/></svg> \ No newline at end of file diff --git a/Website/media/connector-icons/mangakatana.png b/Website/media/connector-icons/mangakatana.png new file mode 100644 index 0000000..2d3e044 Binary files /dev/null and b/Website/media/connector-icons/mangakatana.png differ diff --git a/Website/media/connector-icons/mangalife.png b/Website/media/connector-icons/mangalife.png new file mode 100644 index 0000000..af14c64 Binary files /dev/null and b/Website/media/connector-icons/mangalife.png differ diff --git a/Website/media/connector-icons/manganato.png b/Website/media/connector-icons/manganato.png new file mode 100644 index 0000000..0df9c28 Binary files /dev/null and b/Website/media/connector-icons/manganato.png differ diff --git a/Website/media/connector-icons/mangasee.png b/Website/media/connector-icons/mangasee.png new file mode 100644 index 0000000..7965217 Binary files /dev/null and b/Website/media/connector-icons/mangasee.png differ diff --git a/Website/media/connector-icons/mangaworld.png b/Website/media/connector-icons/mangaworld.png new file mode 100644 index 0000000..04b30e5 Binary files /dev/null and b/Website/media/connector-icons/mangaworld.png differ diff --git a/Website/media/connector-icons/ntfy.svg b/Website/media/connector-icons/ntfy.svg new file mode 100644 index 0000000..8530901 --- /dev/null +++ b/Website/media/connector-icons/ntfy.svg @@ -0,0 +1,40 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="50mm" height="50mm" viewBox="0 0 50 50"> + <defs> + <linearGradient id="b"> + <stop offset="0" style="stop-color:#348878;stop-opacity:1"/> + <stop offset="1" style="stop-color:#52bca6;stop-opacity:1"/> + </linearGradient> + <linearGradient id="a"> + <stop offset="0" style="stop-color:#348878;stop-opacity:1"/> + <stop offset="1" style="stop-color:#56bda8;stop-opacity:1"/> + </linearGradient> + <linearGradient xlink:href="#a" id="e" x1="160.722" x2="168.412" y1="128.533" y2="134.326" gradientTransform="matrix(3.74959 0 0 3.74959 -541.79 -387.599)" gradientUnits="userSpaceOnUse"/> + <linearGradient xlink:href="#b" id="c" x1=".034" x2="50.319" y1="0" y2="50.285" gradientTransform="matrix(.99434 0 0 .99434 -.034 0)" gradientUnits="userSpaceOnUse"/> + <filter id="d" width="1.176" height="1.211" x="-.076" y="-.092" style="color-interpolation-filters:sRGB"> + <feFlood flood-color="#fff" flood-opacity=".192" result="flood"/> + <feComposite in="flood" in2="SourceGraphic" operator="in" result="composite1"/> + <feGaussianBlur in="composite1" result="blur" stdDeviation="4"/> + <feOffset dx="3" dy="2.954" result="offset"/> + <feComposite in="SourceGraphic" in2="offset" result="composite2"/> + </filter> + </defs> + <g style="display:inline"> + <path d="M0 0h50v50H0z" style="fill:url(#c);fill-opacity:1;stroke:none;stroke-width:.286502;stroke-linejoin:bevel"/> + </g> + <g style="display:inline"> + <path d="M50.4 46.883c-9.168 0-17.023 7.214-17.023 16.387v.007l.09 71.37-2.303 16.992 31.313-8.319h77.841c9.17 0 17.024-7.224 17.024-16.396V63.27c0-9.17-7.85-16.383-17.016-16.387h-.008zm0 11.566h89.926c3.222.004 5.45 2.347 5.45 4.82v63.655c0 2.475-2.232 4.82-5.457 4.82h-79.54l-15.908 4.807.162-.938-.088-72.343c0-2.476 2.23-4.82 5.455-4.82z" style="color:#fff;display:inline;fill:#fff;stroke:none;stroke-width:1.93113;-inkscape-stroke:none;filter:url(#d)" transform="scale(.26458)"/> + </g> + <g style="display:inline"> + <path d="M88.2 95.309H64.92c-1.601 0-2.91 1.236-2.91 2.746l.022 18.602-.435 2.506 6.231-1.881H88.2c1.6 0 2.91-1.236 2.91-2.747v-16.48c0-1.51-1.31-2.746-2.91-2.746z" style="color:#fff;fill:url(#e);stroke:none;stroke-width:2.49558;-inkscape-stroke:none" transform="translate(-51.147 -81.516)"/> + <path d="M50.4 46.883c-9.168 0-17.023 7.214-17.023 16.387v.007l.09 71.37-2.303 16.992 31.313-8.319h77.841c9.17 0 17.024-7.224 17.024-16.396V63.27c0-9.17-7.85-16.383-17.016-16.387h-.008zm0 11.566h89.926c3.222.004 5.45 2.347 5.45 4.82v63.655c0 2.475-2.232 4.82-5.457 4.82h-79.54l-15.908 4.807.162-.938-.088-72.343c0-2.476 2.23-4.82 5.455-4.82z" style="color:#fff;fill:#fff;stroke:none;stroke-width:1.93113;-inkscape-stroke:none" transform="scale(.26458)"/> + <g style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill:#fff;stroke:none;stroke-width:.525121"> + <path d="M62.57 116.77v-1.312l3.28-1.459q.159-.068.306-.102.158-.045.283-.068l.271-.022v-.09q-.136-.012-.271-.046-.125-.023-.283-.057-.147-.045-.306-.113l-3.28-1.459v-1.323l5.068 2.319v1.413z" style="color:#fff;-inkscape-font-specification:"JetBrains Mono, Bold";fill:#fff;stroke:none;-inkscape-stroke:none" transform="matrix(1.45366 0 0 1.72815 -75.122 -171.953)"/> + <path d="M62.309 110.31v1.903l3.437 1.53.022.007-.022.008-3.437 1.53v1.892l.37-.17 5.221-2.39v-1.75zm.525.817 4.541 2.08v1.076l-4.541 2.078v-.732l3.12-1.389.003-.002a1.56 1.56 0 0 1 .258-.086h.006l.008-.002c.094-.027.176-.047.246-.06l.498-.041v-.574l-.24-.02a1.411 1.411 0 0 1-.231-.04l-.008-.001-.008-.002a9.077 9.077 0 0 1-.263-.053 2.781 2.781 0 0 1-.266-.097l-.004-.002-3.119-1.39z" + style="color:#fff;-inkscape-font-specification:"JetBrains Mono, Bold";fill:#fff;stroke:none;-inkscape-stroke:none" transform="matrix(1.45366 0 0 1.72815 -75.122 -171.953)"/> + </g> + <g style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill:#fff;stroke:none;stroke-width:.525121"> + <path d="M69.171 117.754h5.43v1.278h-5.43Z" style="color:#fff;-inkscape-font-specification:"JetBrains Mono, Bold";fill:#fff;stroke:none;-inkscape-stroke:none" transform="matrix(1.44935 0 0 1.66414 -74.104 -166.906)"/> + <path d="M68.908 117.492v1.802h5.955v-1.802zm.526.524h4.904v.754h-4.904z" style="color:#fff;-inkscape-font-specification:"JetBrains Mono, Bold";fill:#fff;stroke:none;-inkscape-stroke:none" transform="matrix(1.44935 0 0 1.66414 -74.104 -166.906)"/> + </g> + </g> +</svg> diff --git a/Website/media/link.svg b/Website/media/link.svg new file mode 100644 index 0000000..e73cfdd --- /dev/null +++ b/Website/media/link.svg @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"> + <title> + external link + </title> + <path fill="#fff" d="M6 1h5v5L8.86 3.85 4.7 8 4 7.3l4.15-4.16L6 1Z M2 3h2v1H2v6h6V8h1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z"/> +</svg> diff --git a/Website/media/queue.svg b/Website/media/queue.svg deleted file mode 100644 index 30b9620..0000000 --- a/Website/media/queue.svg +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> -<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none"> - <g fill="#000000"> - <path d="M2.23 2.674a.75.75 0 00-.96 1.152L3.578 5.75 1.27 7.674a.75.75 0 00.96 1.152l3-2.5a.75.75 0 000-1.152l-3-2.5zM8.25 5a.75.75 0 000 1.5h6a.75.75 0 000-1.5h-6zM5.5 9.25a.75.75 0 01.75-.75h8a.75.75 0 010 1.5h-8a.75.75 0 01-.75-.75zM6.25 12a.75.75 0 000 1.5h8a.75.75 0 000-1.5h-8z"/> - </g> - </svg> \ No newline at end of file diff --git a/Website/media/running.svg b/Website/media/running.svg deleted file mode 100644 index ddde0a6..0000000 --- a/Website/media/running.svg +++ /dev/null @@ -1,53 +0,0 @@ -<?xml version="1.0" encoding="iso-8859-1"?> -<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg fill="#000000" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - width="800px" height="800px" viewBox="0 0 235.504 235.504" - xml:space="preserve"> -<g> - <g> - <path d="M195.209,81.456l-49.227-0.15c0.737-0.886,1.351-1.868,2.284-2.583c3.282-2.497,3.911-7.166,1.427-10.438 - c-2.501-3.266-7.161-3.919-10.443-1.423c-4.873,3.715-8.388,8.704-10.255,14.389l-22.191-0.064 - c-9.508,0-19.588,7.398-22.938,16.851l-16.877,47.479c-1.775,5.013-1.338,9.966,1.207,13.568 - c2.412,3.427,6.384,5.318,11.187,5.358l45.126,0.136c-1.509,5.186-4.701,9.622-9.352,12.424 - c-4.891,2.957-10.636,3.814-16.172,2.444c-3.994-0.998-8.031,1.442-9.027,5.418c-0.99,4.012,1.445,8.035,5.432,9.032 - c2.927,0.738,5.879,1.091,8.808,1.091c6.516,0,12.93-1.788,18.645-5.23c8.312-5.013,14.172-12.979,16.484-22.409 - c0.232-0.905,0.232-1.823,0.124-2.713l28.296,0.092h0.049c2.925,0,5.854-0.89,8.684-2.147c0.2,0.493,0.32,1.014,0.661,1.471 - c3.335,4.677,4.629,10.343,3.688,15.993c-0.95,5.627-4.028,10.536-8.688,13.862c-3.351,2.376-4.14,7.037-1.755,10.379 - c1.466,2.04,3.751,3.122,6.062,3.122c1.491,0,3.006-0.429,4.312-1.367c7.919-5.61,13.16-13.966,14.771-23.52 - c1.603-9.565-0.613-19.203-6.28-27.122c-0.48-0.693-1.134-1.19-1.779-1.659c1.318-1.831,2.501-3.763,3.238-5.854l16.863-47.464 - c1.795-5.018,1.351-9.969-1.194-13.58C203.954,83.387,200.015,81.47,195.209,81.456z M201.979,98.405l-16.868,47.464 - c-0.981,2.757-2.941,5.214-5.213,7.329c-0.337,0.16-0.706,0.229-1.026,0.465c-0.673,0.485-1.182,1.122-1.639,1.747 - c-2.962,1.996-6.288,3.339-9.434,3.339v2.989l-0.044-2.989l-33.194-0.101c-0.232-0.076-0.424-0.261-0.661-0.324 - c-1.435-0.353-2.805-0.145-4.095,0.309l-29.768-0.101l1.192-3.358c0.549-1.547-0.269-3.25-1.813-3.795 - c-1.521-0.553-3.25,0.24-3.799,1.804l-1.899,5.334l-14.318-0.044c-2.805,0-5.063-0.998-6.336-2.813 - c-1.437-2.032-1.603-4.921-0.463-8.144l16.877-47.478c2.48-6.979,10.417-12.868,17.356-12.868l12.217,0.038l-1.963,5.536 - c-0.555,1.549,0.262,3.25,1.805,3.797c0.331,0.12,0.661,0.174,0.998,0.174c1.227,0,2.372-0.768,2.793-1.986l2.497-7.019 - c0.064-0.164-0.048-0.322-0.016-0.487h2.512c-0.905,7.758,1.163,15.42,5.947,21.638c5.903,7.687,14.852,11.726,23.873,11.726 - c6.371,0,12.771-2.001,18.186-6.129c3.266-2.488,3.911-7.167,1.426-10.441c-2.508-3.267-7.161-3.901-10.455-1.415 - c-6.612,5.056-16.146,3.775-21.223-2.809c-2.445-3.194-3.487-7.133-2.958-11.117c0.061-0.503,0.353-0.916,0.481-1.402 - l52.216,0.156c2.806,0,5.054,1.004,6.324,2.811C202.928,92.241,203.105,95.223,201.979,98.405z"/> - <path d="M107.997,127.194c-1.531-0.553-3.248,0.244-3.799,1.791l-4.302,12.099c-0.551,1.543,0.265,3.242,1.813,3.795 - c0.331,0.116,0.659,0.16,0.998,0.16c1.214,0,2.372-0.765,2.801-1.976l4.294-12.099 - C110.369,129.446,109.551,127.728,107.997,127.194z"/> - <path d="M116.6,103.014c-1.529-0.541-3.25,0.252-3.805,1.805l-4.298,12.088c-0.547,1.547,0.261,3.252,1.799,3.799 - c0.329,0.12,0.659,0.172,1,0.172c1.222,0,2.368-0.769,2.809-1.983l4.294-12.09C118.955,105.268,118.139,103.555,116.6,103.014z"/> - <path d="M232.527,90.428l-14.896-0.038l0,0c-1.639,0-2.974,1.327-2.997,2.976c0,1.639,1.342,2.981,2.981,2.989l14.896,0.042l0,0 - c1.643,0,2.978-1.331,2.993-2.979C235.504,91.763,234.17,90.436,232.527,90.428z"/> - <path d="M220.333,80.436c0.629,0,1.242-0.188,1.771-0.583l11.994-8.83c1.326-0.974,1.611-2.842,0.645-4.168 - c-0.965-1.327-2.845-1.611-4.163-0.637l-11.998,8.833c-1.323,0.974-1.607,2.841-0.642,4.167 - C218.513,80.003,219.418,80.436,220.333,80.436z"/> - <path d="M209.152,56.279c-1.547-0.549-3.25,0.269-3.787,1.805l-4.997,14.036c-0.537,1.547,0.26,3.252,1.803,3.807 - c0.337,0.12,0.674,0.172,0.994,0.172c1.242,0,2.385-0.757,2.821-1.986l4.985-14.036C211.516,58.541,210.695,56.846,209.152,56.279 - z"/> - <path d="M17.587,100.894h55.208c1.641,0,2.976-1.343,2.976-2.981c0-1.641-1.334-2.988-2.976-2.988H17.587 - c-1.641,0-2.988,1.338-2.988,2.988C14.599,99.559,15.946,100.894,17.587,100.894z"/> - <path d="M68.471,119.328c0-1.641-1.345-2.987-2.986-2.987H10.283c-1.639,0-2.981,1.338-2.981,2.987 - c0,1.639,1.342,2.974,2.981,2.974h55.202C67.119,122.301,68.471,120.967,68.471,119.328z"/> - <path d="M58.188,137.758H2.974c-1.641,0-2.974,1.335-2.974,2.989c0,1.64,1.333,2.974,2.974,2.974h55.214 - c1.639,0,2.981-1.334,2.981-2.974C61.162,139.093,59.827,137.758,58.188,137.758z"/> - <path d="M169.611,28.097c11.821,0,21.403,9.584,21.403,21.41c0,11.82-9.582,21.408-21.403,21.408 - c-11.822,0-21.412-9.588-21.412-21.408C148.199,37.681,157.789,28.097,169.611,28.097z"/> - </g> -</g> -</svg> \ No newline at end of file diff --git a/Website/media/tasks.svg b/Website/media/tasks.svg deleted file mode 100644 index 6e64e66..0000000 --- a/Website/media/tasks.svg +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> -<svg fill="#000000" height="800px" width="800px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve"> -<g id="task"> - <path d="M4,23.4l-3.7-3.7l1.4-1.4L4,20.6l4.3-4.3l1.4,1.4L4,23.4z M24,21H12v-2h12V21z M4,15.4l-3.7-3.7l1.4-1.4L4,12.6l4.3-4.3 - l1.4,1.4L4,15.4z M24,13H12v-2h12V13z M4,7.4L0.3,3.7l1.4-1.4L4,4.6l4.3-4.3l1.4,1.4L4,7.4z M24,5H12V3h12V5z"/> -</g> -</svg> \ No newline at end of file diff --git a/Website/modules/Footer.tsx b/Website/modules/Footer.tsx new file mode 100644 index 0000000..d0d9a44 --- /dev/null +++ b/Website/modules/Footer.tsx @@ -0,0 +1,48 @@ +import React, {useEffect} from 'react'; +import '../styles/footer.css'; +import Job from './Job'; +import Icon from '@mdi/react'; +import { mdiRun, mdiCounter, mdiEyeCheck, mdiTrayFull } from '@mdi/js'; +import QueuePopUp from "./QueuePopUp"; + +export default function Footer({connectedToBackend, apiUri} : {connectedToBackend: boolean, apiUri: string}) { + const [MonitoringJobsCount, setMonitoringJobsCount] = React.useState(0); + const [AllJobsCount, setAllJobsCount] = React.useState(0); + const [RunningJobsCount, setRunningJobsCount] = React.useState(0); + const [StandbyJobsCount, setStandbyJobsCount] = React.useState(0); + const [countUpdateInterval, setCountUpdateInterval] = React.useState<number>(); + + function UpdateBackendState(){ + Job.GetMonitoringJobs(apiUri).then((jobs) => setMonitoringJobsCount(jobs.length)); + Job.GetAllJobs(apiUri).then((jobs) => setAllJobsCount(jobs.length)); + Job.GetRunningJobs(apiUri).then((jobs) => setRunningJobsCount(jobs.length)); + Job.GetStandbyJobs(apiUri).then((jobs) => setStandbyJobsCount(jobs.length)); + } + + useEffect(() => { + if(connectedToBackend){ + UpdateBackendState(); + setCountUpdateInterval(setInterval(() => { + UpdateBackendState(); + }, 2000)); + }else{ + clearInterval(countUpdateInterval); + setCountUpdateInterval(undefined); + } + }, [connectedToBackend]); + + return ( + <footer> + <div className="statusBadge" ><Icon path={mdiEyeCheck} size={1}/> <span>{MonitoringJobsCount}</span></div> + <span>+</span> + <QueuePopUp connectedToBackend={connectedToBackend} apiUri={apiUri}> + <div className="statusBadge hoverHand"><Icon path={mdiRun} size={1}/> <span>{RunningJobsCount}</span> + </div> + <span>+</span> + <div className="statusBadge hoverHand"><Icon path={mdiTrayFull} size={1}/><span>{StandbyJobsCount}</span></div> + </QueuePopUp> + <span>=</span> + <div className="statusBadge"><Icon path={mdiCounter} size={1}/> <span>{AllJobsCount}</span></div> + <p id="madeWith">Made with Blåhaj 🦈</p> + </footer>) +} \ No newline at end of file diff --git a/Website/modules/Header.tsx b/Website/modules/Header.tsx new file mode 100644 index 0000000..bdd76ea --- /dev/null +++ b/Website/modules/Header.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import '../styles/header.css' +import Settings from "./Settings"; +import IFrontendSettings from "./interfaces/IFrontendSettings"; + +export default function Header({backendConnected, apiUri, settings, changeSettings} : {backendConnected: boolean, apiUri: string, settings: IFrontendSettings, changeSettings(settings: IFrontendSettings): void}){ + return ( + <header> + <div id="titlebox"> + <img alt="website image is Blahaj" src="../media/blahaj.png"/> + <span>Tranga</span> + </div> + <Settings settings={settings} changeSettings={changeSettings} backendConnected={backendConnected} apiUri={apiUri}/> + </header>) +} \ No newline at end of file diff --git a/Website/modules/Job.tsx b/Website/modules/Job.tsx new file mode 100644 index 0000000..09709b7 --- /dev/null +++ b/Website/modules/Job.tsx @@ -0,0 +1,142 @@ +import {deleteData, getData, postData} from '../App'; +import IJob from "./interfaces/IJob"; +import IProgressToken from "./interfaces/IProgressToken"; + +export default class Job +{ + static IntervalStringFromDate(date: Date) : string { + let x = new Date(date); + return `${x.getDay()}.${x.getHours()}:${x.getMinutes()}:${x.getSeconds()}`; + } + + static async GetAllJobs(apiUri: string): Promise<string[]> { + //console.info("Getting all Jobs"); + return getData(`${apiUri}/v2/Jobs`) + .then((json) => { + //console.info("Got all Jobs"); + const ret = json as string[]; + //console.debug(ret); + return (ret); + }); + } + + static async GetRunningJobs(apiUri: string): Promise<string[]> { + //console.info("Getting all running Jobs"); + return getData(`${apiUri}/v2/Jobs/Running`) + .then((json) => { + //console.info("Got all running Jobs"); + const ret = json as string[]; + //console.debug(ret); + return (ret); + }); + } + + static async GetWaitingJobs(apiUri: string): Promise<string[]> { + //console.info("Getting all waiting Jobs"); + return getData(`${apiUri}/v2/Jobs/Waiting`) + .then((json) => { + //console.info("Got all waiting Jobs"); + const ret = json as string[]; + //console.debug(ret); + return (ret); + }); + } + + static async GetStandbyJobs(apiUri: string): Promise<string[]> { + //console.info("Getting all standby Jobs"); + return getData(`${apiUri}/v2/Jobs/Standby`) + .then((json) => { + //console.info("Got all standby Jobs"); + const ret = json as string[]; + //console.debug(ret); + return (ret); + }); + } + + static async GetMonitoringJobs(apiUri: string): Promise<string[]> { + //console.info("Getting all monitoring Jobs"); + return getData(`${apiUri}/v2/Jobs/Monitoring`) + .then((json) => { + //console.info("Got all monitoring Jobs"); + const ret = json as string[]; + //console.debug(ret); + return (ret); + }); + } + + static async GetJob(apiUri: string, jobId: string): Promise<IJob>{ + if(jobId === undefined || jobId === null || jobId.length < 1) { + console.error(`JobId was not provided`); + return Promise.reject(); + } + //console.info(`Getting Job ${jobId}`); + return getData(`${apiUri}/v2/Job/${jobId}`) + .then((json) => { + //console.info(`Got Job ${jobId}`); + const ret = json as IJob; + //console.debug(ret); + return (ret); + }); + } + + static async GetJobs(apiUri: string, jobIds: string[]): Promise<IJob[]> { + if(jobIds === undefined || jobIds === null || jobIds.length < 1) { + console.error(`JobIds was not provided`); + return Promise.reject(); + } + let reqStr = jobIds.join(","); + //console.info(`Getting Jobs ${reqStr}`); + return getData(`${apiUri}/v2/Job?jobIds=${reqStr}`) + .then((json) => { + //console.info(`Got Jobs ${reqStr}`); + const ret = json as IJob[]; + //console.debug(ret); + return (ret); + }); + } + + static async GetJobProgress(apiUri: string, jobId: string): Promise<IProgressToken> { + //console.info(`Getting Job ${jobId} Progress`); + return getData(`${apiUri}/v2/Job/${jobId}/Progress`) + .then((json) => { + //console.info(`Got Job ${jobId} Progress`); + const ret = json as IProgressToken; + //console.debug(ret); + return (ret); + }); + } + + static async CreateJobDateInterval(apiUri: string, internalId: string, jobType: string, interval: Date) : Promise<null> { + return this.CreateJob(apiUri, internalId, jobType, this.IntervalStringFromDate(interval)); + } + + static async CreateJob(apiUri: string, internalId: string, jobType: string, interval: string): Promise<null> { + const validate = /(?:[0-9]{1,2}\.)?[0-9]{1,2}:[0-9]{1,2}(?::[0-9]{1,2})?/ + //console.info(`Creating Job for Manga ${internalId} at ${interval} interval`); + if(!validate.test(interval)){ + console.error("Interval was in incorrect format."); + return Promise.reject(); + } + const data = { + internalId: internalId, + interval: interval + }; + return postData(`${apiUri}/v2/Job/Create/${jobType}`, data) + .then((json) => { + //console.info(`Created Job for Manga ${internalId} at ${interval} interval`); + return null; + }); + } + + static DeleteJob(apiUri: string, jobId: string) : Promise<void> { + return deleteData(`${apiUri}/v2/Job/${jobId}`); + } + + static StartJob(apiUri: string, jobId: string) : Promise<object> { + return postData(`${apiUri}/v2/Job/${jobId}/StartNow`, {}); + } + + static CancelJob(apiUri: string, jobId: string) : Promise<object> { + return postData(`${apiUri}/v2/Job/${jobId}/Cancel`, {}); + } +} \ No newline at end of file diff --git a/Website/modules/LibraryConnector.tsx b/Website/modules/LibraryConnector.tsx new file mode 100644 index 0000000..219e48a --- /dev/null +++ b/Website/modules/LibraryConnector.tsx @@ -0,0 +1,135 @@ +import {deleteData, getData, postData} from "../App"; +import ILibraryConnector from "./interfaces/ILibraryConnector"; + +export default abstract class LibraryConnector +{ + static async GetLibraryConnectors(apiUri: string) : Promise<ILibraryConnector[]> { + //console.info("Getting Library Connectors"); + return getData(`${apiUri}/v2/LibraryConnector`) + .then((json) => { + //console.info("Got Library Connectors"); + const ret = json as ILibraryConnector[]; + //console.debug(ret); + return (ret); + }) + .catch(Promise.reject); + } + public url = ""; + + protected constructor(url: string) { + this.url = url; + } + + public SetUrl(url: string){ + this.url = url; + } + + public abstract Test(apiUri: string) : Promise<boolean>; + public abstract Reset(apiUri: string) : Promise<boolean>; + public abstract Create(apiUri: string) : Promise<boolean>; + protected abstract CheckConnector() : boolean; + + protected async TestConnector(apiUri: string, connectorType: string, data: object): Promise<boolean> { + if(!this.CheckConnector()) + return Promise.reject("Connector not fully configured."); + //console.info(`Testing ${connectorType}`); + return postData(`${apiUri}/v2/LibraryConnector/${connectorType}/Test`, data) + .then((json) => { + //console.info(`Successfully tested ${connectorType}`); + return true; + }) + .catch(Promise.reject); + } + + protected async ResetConnector(apiUri: string, connectorType: string): Promise<boolean> { + //console.info(`Deleting ${connectorType}`); + return deleteData(`${apiUri}/v2/LibraryConnector/${connectorType}`) + .then((json) => { + //console.info(`Successfully deleted ${connectorType}`); + return true; + }) + .catch(Promise.reject); + } + + protected async CreateConnector(apiUri: string, connectorType: string, data: object): Promise<boolean> { + if(!this.CheckConnector()) + return Promise.reject("Connector not fully configured."); + //console.info(`Creating ${connectorType}`); + return postData(`${apiUri}/v2/LibraryConnector/${connectorType}`, data) + .then((json) => { + //console.info(`Successfully created ${connectorType}`); + return true; + }) + .catch(Promise.reject); + } +} + +export class Komga extends LibraryConnector +{ + private username = ""; + private password = ""; + + constructor({url, username, password} : {url: string, username: string, password: string}){ + super(url); + this.username = username; + this.password = password; + } + + public async Test(apiUri: string) : Promise<boolean> { + return this.TestConnector(apiUri, "Komga", {url: this.url, username: this.username, password: this.password}).then(() => true).catch(() => false); + } + + public async Reset(apiUri: string) : Promise<boolean> { + return this.ResetConnector(apiUri, "Komga").then(() => true).catch(() => false); + } + + public async Create(apiUri: string) : Promise<boolean> { + return this.CreateConnector(apiUri, "Komga", {url: this.url, username: this.username, password: this.password}).then(() => true).catch(() => false); + } + + protected CheckConnector(): boolean { + try{ + new URL(this.url) + }catch{ + return false; + } + if(this.username.length < 1 || this.password.length < 1) + return false; + return true; + } +} + +export class Kavita extends LibraryConnector +{ + private username = ""; + private password = ""; + + constructor({url, username, password} : {url: string, username: string, password: string}) { + super(url); + this.username = username; + this.password = password; + } + + public async Test(apiUri: string) : Promise<boolean> { + return this.TestConnector(apiUri, "Kavita", {url: this.url, username: this.username, password: this.password}).then(() => true).catch(() => false); + } + + public async Reset(apiUri: string) : Promise<boolean> { + return this.ResetConnector(apiUri, "Kavita").then(() => true).catch(() => false); + } + + public async Create(apiUri: string) : Promise<boolean> { + return this.CreateConnector(apiUri, "Kavita", {url: this.url, username: this.username, password: this.password}).then(() => true).catch(() => false); + } + + protected CheckConnector(): boolean { + try{ + new URL(this.url) + }catch{ + return false; + } + if(this.username.length < 1 || this.password.length < 1) + return false; + return true; + } +} \ No newline at end of file diff --git a/Website/modules/Manga.tsx b/Website/modules/Manga.tsx new file mode 100644 index 0000000..2e0e419 --- /dev/null +++ b/Website/modules/Manga.tsx @@ -0,0 +1,54 @@ +import IManga from './interfaces/IManga'; +import { getData } from '../App'; + +export default class Manga +{ + static async GetAllManga(apiUri: string): Promise<IManga[]> { + //console.info("Getting all Manga"); + return getData(`${apiUri}/v2/Mangas`) + .then((json) => { + //console.info("Got all Manga"); + const ret = json as IManga[]; + //console.debug(ret); + return (ret); + }); + } + + static async SearchManga(apiUri: string, name: string): Promise<IManga[]> { + //console.info(`Getting Manga ${name} from all Connectors`); + return await getData(`${apiUri}/v2/Manga/Search?title=${name}`) + .then((json) => { + //console.info(`Got Manga ${name}`); + const ret = json as IManga[]; + //console.debug(ret); + return (ret); + }); + } + + static async GetMangaById(apiUri: string, internalId: string): Promise<IManga> { + //console.info(`Getting Manga ${internalId}`); + return await getData(`${apiUri}/v2/Manga/${internalId}`) + .then((json) => { + //console.info(`Got Manga ${internalId}`); + const ret = json as IManga; + //console.debug(ret); + return (ret); + }); + } + + static async GetMangaByIds(apiUri: string, internalIds: string[]): Promise<IManga[]> { + //console.debug(`Getting Mangas ${internalIds.join(",")}`); + return await getData(`${apiUri}/v2/Manga?mangaIds=${internalIds.join(",")}`) + .then((json) => { + //console.debug(`Got Manga ${internalIds.join(",")}`); + const ret = json as IManga[]; + //console.debug(ret); + return (ret); + }); + } + + static GetMangaCoverUrl(apiUri: string, internalId: string, ref: HTMLElement): string { + //console.debug(`Getting Manga Cover-Url ${internalId}`); + return `${apiUri}/v2/Manga/${internalId}/Cover?dimensions=${ref.clientWidth*1.5}x${ref.clientHeight*1.5}`; + } +} \ No newline at end of file diff --git a/Website/modules/MangaConnector.tsx b/Website/modules/MangaConnector.tsx new file mode 100644 index 0000000..25637f0 --- /dev/null +++ b/Website/modules/MangaConnector.tsx @@ -0,0 +1,33 @@ +import IMangaConnector from './interfaces/IMangaConnector'; +import IManga from './interfaces/IManga'; +import { getData } from '../App'; + +export class MangaConnector +{ + static async GetAllConnectors(): Promise<IMangaConnector[]> { + //console.info("Getting all MangaConnectors"); + return getData("http://127.0.0.1:6531/v2/Connector/Types") + .then((json) => { + //console.info("Got all MangaConnectors"); + return (json as IMangaConnector[]); + }); + } + + static async GetMangaFromConnectorByTitle(connector: IMangaConnector, name: string): Promise<IManga[]> { + //console.info(`Getting Manga ${name}`); + return await getData(`http://127.0.0.1:6531/v2/Connector/${connector.name}/GetManga?title=${name}`) + .then((json) => { + //console.info(`Got Manga ${name}`); + return (json as IManga[]); + }); + } + + static async GetMangaFromConnectorByUrl(connector: IMangaConnector, url: string): Promise<IManga> { + //console.info(`Getting Manga ${url}`); + return await getData(`http://127.0.0.1:6531/v2/Connector/${connector.name}/GetManga?url=${url}`) + .then((json) => { + //console.info(`Got Manga ${url}`); + return (json as IManga); + }); + } +} \ No newline at end of file diff --git a/Website/modules/MonitorJobsList.tsx b/Website/modules/MonitorJobsList.tsx new file mode 100644 index 0000000..2d1c11b --- /dev/null +++ b/Website/modules/MonitorJobsList.tsx @@ -0,0 +1,100 @@ +import React, {EventHandler, MouseEventHandler, ReactElement, useEffect, useState} from 'react'; +import Job from './Job'; +import '../styles/monitorMangaList.css'; +import IJob from "./interfaces/IJob"; +import IManga, {CoverCard} from "./interfaces/IManga"; +import Manga from './Manga'; +import '../styles/MangaCoverCard.css' +import Icon from '@mdi/react'; +import { mdiTrashCanOutline, mdiPlayBoxOutline } from '@mdi/js'; + +export default function MonitorJobsList({onStartSearch, onJobsChanged, connectedToBackend, apiUri, updateList} : {onStartSearch() : void, onJobsChanged: EventHandler<any>, connectedToBackend: boolean, apiUri: string, updateList: Date}) { + const [MonitoringJobs, setMonitoringJobs] = useState<IJob[]>([]); + const [AllManga, setAllManga] = useState<IManga[]>([]); + const [joblistUpdateInterval, setJoblistUpdateInterval] = React.useState<number>(); + + useEffect(() => { + //console.debug("Updating display list."); + //Remove all Manga that are not associated with a Job + setAllManga(AllManga.filter(manga => MonitoringJobs.find(job => job.mangaInternalId == manga.internalId))); + //Fetch Manga that are missing (from Jobs) + if(MonitoringJobs === null) + return; + MonitoringJobs.forEach(job => { + if(AllManga.find(manga => manga.internalId == job.mangaInternalId)) + return; + Manga.GetMangaById(apiUri, job.mangaInternalId != undefined ? job.mangaInternalId : job.chapter != undefined ? job.chapter.parentManga.internalId : ""). + then((manga: IManga) => setAllManga([...AllManga, manga])); + }); + }, [MonitoringJobs]); + + useEffect(() => { + if(connectedToBackend){ + UpdateMonitoringJobsList(); + setJoblistUpdateInterval(setInterval(() => { + UpdateMonitoringJobsList(); + }, 1000)); + }else{ + clearInterval(joblistUpdateInterval); + setJoblistUpdateInterval(undefined); + } + }, [connectedToBackend]); + + useEffect(() => { + UpdateMonitoringJobsList(); + }, [updateList]); + + function UpdateMonitoringJobsList(){ + if(!connectedToBackend) + return; + //console.debug("Updating MonitoringJobsList"); + Job.GetMonitoringJobs(apiUri) + .then((jobs) => { + if(jobs.length > 0) + return Job.GetJobs(apiUri, jobs) + return []; + }) + .then((jobs) => setMonitoringJobs(jobs)); + } + + function StartSearchMangaEntry() : ReactElement { + return (<div key="monitorMangaEntry.StartSearch" className="monitorMangaEntry" onClick={onStartSearch}> + <div className="Manga" key="StartSearch.Manga"> + <img src="../media/blahaj.png" alt="Blahaj"></img> + <div> + <p style={{textAlign: "center", width: "100%"}} className="Manga-name">Add new Manga</p> + <p style={{fontSize: "42pt", textAlign: "center"}}>+</p> + </div> + </div> + </div>); + } + + const DeleteJob : MouseEventHandler = (e) => { + const jobId = e.currentTarget.id.slice(e.currentTarget.id.indexOf("-")+1); + //console.info(`Pressed ${e.currentTarget.id} => ${jobId}`); + Job.DeleteJob(apiUri, jobId).then(() => onJobsChanged(jobId)); + } + + const StartJob : MouseEventHandler = (e) => { + const jobId = e.currentTarget.id.slice(e.currentTarget.id.indexOf("-")+1); + //console.info(`Pressed ${e.currentTarget.id} => ${jobId}`); + Job.StartJob(apiUri, jobId); + } + + return ( + <div id="MonitorMangaList"> + {StartSearchMangaEntry()} + {AllManga.map((manga: IManga) => { + const job = MonitoringJobs.find(job => job.mangaInternalId == manga.internalId); + if (job === undefined || job == null) + return <div>Error. Could not find matching job for {manga.internalId}</div> + return <div key={"monitorMangaEntry-" + manga.internalId} className="monitorMangaEntry"> + {CoverCard(apiUri, manga)} + <div className="MangaActionButtons"> + <div id={"Delete-"+job.id} className="DeleteJobButton" onClick={DeleteJob}><Icon path={mdiTrashCanOutline} size={1.5} /></div> + <div id={"Start-"+job.id} className="StartJobNowButton" onClick={StartJob}><Icon path={mdiPlayBoxOutline} size={1.5} /></div> + </div> + </div>; + })} + </div>) +} \ No newline at end of file diff --git a/Website/modules/NotificationConnector.tsx b/Website/modules/NotificationConnector.tsx new file mode 100644 index 0000000..6223653 --- /dev/null +++ b/Website/modules/NotificationConnector.tsx @@ -0,0 +1,164 @@ +import INotificationConnector from "./interfaces/INotificationConnector"; +import {deleteData, getData, postData} from "../App"; + +export default abstract class NotificationConnector { + + static async GetNotificationConnectors(apiUri: string) : Promise<INotificationConnector[]> { + //console.info("Getting Notification Connectors"); + return getData(`${apiUri}/v2/NotificationConnector`) + .then((json) => { + //console.info("Got Notification Connectors"); + const ret = json as INotificationConnector[]; + //console.debug(ret); + return (ret); + }) + .catch(Promise.reject); + } + + protected constructor() { + + } + + public abstract Test(apiUri: string) : Promise<boolean>; + public abstract Reset(apiUri: string) : Promise<boolean>; + public abstract Create(apiUri: string) : Promise<boolean>; + protected abstract CheckConnector() : boolean; + + protected async TestConnector(apiUri: string, connectorType: string, data: object): Promise<boolean> { + if(!this.CheckConnector()) + return Promise.reject("Connector not fully configured."); + //console.info(`Testing ${connectorType}`); + return postData(`${apiUri}/v2/NotificationConnector/${connectorType}/Test`, data) + .then((json) => { + //console.info(`Successfully tested ${connectorType}`); + return true; + }) + .catch(Promise.reject); + } + + protected async ResetConnector(apiUri: string, connectorType: string): Promise<boolean> { + if(!this.CheckConnector()) + return Promise.reject("Connector not fully configured."); + //console.info(`Deleting ${connectorType}`); + return deleteData(`${apiUri}/v2/NotificationConnector/${connectorType}`) + .then((json) => { + //console.info(`Successfully deleted ${connectorType}`); + return true; + }) + .catch(Promise.reject); + } + + protected async CreateConnector(apiUri: string, connectorType: string, data: object): Promise<boolean> { + if(!this.CheckConnector()) + return Promise.reject("Connector not fully configured."); + //console.info(`Creating ${connectorType}`); + return postData(`${apiUri}/v2/NotificationConnector/${connectorType}`, data) + .then((json) => { + //console.info(`Successfully created ${connectorType}`); + return true; + }) + .catch(Promise.reject); + } +} + +export class Gotify extends NotificationConnector +{ + public url = ""; + private appToken = ""; + + constructor({url, appToken} : {url: string, appToken:string}){ + super(); + this.url = url; + this.appToken = appToken; + } + + public async Test(apiUri: string) : Promise<boolean> { + return this.TestConnector(apiUri, "Gotify", {url: this.url, appToken: this.appToken}).then(() => true).catch(() => false); + } + + public async Reset(apiUri: string) : Promise<boolean> { + return this.ResetConnector(apiUri, "Gotify").then(() => true).catch(() => false); + } + + public async Create(apiUri: string) : Promise<boolean> { + return this.CreateConnector(apiUri, "Gotify", {url: this.url, appToken: this.appToken}).then(() => true).catch(() => false); + } + + protected CheckConnector(): boolean { + try{ + new URL(this.url) + }catch{ + return false; + } + if(this.appToken.length < 1 || this.appToken.length < 1) + return false; + return true; + } +} + +export class Lunasea extends NotificationConnector +{ + private webhook = ""; + + constructor({webhook} : {webhook: string}){ + super(); + this.webhook = webhook; + } + + public async Test(apiUri: string) : Promise<boolean> { + return this.TestConnector(apiUri, "LunaSea", {webhook: this.webhook}).then(() => true).catch(() => false); + } + + public async Reset(apiUri: string) : Promise<boolean> { + return this.ResetConnector(apiUri, "LunaSea").then(() => true).catch(() => false); + } + + public async Create(apiUri: string) : Promise<boolean> { + return this.CreateConnector(apiUri, "LunaSea", {webhook: this.webhook}).then(() => true).catch(() => false); + } + + protected CheckConnector(): boolean { + if(this.webhook.length < 1 || this.webhook.length < 1) + return false; + return true; + } +} + +export class Ntfy extends NotificationConnector +{ + public url = ""; + private username = ""; + private password = ""; + public topic:string | undefined = undefined; + + constructor({url, username, password, topic} : {url: string, username: string, password: string, topic : string | undefined}){ + super(); + this.url = url; + this.username = username; + this.password = password; + this.topic = topic; + } + + public async Test(apiUri: string) : Promise<boolean> { + return this.TestConnector(apiUri, "Ntfy", {url: this.url, username: this.username, password: this.password, topic: this.topic}).then(() => true).catch(() => false); + } + + public async Reset(apiUri: string) : Promise<boolean> { + return this.ResetConnector(apiUri, "Ntfy").then(() => true).catch(() => false); + } + + public async Create(apiUri: string) : Promise<boolean> { + return this.CreateConnector(apiUri, "Ntfy", {url: this.url, username: this.username, password: this.password, topic: this.topic}).then(() => true).catch(() => false); + } + + protected CheckConnector(): boolean { + try{ + new URL(this.url) + }catch{ + return false; + } + if(this.username.length < 1 || this.password.length < 1) + return false; + return true; + } +} \ No newline at end of file diff --git a/Website/modules/QueuePopUp.tsx b/Website/modules/QueuePopUp.tsx new file mode 100644 index 0000000..f9e4b57 --- /dev/null +++ b/Website/modules/QueuePopUp.tsx @@ -0,0 +1,120 @@ +import React, {useEffect, useState} from 'react'; +import IJob from "./interfaces/IJob"; +import '../styles/queuePopUp.css'; +import '../styles/popup.css'; +import Job from "./Job"; +import IManga, {QueueItem} from "./interfaces/IManga"; +import Manga from "./Manga"; + +export default function QueuePopUp({connectedToBackend, children, apiUri} : {connectedToBackend: boolean, children: JSX.Element[], apiUri: string}) { + + const [StandbyJobs, setStandbyJobs] = React.useState<IJob[]>([]); + const [StandbyJobsManga, setStandbyJobsManga] = React.useState<IManga[]>([]); + const [RunningJobs, setRunningJobs] = React.useState<IJob[]>([]); + const [RunningJobsManga, setRunningJobsManga] = React.useState<IManga[]>([]); + const [showQueuePopup, setShowQueuePopup] = useState<boolean>(false); + const [queueListInterval, setQueueListInterval] = React.useState<number>(); + + useEffect(() => { + if(!showQueuePopup) + return; + UpdateMonitoringJobsList(); + }, [showQueuePopup]); + + useEffect(() => { + if(connectedToBackend){ + UpdateMonitoringJobsList(); + setQueueListInterval(setInterval(() => { + UpdateMonitoringJobsList(); + }, 2000)); + }else{ + clearInterval(queueListInterval); + setQueueListInterval(undefined); + } + }, [connectedToBackend]); + + function UpdateMonitoringJobsList(){ + Job.GetStandbyJobs(apiUri) + .then((jobs:string[]) => { + if(jobs.length > 0) + return Job.GetJobs(apiUri, jobs); + return []; + }) + .then((jobs:IJob[]) => { + //console.debug("Removing Metadata Jobs"); + //console.log(StandbyJobs) + setStandbyJobs(jobs.filter(job => job.jobType <= 2)); + //console.log(StandbyJobs) + }); + Job.GetRunningJobs(apiUri) + .then((jobs:string[]) => { + if(jobs.length > 0) + return Job.GetJobs(apiUri, jobs); + return []; + }) + .then((jobs:IJob[]) =>{ + //console.debug("Removing Metadata Jobs"); + setRunningJobs(jobs.filter(job => job.jobType <= 2)); + }); + } + + useEffect(() => { + if(StandbyJobs.length < 1) + return; + const mangaIds = StandbyJobs.filter(job => job.jobType<=2).map((job) => job.mangaInternalId != undefined ? job.mangaInternalId : job.chapter != undefined ? job.chapter.parentManga.internalId : ""); + //console.debug(`Waiting mangaIds: ${mangaIds.join(",")}`); + Manga.GetMangaByIds(apiUri, mangaIds) + .then(setStandbyJobsManga); + }, [StandbyJobs]); + + useEffect(() => { + if(RunningJobs.length < 1) + return; + //console.log(RunningJobs); + const mangaIds = RunningJobs.filter(job => job.jobType<=2).map((job) => job.mangaInternalId != undefined ? job.mangaInternalId : job.chapter != undefined ? job.chapter.parentManga.internalId : ""); + //console.debug(`Running mangaIds: ${mangaIds.join(",")}`); + Manga.GetMangaByIds(apiUri, mangaIds) + .then(setRunningJobsManga); + }, [RunningJobs]); + + return (<> + <div onClick={() => setShowQueuePopup(true)}> + {children} + </div> + {showQueuePopup + ? <div className="popup" id="QueuePopUp"> + <div className="popupHeader"> + <h1>Queue Status</h1> + <img alt="Close Search" className="close" src="../media/close-x.svg" + onClick={() => setShowQueuePopup(false)}/> + </div> + <div id="QueuePopUpBody" className="popupBody"> + <div id="RunningJobQueue"> + <h1>Running</h1> + <div className="JobQueue"> + {RunningJobs.map((job: IJob) => { + const manga = RunningJobsManga.find(manga => manga.internalId == job.mangaInternalId || manga.internalId == job.chapter?.parentManga.internalId); + if (manga === undefined || manga === null) + return <div key={"QueueJob-" + job.id}>Error. Could not find matching manga for {job.id}</div> + return QueueItem(apiUri, manga, job, UpdateMonitoringJobsList); + })} + </div> + </div> + <div id="WaitingJobQueue"> + <h1>Standby</h1> + <div className="JobQueue"> + {StandbyJobs.map((job: IJob) => { + const manga = StandbyJobsManga.find(manga => manga.internalId == job.mangaInternalId || manga.internalId == job.chapter?.parentManga.internalId); + if (manga === undefined || manga === null) + return <div key={"QueueJob-" + job.id}>Error. Could not find matching manga for {job.id}</div> + return QueueItem(apiUri, manga, job, UpdateMonitoringJobsList); + })} + </div> + </div> + </div> + </div> + : <></> + } + </> + ); +} \ No newline at end of file diff --git a/Website/modules/Search.tsx b/Website/modules/Search.tsx new file mode 100644 index 0000000..f0d1c0a --- /dev/null +++ b/Website/modules/Search.tsx @@ -0,0 +1,111 @@ +import React, {ChangeEventHandler, EventHandler, useEffect, useState} from 'react'; +import {MangaConnector} from "./MangaConnector"; +import IMangaConnector from "./interfaces/IMangaConnector"; +import {isValidUri} from "../App"; +import IManga, {SearchResult} from "./interfaces/IManga"; +import '../styles/search.css'; +import '../styles/MangaSearchResult.css' + +export default function Search({apiUri, jobInterval, onJobsChanged, closeSearch} : {apiUri: string, jobInterval: Date, onJobsChanged: (internalId: string) => void, closeSearch(): void}) { + const [mangaConnectors, setConnectors] = useState<IMangaConnector[]>(); + const [selectedConnector, setSelectedConnector] = useState<IMangaConnector>(); + const [selectedLanguage, setSelectedLanguage] = useState<string>(); + const [searchBoxValue, setSearchBoxValue] = useState(""); + const [searchResults, setSearchResults] = useState<IManga[]>(); + + const pattern = /https:\/\/([a-z0-9.]+\.[a-z0-9]{2,})(?:\/.*)?/i + + useEffect(() => { + if(mangaConnectors === undefined) { + MangaConnector.GetAllConnectors().then(setConnectors); + return; + } + }, [mangaConnectors]); + + const selectedConnectorChanged : ChangeEventHandler<HTMLSelectElement> = (event) => { + event.preventDefault(); + if(mangaConnectors === undefined) + return; + const selectedConnector = mangaConnectors.find((con: IMangaConnector) => con.name == event.target.value); + if(selectedConnector === undefined) + return; + setSelectedConnector(selectedConnector); + setSelectedLanguage(selectedConnector.SupportedLanguages[0]); + } + + const searchBoxValueChanged : ChangeEventHandler<HTMLInputElement> = (event) => { + event.currentTarget.style.width = event.target.value.length + "ch"; + if(mangaConnectors === undefined) + return; + var str : string = event.target.value; + setSearchBoxValue(str); + if(isValidUri(str)) + setSelectedConnector(undefined); + const match = str.match(pattern); + if(match === null) + return; + let baseUri = match[1]; + const selectCon = mangaConnectors.find((con: IMangaConnector) => { + return con.BaseUris.find(uri => uri == baseUri); + }); + if(selectCon != undefined){ + setSelectedConnector(selectCon); + setSelectedLanguage(selectCon.SupportedLanguages[0]); + } + } + + const ExecuteSearch : EventHandler<any> = () => { + if(searchBoxValue.length < 1 || selectedConnector === undefined || selectedLanguage === ""){ + console.error("Tried initiating search while not all fields where submitted.") + return; + } + //console.info(`Searching Name: ${searchBoxValue} Connector: ${selectedConnector.name} Language: ${selectedLanguage}`); + if(isValidUri(searchBoxValue) && !selectedConnector.BaseUris.find((uri: string) => { + const match = searchBoxValue.match(pattern); + if(match === null) + return false; + return match[1] == uri + })) + { + console.error("URL in Searchbox detected, but does not match selected connector."); + return; + } + if(!isValidUri(searchBoxValue)){ + MangaConnector.GetMangaFromConnectorByTitle(selectedConnector, searchBoxValue) + .then((mangas: IManga[]) => { + setSearchResults(mangas); + }); + }else{ + MangaConnector.GetMangaFromConnectorByUrl(selectedConnector, searchBoxValue) + .then((manga: IManga) => { + setSearchResults([manga]); + }); + } + } + + const changeSelectedLanguage : ChangeEventHandler<HTMLSelectElement> = (event) => setSelectedLanguage(event.target.value); + + return (<div id="Search"> + <div id="SearchBox"> + <input type="text" placeholder="Manganame" id="Searchbox-Manganame" onKeyDown={(e) => {if(e.key == "Enter") ExecuteSearch(null);}} onChange={searchBoxValueChanged}></input> + <select id="Searchbox-Connector" value={selectedConnector === undefined ? "" : selectedConnector.name} onChange={selectedConnectorChanged}> + <option value="" disabled hidden>Select</option> + {mangaConnectors === undefined + ? <option value="Loading">Loading</option> + : mangaConnectors.map(con => <option value={con.name} key={con.name}>{con.name}</option>)} + </select> + <select id="Searchbox-language" onChange={changeSelectedLanguage} value={selectedLanguage === null ? "" : selectedLanguage}> + {selectedConnector === undefined + ? <option value="" disabled hidden>Select Connector</option> + : selectedConnector.SupportedLanguages.map(language => <option value={language} key={language}>{language}</option>)} + </select> + <button id="Searchbox-button" type="submit" onClick={ExecuteSearch}>Search</button> + </div> + <img alt="Close Search" id="closeSearch" src="../media/close-x.svg" onClick={closeSearch} /> + <div id="SearchResults"> + {searchResults === undefined + ? <p></p> + : searchResults.map(result => SearchResult(apiUri, result, jobInterval, onJobsChanged))} + </div> + </div>) +} \ No newline at end of file diff --git a/Website/modules/Settings.tsx b/Website/modules/Settings.tsx new file mode 100644 index 0000000..6c16302 --- /dev/null +++ b/Website/modules/Settings.tsx @@ -0,0 +1,318 @@ +import React, {ChangeEventHandler, KeyboardEventHandler, MouseEventHandler, useEffect, useState} from 'react'; +import IFrontendSettings from "./interfaces/IFrontendSettings"; +import '../styles/settings.css'; +import IBackendSettings from "./interfaces/IBackendSettings"; +import {getData, postData} from "../App"; +import LibraryConnector, {Kavita, Komga} from "./LibraryConnector"; +import NotificationConnector, {Gotify, Lunasea, Ntfy} from "./NotificationConnector"; +import ILibraryConnector from "./interfaces/ILibraryConnector"; +import INotificationConnector from "./interfaces/INotificationConnector"; +import Toggle from 'react-toggle'; +import '../styles/react-toggle.css'; + +export default function Settings({backendConnected, apiUri, settings, changeSettings} : {backendConnected: boolean, apiUri: string, settings: IFrontendSettings, changeSettings: (settings: IFrontendSettings) => void}) { + const [frontendSettings, setFrontendSettings] = useState<IFrontendSettings>(settings); + const [backendSettings, setBackendSettings] = useState<IBackendSettings>(); + const [showSettings, setShowSettings] = useState<boolean>(false); + const [libraryConnectors, setLibraryConnectors] = useState<ILibraryConnector[]>(); + const [notificationConnectors, setNotificationConnectors] = useState<INotificationConnector[]>(); + const [komgaSettings, setKomgaSettings] = useState<{url: string, username: string, password: string}>({url: "", username: "", password: ""}); + const [kavitaSettings, setKavitaSettings] = useState<{url: string, username: string, password: string}>({url: "", username: "", password: ""}); + const [gotifySettings, setGotifySettings] = useState<{url: string, appToken: string}>({url: "", appToken: ""}); + const [lunaseaSettings, setLunaseaSettings] = useState<{webhook: string}>({webhook: ""}); + const [ntfySettings, setNtfySettings] = useState<{url: string, username: string, password: string, topic: string | undefined}>({url: "", username: "", password: "", topic: undefined}); + + useEffect(() => { + console.debug(`${showSettings ? "Showing" : "Not showing"} settings.`); + if(!showSettings || !backendConnected) + return; + UpdateBackendSettings(); + LibraryConnector.GetLibraryConnectors(apiUri).then(setLibraryConnectors).catch(console.error); + NotificationConnector.GetNotificationConnectors(apiUri).then(setNotificationConnectors).catch(console.error); + }, [showSettings]); + + useEffect(() => { + changeSettings(frontendSettings); + }, [frontendSettings]); + + async function GetSettings(apiUri: string) : Promise<IBackendSettings> { + //console.info("Getting Settings"); + return getData(`${apiUri}/v2/Settings`) + .then((json) => { + //console.info("Got Settings"); + const ret = json as IBackendSettings; + //console.debug(ret); + return (ret); + }) + .catch(Promise.reject); + } + + function UpdateBackendSettings() { + GetSettings(apiUri).then(setBackendSettings).catch(console.error); + } + + const GetKomga = () : ILibraryConnector | undefined => + libraryConnectors?.find(con => con.libraryType == 0); + + const KomgaConnected = () : boolean => GetKomga() != undefined; + + const GetKavita = () : ILibraryConnector | undefined => + libraryConnectors?.find(con => con.libraryType == 1); + + const KavitaConnected = () : boolean => GetKavita() != undefined; + + const GetGotify = () : INotificationConnector | undefined => + notificationConnectors?.find(con => con.notificationConnectorType == 0); + + const GotifyConnected = () : boolean => GetGotify() != undefined; + + const GetLunasea = () : INotificationConnector | undefined => + notificationConnectors?.find(con => con.notificationConnectorType == 1); + + const LunaseaConnected = () : boolean => GetLunasea() != undefined; + + const GetNtfy = () : INotificationConnector | undefined => + notificationConnectors?.find(con => con.notificationConnectorType == 2); + + const NtfyConnected = () : boolean => GetNtfy() != undefined; + + const SubmitApiUri : KeyboardEventHandler<HTMLInputElement> = (e) => { + if(e.currentTarget.value.length < 1) + return; + if(e.key == "Enter"){ + setFrontendSettings({...frontendSettings, apiUri: e.currentTarget.value}); + RefreshInputs(); + } + } + + const SubmitUserAgent: KeyboardEventHandler<HTMLInputElement> = (e) => { + if(e.currentTarget.value.length < 1 || backendSettings === undefined) + return; + if(e.key == "Enter"){ + //console.info(`Updating Useragent ${e.currentTarget.value}`); + postData(`${apiUri}/v2/Settings/UserAgent`, {value: e.currentTarget.value}) + .then((json) => { + //console.info(`Successfully updated Useragent ${e.currentTarget.value}`); + UpdateBackendSettings(); + RefreshInputs(); + }) + .catch(() => alert("Failed to update Useragent.")); + } + } + + const ResetUserAgent: MouseEventHandler<HTMLSpanElement> = () => { + //console.info(`Resetting Useragent`); + postData(`${apiUri}/v2/Settings/UserAgent`, {value: undefined}) + .then((json) => { + //console.info(`Successfully reset Useragent`); + UpdateBackendSettings(); + RefreshInputs(); + }) + .catch(() => alert("Failed to update Useragent.")); + } + + const SetAprilFoolsMode : ChangeEventHandler<HTMLInputElement> = (e) => { + //console.info(`Updating AprilFoolsMode ${e.target.checked}`); + postData(`${apiUri}/v2/Settings/AprilFoolsMode`, {value: e.target.checked}) + .then((json) => { + //console.info(`Successfully updated AprilFoolsMode ${e.currentTarget.value}`); + UpdateBackendSettings(); + }) + } + + const SetCompressImages : MouseEventHandler<HTMLInputElement> = (e) => { + //console.info(`Updating ImageCompression ${e.currentTarget.value}`); + postData(`${apiUri}/v2/Settings/CompressImages`, {value: e.currentTarget.value}) + .then((json) => { + //console.info(`Successfully updated ImageCompression ${e.currentTarget.value}`); + UpdateBackendSettings(); + }) + } + + const SetBWImages : ChangeEventHandler<HTMLInputElement> = (e) => { + //console.info(`Updating B/W Images ${e.target.checked}`); + postData(`${apiUri}/v2/Settings/BWImages`, {value: e.target.checked}) + .then((json) => { + //console.info(`Successfully updated B/W Images ${e.target.checked}`); + UpdateBackendSettings(); + }) + } + + function RefreshInputs(){ + alert("Saved."); + setShowSettings(false); + } + + return ( + <div id="Settings"> + <img id="SettingsIcon" src="../media/settings-cogwheel.svg" alt="cogwheel" onClick={() => setShowSettings(true)}/> + {showSettings + ? <div className="popup"> + <div className="popupHeader"> + <h1>Settings</h1> + <img alt="Close Settings" className="close" src="../media/close-x.svg" onClick={() => setShowSettings(false)}/> + </div> + <div id="settingsPopupBody" className="popupBody"> + <div className="settings-section"> + TRANGA + <div className="settings-section-content"> + <div className="section-item"> + <span className="settings-section-title">API Settings</span> + <label className="tooltip" data-tooltip="Set the URI of the Backend. Include https:// and if necessary port." htmlFor="settingApiUri">API URI:</label> + <input placeholder={frontendSettings.apiUri} type="text" id="settingApiUri" + onKeyDown={SubmitApiUri}/> + <label htmlFor="userAgent" className="tooltip" data-tooltip="Set a custom User-Agent. This will allow more frequent requests to sites.">User Agent:</label> + <input id="userAgent" type="text" + placeholder={backendSettings != undefined ? backendSettings.userAgent : "UserAgent"} + onKeyDown={SubmitUserAgent}/> + <span id="resetUserAgent" onClick={ResetUserAgent}>Reset</span> + <label htmlFor="aprilFoolsMode" className="tooltip" data-tooltip="Disable all downloads on April 1st">April Fools Mode</label> + <Toggle id="aprilFoolsMode" + checked={backendSettings?.aprilFoolsMode ?? false} + onChange={SetAprilFoolsMode}/> + <label htmlFor="compression" className="tooltip" data-tooltip="JPEG Compression Quality">Image Compression</label> + <input type="range" min="1" max="100" defaultValue={backendSettings?.compression ?? 50} className="slider" id="compression" onMouseUp={SetCompressImages}/> + <label htmlFor="bwImages">B/W Images</label> + <Toggle id="bwImages" + checked={backendSettings?.bwImages ?? false} + onChange={SetBWImages}/> + </div> + <div className="section-item"> + <span className="settings-section-title">Rate Limits</span> + <label htmlFor="DefaultRL">Default:</label> + <input id="defaultRL" type="text" placeholder={backendSettings != undefined ? backendSettings.requestLimits.Default.toString() : "-1"} /> + <label htmlFor="CoverRL">Manga Covers:</label> + <input id="coverRL" type="text" placeholder={backendSettings != undefined ? backendSettings.requestLimits.MangaCover.toString() : "-1"} /> + <label htmlFor="ImageRL">Manga Images:</label> + <input id="imageRL" type="text" placeholder={backendSettings != undefined ? backendSettings.requestLimits.MangaImage.toString() : "-1"} /> + <label htmlFor="InfoRL">Manga Info:</label> + <input id="infoRL" type="text" placeholder={backendSettings != undefined ? backendSettings.requestLimits.MangaInfo.toString() : "-1"} /> + </div> + <div className="section-item"> + <span className="settings-section-title">Appearance</span> + <label htmlFor="cssStyle">Library Style:</label> + <select id="cssStyle"> + <option id="card_compact" value="card_compact">Cards (Compact)</option> + <option id="card_hover" value="card_hover">Cards (Hover)</option> + </select> + </div> + </div> + </div> + + <div className="settings-section"> + <span className="settings-section-title">Sources</span> + <div className="settings-section-content"> + <div className="section-item"> + <span className="settings-section-title"> + <img src="../media/connector-icons/mangadex-logo.svg" alt="Mangadex Logo" /> + <a href="https://mangadex.org">MangaDex</a> + </span> + <label htmlFor="mDexFeedRL">Feed Rate Limit:</label> + <input id="mDexFeedRL" type="text" placeholder={backendSettings != undefined ? backendSettings.requestLimits.MangaDexFeed.toString() : "-1"} /> + <label htmlFor="mDexImageRL">Image Rate Limit:</label> + <input id="mDexImageRL" type="number" placeholder={backendSettings != undefined ? backendSettings.requestLimits.MangaDexImage.toString() : "-1"} /> + </div> + </div> + </div> + + <div className="settings-section" > + LIBRARY CONNECTORS + <div className="settings-section-content"> + <div className="section-item" connector-status={KomgaConnected() ? "Configured" : "Not Configured"}> + <span className="settings-section-title"> + <img src='../media/connector-icons/komga.svg' alt="Komga Logo"/> + Komga + </span> + <label htmlFor="komgaUrl">URL</label> + <input placeholder={GetKomga()?.baseUrl ?? "URL"} id="komgaUrl" type="text" onChange={(e) => setKomgaSettings(s => ({...s, url: e.target.value}))} /> + <label htmlFor="komgaUsername">Username</label> + <input placeholder={KomgaConnected() ? "***" : "Username"} id="komgaUsername" type="text" onChange={(e) => setKomgaSettings(s => ({...s, username: e.target.value}))} /> + <label htmlFor="komgaPassword">Password</label> + <input placeholder={KomgaConnected() ? "***" : "Password"} id="komgaPassword" type="password" onChange={(e) => setKomgaSettings(s => ({...s, password: e.target.value}))} /> + <div className="section-actions"> + <span onClick={() => new Komga(komgaSettings).Test(apiUri).then(()=>alert("Test successful"))}>Test</span> + <span onClick={() => new Komga(komgaSettings).Reset(apiUri).then(RefreshInputs)}>Reset</span> + <span onClick={() => new Komga(komgaSettings).Create(apiUri).then(RefreshInputs)}>Apply</span> + </div> + </div> + <div className="section-item" connector-status={KavitaConnected() ? "Configured" : "Not Configured" }> + <span className="settings-section-title"> + <img src='../media/connector-icons/kavita.png' alt="Kavita Logo"/> + Kavita + </span> + <label htmlFor="kavitaUrl">URL</label> + <input placeholder={GetKavita()?.baseUrl ?? "URL"} id="kavitaUrl" type="text" onChange={(e) => setKavitaSettings(s => ({...s, url: e.target.value}))} /> + <label htmlFor="kavitaUsername">Username</label> + <input placeholder={KavitaConnected() ? "***" : "Username"} id="kavitaUsername" type="text" onChange={(e) => setKavitaSettings(s => ({...s, username: e.target.value}))} /> + <label htmlFor="kavitaPassword">Password</label> + <input placeholder={KavitaConnected() ? "***" : "Password"} id="kavitaPassword" type="password" onChange={(e) => setKavitaSettings(s => ({...s, password: e.target.value}))} /> + <div className="section-actions"> + <span onClick={() => new Kavita(kavitaSettings).Test(apiUri).then(()=>alert("Test successful"))}>Test</span> + <span onClick={() => new Kavita(kavitaSettings).Reset(apiUri).then(RefreshInputs)}>Reset</span> + <span onClick={() => new Kavita(kavitaSettings).Create(apiUri).then(RefreshInputs)}>Apply</span> + </div> + </div> + </div> + </div> + + <div className="settings-section"> + NOTIFICATION CONNECTORS + <div className="settings-section-content"> + <div className="section-item" connector-status={GotifyConnected() ? "Configured" : "Not Configured"}> + <span className="settings-section-title"> + <img src='../media/connector-icons/gotify-logo.png' alt="Gotify Logo"/> + Gotify + </span> + <label htmlFor="gotifyUrl">URL</label> + <input placeholder={GetGotify()?.endpoint ?? "URL"} id="gotifyUrl" type="text" onChange={(e) => setGotifySettings(s => ({...s, url: e.target.value}))} /> + <label htmlFor="gotifyAppToken">AppToken</label> + <input placeholder={GotifyConnected() ? "***" : "AppToken"} id="gotifyAppToken" type="text" onChange={(e) => setGotifySettings(s => ({...s, appToken: e.target.value}))} /> + <div className="section-actions"> + <span onClick={() => new Gotify(gotifySettings).Test(apiUri).then(()=>alert("Test successful"))}>Test</span> + <span onClick={() => new Gotify(gotifySettings).Reset(apiUri).then(RefreshInputs)}>Reset</span> + <span onClick={() => new Gotify(gotifySettings).Create(apiUri).then(RefreshInputs)}>Apply</span> + </div> + </div> + <div className="section-item" + connector-status={LunaseaConnected() ? "Configured" : "Not Configured"}> + <span className="settings-section-title"> + <img src='../media/connector-icons/lunasea.png' alt="Lunasea Logo"/> + LunaSea + </span> + <label htmlFor="lunaseaWebhook">Webhook id</label> + <input placeholder={GetLunasea() != undefined ? "***" : "device/:id or user/:id"} id="lunaseaWebhook" type="text" onChange={(e) => setLunaseaSettings(s => ({...s, webhook: e.target.value}))} /> + <div className="section-actions"> + <span onClick={() => new Lunasea(lunaseaSettings).Test(apiUri).then(()=>alert("Test successful"))}>Test</span> + <span onClick={() => new Lunasea(lunaseaSettings).Reset(apiUri).then(RefreshInputs)}>Reset</span> + <span onClick={() => new Lunasea(lunaseaSettings).Create(apiUri).then(RefreshInputs)}>Apply</span> + </div> + </div> + <div className="section-item" + connector-status={NtfyConnected() ? "Configured" : "Not Configured"}> + <span className="settings-section-title"> + <img src='../media/connector-icons/ntfy.svg' alt="ntfy Logo"/> + Ntfy + </span> + <label htmlFor="ntfyEndpoint">URL</label> + <input placeholder={GetNtfy()?.endpoint ?? "URL"} id="ntfyEndpoint" type="text" onChange={(e) => setNtfySettings(s => ({...s, url: e.target.value}))} /> + <label htmlFor="ntfyUsername">Username</label> + <input placeholder={NtfyConnected() ? "***" : "Username"} id="ntfyUsername" type="text" onChange={(e) => setNtfySettings(s => ({...s, username: e.target.value}))} /> + <label htmlFor="ntfyPassword">Password</label> + <input placeholder={NtfyConnected() ? "***" : "Password"} id="ntfyPassword" type="password" onChange={(e) => setNtfySettings(s => ({...s, password: e.target.value}))} /> + <label htmlFor="ntfyTopic">Topic</label> + <input placeholder={GetNtfy()?.topic ?? "Topic"} id="ntfyTopic" type="text" onChange={(e) => setNtfySettings(s => ({...s, topic: e.target.value}))} /> + <div className="section-actions"> + <span onClick={() => new Ntfy(ntfySettings).Test(apiUri).then(()=>alert("Test successful"))}>Test</span> + <span onClick={() => new Ntfy(ntfySettings).Reset(apiUri).then(RefreshInputs)}>Reset</span> + <span onClick={() => new Ntfy(ntfySettings).Create(apiUri).then(RefreshInputs)}>Apply</span> + </div> + </div> + </div> + </div> + </div> + </div> + : <></> + } + </div> + ); +} \ No newline at end of file diff --git a/Website/modules/interfaces/IBackendSettings.tsx b/Website/modules/interfaces/IBackendSettings.tsx new file mode 100644 index 0000000..a4edf2a --- /dev/null +++ b/Website/modules/interfaces/IBackendSettings.tsx @@ -0,0 +1,20 @@ +export default interface IBackendSettings { + "downloadLocation": string; + "workingDirectory": string; + "apiPortNumber": number; + "userAgent": string; + "bufferLibraryUpdates": boolean; + "bufferNotifications": boolean; + "version": number; + "aprilFoolsMode": boolean; + "compression": number; + "bwImages": boolean; + "requestLimits": { + "MangaInfo": number; + "MangaDexFeed": number; + "MangaDexImage": number; + "MangaImage": number; + "MangaCover": number; + "Default": number + } +} \ No newline at end of file diff --git a/Website/modules/interfaces/IChapter.tsx b/Website/modules/interfaces/IChapter.tsx new file mode 100644 index 0000000..5d4eb41 --- /dev/null +++ b/Website/modules/interfaces/IChapter.tsx @@ -0,0 +1,10 @@ +import IManga from "./IManga"; + +export default interface IChapter{ + parentManga: IManga; + name: string | undefined; + volumeNumber: string; + chapterNumber: string; + url: string; + fileName: string; +} \ No newline at end of file diff --git a/Website/modules/interfaces/IFrontendSettings.tsx b/Website/modules/interfaces/IFrontendSettings.tsx new file mode 100644 index 0000000..1b56ab4 --- /dev/null +++ b/Website/modules/interfaces/IFrontendSettings.tsx @@ -0,0 +1,18 @@ +import {Cookies} from "react-cookie"; + +export default interface IFrontendSettings { + jobInterval: Date; + apiUri: string; +} + +export function LoadFrontendSettings(): IFrontendSettings { + const cookies = new Cookies(); + return { + jobInterval: cookies.get('jobInterval') === undefined + ? new Date(0,0,0,3) + : cookies.get('jobInterval'), + apiUri: cookies.get('apiUri') === undefined + ? `${window.location.protocol}//${window.location.host}/api` + : cookies.get('apiUri') + } +} \ No newline at end of file diff --git a/Website/modules/interfaces/IJob.tsx b/Website/modules/interfaces/IJob.tsx new file mode 100644 index 0000000..85baf23 --- /dev/null +++ b/Website/modules/interfaces/IJob.tsx @@ -0,0 +1,28 @@ +import IMangaConnector from "./IMangaConnector"; +import IProgressToken from "./IProgressToken"; +import IChapter from "./IChapter"; + +export default interface IJob{ + progressToken: IProgressToken; + recurring: boolean; + recurrenceTime: string; + lastExecution: Date; + nextExecution: Date; + id: string; + jobType: number; + parentJobId: string | null; + mangaConnector: IMangaConnector; + mangaInternalId: string | undefined; //only on DownloadNewChapters + translatedLanguage: string | undefined; //only on DownloadNewChapters + chapter: IChapter | undefined; //only on DownloadChapter +} + +export function JobTypeFromNumber(n: number): string { + switch(n) { + case 0: return "Download Chapter"; + case 1: return "Download New Chapters"; + case 2: return "Update Metadata"; + case 3: return "Monitor"; + } + return ""; +} \ No newline at end of file diff --git a/Website/modules/interfaces/ILibraryConnector.tsx b/Website/modules/interfaces/ILibraryConnector.tsx new file mode 100644 index 0000000..c1520c6 --- /dev/null +++ b/Website/modules/interfaces/ILibraryConnector.tsx @@ -0,0 +1,13 @@ +export default interface ILibraryConnector { + libraryType: number; + baseUrl: string; + auth: string; +} + +export function GetLibraryConnectorNameFromNumber(n: number): string { + switch(n){ + case 0: return "Komga"; + case 1: return "Kavita"; + } + return ""; +} \ No newline at end of file diff --git a/Website/modules/interfaces/IManga.tsx b/Website/modules/interfaces/IManga.tsx new file mode 100644 index 0000000..eba776c --- /dev/null +++ b/Website/modules/interfaces/IManga.tsx @@ -0,0 +1,130 @@ +import IMangaConnector from "./IMangaConnector"; +import KeyValuePair from "./KeyValuePair"; +import Manga from "../Manga"; +import React, {EventHandler, ReactElement, ReactEventHandler} from "react"; +import Icon from '@mdi/react'; +import { mdiTagTextOutline, mdiAccountEdit } from '@mdi/js'; +import MarkdownPreview from '@uiw/react-markdown-preview'; +import IJob, {JobTypeFromNumber} from "./IJob"; +import Job from "../Job"; +import ProgressBar from "@ramonak/react-progress-bar"; + +export default interface IManga{ + "sortName": string, + "authors": string[], + "altTitles": KeyValuePair[], + "description": string, + "tags": string[], + "coverUrl": string, + "coverFileNameInCache": string, + "links": KeyValuePair[], + "year": number, + "originalLanguage": string, + "releaseStatus": number, + "folderName": string, + "publicationId": string, + "internalId": string, + "ignoreChaptersBelow": number, + "latestChapterDownloaded": number, + "latestChapterAvailable": number, + "websiteUrl": string, + "mangaConnector": IMangaConnector +} + +export function ReleaseStatusFromNumber(n: number): string { + switch(n) { + case 0: return "Ongoing"; + case 1: return "Completed"; + case 2: return "OnHiatus"; + case 3: return "Cancelled"; + case 4: return "Unreleased"; + } + return ""; +} + + + +export function CoverCard(apiUri: string, manga: IManga) : ReactElement { + const MangaCover : ReactEventHandler<HTMLImageElement> = (e) => { + if(e.currentTarget.src != Manga.GetMangaCoverUrl(apiUri, manga.internalId, e.currentTarget)) + e.currentTarget.src = Manga.GetMangaCoverUrl(apiUri, manga.internalId, e.currentTarget); + } + + return( + <div className="Manga" key={manga.internalId}> + <img src="../../media/blahaj.png" onLoad={MangaCover} alt="Manga Cover"></img> + <div> + <p className="pill connector-name">{manga.mangaConnector.name}</p> + <div className="Manga-status" release-status={ReleaseStatusFromNumber(manga.releaseStatus)}></div> + <p className="Manga-name">{manga.sortName}</p> + </div> + </div>); +} + +export function SearchResult(apiUri: string, manga: IManga, interval: Date, onJobsChanged: (internalId: string) => void) : ReactElement { + const MangaCover : ReactEventHandler<HTMLImageElement> = (e) => { + if(e.currentTarget.src != Manga.GetMangaCoverUrl(apiUri, manga.internalId, e.currentTarget)) + e.currentTarget.src = Manga.GetMangaCoverUrl(apiUri, manga.internalId, e.currentTarget); + } + + return( + <div className="SearchResult" key={manga.internalId}> + <img src="../../media/blahaj.png" onLoad={MangaCover} alt="Manga Cover"></img> + <p className="connector-name">{manga.mangaConnector.name}</p> + <div className="Manga-status" release-status={ReleaseStatusFromNumber(manga.releaseStatus)}></div> + <p className="Manga-name"><a href={manga.websiteUrl}>{manga.sortName}<img src="../../media/link.svg" + alt=""/></a></p> + <div className="Manga-tags"> + {manga.authors.map(author => <p className="Manga-author" key={manga.internalId + "-author-" + author}> + <Icon path={mdiAccountEdit} size={0.5}/> {author}</p>)} + {manga.tags.map(tag => <p className="Manga-tag" key={manga.internalId + "-tag-" + tag}><Icon + path={mdiTagTextOutline} size={0.5}/> {tag}</p>)} + </div> + <MarkdownPreview className="Manga-description" source={manga.description} + style={{backgroundColor: "transparent", color: "black"}}/> + <button className="Manga-AddButton" onClick={() => { + Job.CreateJobDateInterval(apiUri, manga.internalId, "MonitorManga", interval).then(() => onJobsChanged(manga.internalId)); + }}>Monitor + </button> + </div>); +} + +function ProgressbarStr(job: IJob): string { + return job.progressToken.timeRemaining.substring(0,job.progressToken.timeRemaining.indexOf(".")).concat(" ", ToPercentString(job.progressToken.progress)); +} + +function ToPercentString(n: number): string { + return n.toString().substring(2,4).concat("%"); +} + +export function QueueItem(apiUri: string, manga: IManga, job: IJob, triggerUpdate: () => void){ + const MangaCover : ReactEventHandler<HTMLImageElement> = (e) => { + if(e.currentTarget.src != Manga.GetMangaCoverUrl(apiUri, manga.internalId, e.currentTarget)) + e.currentTarget.src = Manga.GetMangaCoverUrl(apiUri, manga.internalId, e.currentTarget); + } + + return ( + <div className="QueueJob" key={"QueueJob-" + job.id}> + <img src="../../media/blahaj.png" onLoad={MangaCover} alt="Manga Cover"></img> + <p className="QueueJob-Name">{manga.sortName}</p> + <p className="QueueJob-JobType">{JobTypeFromNumber(job.jobType)}</p> + <p className="QueueJob-additional">{job.jobType == 0 ? `Vol.${job.chapter?.volumeNumber} Ch.${job.chapter?.chapterNumber}` : ""}</p> + {job.progressToken.state === 0 + ? <ProgressBar labelColor={"#000"} height={"10px"} labelAlignment={"outside"} + className="QueueJob-Progressbar" completed={job.progressToken.progress} maxCompleted={1} + customLabel={ProgressbarStr(job)}/> + : <div className="QueueJob-Progressbar"></div>} + <div className="QueueJob-actions"> + <button className="QueueJob-Cancel" + onClick={() => Job.CancelJob(apiUri, job.id).then(triggerUpdate)}>Cancel + </button> + {job.parentJobId != null + ? <button className="QueueJob-Cancel" + onClick={() => Job.CancelJob(apiUri, job.parentJobId!).then(triggerUpdate)}>Cancel all + related</button> + : <></> + } + </div> + </div> + ); +} \ No newline at end of file diff --git a/Website/modules/interfaces/IMangaConnector.tsx b/Website/modules/interfaces/IMangaConnector.tsx new file mode 100644 index 0000000..94fd237 --- /dev/null +++ b/Website/modules/interfaces/IMangaConnector.tsx @@ -0,0 +1,5 @@ +export default interface IMangaConnector { + SupportedLanguages: string[]; + name: string; + BaseUris: string[]; +} \ No newline at end of file diff --git a/Website/modules/interfaces/INotificationConnector.tsx b/Website/modules/interfaces/INotificationConnector.tsx new file mode 100644 index 0000000..a3ff39d --- /dev/null +++ b/Website/modules/interfaces/INotificationConnector.tsx @@ -0,0 +1,8 @@ +export default interface INotificationConnector { + "notificationConnectorType": number; //see NotificationConnectorType + "endpoint": string | undefined;//only on Ntfy, Gotify + "appToken": string | undefined;//only on Gotify + "auth": string | undefined;//only on Ntfy + "topic": string | undefined;//only on Ntfy + "id": string | undefined;//only on LunaSea +} \ No newline at end of file diff --git a/Website/modules/interfaces/IProgressToken.tsx b/Website/modules/interfaces/IProgressToken.tsx new file mode 100644 index 0000000..f33a507 --- /dev/null +++ b/Website/modules/interfaces/IProgressToken.tsx @@ -0,0 +1,21 @@ +export default interface IProgressToken{ + cancellationRequested: boolean; + increments: number; + incrementsCompleted: number; + progress: number; + lastUpdate: Date; + executionStarted: Date; + timeRemaining: string; + state: number; +} + +export function GetProgressStateFromNumber(n: number): string { + switch (n){ + case 0: return "Running"; + case 1: return "Complete"; + case 2: return "Standby"; + case 3: return "Cancelled"; + case 4: return "Waiting"; + } + return ""; +} \ No newline at end of file diff --git a/Website/modules/interfaces/KeyValuePair.tsx b/Website/modules/interfaces/KeyValuePair.tsx new file mode 100644 index 0000000..165ba57 --- /dev/null +++ b/Website/modules/interfaces/KeyValuePair.tsx @@ -0,0 +1,4 @@ +export default interface KeyValuePair { + key: string; + value: string; +} \ No newline at end of file diff --git a/Website/styles/card_compact.css b/Website/styles/MangaCoverCard.css similarity index 59% rename from Website/styles/card_compact.css rename to Website/styles/MangaCoverCard.css index 74a6648..bdbe35e 100644 --- a/Website/styles/card_compact.css +++ b/Website/styles/MangaCoverCard.css @@ -1,24 +1,3 @@ -#addPublication { - cursor: pointer; - background-color: var(--secondary-color); - width: 180px; - height: 300px; - border-radius: 5px; - margin: 10px 10px; - padding: 15px 20px; - position: relative; -} - -#addPublication p{ - width: 100%; - text-align: center; - font-size: 150pt; - vertical-align: middle; - line-height: 300px; - margin: 0; - color: var(--accent-color); -} - .pill { flex-grow: 0; height: 14pt; @@ -27,64 +6,40 @@ background-color: var(--primary-color); padding: 2pt 17px; color: black; + width: fit-content; + margin: 10px 0; } -publication{ +.Manga{ cursor: pointer; background-color: var(--secondary-color); width: 180px; height: 300px; border-radius: 5px; margin: 10px 10px; - padding: 15px 19px; + padding: 14px 20px; position: relative; flex-shrink: 0; } -publication::after{ +.Manga::after{ content: ''; position: absolute; left: 0; top: 0; border-radius: 5px; width: 100%; height: 100%; background: linear-gradient(rgba(0,0,0,0.8), rgba(0, 0, 0, 0.7),rgba(0, 0, 0, 0.2)); + z-index: 0; } -publication-information { - display: flex; - flex-direction: column; - justify-content: start; -} - -publication-details { - display: flex; - flex-direction: column; - justify-content: start; -} - -publication-information * { - z-index: 1; - color: var(--accent-color); -} - -publication-details * { - z-index: 1; - color: var(--accent-color); -} - -connector-name{ - width: fit-content; - margin: 10px 0; -} - -publication-name{ +.Manga-name{ width: fit-content; font-size: 16pt; font-weight: bold; color: white; } -publication-status { +.Manga-status { display:block; height: 10px; width: 10px; @@ -97,7 +52,7 @@ publication-status { box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 10px, rgb(51, 51, 51) 0px 0px 10px 3px; } -publication-status::after { +.Manga-status::after { content: attr(release-status); position: absolute; top: 0; @@ -118,42 +73,55 @@ publication-status::after { background-color: inherit; } -publication-status:hover::after{ +.Manga-status:hover::after{ visibility:visible; } -publication-status[release-status="Ongoing"]{ +.Manga-status[release-status="Ongoing"]{ background-color: limegreen; } -publication-status[release-status="Completed"]{ +.Manga-status[release-status="Completed"]{ background-color: blueviolet; } -publication-status[release-status="On Hiatus"]{ +.Manga-status[release-status="On Hiatus"]{ background-color: darkorange; } -publication-status[release-status="Cancelled"]{ +.Manga-status[release-status="Cancelled"]{ background-color: firebrick; } -publication-status[release-status="Upcoming"]{ +.Manga-status[release-status="Upcoming"]{ background-color: aqua; } -publication-status[release-status="Status Unavailable"]{ +.Manga-status[release-status="Status Unavailable"]{ background-color: gray; } -publication img { +.Manga img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; - z-index: 0; border-radius: 5px; + z-index: 0; +} + +.Manga p { + margin: 2px 0; +} + +.Manga > div { + position: relative; + z-index: 1; + width: 100%; + height: 100%; + left: 0; + top: 0; } \ No newline at end of file diff --git a/Website/styles/MangaSearchResult.css b/Website/styles/MangaSearchResult.css new file mode 100644 index 0000000..3b22ddd --- /dev/null +++ b/Website/styles/MangaSearchResult.css @@ -0,0 +1,119 @@ +.SearchResult { + background-color: var(--second-background-color); + border-radius: 2px; + padding: 5px 5px 9px 5px; + position: relative; + max-width: 100%; + width: fit-content; + height: 328px; + display: grid; + grid-template-columns: 220px 600px 80px; + grid-template-rows: 55px 55px 190px auto; + column-gap: 10px; + grid-template-areas: + "cover header header" + "cover alltags alltags" + "cover description description" + "cover footer button"; +} + +.SearchResult p { + margin: 2px 0; +} + +.SearchResult > img { + grid-area: cover; + position: relative; + height: 100%; + width: 100%; + z-index: 0; + border: 2px solid var(--primary-color); + border-radius: 4px; +} + +.SearchResult > .connector-name { + grid-area: cover; + position: absolute; + z-index: 1; + left: 2px; + top: 2px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + width: 100%; + background-color: var(--accent-color); + margin: 0; + padding: 2px 0; + text-align: center; + color: var(--secondary-color); +} + +.SearchResult > .Manga-status { + grid-area: header; +} + +.SearchResult > .Manga-name { + grid-area: header; + color: black; + padding: 0 30px 0 0; +} + +.SearchResult > .Manga-tags { + display: flex; + flex-direction: row; + flex-wrap: wrap; + grid-area: alltags; + color: white; + padding: 0; + margin: 0; + white-space: nowrap; +} + +.SearchResult > .Manga-tags p { + margin: 0 2px; + padding: 5px; + font-size: 10pt; + height: fit-content; + width: min-content; +} + +.SearchResult .Manga-author { + background-color: green; +} + +.SearchResult .Manga-tag { + background-color: blue; +} + +.SearchResult > .Manga-description { + grid-area: description; + color: black; + overflow-y: scroll; +} + +.SearchResult > .Manga-AddButton { + grid-area: button; + background-color: white; + border: 1px solid var(--primary-color); + border-radius: 4px; + width: fit-content; + height: fit-content; + padding: 5px 10px; +} + +.SearchResult > .Manga-AddButton:hover { + background-color: #eee; +} + +.SearchResult a, .SearchResult a:visited { + color: initial; +} + +.SearchResult a img { + filter: brightness(0) saturate(100%) invert(0%) sepia(0%) saturate(7480%) hue-rotate(141deg) brightness(111%) contrast(99%); + position: relative; + bottom: 7px; +} + +.monitorMangaEntry { + position: relative; +} \ No newline at end of file diff --git a/Website/styles/base.css b/Website/styles/base.css deleted file mode 100644 index ee80fc5..0000000 --- a/Website/styles/base.css +++ /dev/null @@ -1,960 +0,0 @@ -:root{ - --background-color: #030304; - --second-background-color: white; - --primary-color: #f5a9b8; - --secondary-color: #5bcefa; - --blur-background: rgba(245, 169, 184, 0.58); - --accent-color: #fff; - /* --primary-color: green; - --secondary-color: gold; - --blur-background: rgba(86, 131, 36, 0.8); - --accent-color: olive; */ - --topbar-height: 60px; - box-sizing: border-box; -} - -body{ - padding: 0; - margin: 0; - height: 100vh; - background-color: var(--background-color); - font-family: "Inter", sans-serif; - overflow-x: hidden; -} - -wrapper { - display: flex; - flex-flow: column; - flex-wrap: nowrap; - height: 100vh; -} - -background-placeholder{ - background-color: var(--second-background-color); - opacity: 1; - position: absolute; - width: 100%; - height: 100%; - border-radius: 0 0 5px 0; - z-index: -1; -} - -topbar { - display: flex; - align-items: center; - height: var(--topbar-height); - background-color: var(--secondary-color); - z-index: 100; - box-shadow: 0 0 20px black; -} - -titlebox { - position: relative; - display: flex; - margin: 0 0 0 40px; - height: 100%; - align-items:center; - justify-content:center; -} - -titlebox span{ - cursor: default; - font-size: 24pt; - font-weight: bold; - background: linear-gradient(150deg, var(--primary-color), var(--accent-color)); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - margin-left: 20px; -} - -titlebox img { - height: 100%; - cursor: grab; -} - -spacer{ - flex-grow: 1; -} - -filter-box { - display: none; - align-self: center; - flex-direction: column; - position: relative; - - margin: 10px; - background-color: var(--second-background-color); - border-style: solid; - border-color: var(--primary-color); - border-width: 2px; - border-radius: 15px; - min-width: 300px; - width: 50%; - overflow: hidden; - max-height: 50%; - height: 600px; -} - -filter-box.animate { - display: flex; -} - -filter-box border-bar popup-title{ - font-size: 12pt; -} - -filter-box border-bar popup-close { - height: 20px; - width: 20px; - font-size: 12pt; - -webkit-user-select: none; /* Safari */ - -ms-user-select: none; /* IE 10 and IE 11 */ - user-select: none; /* Standard syntax */ -} - -border-bar-button.clearFilter{ - font-weight: bold; - margin: 0px 10px 10px 10px; - border-color: lightgray; - color: gray; - align-content: center; - justify-content: center; -} - -border-bar-button.clearFilter:hover { - background-color: red; - border-color: var(--second-background-color); - color: var(--second-background-color); -} - -status-filter { - display: block; - margin: 10px; - - /*Text Properties*/ - font-size:10pt; - font-weight:bold; - color:white; - text-align: center; - - /*Size*/ - padding: 3px 8px; - border-radius: 6px; - border: 0px; - background-color: inherit; - - cursor: pointer; - -webkit-user-select: none; /* Safari */ - -ms-user-select: none; /* IE 10 and IE 11 */ - user-select: none; /* Standard syntax */ -} - -status-filter[release-status="Ongoing"]{ - background-color: limegreen; -} - -status-filter[release-status="Completed"]{ - background-color: blueviolet; -} - -status-filter[release-status="On Hiatus"]{ - background-color: darkorange; -} - -status-filter[release-status="Cancelled"]{ - background-color: firebrick; -} - -status-filter[release-status="Upcoming"]{ - background-color: aqua; -} - -status-filter[release-status="Status Unavailable"]{ - background-color: gray; -} - - -searchdiv{ - display: flex; - width: 100%; -} - -#searchbox { - display: flex; - padding: 3px 5px; - margin: 5px; - border-style: solid; - border-width: 2px; - border-radius: 10px; - font-size: 12pt; - outline: none; - border-color: lightgray; - flex-grow: 1; - flex-shrink: 1; -} - -#searchbox:focus { - border-color: var(--secondary-color); -} - -.pill { - flex-grow: 0; - height: 14pt; - font-size: 12pt; - border-radius: 9pt; - background-color: var(--primary-color); - padding: 2pt 17px; - color: black; -} - -#connectorFilterBox .pill { - margin: 10px; - cursor: pointer; - -webkit-user-select: none; /* Safari */ - -ms-user-select: none; /* IE 10 and IE 11 */ - user-select: none; /* Standard syntax */ -} - -#settingscog { - cursor: pointer; - margin: 0px 30px; - margin-left: 15px; - height: 50%; - filter: invert(100%) sepia(0%) saturate(7465%) hue-rotate(115deg) brightness(116%) contrast(101%); -} - -#filterFunnel { - cursor: pointer; - margin: 0px 15px; - height: 50%; - filter: invert(100%) sepia(0%) saturate(7465%) hue-rotate(115deg) brightness(116%) contrast(101%); -} - -viewport { - position: relative; - display: flex; - flex-flow: row; - flex-wrap: nowrap; - flex-grow: 1; - height: 100%; - overflow-y: scroll; - scrollbar-color: var(--accent-color) var(--primary-color); - scrollbar-width: thin; -} - -footer { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - width: 100%; - height: 40px; - align-items: center; - justify-content: center; - background-color: var(--primary-color); - align-content: center; -} - -footer > div { - height: 100%; - margin: 0 30px; - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: center; - cursor: pointer; -} - -footer > div > *{ - height: 40%; - margin: 0 5px; -} - -#madeWith { - flex-grow: 1; - text-align: right; - margin-right: 20px; - cursor: url("media/blahaj.png"), grab; -} - -content { - position: relative; - flex-grow: 1; - border-radius: 5px; - display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: start; - align-content: start; -} - -#settingsPopup{ - z-index: 300; -} - -popup{ - display: none; - width: 100%; - min-height: 100%; - top: 0; - left: 0; - position: fixed; - z-index: 2; - flex-direction: column; -} - -border-bar { - display: flex; - flex-direction: row; - background-color: var(--primary-color); - color: var(--accent-color); - font-weight: bolder; - padding: 7px 5px; - margin:0; - align-items: center; - position: relative; - width: 100%; -} - -popup-title { - font-size: 14pt; - display: flex; - margin-top: 3px; - margin-left: 5px; - color: var(--second-background-color); -} - -popup-close { - border: none; - background-color: inherit; - color: var(--second-background-color);; - font-weight: inherit; - font-size: 27px; - font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; - display: flex; - cursor: pointer; - margin-left: auto; - margin-right: 15px; - height: 32px; - width: 32px; - border-radius: 16px; - align-content: center; - justify-content: center; -} - -popup-close:hover { - background-color: var(--secondary-color); -} - -border-bar > .button-container { - display: flex; - flex-direction: row; - align-items: center; - flex-wrap: wrap; - margin-right: 0; - margin-left: auto; -} - -border-bar-button { - border-style: solid; - border-width: 2px; - background-color: inherit; - color: var(--second-background-color); - font-weight: inherit; - font-size: inherit; - font-family: inherit; - display: flex; - cursor: pointer; - margin: 0px 5px; - padding: 5px 20px; - border-radius: 20px; - height: 20px; - align-items: center; - border-color: var(--accent-color); - -webkit-user-select: none; /* Safari */ - -ms-user-select: none; /* IE 10 and IE 11 */ - user-select: none; /* Standard syntax */ -} - -border-bar-button:hover { - border-color: var(--secondary-color); -} - -border-bar-button.primary { - background-color: var(--secondary-color); - color: var(--accent-color); - border-color: var(--primary-color); - margin-right: 10px; -} - -border-bar-button.primary:hover { - border-color: var(--accent-color); -} - -border-bar-button.section { - font-weight: bold; - color: darkgray; - border-color: darkgray; - text-align: center; - padding: 5px; - flex-grow: 1; - justify-content: center; -} - -border-bar-button.section:hover { - color: var(--secondary-color); - border-color: var(--secondary-color); -} - - -popup popup-window { - position: absolute; - z-index: 3; - left: 10%; - top: 10%; - height: 80%; - width: 80%; - display: flex; - flex-direction: column; - background-color: var(--second-background-color); - border-radius: 15px; - overflow: hidden; -} - -popup#jobStatusView popup-window { - left: 20%; - top: 20%; - height: 60%; - width: 60%; -} - -popup-content{ - display: flex; - flex-direction: column; - align-items: left; - height: calc(100% - 60px); - overflow-y: auto; - overflow-x: hidden; - scrollbar-width: thin; - scrollbar-color: var(--secondary-color) var(--second-background-color); -} - -popup-content > .popup-section { - margin: 5px; - margin-bottom: 10px; - font-size: 10pt; - font-weight: 100; - display: block; - border-top-style: solid; - border-top-width: 1px; - border-top-color: lightgray; - width: calc(100%-10px); - padding: 10px; -} - -.section-content { - display: flex; - flex-direction: row; - width: 100%; - flex-wrap: wrap; -} - -.section-item { - display: flex; - flex-direction: column; - width: 22%; - min-width: 300px; - height: auto; - border-radius: 10px; - border-style: solid; - border-width: 1px; - border-color: lightgray; - margin: 7px; - padding: 5px; -} - -.section-item.dyn-height { - height: fit-content; -} - -.section-item > .title { - font-weight: bold; - vertical-align: bottom; - line-height: 32px; - font-size: 12pt; - width: 100%; -} - -a:link { - color: inherit; - text-decoration: none; -} - -a:visited { - color: inherit; - text-decoration: none; -} - -a:hover { - color: inherit; - text-decoration: underline solid var(--secondary-color) 3px; -} - -a:active { - color: inherit; - text-decoration: none; -} - -.section-item > .title > img { - width: auto; - height: 32px; - margin: 5px; - vertical-align: middle; - border-radius: 5px; -} - -.section-item > .title > connector-configured { - display:block; - height: 10px; - width: 10px; - border-radius: 50%; - margin: 5px; - float: right; - top: 5px; - right: 5px; -} - -.section-item > .title > connector-configured::after { - display: block; - content: attr(configuration); - float: right; - width: max-content; - width: -webkit-max-content; - width: -mox-max-content; - width: intrinsic; - - visibility: hidden; - - /*Text Properties*/ - font-size:8pt; - font-weight:bold; - color:white; - text-align: right; - - /*Size*/ - padding: 0px 8px; - border-radius: 6px; - border: 0px; - background-color: inherit; -} - -.section-item > .title > connector-configured:hover::after{ - visibility:visible; -} - -.section-item > .title > connector-configured[configuration="Active"] { - background-color: limegreen; -} - -.section-item > .title > connector-configured[configuration="Not Configured"] { - background-color: gray; -} - -.section-item > input { - margin: 2px; - padding: 5px; - height: 20px; - border-radius: 10px; - border-style: solid; - outline: none; -} -.section-item > input:focus { - border-color: var(--secondary-color); -} - -.section-item > row { - width: calc(100%-20px); - display: flex; - flex-direction: row; - align-items: center; - margin-left: 5px; - margin-bottom: 5px; -} - -.section-item > row > input { - margin-left: auto; - margin-right: 2px; - padding: 5px; - height: 20px; - border-radius: 10px; - border-style: solid; - outline: none; - flex-grow: 0; - text-align: end; - float: right; - width: 200px; -} -.section-item > row > input:focus { - border-color: var(--secondary-color); -} - -.section-item > row > select { - margin-left: auto; - margin-right: 2px; - padding: 2px; - height: 30px; - border-radius: 10px; - border-style: solid; - outline: none; - flex-grow: 0; - text-align: end; - float: right; - width: 200px; -} - -.section-item > row > select:focus { - border-color: var(--secondary-color); -} - -.section-buttons-container { - display: flex; - flex-direction: row; - align-items: center; - position: relative; - margin-left: auto; - margin-top: auto; - margin-bottom: 0; - margin-right: 0; -} - -.section-buttons-container > .section-button { - font-size: 12px; - padding: 3px 10px; - margin: 3px; - border-radius: 5px; - border-style: solid; - border-width: 1px; - border-color: lightgray; - font-weight: bold; - color: gray; - cursor: pointer; - -webkit-user-select: none; /* Safari */ - -ms-user-select: none; /* IE 10 and IE 11 */ - user-select: none; /* Standard syntax */ -} - -.section-button#reset:hover { - color: red; - border-color: red; -} -.section-buttons-container > .section-button:hover { - border-color: var(--secondary-color); - color: var(--secondary-color); -} - -#newMangaPopup > div { - z-index: 3; - position: relative; -} - -#newMangaPopup > #newMangaPopupSelector { - width: 600px; - height: 40px; - margin: 80px auto 0; -} - -#newMangaPopup > div > #newMangaConnector, #newMangaTitle, #newMangaTranslatedLanguage { - margin: 0; - display: inline-block; - height: 40px; -} - -#newMangaPopup #newMangaConnector { - width: 100px; - padding: 0 0 0 5px; - border-radius: 5px 0 0 5px; - border: 0; - border-right: 1px solid darkgray; -} - -#newMangaPopup #newMangaTitle{ - width: 445px; - padding: 0 5px 0 5px; - border: 0; -} - -#newMangaPopup #newMangaTranslatedLanguage { - width: 45px; - border-radius: 0 5px 5px 0; - border: 0; - border-left: 1px solid darkgray; - margin-left: -5px; -} - -#newMangaResult { - display: none; - flex-direction: row; - justify-content: flex-start; - margin: 5px auto 0; - border-radius: 5px; - padding: 5px; - width: min-content; - max-width: 98%; - max-height: 400px; - overflow-x: scroll; - overflow-y: hidden; -} - -blur-background { - width: 100%; - height: 100%; - position: absolute; - left: 0; - background: var(--blur-background); - box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); - backdrop-filter: blur(4.5px); - -webkit-backdrop-filter: blur(4.5px); -} - -#publicationViewerPopup{ - z-index: 5; -} - -publication-viewer{ - display: block; - width: 460px; - position: absolute; - top: 200px; - left: 400px; - background-color: var(--accent-color); - border-radius: 5px; - overflow: hidden; - padding: 15px; -} - -publication-viewer::after{ - content: ''; - position: absolute; - left: 0; top: 0; - border-radius: 5px; - width: 100%; - height: 100%; - background: rgba(0,0,0,0.8); - backdrop-filter: blur(3px); -} - -publication-viewer img { - position: absolute; - left: 0; - top: 0; - height: 100%; - width: 100%; - object-fit: cover; - border-radius: 5px; - z-index: 0; -} - -publication-viewer publication-details > * { - margin: 5px 0; -} - -publication-viewer publication-details publication-name { - width: initial; - overflow-x: scroll; - white-space: nowrap; - scrollbar-width: none; -} - -publication-viewer publication-details publication-tags::before { - content: "Tags"; - display: block; - font-weight: bolder; -} - -publication-viewer publication-details publication-tags { - overflow-x: scroll; - white-space: nowrap; - scrollbar-width: none; -} - -publication-viewer publication-details publication-author::before { - content: "Author: "; - font-weight: bolder; -} - -publication-viewer publication-details publication-description::before { - content: "Description"; - display: block; - font-weight: bolder; -} - -publication-viewer publication-details publication-description { - font-size: 12pt; - margin: 5px 0; - height: 145px; - overflow-x: scroll; -} - -publication-viewer publication-details publication-interactions { - display: flex; - flex-direction: row; - justify-content: end; - align-items: start; - width: 100%; -} - -publication-viewer publication-details publication-interactions > * { - margin: 0 10px; - font-size: 16pt; - cursor: pointer; -} - -publication-viewer publication-details publication-interactions publication-starttask { - color: var(--secondary-color); -} - -publication-viewer publication-details publication-interactions publication-delete { - color: red; -} - -publication-view publication-details publication-interactions publication-canceltask { - color: yellow; -} - -publication-viewer publication-details publication-interactions publication-add { - color: limegreen; -} - -footer-tag-popup { - display: none; - padding: 2px 4px; - position: fixed; - bottom: 58px; - left: 20px; - background-color: var(--second-background-color); - z-index: 8; - border-radius: 5px; - max-height: 400px; -} - -footer-tag-content{ - position: relative; - max-height: 400px; - display: flex; - flex-direction: column; - flex-wrap: nowrap; - overflow-y: scroll; -} - -footer-tag-content > * { - margin: 2px 5px; -} - -footer-tag-popup::before{ - content: ""; - width: 0; - height: 0; - position: absolute; - border-right: 10px solid var(--second-background-color); - border-left: 10px solid transparent; - border-top: 10px solid var(--second-background-color); - border-bottom: 10px solid transparent; - left: 0; - bottom: -17px; - border-radius: 0 0 0 5px; -} - -#loaderdiv { - position: absolute; - top: 0px; - left: 0px; - width: 100%; - height: 100%; - z-index: 200; -} - -#loader { - border: 16px solid transparent; - border-top: 16px solid var(--secondary-color); - border-bottom: 16px solid var(--primary-color); - border-radius: 50%; - width: 120px; - height: 120px; - animation: spin 2s linear infinite; - position: absolute; - left: calc(50% - 60px); - top: calc(50% - 120px); - z-index: 201; -} - -#loaderText { - position: relative; - margin: 0 auto; - top: calc(50% + 80px); - z-index: 201; - text-align: center; - color: var(--second-background-color); - font-size: 20pt; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -#jobStatusRunning > .section-item { - flex-direction: row; - height: 150px; - padding: 0; - overflow: hidden; -} - -#jobStatusWaiting > .section-item { - flex-direction: row; - height: 150px; - padding: 0; - overflow: hidden; -} - -.section-item > .jobImage { - height: 100%; - width: auto; - left: 0; - top: 0; - border-radius: 10px; -} - -.jobDetails { - display: flex; - flex-direction: column; - height: 100%; - width: 100%; -} - -.section-item > .jobDetails > .jobTitle { - margin: 5px; - font-size: 11pt; - font-weight: bold; - text-wrap: wrap; -} - -.section-item > .jobDetails > .jobProgressBar { - margin: 5px; - height: 10px; - border-radius: 7px; -} - -.section-item > .jobDetails > .jobProgressSpan { - margin: 5px; - margin-left: auto; - margin-right: 5px; -} - -.section-item > .jobDetails > .jobCancel { - margin-top: auto; - margin-bottom: 5px; - margin-left: auto; - margin-right: 5px; - font-size: 12pt; - color: var(--secondary-color); - cursor: pointer; -} \ No newline at end of file diff --git a/Website/styles/card_hover.css b/Website/styles/card_hover.css deleted file mode 100644 index 6d830ed..0000000 --- a/Website/styles/card_hover.css +++ /dev/null @@ -1,172 +0,0 @@ -#addPublication { - cursor: pointer; - background-color: var(--secondary-color); - width: 180px; - height: 300px; - border-radius: 5px; - margin: 10px 10px; - padding: 15px 20px; - position: relative; -} - -#addPublication p{ - width: 100%; - text-align: center; - font-size: 150pt; - vertical-align: middle; - line-height: 300px; - margin: 0; - color: var(--accent-color); -} - -.pill { - flex-grow: 0; - height: 14pt; - font-size: 12pt; - border-radius: 9pt; - background-color: var(--primary-color); - padding: 2pt 17px; - color: black; -} - -publication{ - cursor: pointer; - background-color: var(--secondary-color); - width: 180px; - height: 300px; - border-radius: 5px; - margin: 10px 10px; - padding: 15px 19px; - position: relative; - flex-shrink: 0; -} - -publication:hover { - background-color: black; -} - -publication:hover::after{ - background: linear-gradient(rgba(0,0,0,0.8), rgba(0, 0, 0, 0.7),rgba(0, 0, 0, 0.2)); -} - -publication:hover > publication-information { - display: flex; - opacity:1; -} - -publication::after{ - content: ''; - position: absolute; - left: 0; top: 0; - border-radius: 5px; - width: 100%; height: 100%; - background: none; -} - -publication-information { - display: none; - flex-direction: column; - justify-content: start; -} - -publication-information * { - z-index: 1; - color: white; -} - -connector-name{ - width: fit-content; - margin: 10px 0; -} - -publication-name{ - width: fit-content; - font-size: 16pt; - font-weight: bold; -} - -publication-status { - display:block; - height: 10px; - width: 10px; - border-radius: 50%; - margin: 5px; - position: absolute; - top: 5px; - right: 5px; - z-index: 2; - box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 10px, rgb(51, 51, 51) 0px 0px 10px 3px; -} - -publication-status::after { - content: attr(release-status); - position: absolute; - top: 0; - right: 0; - - visibility: hidden; - - /*Text Properties*/ - font-size:10pt; - font-weight:bold; - color:white; - text-align: center; - - /*Size*/ - padding: 3px 8px; - border-radius: 6px; - border: 0px; - background-color: inherit; -} - -publication-status:hover::after{ - visibility:visible; -} - - -publication-status[release-status="Ongoing"]{ - background-color: limegreen; -} - -publication-status[release-status="Completed"]{ - background-color: blueviolet; -} - -publication-status[release-status="On Hiatus"]{ - background-color: darkorange; -} - -publication-status[release-status="Cancelled"]{ - background-color: firebrick; -} - -publication-status[release-status="Upcoming"]{ - background-color: aqua; -} - -publication-status[release-status="Status Unavailable"]{ - background-color: gray; -} - - -publication-details { - display: flex; - flex-direction: column; - justify-content: start; -} - -publication-details * { - z-index: 1; - color: var(--accent-color); -} - -publication img { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - z-index: 0; - border-radius: 5px; -} \ No newline at end of file diff --git a/Website/styles/footer.css b/Website/styles/footer.css new file mode 100644 index 0000000..54f5818 --- /dev/null +++ b/Website/styles/footer.css @@ -0,0 +1,42 @@ +footer { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + width: 100%; + height: 40px; + align-items: center; + justify-content: center; + background-color: var(--primary-color); + align-content: center; + position: fixed; + bottom: 0; + color: white; + z-index: 10; +} + +#madeWith { + flex-grow: 1; + text-align: right; + margin-right: 20px; + cursor: url("Website/media/blahaj.png"), grab; +} + +footer .statusBadge { + margin: 0 10px; + display: flex; + align-items: center; + justify-items: center; + background-color: rgba(255,255,255, 0.3); + border-radius: 10px; + padding: 2px 5px; +} + +footer > div { + display: flex; + align-items: center; + justify-items: center; +} + +footer .hoverHand { + cursor: pointer; +} \ No newline at end of file diff --git a/Website/styles/header.css b/Website/styles/header.css new file mode 100644 index 0000000..3e5a26f --- /dev/null +++ b/Website/styles/header.css @@ -0,0 +1,33 @@ +header { + display: flex; + align-items: center; + justify-content: space-between; + height: var(--topbar-height); + background-color: var(--secondary-color); + z-index: 100; + box-shadow: 0 0 20px black; +} + +header > #titlebox { + position: relative; + display: flex; + margin: 0 0 0 40px; + height: 100%; + align-items:center; + justify-content:center; +} + +header > #titlebox > span{ + cursor: default; + font-size: 24pt; + font-weight: bold; + background: linear-gradient(150deg, var(--primary-color), var(--accent-color)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + margin-left: 20px; +} + +header > #titlebox > img { + height: 100%; + cursor: grab; +} \ No newline at end of file diff --git a/Website/styles/index.css b/Website/styles/index.css new file mode 100644 index 0000000..3e577b6 --- /dev/null +++ b/Website/styles/index.css @@ -0,0 +1,42 @@ +:root{ + --background-color: #030304; + --second-background-color: white; + --primary-color: #f5a9b8; + --secondary-color: #5bcefa; + --blur-background: rgba(245, 169, 184, 0.58); + --accent-color: #fff; + /* --primary-color: green; + --secondary-color: gold; + --blur-background: rgba(86, 131, 36, 0.8); + --accent-color: olive; */ + --topbar-height: 60px; + box-sizing: border-box; +} + +body{ + padding: 0; + margin: 0; + height: 100vh; + background-color: var(--background-color); + font-family: "Inter", sans-serif; + overflow-x: hidden; + color: var(--primary-color); +} + +.tooltip { + position: relative; +} + +.tooltip:hover:before { + display: block; + content: attr(data-tooltip, "tooltip"); + background-color: var(--second-background-color); + color: var(--secondary-color); + border: 1px solid var(--secondary-color); + border-radius: 6px; + bottom: 1em; + max-width: 90%; + position: absolute; + padding: 3px 7px 1px; + z-index: 999; +} \ No newline at end of file diff --git a/Website/styles/monitorMangaList.css b/Website/styles/monitorMangaList.css new file mode 100644 index 0000000..9343c51 --- /dev/null +++ b/Website/styles/monitorMangaList.css @@ -0,0 +1,39 @@ +#MonitorMangaList { + position: relative; + display: flex; + flex-flow: row; + flex-wrap: nowrap; + flex-grow: 1; + height: 100%; + overflow-y: scroll; + scrollbar-color: var(--accent-color) var(--primary-color); + scrollbar-width: thin; +} + +.MangaActionButtons { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.MangaActionButtons > div { + position: absolute; + margin: 10px; + border: 0; + background: none; + cursor: pointer; +} + +.DeleteJobButton { + bottom: 0; + left: 0; + filter: invert(21%) sepia(63%) saturate(7443%) hue-rotate(355deg) brightness(93%) contrast(118%); +} + +.StartJobNowButton { + bottom: 0; + right: 0; + filter: invert(58%) sepia(16%) saturate(4393%) hue-rotate(103deg) brightness(102%) contrast(103%); +} \ No newline at end of file diff --git a/Website/styles/popup.css b/Website/styles/popup.css new file mode 100644 index 0000000..c41a4f8 --- /dev/null +++ b/Website/styles/popup.css @@ -0,0 +1,43 @@ +.popup { + position: fixed; + left: 10%; + top: 7.5%; + width: 80%; + height: 80%; + margin: auto; + z-index: 100; + background-color: var(--second-background-color); + border-radius: 10px; + overflow: hidden; +} + +.popup .popupHeader { + position: absolute; + top: 0; + left: 0; + height: 40px; + width: 100%; + background-color: var(--primary-color); + color: var(--accent-color); +} + +.popup .popupHeader h1 { + margin: 4px 10px; + font-size: 20pt; +} + +.popup .close { + position: absolute; + top: 0; + right: 0; + height: 100%; + cursor: pointer; +} + +.popup .popupBody { + position: absolute; + top: 40px; + left: 0; + width: 100%; + height: calc(100% - 40px); +} \ No newline at end of file diff --git a/Website/styles/queuePopUp.css b/Website/styles/queuePopUp.css new file mode 100644 index 0000000..f2abf91 --- /dev/null +++ b/Website/styles/queuePopUp.css @@ -0,0 +1,81 @@ +#QueuePopUp #QueuePopUpBody { + display: flex; +} + +#QueuePopUp #QueuePopUpBody > * { + padding: 20px; + width: calc(50% - 40px); + height: calc(100% - 40px); + overflow-y: scroll; +} + +#QueuePopUp #QueuePopUpBody h1 { + padding: 0; + margin: 0 0 5px 0; + color: var(--primary-color); +} + +#QueuePopUp #QueuePopUpBody > *:first-child { + border-right: 1px solid var(--primary-color); +} + +#QueuePopUp #QueuePopUpBody .JobQueue { + display: flex; + flex-direction: column; +} + +.QueueJob { + color: black; + margin: 5px 0; + position: relative; + height: 200px; + display: grid; + grid-template-columns: 150px auto; + grid-template-rows: 25% 20% auto 15% 12%; + column-gap: 10px; + grid-template-areas: + "cover name" + "cover jobType" + "cover additionalInfo" + "cover progress" + "cover actions" +} + +.QueueJob p { + margin: 2px 0; +} + +.QueueJob img{ + grid-area: cover; + height: 100%; + max-width: 100%; +} + +.QueueJob .QueueJob-Name{ + grid-area: name; + font-weight: bold; + font-size: 14pt; +} + +.QueueJob .JobType{ + grid-area: jobType; +} + +.QueueJob .QueueJob-additionalInfo { + grid-area: additionalInfo; +} + +.QueueJob .QueueJob-actions { + grid-area: actions; + display: flex; + justify-content: space-evenly; +} + +.QueueJob .QueueJob-Cancel { + grid-area: actions; + width: 150px; +} + +.QueueJob .QueueJob-Progressbar { + grid-area: progress; +} \ No newline at end of file diff --git a/Website/styles/react-toggle.css b/Website/styles/react-toggle.css new file mode 100644 index 0000000..1a139ba --- /dev/null +++ b/Website/styles/react-toggle.css @@ -0,0 +1,143 @@ +/* https://raw.githubusercontent.com/instructure-react/react-toggle/master/style.css */ + +.react-toggle { + touch-action: pan-x; + + display: inline-block; + position: relative; + cursor: pointer; + background-color: transparent; + border: 0; + padding: 0; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + -webkit-tap-highlight-color: rgba(0,0,0,0); + -webkit-tap-highlight-color: transparent; +} + +.react-toggle-screenreader-only { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +.react-toggle--disabled { + cursor: not-allowed; + opacity: 0.5; + -webkit-transition: opacity 0.25s; + transition: opacity 0.25s; +} + +.react-toggle-track { + width: 50px; + height: 24px; + padding: 0; + border-radius: 30px; + background-color: #4D4D4D; + -webkit-transition: all 0.2s ease; + -moz-transition: all 0.2s ease; + transition: all 0.2s ease; +} + +.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track { + background-color: #000000; +} + +.react-toggle--checked .react-toggle-track { + background-color: #19AB27; +} + +.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track { + background-color: #128D15; +} + +.react-toggle-track-check { + position: absolute; + width: 14px; + height: 10px; + top: 0px; + bottom: 0px; + margin-top: auto; + margin-bottom: auto; + line-height: 0; + left: 8px; + opacity: 0; + -webkit-transition: opacity 0.25s ease; + -moz-transition: opacity 0.25s ease; + transition: opacity 0.25s ease; +} + +.react-toggle--checked .react-toggle-track-check { + opacity: 1; + -webkit-transition: opacity 0.25s ease; + -moz-transition: opacity 0.25s ease; + transition: opacity 0.25s ease; +} + +.react-toggle-track-x { + position: absolute; + width: 10px; + height: 10px; + top: 0px; + bottom: 0px; + margin-top: auto; + margin-bottom: auto; + line-height: 0; + right: 10px; + opacity: 1; + -webkit-transition: opacity 0.25s ease; + -moz-transition: opacity 0.25s ease; + transition: opacity 0.25s ease; +} + +.react-toggle--checked .react-toggle-track-x { + opacity: 0; +} + +.react-toggle-thumb { + transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms; + position: absolute; + top: 1px; + left: 1px; + width: 22px; + height: 22px; + border: 1px solid #4D4D4D; + border-radius: 50%; + background-color: #FAFAFA; + + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + + -webkit-transition: all 0.25s ease; + -moz-transition: all 0.25s ease; + transition: all 0.25s ease; +} + +.react-toggle--checked .react-toggle-thumb { + left: 27px; + border-color: #19AB27; +} + +.react-toggle--focus .react-toggle-thumb { + -webkit-box-shadow: 0px 0px 3px 2px #0099E0; + -moz-box-shadow: 0px 0px 3px 2px #0099E0; + box-shadow: 0px 0px 2px 3px #0099E0; +} + +.react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb { + -webkit-box-shadow: 0px 0px 5px 5px #0099E0; + -moz-box-shadow: 0px 0px 5px 5px #0099E0; + box-shadow: 0px 0px 5px 5px #0099E0; +} diff --git a/Website/styles/search.css b/Website/styles/search.css new file mode 100644 index 0000000..270a58f --- /dev/null +++ b/Website/styles/search.css @@ -0,0 +1,58 @@ +#Search{ + position: relative; + width: 98vw; + margin: auto; +} + +#SearchBox{ + display: flex; + align-content: center; + justify-content: center; + margin: 10px 0; +} + +#SearchResults { + width: 100%; +} + +#SearchBox select, #SearchBox button, #SearchBox input { + border-color: var(--primary-color); + border-style: solid; + border-width: 0; + border-bottom-width: 2px; + border-top-width: 2px; + padding: 2px 5px; + font-size: 12pt; +} + +#Searchbox-Manganame { + border-bottom-left-radius: 2px; + border-top-left-radius: 2px; + border-left-width: 2px !important; + min-width: 300px; + max-width: 50vw; +} + +#Searchbox-connector { + width: max-content; +} + +#Searchbox-language { + width: 90px; +} + +#Searchbox-button { + border-bottom-right-radius: 2px; + border-top-right-radius: 2px; + border-right-width: 2px !important; + width: 90px; +} + +#closeSearch { + position: absolute; + top: 0; + right: 0; + width: 30px; + height: 30px; + filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(1%) hue-rotate(20deg) brightness(103%) contrast(101%); +} \ No newline at end of file diff --git a/Website/styles/settings.css b/Website/styles/settings.css new file mode 100644 index 0000000..85a515f --- /dev/null +++ b/Website/styles/settings.css @@ -0,0 +1,101 @@ +#Settings { + position: relative; + height: 100%; +} + +#SettingsIcon { + height: calc(100% - 26px); + margin: 13px; + filter: invert(100%) sepia(65%) saturate(2%) hue-rotate(215deg) brightness(113%) contrast(101%); +} + +#settingsPopupBody { + display: flex; + flex-direction: column; + overflow-y: scroll; +} + +.settings-section { + margin: 5px 5px 10px; + font-size: 10pt; + font-weight: 100; + display: block; + border-top-style: solid; + border-top-width: 1px; + border-top-color: lightgray; + width: calc(100% - 30px); + padding: 10px; +} + +.settings-section-content { + display: flex; + flex-direction: row; + width: 100%; + flex-wrap: wrap; + +} + +.section-item { + position: relative; + display: flex; + flex-direction: column; + width: 22%; + min-width: 300px; + height: auto; + border-radius: 10px; + border-style: solid; + border-width: 1px; + border-color: lightgray; + margin: 7px; + padding: 5px 5px 35px; +} + +.section-item > * { + margin: 2px 0; +} + +.section-item[connector-status="Not Configured"]{ + border-color: var(--primary-color); +} + +.section-item[connector-status="Configured"]{ + border-color: green; +} + +.section-item > .settings-section-title { + font-weight: bold; + vertical-align: bottom; + line-height: 32px; + font-size: 12pt; + width: 100%; +} + +.section-item > .settings-section-title > img { + width: auto; + height: 32px; + margin: 5px; + vertical-align: middle; + border-radius: 5px; +} + +.section-item .section-actions { + position: absolute; + bottom: 0; + display: flex; + justify-content: space-around; + margin: 5px; + width: calc(100% - 20px); +} + +.section-actions > span, #resetUserAgent { + border: 1px solid lightgray; + padding: 3px 5px 2px; + width: 10ch; + text-align: center; + border-radius: 3px; +} + +#resetUserAgent{ + align-self: flex-end; + margin-top: 3px; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b151042 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3213 @@ +{ + "name": "tranga-website", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@ramonak/react-progress-bar": "^5.3.0", + "@uiw/react-markdown-preview": "^5.1.3" + }, + "devDependencies": { + "@mdi/js": "^7.4.47", + "@mdi/react": "^1.6.1", + "@types/react": "^18.2.0", + "@types/react-toggle": "^4.0.5", + "react": "^18.3.1", + "react-cookie": "^7.2.1", + "react-dom": "^18.3.1", + "react-toggle": "^4.1.3", + "typescript": "^5.6.3", + "vite": "^5.4.9" + } + }, + "node_modules/@babel/runtime": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@mdi/js": { + "version": "7.4.47", + "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz", + "integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@mdi/react": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.6.1.tgz", + "integrity": "sha512-4qZeDcluDFGFTWkHs86VOlHkm6gnKaMql13/gpIcUQ8kzxHgpj31NuCkD8abECVfbULJ3shc7Yt4HJ6Wu6SN4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + } + }, + "node_modules/@ramonak/react-progress-bar": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@ramonak/react-progress-bar/-/react-progress-bar-5.3.0.tgz", + "integrity": "sha512-PjpOcSBAVSQNyx2cvYyBCI14Tg2eFM0psC9m2ic33PYBIdOzO9/DieWndq9BUQTSjIIarhSpa/lqJ33W/mFJMw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.0.0 || ^17 || ^18", + "react-dom": "^16.0.0 || ^17 || ^18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "license": "MIT" + }, + "node_modules/@types/prismjs": { + "version": "1.26.4", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.4.tgz", + "integrity": "sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.0.tgz", + "integrity": "sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-toggle": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/react-toggle/-/react-toggle-4.0.5.tgz", + "integrity": "sha512-MHHEDe7GnF/EhLtI5sT70Dqab8rwlgjRZtu/u6gmfbYd+HeYxWiUSRog16+1BCfkz7Wy2VU6+TPU2oCsDtqDzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@uiw/copy-to-clipboard": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@uiw/copy-to-clipboard/-/copy-to-clipboard-1.0.17.tgz", + "integrity": "sha512-O2GUHV90Iw2VrSLVLK0OmNIMdZ5fgEg4NhvtwINsX+eZ/Wf6DWD0TdsK9xwV7dNRnK/UI2mQtl0a2/kRgm1m1A==", + "license": "MIT", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/react-markdown-preview": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@uiw/react-markdown-preview/-/react-markdown-preview-5.1.3.tgz", + "integrity": "sha512-jV02wO4XHWFk54kz7sLqOkdPgJLttSfKLyen47XgjcyGgQXU2I4WJBygmdpV2AT9m/MiQ8qrN1Y+E5Syv9ZDpw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@uiw/copy-to-clipboard": "~1.0.12", + "react-markdown": "~9.0.1", + "rehype-attr": "~3.0.1", + "rehype-autolink-headings": "~7.1.0", + "rehype-ignore": "^2.0.0", + "rehype-prism-plus": "2.0.0", + "rehype-raw": "^7.0.0", + "rehype-rewrite": "~4.0.0", + "rehype-slug": "~6.0.0", + "remark-gfm": "~4.0.0", + "remark-github-blockquote-alert": "^1.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "license": "ISC" + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "dev": true, + "license": "MIT" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/css-selector-parser": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.0.5.tgz", + "integrity": "sha512-3itoDFbKUNx1eKmVpYMFyqKX04Ww9osZ+dLgrk6GEv6KMVeXUhUnp4I5X+evw+u3ZxVU6RFXSSRxlTeMh8bA+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", + "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^8.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/hastscript": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", + "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-heading-rank": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", + "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/hast-util-parse-selector/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/hast-util-raw": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", + "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.3.tgz", + "integrity": "sha512-OVRQlQ1XuuLP8aFVLYmC2atrfWHS5UD3shonxpnyrjcCkwtvmt/+N6kYJdcY4mkMJhxp4kj2EFIxQ9kvkkt/eQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", + "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/hastscript/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz", + "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", + "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz", + "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz", + "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", + "license": "ISC" + }, + "node_modules/parse5": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.0.tgz", + "integrity": "sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-cookie": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.2.1.tgz", + "integrity": "sha512-UcE6njAy5UmKzU9RywIvvtbHIQydhh6JP55yVbGwQ8Tp/wSPp2HOYVZdRI55zfbkW6zQv9SEs42mS3a9cIUQ0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.5", + "hoist-non-react-statics": "^3.3.2", + "universal-cookie": "^7.0.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-markdown": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", + "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-toggle": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.1.3.tgz", + "integrity": "sha512-WoPrvbwfQSvoagbrDnXPrlsxwzuhQIrs+V0I162j/s+4XPgY/YDAUmHSeWiroznfI73wj+MBydvW95zX8ABbSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "classnames": "^2.2.5" + }, + "peerDependencies": { + "prop-types": ">= 15.3.0 < 19", + "react": ">= 15.3.0 < 19", + "react-dom": ">= 15.3.0 < 19" + } + }, + "node_modules/refractor": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.8.1.tgz", + "integrity": "sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/prismjs": "^1.0.0", + "hastscript": "^7.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/refractor/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/rehype-attr": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/rehype-attr/-/rehype-attr-3.0.3.tgz", + "integrity": "sha512-Up50Xfra8tyxnkJdCzLBIBtxOcB2M1xdeKe1324U06RAvSjYm7ULSeoM+b/nYPQPVd7jsXJ9+39IG1WAJPXONw==", + "license": "MIT", + "dependencies": { + "unified": "~11.0.0", + "unist-util-visit": "~5.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/rehype-autolink-headings": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz", + "integrity": "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-ignore": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rehype-ignore/-/rehype-ignore-2.0.2.tgz", + "integrity": "sha512-BpAT/3lU9DMJ2siYVD/dSR0A/zQgD6Fb+fxkJd4j+wDVy6TYbYpK+FZqu8eM9EuNKGvi4BJR7XTZ/+zF02Dq8w==", + "license": "MIT", + "dependencies": { + "hast-util-select": "^6.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-prism-plus": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-2.0.0.tgz", + "integrity": "sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ==", + "license": "MIT", + "dependencies": { + "hast-util-to-string": "^3.0.0", + "parse-numeric-range": "^1.3.0", + "refractor": "^4.8.0", + "rehype-parse": "^9.0.0", + "unist-util-filter": "^5.0.0", + "unist-util-visit": "^5.0.0" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-rewrite": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/rehype-rewrite/-/rehype-rewrite-4.0.2.tgz", + "integrity": "sha512-rjLJ3z6fIV11phwCqHp/KRo8xuUCO8o9bFJCNw5o6O2wlLk6g8r323aRswdGBQwfXPFYeSuZdAjp4tzo6RGqEg==", + "license": "MIT", + "dependencies": { + "hast-util-select": "^6.0.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/rehype-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", + "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "github-slugger": "^2.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", + "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-github-blockquote-alert": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/remark-github-blockquote-alert/-/remark-github-blockquote-alert-1.2.1.tgz", + "integrity": "sha512-qNf2mSAoZgh3Cl23/9Y1L7S4Kbf9NsdHvYK398ab/52yEsDPDU5I4cuTcgDRrdIX7Ltc6RK+KCLRtWkbFnL6Dg==", + "license": "MIT", + "dependencies": { + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", + "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rollup": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-filter": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/unist-util-filter/-/unist-util-filter-5.0.1.tgz", + "integrity": "sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universal-cookie": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.1.tgz", + "integrity": "sha512-GEKneQ0sz8qbobkYM5s9elAx6l7GQDNVl3Siqmc7bt/YccyyXWDPn+fht3J1qMcaLwPrzkty3i+dNfPGP2/9hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^0.7.2" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz", + "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fa7913b --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "devDependencies": { + "@mdi/js": "^7.4.47", + "@mdi/react": "^1.6.1", + "@types/react": "^18.2.0", + "@types/react-toggle": "^4.0.5", + "react": "^18.3.1", + "react-cookie": "^7.2.1", + "react-dom": "^18.3.1", + "react-toggle": "^4.1.3", + "typescript": "^5.6.3", + "vite": "^5.4.9" + }, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@ramonak/react-progress-bar": "^5.3.0", + "@uiw/react-markdown-preview": "^5.1.3" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..231dd12 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "lib": [ + "es2020", + "dom" + ], + "jsx": "react-jsx" + } +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..267cee6 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,9 @@ +// vite.config.js +import { defineConfig } from 'vite' + +export default defineConfig({ + server: { + port: '8080' + }, + root: 'Website' +}) \ No newline at end of file