mirror of
https://github.com/C9Glax/tranga.git
synced 2025-06-14 15:27:53 +02:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
732c2f119c | |||
81638f4b4a | |||
c547aa6422 | |||
d80980512e | |||
f9f802155d | |||
eef0955009 | |||
ec25900ac0 | |||
e5fe14a09e | |||
5dc91095f8 | |||
985ac8fc7a | |||
c9537a9963 | |||
4fd3c03804 | |||
a1e9dd0232 | |||
aa1f9b1b56 | |||
6069578b6e | |||
a84b768e24 | |||
d1a21af15d |
@ -55,12 +55,13 @@ public abstract class Connector
|
|||||||
/// <param name="publication">Publication that contains Chapter</param>
|
/// <param name="publication">Publication that contains Chapter</param>
|
||||||
/// <param name="chapter">Chapter with Images to retrieve</param>
|
/// <param name="chapter">Chapter with Images to retrieve</param>
|
||||||
public abstract void DownloadChapter(Publication publication, Chapter chapter);
|
public abstract void DownloadChapter(Publication publication, Chapter chapter);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the Cover from the Website
|
/// Retrieves the Cover from the Website
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="publication">Publication to retrieve Cover for</param>
|
/// <param name="publication">Publication to retrieve Cover for</param>
|
||||||
public abstract void DownloadCover(Publication publication);
|
/// <param name="settings">TrangaSettings</param>
|
||||||
|
public abstract void CloneCoverFromCache(Publication publication, TrangaSettings settings);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves the series-info to series.json in the Publication Folder
|
/// Saves the series-info to series.json in the Publication Folder
|
||||||
@ -86,7 +87,7 @@ public abstract class Connector
|
|||||||
/// <returns>XML-string</returns>
|
/// <returns>XML-string</returns>
|
||||||
protected static string CreateComicInfo(Publication publication, Chapter chapter, Logger? logger)
|
protected static string CreateComicInfo(Publication publication, Chapter chapter, Logger? logger)
|
||||||
{
|
{
|
||||||
logger?.WriteLine("Connector", $"Creating ComicInfo.Xml for {publication.sortName} Chapter {chapter.volumeNumber} {chapter.chapterNumber}");
|
logger?.WriteLine("Connector", $"Creating ComicInfo.Xml for {publication.sortName} {publication.internalId} {chapter.volumeNumber}-{chapter.chapterNumber}");
|
||||||
XElement comicInfo = new XElement("ComicInfo",
|
XElement comicInfo = new XElement("ComicInfo",
|
||||||
new XElement("Tags", string.Join(',',publication.tags)),
|
new XElement("Tags", string.Join(',',publication.tags)),
|
||||||
new XElement("LanguageISO", publication.originalLanguage),
|
new XElement("LanguageISO", publication.originalLanguage),
|
||||||
@ -137,11 +138,12 @@ public abstract class Connector
|
|||||||
/// <param name="imageUrls">List of URLs to download Images from</param>
|
/// <param name="imageUrls">List of URLs to download Images from</param>
|
||||||
/// <param name="saveArchiveFilePath">Full path to save archive to (without file ending .cbz)</param>
|
/// <param name="saveArchiveFilePath">Full path to save archive to (without file ending .cbz)</param>
|
||||||
/// <param name="downloadClient">DownloadClient of the connector</param>
|
/// <param name="downloadClient">DownloadClient of the connector</param>
|
||||||
|
/// <param name="logger"></param>
|
||||||
/// <param name="comicInfoPath">Path of the generate Chapter ComicInfo.xml, if it was generated</param>
|
/// <param name="comicInfoPath">Path of the generate Chapter ComicInfo.xml, if it was generated</param>
|
||||||
/// <param name="requestType">RequestType for RateLimits</param>
|
/// <param name="requestType">RequestType for RateLimits</param>
|
||||||
protected static void DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, DownloadClient downloadClient, byte requestType, Logger? logger, string? comicInfoPath = null)
|
protected static void DownloadChapterImages(string[] imageUrls, string saveArchiveFilePath, DownloadClient downloadClient, byte requestType, Logger? logger, string? comicInfoPath = null)
|
||||||
{
|
{
|
||||||
logger?.WriteLine("Connector", "Downloading Images");
|
logger?.WriteLine("Connector", $"Downloading Images for {saveArchiveFilePath}");
|
||||||
//Check if Publication Directory already exists
|
//Check if Publication Directory already exists
|
||||||
string directoryPath = Path.GetDirectoryName(saveArchiveFilePath)!;
|
string directoryPath = Path.GetDirectoryName(saveArchiveFilePath)!;
|
||||||
if (!Directory.Exists(directoryPath))
|
if (!Directory.Exists(directoryPath))
|
||||||
@ -159,13 +161,14 @@ public abstract class Connector
|
|||||||
{
|
{
|
||||||
string[] split = imageUrl.Split('.');
|
string[] split = imageUrl.Split('.');
|
||||||
string extension = split[^1];
|
string extension = split[^1];
|
||||||
|
logger?.WriteLine("Connector", $"Downloading Image {chapter + 1}/{imageUrls.Length}");
|
||||||
DownloadImage(imageUrl, Path.Join(tempFolder, $"{chapter++}.{extension}"), downloadClient, requestType);
|
DownloadImage(imageUrl, Path.Join(tempFolder, $"{chapter++}.{extension}"), downloadClient, requestType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(comicInfoPath is not null)
|
if(comicInfoPath is not null)
|
||||||
File.Copy(comicInfoPath, Path.Join(tempFolder, "ComicInfo.xml"));
|
File.Copy(comicInfoPath, Path.Join(tempFolder, "ComicInfo.xml"));
|
||||||
|
|
||||||
logger?.WriteLine("Connector", "Creating archive");
|
logger?.WriteLine("Connector", $"Creating archive {saveArchiveFilePath}");
|
||||||
//ZIP-it and ship-it
|
//ZIP-it and ship-it
|
||||||
ZipFile.CreateFromDirectory(tempFolder, saveArchiveFilePath);
|
ZipFile.CreateFromDirectory(tempFolder, saveArchiveFilePath);
|
||||||
Directory.Delete(tempFolder, true); //Cleanup
|
Directory.Delete(tempFolder, true); //Cleanup
|
||||||
@ -227,6 +230,7 @@ public abstract class Connector
|
|||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
{
|
{
|
||||||
logger?.WriteLine(this.GetType().ToString(), e.Message);
|
logger?.WriteLine(this.GetType().ToString(), e.Message);
|
||||||
|
logger?.WriteLine(this.GetType().ToString(), $"Waiting {_rateLimit[requestType] * 2}");
|
||||||
Thread.Sleep(_rateLimit[requestType] * 2);
|
Thread.Sleep(_rateLimit[requestType] * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ public class MangaDex : Connector
|
|||||||
total = result["total"]!.GetValue<int>(); //Update the total number of Publications
|
total = result["total"]!.GetValue<int>(); //Update the total number of Publications
|
||||||
|
|
||||||
JsonArray mangaInResult = result["data"]!.AsArray(); //Manga-data-Array
|
JsonArray mangaInResult = result["data"]!.AsArray(); //Manga-data-Array
|
||||||
|
logger?.WriteLine(this.GetType().ToString(), $"Getting publication data.");
|
||||||
//Loop each Manga and extract information from JSON
|
//Loop each Manga and extract information from JSON
|
||||||
foreach (JsonNode? mangeNode in mangaInResult)
|
foreach (JsonNode? mangeNode in mangaInResult)
|
||||||
{
|
{
|
||||||
@ -142,12 +143,13 @@ public class MangaDex : Connector
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger?.WriteLine(this.GetType().ToString(), $"Done getting publications (title={publicationTitle})");
|
||||||
return publications.ToArray();
|
return publications.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Chapter[] GetChapters(Publication publication, string language = "")
|
public override Chapter[] GetChapters(Publication publication, string language = "")
|
||||||
{
|
{
|
||||||
logger?.WriteLine(this.GetType().ToString(), $"Getting Chapters {publication.sortName} (language={language})");
|
logger?.WriteLine(this.GetType().ToString(), $"Getting Chapters for {publication.sortName} {publication.internalId} (language={language})");
|
||||||
const int limit = 100; //How many values we want returned at once
|
const int limit = 100; //How many values we want returned at once
|
||||||
int offset = 0; //"Page"
|
int offset = 0; //"Page"
|
||||||
int total = int.MaxValue; //How many total results are there, is updated on first request
|
int total = int.MaxValue; //How many total results are there, is updated on first request
|
||||||
@ -197,12 +199,13 @@ public class MangaDex : Connector
|
|||||||
{
|
{
|
||||||
NumberDecimalSeparator = "."
|
NumberDecimalSeparator = "."
|
||||||
};
|
};
|
||||||
|
logger?.WriteLine(this.GetType().ToString(), $"Done getting Chapters for {publication.internalId}");
|
||||||
return chapters.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, chapterNumberFormatInfo)).ToArray();
|
return chapters.OrderBy(chapter => Convert.ToSingle(chapter.chapterNumber, chapterNumberFormatInfo)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DownloadChapter(Publication publication, Chapter chapter)
|
public override void DownloadChapter(Publication publication, Chapter chapter)
|
||||||
{
|
{
|
||||||
logger?.WriteLine(this.GetType().ToString(), $"Download Chapter {publication.sortName} {chapter.volumeNumber}-{chapter.chapterNumber}");
|
logger?.WriteLine(this.GetType().ToString(), $"Downloading Chapter-Info {publication.sortName} {publication.internalId} {chapter.volumeNumber}-{chapter.chapterNumber}");
|
||||||
//Request URLs for Chapter-Images
|
//Request URLs for Chapter-Images
|
||||||
DownloadClient.RequestResult requestResult =
|
DownloadClient.RequestResult requestResult =
|
||||||
downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false'", (byte)RequestType.AtHomeServer);
|
downloadClient.MakeRequest($"https://api.mangadex.org/at-home/server/{chapter.url}?forcePort443=false'", (byte)RequestType.AtHomeServer);
|
||||||
@ -229,9 +232,10 @@ public class MangaDex : Connector
|
|||||||
|
|
||||||
private string? GetCoverUrl(string publicationId, string? posterId)
|
private string? GetCoverUrl(string publicationId, string? posterId)
|
||||||
{
|
{
|
||||||
|
logger?.WriteLine(this.GetType().ToString(), $"Getting CoverUrl for {publicationId}");
|
||||||
if (posterId is null)
|
if (posterId is null)
|
||||||
{
|
{
|
||||||
logger?.WriteLine(this.GetType().ToString(), $"No posterId");
|
logger?.WriteLine(this.GetType().ToString(), $"No posterId, aborting");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,6 +251,7 @@ public class MangaDex : Connector
|
|||||||
string fileName = result["data"]!["attributes"]!["fileName"]!.GetValue<string>();
|
string fileName = result["data"]!["attributes"]!["fileName"]!.GetValue<string>();
|
||||||
|
|
||||||
string coverUrl = $"https://uploads.mangadex.org/covers/{publicationId}/{fileName}";
|
string coverUrl = $"https://uploads.mangadex.org/covers/{publicationId}/{fileName}";
|
||||||
|
logger?.WriteLine(this.GetType().ToString(), $"Got Cover-Url for {publicationId} -> {coverUrl}");
|
||||||
return coverUrl;
|
return coverUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,14 +269,16 @@ public class MangaDex : Connector
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
string author = result["data"]!["attributes"]!["name"]!.GetValue<string>();
|
string author = result["data"]!["attributes"]!["name"]!.GetValue<string>();
|
||||||
|
logger?.WriteLine(this.GetType().ToString(), $"Got author {authorId} -> {author}");
|
||||||
return author;
|
return author;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DownloadCover(Publication publication)
|
public override void CloneCoverFromCache(Publication publication, TrangaSettings settings)
|
||||||
{
|
{
|
||||||
logger?.WriteLine(this.GetType().ToString(), $"Download cover {publication.sortName}");
|
logger?.WriteLine(this.GetType().ToString(), $"Cloning cover {publication.sortName}");
|
||||||
//Check if Publication already has a Folder and cover
|
//Check if Publication already has a Folder and cover
|
||||||
string publicationFolder = Path.Join(downloadLocation, publication.folderName);
|
string publicationFolder = Path.Join(downloadLocation, publication.folderName);
|
||||||
|
|
||||||
if(!Directory.Exists(publicationFolder))
|
if(!Directory.Exists(publicationFolder))
|
||||||
Directory.CreateDirectory(publicationFolder);
|
Directory.CreateDirectory(publicationFolder);
|
||||||
DirectoryInfo dirInfo = new (publicationFolder);
|
DirectoryInfo dirInfo = new (publicationFolder);
|
||||||
@ -281,21 +288,10 @@ public class MangaDex : Connector
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (publication.posterUrl is null || publication.posterUrl!.Contains("http"))
|
string fileInCache = Path.Join(settings.coverImageCache, publication.coverFileNameInCache);
|
||||||
{
|
string newFilePath = Path.Join(publicationFolder, $"cover.{Path.GetFileName(fileInCache).Split('.')[^1]}" );
|
||||||
logger?.WriteLine(this.GetType().ToString(), $"No Poster-URL in publication");
|
logger?.WriteLine(this.GetType().ToString(), $"Cloning cover {fileInCache} -> {newFilePath}");
|
||||||
return;
|
File.Copy(fileInCache, newFilePath, true);
|
||||||
}
|
|
||||||
|
|
||||||
//Get file-extension (jpg, png)
|
|
||||||
string[] split = publication.posterUrl.Split('.');
|
|
||||||
string extension = split[^1];
|
|
||||||
|
|
||||||
string outFolderPath = Path.Join(downloadLocation, publication.folderName);
|
|
||||||
Directory.CreateDirectory(outFolderPath);
|
|
||||||
|
|
||||||
//Download cover-Image
|
|
||||||
DownloadImage(publication.posterUrl, Path.Join(downloadLocation, publication.folderName, $"cover.{extension}"), this.downloadClient, (byte)RequestType.AtHomeServer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string SaveImage(string url)
|
private string SaveImage(string url)
|
||||||
@ -311,6 +307,7 @@ public class MangaDex : Connector
|
|||||||
using MemoryStream ms = new();
|
using MemoryStream ms = new();
|
||||||
coverResult.result.CopyTo(ms);
|
coverResult.result.CopyTo(ms);
|
||||||
File.WriteAllBytes(saveImagePath, ms.ToArray());
|
File.WriteAllBytes(saveImagePath, ms.ToArray());
|
||||||
|
logger?.WriteLine(this.GetType().ToString(), $"Saving image to {saveImagePath}");
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -42,6 +42,8 @@ public readonly struct Publication
|
|||||||
this.status = status;
|
this.status = status;
|
||||||
this.publicationId = publicationId;
|
this.publicationId = publicationId;
|
||||||
this.folderName = string.Concat(LegalCharacters.Matches(sortName));
|
this.folderName = string.Concat(LegalCharacters.Matches(sortName));
|
||||||
|
while (this.folderName.EndsWith('.'))
|
||||||
|
this.folderName = this.folderName.Substring(0, this.folderName.Length - 1);
|
||||||
string onlyLowerLetters = string.Concat(this.sortName.ToLower().Where(Char.IsLetter));
|
string onlyLowerLetters = string.Concat(this.sortName.ToLower().Where(Char.IsLetter));
|
||||||
this.internalId = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{onlyLowerLetters}{this.year}"));
|
this.internalId = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{onlyLowerLetters}{this.year}"));
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ public static class TaskExecutor
|
|||||||
switch (trangaTask.task)
|
switch (trangaTask.task)
|
||||||
{
|
{
|
||||||
case TrangaTask.Task.DownloadNewChapters:
|
case TrangaTask.Task.DownloadNewChapters:
|
||||||
DownloadNewChapters(connector!, (Publication)trangaTask.publication!, trangaTask.language, ref taskManager._chapterCollection);
|
DownloadNewChapters(connector!, (Publication)trangaTask.publication!, trangaTask.language, ref taskManager._chapterCollection, taskManager.settings);
|
||||||
break;
|
break;
|
||||||
case TrangaTask.Task.UpdateChapters:
|
case TrangaTask.Task.UpdateChapters:
|
||||||
UpdateChapters(connector!, (Publication)trangaTask.publication!, trangaTask.language, ref taskManager._chapterCollection);
|
UpdateChapters(connector!, (Publication)trangaTask.publication!, trangaTask.language, ref taskManager._chapterCollection);
|
||||||
@ -90,7 +90,7 @@ public static class TaskExecutor
|
|||||||
/// <param name="publication">Publication to check</param>
|
/// <param name="publication">Publication to check</param>
|
||||||
/// <param name="language">Language to receive chapters for</param>
|
/// <param name="language">Language to receive chapters for</param>
|
||||||
/// <param name="chapterCollection"></param>
|
/// <param name="chapterCollection"></param>
|
||||||
private static void DownloadNewChapters(Connector connector, Publication publication, string language, ref Dictionary<Publication, List<Chapter>> chapterCollection)
|
private static void DownloadNewChapters(Connector connector, Publication publication, string language, ref Dictionary<Publication, List<Chapter>> chapterCollection, TrangaSettings settings)
|
||||||
{
|
{
|
||||||
//Check if Publication already has a Folder
|
//Check if Publication already has a Folder
|
||||||
string publicationFolder = Path.Join(connector.downloadLocation, publication.folderName);
|
string publicationFolder = Path.Join(connector.downloadLocation, publication.folderName);
|
||||||
@ -98,7 +98,7 @@ public static class TaskExecutor
|
|||||||
Directory.CreateDirectory(publicationFolder);
|
Directory.CreateDirectory(publicationFolder);
|
||||||
List<Chapter> newChapters = UpdateChapters(connector, publication, language, ref chapterCollection);
|
List<Chapter> newChapters = UpdateChapters(connector, publication, language, ref chapterCollection);
|
||||||
|
|
||||||
connector.DownloadCover(publication);
|
connector.CloneCoverFromCache(publication, settings);
|
||||||
|
|
||||||
string seriesInfoPath = Path.Join(publicationFolder, "series.json");
|
string seriesInfoPath = Path.Join(publicationFolder, "series.json");
|
||||||
if(!File.Exists(seriesInfoPath))
|
if(!File.Exists(seriesInfoPath))
|
||||||
|
@ -141,14 +141,11 @@ public class TaskManager
|
|||||||
TrangaTask newTask;
|
TrangaTask newTask;
|
||||||
if (task == TrangaTask.Task.UpdateKomgaLibrary)
|
if (task == TrangaTask.Task.UpdateKomgaLibrary)
|
||||||
{
|
{
|
||||||
newTask = new TrangaTask(task, null, null, reoccurrence, language);
|
newTask = new TrangaTask(task, null, null, reoccurrence);
|
||||||
|
logger?.WriteLine(this.GetType().ToString(), $"Removing old {task}-Task.");
|
||||||
//Check if same task already exists
|
//Only one UpdateKomgaLibrary Task
|
||||||
// ReSharper disable once SimplifyLinqExpressionUseAll readabilty
|
_allTasks.RemoveWhere(trangaTask => trangaTask.task is TrangaTask.Task.UpdateKomgaLibrary);
|
||||||
if (!_allTasks.Any(trangaTask => trangaTask.task == task))
|
_allTasks.Add(newTask);
|
||||||
{
|
|
||||||
_allTasks.Add(newTask);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
BIN
Website/favicon.ico
Normal file
BIN
Website/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
@ -6,106 +6,117 @@
|
|||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<topbar>
|
<wrapper>
|
||||||
<titlebox>
|
<topbar>
|
||||||
<img src="media/blahaj.png">
|
<titlebox>
|
||||||
<span>Tranga</span>
|
<img src="media/blahaj.png">
|
||||||
</titlebox>
|
<span>Tranga</span>
|
||||||
<spacer></spacer>
|
</titlebox>
|
||||||
<searchdiv>
|
<spacer></spacer>
|
||||||
<input id="searchbox" placeholder="Filter" type="text">
|
<searchdiv>
|
||||||
</searchdiv>
|
<input id="searchbox" placeholder="Filter" type="text">
|
||||||
<img id="settingscog" src="media/settings-cogwheel.svg" height="100%" alt="settingscog">
|
</searchdiv>
|
||||||
</topbar>
|
<img id="settingscog" src="media/settings-cogwheel.svg" height="100%" alt="settingscog">
|
||||||
<viewport>
|
</topbar>
|
||||||
<content>
|
<viewport>
|
||||||
<div id="addPublication">
|
<content>
|
||||||
<p>+</p>
|
<div id="addPublication">
|
||||||
|
<p>+</p>
|
||||||
|
</div>
|
||||||
|
<publication>
|
||||||
|
<img src="media/cover.jpg">
|
||||||
|
<publication-information>
|
||||||
|
<connector-name class="pill">MangaDex</connector-name>
|
||||||
|
<publication-name>Tensei Pandemic</publication-name>
|
||||||
|
</publication-information>
|
||||||
|
</publication>
|
||||||
|
</content>
|
||||||
|
|
||||||
|
<popup id="addTaskPopup">
|
||||||
|
<blur-background id="blurBackgroundTaskPopup"></blur-background>
|
||||||
|
<addtask-window>
|
||||||
|
<window-titlebar>
|
||||||
|
<p>Add Task</p>
|
||||||
|
<img id="closePopupImg" src="media/close-x.svg" alt="Close">
|
||||||
|
</window-titlebar>
|
||||||
|
<window-content>
|
||||||
|
<addtask-settings>
|
||||||
|
<addtask-setting><label for="selectReccurrence">Recurrence</label><input id="selectReccurrence" type="time" value="01:00:00" step="3600"></addtask-setting>
|
||||||
|
<addtask-setting><label for="connectors">Connector</label>
|
||||||
|
<select id="connectors">
|
||||||
|
<option value=""></option>
|
||||||
|
</select>
|
||||||
|
</addtask-setting>
|
||||||
|
<addtask-setting><label for="searchPublicationQuery">Search Title</label><input id="searchPublicationQuery" type="text"></addtask-setting>
|
||||||
|
<input type="submit" value="Search" onclick="NewSearch();">
|
||||||
|
</addtask-settings>
|
||||||
|
<div id="taskSelectOutput"></div>
|
||||||
|
</window-content>
|
||||||
|
</addtask-window>
|
||||||
|
</popup>
|
||||||
|
<popup id="publicationViewerPopup">
|
||||||
|
<blur-background id="blurBackgroundPublicationPopup"></blur-background>
|
||||||
|
<publication-viewer>
|
||||||
|
<img id="pubviewcover" src="media/cover.jpg" alt="cover">
|
||||||
|
<publication-information>
|
||||||
|
<publication-name id="publicationViewerName">Tensei Pandemic</publication-name>
|
||||||
|
<publication-author id="publicationViewerAuthor">Imamura Hinata</publication-author>
|
||||||
|
<publication-description id="publicationViewerDescription">Imamura Hinata is a high school boy with a cute appearance.
|
||||||
|
Since his trauma with the first love, he wanted to be more manly than anybody else. But one day he woke up to something different…
|
||||||
|
The total opposite of his ideal male body!
|
||||||
|
Pandemic love comedy!
|
||||||
|
</publication-description>
|
||||||
|
<publication-interactions>
|
||||||
|
<publication-starttask>Start Task ▶️</publication-starttask>
|
||||||
|
<publication-delete>Delete Task ❌</publication-delete>
|
||||||
|
<publication-add>Add Task ➕</publication-add>
|
||||||
|
</publication-interactions>
|
||||||
|
</publication-information>
|
||||||
|
</publication-viewer>
|
||||||
|
</popup>
|
||||||
|
<popup id="settingsPopup">
|
||||||
|
<blur-background id="blurBackgroundSettingsPopup"></blur-background>
|
||||||
|
<settings>
|
||||||
|
<span style="font-weight: bold; text-align: center; font-size: 16pt;">Settings</span>
|
||||||
|
<div>
|
||||||
|
<p class="title">Download Location:</p>
|
||||||
|
<span id="downloadLocation"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="title">API-URI</p>
|
||||||
|
<label for="settingApiUri"></label><input placeholder="https://" type="text" id="settingApiUri">
|
||||||
|
</div>
|
||||||
|
<komga-settings>
|
||||||
|
<span class="title">Komga</span>
|
||||||
|
<div>Configured: <span id="komgaConfigured">✅❌</span></div>
|
||||||
|
<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">
|
||||||
|
<label for="komgaUpdateTime" style="margin-right: 5px;">Update Time</label><input id="komgaUpdateTime" type="time" value="00:01:00" step="10">
|
||||||
|
<input type="submit" value="Update" onclick="UpdateKomgaSettings()">
|
||||||
|
</komga-settings>
|
||||||
|
</settings>
|
||||||
|
</popup>
|
||||||
|
</viewport>
|
||||||
|
<footer>
|
||||||
|
<div>
|
||||||
|
<img src="media/running.svg" alt="running"><div id="tasksRunningTag">0</div>
|
||||||
</div>
|
</div>
|
||||||
<publication>
|
<div>
|
||||||
<img src="media/cover.jpg">
|
<img src="media/queue.svg" alt="queue"><div id="tasksQueuedTag">0</div>
|
||||||
<publication-information>
|
</div>
|
||||||
<connector-name class="pill">MangaDex</connector-name>
|
<div>
|
||||||
<publication-name>Tensei Pandemic</publication-name>
|
<img src="media/tasks.svg" alt="queue"><div id="totalTasksTag">0</div>
|
||||||
</publication-information>
|
</div>
|
||||||
</publication>
|
<p id="madeWith">Made with Blåhaj 🦈</p>
|
||||||
</content>
|
</footer>
|
||||||
|
</wrapper>
|
||||||
<popup id="addTaskPopup">
|
<footer-tag-popup>
|
||||||
<blur-background id="blurBackgroundTaskPopup"></blur-background>
|
<footer-tag-content>
|
||||||
<addtask-window>
|
<footer-tag-task-name>Test</footer-tag-task-name>
|
||||||
<window-titlebar>
|
</footer-tag-content>
|
||||||
<p>Add Task</p>
|
</footer-tag-popup>
|
||||||
<img id="closePopupImg" src="media/close-x.svg" alt="Close">
|
|
||||||
</window-titlebar>
|
|
||||||
<window-content>
|
|
||||||
<addtask-settings>
|
|
||||||
<addtask-setting><label for="selectReccurrence">Recurrence</label><input id="selectReccurrence" type="time" value="01:00:00" step="3600"></addtask-setting>
|
|
||||||
<addtask-setting><label for="connectors">Connector</label>
|
|
||||||
<select id="connectors">
|
|
||||||
<option value=""></option>
|
|
||||||
</select>
|
|
||||||
</addtask-setting>
|
|
||||||
<addtask-setting><label for="searchPublicationQuery">Search Title</label><input id="searchPublicationQuery" type="text"></addtask-setting>
|
|
||||||
<input type="submit" value="Search" onclick="NewSearch();">
|
|
||||||
</addtask-settings>
|
|
||||||
<div id="taskSelectOutput"></div>
|
|
||||||
</window-content>
|
|
||||||
</addtask-window>
|
|
||||||
</popup>
|
|
||||||
<popup id="publicationViewerPopup">
|
|
||||||
<blur-background id="blurBackgroundPublicationPopup"></blur-background>
|
|
||||||
<publication-viewer>
|
|
||||||
<img id="pubviewcover" src="media/cover.jpg" alt="cover">
|
|
||||||
<publication-information>
|
|
||||||
<publication-name id="publicationViewerName">Tensei Pandemic</publication-name>
|
|
||||||
<publication-author id="publicationViewerAuthor">Imamura Hinata</publication-author>
|
|
||||||
<publication-description id="publicationViewerDescription">Imamura Hinata is a high school boy with a cute appearance.
|
|
||||||
Since his trauma with the first love, he wanted to be more manly than anybody else. But one day he woke up to something different…
|
|
||||||
The total opposite of his ideal male body!
|
|
||||||
Pandemic love comedy!
|
|
||||||
</publication-description>
|
|
||||||
<publication-delete>Delete Task ❌</publication-delete>
|
|
||||||
<publication-add>Add Task ➕</publication-add>
|
|
||||||
</publication-information>
|
|
||||||
</publication-viewer>
|
|
||||||
</popup>
|
|
||||||
<popup id="settingsPopup">
|
|
||||||
<blur-background id="blurBackgroundSettingsPopup"></blur-background>
|
|
||||||
<settings>
|
|
||||||
<span style="font-weight: bold; text-align: center; font-size: 16pt;">Settings</span>
|
|
||||||
<div>
|
|
||||||
<p class="title">Download Location:</p>
|
|
||||||
<span id="downloadLocation"></span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="title">API-URI</p>
|
|
||||||
<label for="settingApiUri"></label><input placeholder="https://" type="text" id="settingApiUri">
|
|
||||||
</div>
|
|
||||||
<komga-settings>
|
|
||||||
<span class="title">Komga</span>
|
|
||||||
<div>Configured: <span id="komgaConfigured">✅❌</span></div>
|
|
||||||
<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">
|
|
||||||
<label for="komgaUpdateTime" style="margin-right: 5px;">Update Time</label><input id="komgaUpdateTime" type="time" value="00:01:00" step="10">
|
|
||||||
<input type="submit" value="Update" onclick="UpdateKomgaSettings()">
|
|
||||||
</komga-settings>
|
|
||||||
</settings>
|
|
||||||
</popup>
|
|
||||||
</viewport>
|
|
||||||
<footer>
|
|
||||||
<div>
|
|
||||||
<img src="media/running.svg" alt="running"><div id="tasksRunningTag">0</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<img src="media/queue.svg" alt="queue"><div id="tasksQueuedTag">0</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<img src="media/tasks.svg" alt="queue"><div id="totalTasksTag">0</div>
|
|
||||||
</div>
|
|
||||||
<p id="madeWith">Made with Blåhaj 🦈</p>
|
|
||||||
</footer>
|
|
||||||
<script src="apiConnector.js"></script>
|
<script src="apiConnector.js"></script>
|
||||||
<script src="interaction.js"></script>
|
<script src="interaction.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -18,6 +18,7 @@ const publicationViewerAuthor = document.querySelector("#publicationViewerAuthor
|
|||||||
const pubviewcover = document.querySelector("#pubviewcover");
|
const pubviewcover = document.querySelector("#pubviewcover");
|
||||||
const publicationDelete = document.querySelector("publication-delete");
|
const publicationDelete = document.querySelector("publication-delete");
|
||||||
const publicationAdd = document.querySelector("publication-add");
|
const publicationAdd = document.querySelector("publication-add");
|
||||||
|
const publicationTaskStart = document.querySelector("publication-starttask");
|
||||||
const closetaskpopup = document.querySelector("#closePopupImg");
|
const closetaskpopup = document.querySelector("#closePopupImg");
|
||||||
const settingDownloadLocation = document.querySelector("#downloadLocation");
|
const settingDownloadLocation = document.querySelector("#downloadLocation");
|
||||||
const settingKomgaUrl = document.querySelector("#komgaUrl");
|
const settingKomgaUrl = document.querySelector("#komgaUrl");
|
||||||
@ -29,6 +30,8 @@ const settingApiUri = document.querySelector("#settingApiUri");
|
|||||||
const tagTasksRunning = document.querySelector("#tasksRunningTag");
|
const tagTasksRunning = document.querySelector("#tasksRunningTag");
|
||||||
const tagTasksQueued = document.querySelector("#tasksQueuedTag");
|
const tagTasksQueued = document.querySelector("#tasksQueuedTag");
|
||||||
const tagTasksTotal = document.querySelector("#totalTasksTag");
|
const tagTasksTotal = document.querySelector("#totalTasksTag");
|
||||||
|
const tagTaskPopup = document.querySelector("footer-tag-popup");
|
||||||
|
const tagTasksPopupContent = document.querySelector("footer-tag-content");
|
||||||
|
|
||||||
settingsCog.addEventListener("click", () => OpenSettings());
|
settingsCog.addEventListener("click", () => OpenSettings());
|
||||||
document.querySelector("#blurBackgroundSettingsPopup").addEventListener("click", () => HideSettings());
|
document.querySelector("#blurBackgroundSettingsPopup").addEventListener("click", () => HideSettings());
|
||||||
@ -37,6 +40,7 @@ document.querySelector("#blurBackgroundTaskPopup").addEventListener("click", ()
|
|||||||
document.querySelector("#blurBackgroundPublicationPopup").addEventListener("click", () => HidePublicationPopup());
|
document.querySelector("#blurBackgroundPublicationPopup").addEventListener("click", () => HidePublicationPopup());
|
||||||
publicationDelete.addEventListener("click", () => DeleteTaskClick());
|
publicationDelete.addEventListener("click", () => DeleteTaskClick());
|
||||||
publicationAdd.addEventListener("click", () => AddTaskClick());
|
publicationAdd.addEventListener("click", () => AddTaskClick());
|
||||||
|
publicationTaskStart.addEventListener("click", () => StartTaskClick());
|
||||||
settingApiUri.addEventListener("keypress", (event) => {
|
settingApiUri.addEventListener("keypress", (event) => {
|
||||||
if(event.key === "Enter"){
|
if(event.key === "Enter"){
|
||||||
apiUri = settingApiUri.value;
|
apiUri = settingApiUri.value;
|
||||||
@ -49,6 +53,12 @@ searchPublicationQuery.addEventListener("keypress", (event) => {
|
|||||||
NewSearch();
|
NewSearch();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
tagTasksRunning.addEventListener("mouseover", (event) => ShowRunningTasks(event));
|
||||||
|
tagTasksRunning.addEventListener("mouseout", () => CloseTasksPopup());
|
||||||
|
tagTasksQueued.addEventListener("mouseover", (event) => ShowQueuedTasks(event));
|
||||||
|
tagTasksQueued.addEventListener("mouseout", () => CloseTasksPopup());
|
||||||
|
tagTasksTotal.addEventListener("mouseover", (event) => ShowAllTasks(event));
|
||||||
|
tagTasksTotal.addEventListener("mouseout", () => CloseTasksPopup());
|
||||||
|
|
||||||
let availableConnectors;
|
let availableConnectors;
|
||||||
GetAvailableControllers()
|
GetAvailableControllers()
|
||||||
@ -122,6 +132,12 @@ function AddTaskClick(){
|
|||||||
HidePublicationPopup();
|
HidePublicationPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function StartTaskClick(){
|
||||||
|
var toEditTask = tasks.filter(task => task.publication.internalId == toEditId)[0];
|
||||||
|
StartTask("DownloadNewChapters", toEditTask.connectorName, toEditId);
|
||||||
|
HidePublicationPopup();
|
||||||
|
}
|
||||||
|
|
||||||
function ResetContent(){
|
function ResetContent(){
|
||||||
//Delete everything
|
//Delete everything
|
||||||
tasksContent.replaceChildren();
|
tasksContent.replaceChildren();
|
||||||
@ -136,8 +152,6 @@ function ResetContent(){
|
|||||||
tasksContent.appendChild(add);
|
tasksContent.appendChild(add);
|
||||||
}
|
}
|
||||||
function ShowPublicationViewerWindow(publicationId, event, add){
|
function ShowPublicationViewerWindow(publicationId, event, add){
|
||||||
|
|
||||||
|
|
||||||
//Show popup
|
//Show popup
|
||||||
publicationViewerPopup.style.display = "block";
|
publicationViewerPopup.style.display = "block";
|
||||||
|
|
||||||
@ -162,12 +176,14 @@ function ShowPublicationViewerWindow(publicationId, event, add){
|
|||||||
|
|
||||||
//Check what action should be listed
|
//Check what action should be listed
|
||||||
if(add){
|
if(add){
|
||||||
publicationAdd.style.display = "block";
|
publicationAdd.style.display = "initial";
|
||||||
publicationDelete.style.display = "none";
|
publicationDelete.style.display = "none";
|
||||||
|
publicationTaskStart.style.display = "none";
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
publicationAdd.style.display = "none";
|
publicationAdd.style.display = "none";
|
||||||
publicationDelete.style.display = "block";
|
publicationDelete.style.display = "initial";
|
||||||
|
publicationTaskStart.style.display = "initial";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +235,7 @@ function GetSettingsClick(){
|
|||||||
});
|
});
|
||||||
|
|
||||||
GetKomgaTask().then(json => {
|
GetKomgaTask().then(json => {
|
||||||
|
settingKomgaTime.value = json[0].reoccurrence;
|
||||||
if(json.length > 0)
|
if(json.length > 0)
|
||||||
settingKomgaConfigured.innerText = "✅";
|
settingKomgaConfigured.innerText = "✅";
|
||||||
else
|
else
|
||||||
@ -227,17 +244,79 @@ function GetSettingsClick(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function UpdateKomgaSettings(){
|
function UpdateKomgaSettings(){
|
||||||
var auth = utf8_to_b64(`${settingKomgaUser.value}:${settingKomgaPass.value}`);
|
if(settingKomgaUser.value != "" && settingKomgaPass != ""){
|
||||||
console.log(auth);
|
var auth = utf8_to_b64(`${settingKomgaUser.value}:${settingKomgaPass.value}`);
|
||||||
UpdateSettings("", settingKomgaUrl.value, auth);
|
console.log(auth);
|
||||||
|
|
||||||
|
if(settingKomgaUrl.value != "")
|
||||||
|
UpdateSettings("", settingKomgaUrl.value, auth);
|
||||||
|
else
|
||||||
|
UpdateSettings("", settingKomgaUrl.placeholder, auth);
|
||||||
|
}
|
||||||
CreateTask("UpdateKomgaLibrary", settingKomgaTime.value, "","","");
|
CreateTask("UpdateKomgaLibrary", settingKomgaTime.value, "","","");
|
||||||
setTimeout(() => GetSettingsClick(), 500);
|
setTimeout(() => GetSettingsClick(), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
function utf8_to_b64( str ) {
|
function utf8_to_b64( str ) {
|
||||||
return window.btoa(unescape(encodeURIComponent( str )));
|
return window.btoa(unescape(encodeURIComponent( str )));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ShowRunningTasks(event){
|
||||||
|
GetRunningTasks()
|
||||||
|
.then(json => {
|
||||||
|
tagTasksPopupContent.replaceChildren();
|
||||||
|
json.forEach(task => {
|
||||||
|
console.log(task);
|
||||||
|
if(task.publication != null){
|
||||||
|
var taskname = document.createElement("footer-tag-task-name");
|
||||||
|
taskname.innerText = task.publication.sortName;
|
||||||
|
tagTasksPopupContent.appendChild(taskname);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(tagTasksPopupContent.children.length > 0){
|
||||||
|
tagTaskPopup.style.display = "block";
|
||||||
|
tagTaskPopup.style.left = `${tagTasksRunning.offsetLeft - 20}px`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShowQueuedTasks(event){
|
||||||
|
GetQueue()
|
||||||
|
.then(json => {
|
||||||
|
tagTasksPopupContent.replaceChildren();
|
||||||
|
json.forEach(task => {
|
||||||
|
var taskname = document.createElement("footer-tag-task-name");
|
||||||
|
taskname.innerText = task.publication.sortName;
|
||||||
|
tagTasksPopupContent.appendChild(taskname);
|
||||||
|
});
|
||||||
|
if(json.length > 0){
|
||||||
|
tagTaskPopup.style.display = "block";
|
||||||
|
tagTaskPopup.style.left = `${tagTasksQueued.offsetLeft- 20}px`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShowAllTasks(event){
|
||||||
|
GetDownloadTasks()
|
||||||
|
.then(json => {
|
||||||
|
tagTasksPopupContent.replaceChildren();
|
||||||
|
json.forEach(task => {
|
||||||
|
var taskname = document.createElement("footer-tag-task-name");
|
||||||
|
taskname.innerText = task.publication.sortName;
|
||||||
|
tagTasksPopupContent.appendChild(taskname);
|
||||||
|
});
|
||||||
|
if(json.length > 0){
|
||||||
|
tagTaskPopup.style.display = "block";
|
||||||
|
tagTaskPopup.style.left = `${tagTasksTotal.offsetLeft - 20}px`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function CloseTasksPopup(){
|
||||||
|
tagTaskPopup.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
//Resets the tasks shown
|
//Resets the tasks shown
|
||||||
ResetContent();
|
ResetContent();
|
||||||
//Get Tasks and show them
|
//Get Tasks and show them
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
:root{
|
:root{
|
||||||
--background-color: #eee;
|
--background-color: #030304;
|
||||||
--second-background-color: #fff;
|
--second-background-color: #fff;
|
||||||
--primary-color: #f5a9b8;
|
--primary-color: #f5a9b8;
|
||||||
--secondary-color: #5bcefa;
|
--secondary-color: #5bcefa;
|
||||||
@ -11,15 +11,19 @@
|
|||||||
body{
|
body{
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
font-family: "Inter", sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
background-placeholder{
|
background-placeholder{
|
||||||
background-color: var(--second-background-color);
|
background-color: var(--second-background-color);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@ -229,7 +233,7 @@ publication::after{
|
|||||||
left: 0; top: 0;
|
left: 0; top: 0;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 100%; height: 100%;
|
width: 100%; height: 100%;
|
||||||
background: linear-gradient(rgba(0,0,0,0.7), rgba(0, 0, 0, 0.6),rgba(0, 0, 0, 0.2));
|
background: linear-gradient(rgba(0,0,0,0.8), rgba(0, 0, 0, 0.7),rgba(0, 0, 0, 0.2));
|
||||||
}
|
}
|
||||||
|
|
||||||
publication-information {
|
publication-information {
|
||||||
@ -379,9 +383,6 @@ publication-viewer{
|
|||||||
background-color: var(--accent-color);
|
background-color: var(--accent-color);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
|
||||||
|
|
||||||
publication-viewer{
|
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,20 +432,69 @@ publication-viewer publication-information publication-description {
|
|||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
publication-viewer publication-information publication-delete {
|
publication-viewer publication-information publication-interactions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0px;
|
justify-content: end;
|
||||||
right: 0px;
|
align-items: start;
|
||||||
color: red;
|
bottom: 0;
|
||||||
margin: 20px;
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
publication-viewer publication-information publication-interactions > * {
|
||||||
|
margin: 0 10px 10px 10px;
|
||||||
font-size: 16pt;
|
font-size: 16pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
publication-viewer publication-information publication-add {
|
publication-viewer publication-information publication-interactions publication-starttask {
|
||||||
position: absolute;
|
color: var(--secondary-color);
|
||||||
bottom: 0px;
|
}
|
||||||
right: 0px;
|
|
||||||
|
publication-viewer publication-information publication-interactions publication-delete {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
publication-viewer publication-information publication-interactions publication-add {
|
||||||
color: limegreen;
|
color: limegreen;
|
||||||
margin: 20px;
|
}
|
||||||
font-size: 16pt;
|
|
||||||
|
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 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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: 0px;
|
||||||
|
bottom: -17px;
|
||||||
|
border-radius: 0 0 0 5px;
|
||||||
}
|
}
|
Reference in New Issue
Block a user