1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
<script lang="ts">
/* eslint svelte/no-at-html-tags: "off" */
import { browser } from '$app/environment';
import HeadTitle from '$lib/Home/HeadTitle.svelte';
import Skeleton from '$lib/Loading/Skeleton.svelte';
import { createHeightObserver } from '$lib/Utility/html';
import root from '$lib/Utility/root';
import { onDestroy, onMount } from 'svelte';
let feed: { items: { title: string; link: string; content: string }[] } | null | undefined =
undefined;
let novelFeed:
| {
data: {
items: { srcurl: string; postfix?: string; chapter: number; series: { name: string } }[];
};
}
| undefined = undefined;
let startTime: number;
let mangaEndTime: number;
let novelEndTime: number;
let directLink = browser ? new URLSearchParams(window.location.search).has('d') : false;
let heightObserver: NodeJS.Timeout;
onMount(async () => {
heightObserver = setInterval(() => createHeightObserver(false), 0);
startTime = performance.now();
novelFeed = await (await fetch(root('/api/updates/all-novels'))).json();
novelEndTime = performance.now() - startTime;
startTime = performance.now();
feed = await (await fetch(root('/api/updates/manga'))).json();
mangaEndTime = performance.now() - startTime;
});
onDestroy(() => clearInterval(heightObserver));
const reformatChapter = (title: string) =>
title
.replace(/\[.*?\]\s/, '')
.replace(/c\.Oneshot/, 'Oneshot')
.replace(/c\.(\d+-\d+)/, 'Ch. $1')
.replace(/v\.(\d+)\s/, 'Vol. $1 ')
.replace(/c\.(\d+)/, 'Ch. $1');
const clipTitle = (title: string) =>
title
.replace(/(Vol\. \d+ )?Ch\. \d+(-\d+(\.\d+)?)?$/, '')
.replace(/\? ~.*$/, '')
.trim();
// const italicTitle = (title: string) =>
// title.replace(/^(.*?) (Vol\.|Ch\.|\bOneshot\b)/, '<i>$1</i> $2');
const chapterTitle = (title: string) => title.replace(/^(.*?) (Vol\.|Ch\.|\bOneshot\b)/, '$2');
</script>
<HeadTitle route="Updates" path="/updates" />
<div class="list-container">
<details open class="list">
<summary>
Manga
<small class="opaque">{mangaEndTime ? mangaEndTime / 1000 : '...'}s</small>
</summary>
{#if feed === null}
Failed to load feed
{:else if feed !== undefined}
<ul>
{#each feed.items as item}
<li>
{#if directLink}
<i>{reformatChapter(item.title)}</i>
{@html item.content}
{:else}
<a
href={`https://anilist.co/search/manga?search=${clipTitle(
reformatChapter(item.title)
)}&sort=SEARCH_MATCH`}
>
<i>{@html clipTitle(reformatChapter(item.title))}</i>
</a>
{@html chapterTitle(reformatChapter(item.title))}
{/if}
</li>
{/each}
</ul>
{:else}
<Skeleton card={false} count={5} height="0.9rem" list />
{/if}
</details>
<details open class="list">
<summary>
Novels
<small class="opaque">{novelEndTime ? novelEndTime / 1000 : '...'}s</small>
</summary>
{#if novelFeed === null}
Failed to load feed
{:else if novelFeed !== undefined}
<ul>
{#each novelFeed.data.items as item}
<li>
{#if directLink}
<a href={item.srcurl}>
<i>{@html item.series.name}</i>
{@html item.postfix || `Ch. ${item.chapter}`}
</a>
{:else}
<a
href={`https://anilist.co/search/manga?search=${item.series.name}&sort=SEARCH_MATCH`}
>
<i>{@html item.series.name}</i>
</a>
{@html item.postfix || `Ch. ${item.chapter}`}
{/if}
</li>
{/each}
</ul>
{:else}
<Skeleton card={false} count={5} height="0.9rem" list />
{/if}
</details>
</div>
<style>
.list-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
align-items: start;
gap: 1em;
flex-wrap: wrap;
}
.list {
overflow-y: auto;
min-width: 300px;
flex: 1 1 300px;
}
</style>
|