Compare commits
10 Commits
feature/FA
...
hotfix/FA-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fd76f6787 | ||
|
|
4fb34bdef7 | ||
|
|
f830773af5 | ||
| 7185b95c65 | |||
|
|
8b44cf2f0c | ||
| ac48889f4c | |||
|
|
5c52d29da9 | ||
| 16004ad938 | |||
|
|
d109db2155 | ||
| 89dff0980b |
@@ -24,6 +24,8 @@ RUN dotnet build "./FictionArchive.API.csproj" -c $BUILD_CONFIGURATION -o /app/b
|
|||||||
FROM build AS publish
|
FROM build AS publish
|
||||||
ARG BUILD_CONFIGURATION=Release
|
ARG BUILD_CONFIGURATION=Release
|
||||||
RUN dotnet publish "./FictionArchive.API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false /p:SkipFusionBuild=true
|
RUN dotnet publish "./FictionArchive.API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false /p:SkipFusionBuild=true
|
||||||
|
# Copy pre-composed gateway.fgp from CI build
|
||||||
|
COPY FictionArchive.API/gateway.fgp /app/publish/
|
||||||
|
|
||||||
FROM base AS final
|
FROM base AS final
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ public class Program
|
|||||||
.ConfigureFromFile("gateway.fgp")
|
.ConfigureFromFile("gateway.fgp")
|
||||||
.CoreBuilder.ApplySaneDefaults();
|
.CoreBuilder.ApplySaneDefaults();
|
||||||
|
|
||||||
builder.Services.AddOidcAuthentication(builder.Configuration);
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
var allowedOrigin = builder.Configuration["Cors:AllowedOrigin"] ?? "http://localhost:4321";
|
var allowedOrigin = builder.Configuration["Cors:AllowedOrigin"] ?? "http://localhost:4321";
|
||||||
|
|||||||
@@ -36,7 +36,15 @@ namespace FictionArchive.Service.FileService.Controllers
|
|||||||
Key = decodedPath
|
Key = decodedPath
|
||||||
});
|
});
|
||||||
|
|
||||||
return new FileStreamResult(s3Response.ResponseStream, s3Response.Headers.ContentType);
|
Response.Headers.CacheControl = "public, max-age=604800"; // 7 days
|
||||||
|
Response.Headers.LastModified = s3Response.LastModified?.ToString("R");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(s3Response.ETag))
|
||||||
|
{
|
||||||
|
Response.Headers.ETag = s3Response.ETag;
|
||||||
|
}
|
||||||
|
|
||||||
|
return File(s3Response.ResponseStream, s3Response.Headers.ContentType);
|
||||||
}
|
}
|
||||||
catch (AmazonS3Exception e)
|
catch (AmazonS3Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -137,11 +137,9 @@ public class NovelpiaAdapter : ISourceAdapter
|
|||||||
uint page = 0;
|
uint page = 0;
|
||||||
List<ChapterMetadata> chapters = new List<ChapterMetadata>();
|
List<ChapterMetadata> chapters = new List<ChapterMetadata>();
|
||||||
List<uint> seenChapterIds = new List<uint>();
|
List<uint> seenChapterIds = new List<uint>();
|
||||||
uint chapterOrder = 0;
|
uint chapterOrder = 1;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
await Task.Delay(500);
|
|
||||||
_logger.LogInformation("Next chapter batch");
|
|
||||||
var response = await _httpClient.PostAsync(EpisodeListEndpoint, new FormUrlEncodedContent(new Dictionary<string, string>
|
var response = await _httpClient.PostAsync(EpisodeListEndpoint, new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{"novel_no", novelId.ToString()},
|
{"novel_no", novelId.ToString()},
|
||||||
|
|||||||
14
fictionarchive-web-astro/package-lock.json
generated
14
fictionarchive-web-astro/package-lock.json
generated
@@ -12,6 +12,7 @@
|
|||||||
"@astrojs/svelte": "^7.2.2",
|
"@astrojs/svelte": "^7.2.2",
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@urql/core": "^6.0.1",
|
"@urql/core": "^6.0.1",
|
||||||
|
"@urql/exchange-auth": "^3.0.0",
|
||||||
"@urql/svelte": "^5.0.0",
|
"@urql/svelte": "^5.0.0",
|
||||||
"astro": "^5.16.2",
|
"astro": "^5.16.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@@ -4304,6 +4305,19 @@
|
|||||||
"wonka": "^6.3.2"
|
"wonka": "^6.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@urql/exchange-auth": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@urql/exchange-auth/-/exchange-auth-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-tj09xiOR2f1J2h8TE9uZWjRZipCdmDoTewEytOacDQ+0Teo+yIZxm3ppHxolQtiA51OHrGYiNTkMte8HtfvaBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@urql/core": "^6.0.0",
|
||||||
|
"wonka": "^6.3.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@urql/core": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@urql/svelte": {
|
"node_modules/@urql/svelte": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@urql/svelte/-/svelte-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@urql/svelte/-/svelte-5.0.0.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"@astrojs/svelte": "^7.2.2",
|
"@astrojs/svelte": "^7.2.2",
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@urql/core": "^6.0.1",
|
"@urql/core": "^6.0.1",
|
||||||
|
"@urql/exchange-auth": "^3.0.0",
|
||||||
"@urql/svelte": "^5.0.0",
|
"@urql/svelte": "^5.0.0",
|
||||||
"astro": "^5.16.2",
|
"astro": "^5.16.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
|
|||||||
@@ -117,3 +117,14 @@ export async function logout() {
|
|||||||
user.set(null);
|
user.set(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function refreshToken(): Promise<User | null> {
|
||||||
|
if (!userManager) return null;
|
||||||
|
try {
|
||||||
|
const newUser = await userManager.signinSilent();
|
||||||
|
return newUser;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Token refresh failed:', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const authority = import.meta.env.PUBLIC_OIDC_AUTHORITY;
|
|||||||
const clientId = import.meta.env.PUBLIC_OIDC_CLIENT_ID;
|
const clientId = import.meta.env.PUBLIC_OIDC_CLIENT_ID;
|
||||||
const redirectUri = import.meta.env.PUBLIC_OIDC_REDIRECT_URI;
|
const redirectUri = import.meta.env.PUBLIC_OIDC_REDIRECT_URI;
|
||||||
const postLogoutRedirectUri = import.meta.env.PUBLIC_OIDC_POST_LOGOUT_REDIRECT_URI ?? redirectUri;
|
const postLogoutRedirectUri = import.meta.env.PUBLIC_OIDC_POST_LOGOUT_REDIRECT_URI ?? redirectUri;
|
||||||
const scope = import.meta.env.PUBLIC_OIDC_SCOPE ?? 'openid profile email';
|
const scope = import.meta.env.PUBLIC_OIDC_SCOPE ?? 'openid profile email offline_access';
|
||||||
|
|
||||||
export const isOidcConfigured =
|
export const isOidcConfigured =
|
||||||
Boolean(authority) && Boolean(clientId) && Boolean(redirectUri);
|
Boolean(authority) && Boolean(clientId) && Boolean(redirectUri);
|
||||||
@@ -20,7 +20,7 @@ function buildSettings(): UserManagerSettings | null {
|
|||||||
response_type: 'code',
|
response_type: 'code',
|
||||||
scope,
|
scope,
|
||||||
loadUserInfo: true,
|
loadUserInfo: true,
|
||||||
automaticSilentRenew: true,
|
automaticSilentRenew: false, // We handle refresh reactively via authExchange
|
||||||
userStore:
|
userStore:
|
||||||
typeof window !== 'undefined'
|
typeof window !== 'undefined'
|
||||||
? new WebStorageStateStore({ store: window.localStorage })
|
? new WebStorageStateStore({ store: window.localStorage })
|
||||||
|
|||||||
@@ -155,10 +155,9 @@
|
|||||||
<Card>
|
<Card>
|
||||||
<CardContent class="px-6 py-8 md:px-12">
|
<CardContent class="px-6 py-8 md:px-12">
|
||||||
<article
|
<article
|
||||||
class="prose prose-lg dark:prose-invert mx-auto max-w-none whitespace-pre-line
|
class="prose prose-lg dark:prose-invert mx-auto max-w-none
|
||||||
prose-p:text-foreground prose-p:mb-4 prose-p:leading-relaxed
|
|
||||||
prose-headings:text-foreground
|
prose-headings:text-foreground
|
||||||
first:prose-p:mt-0 last:prose-p:mb-0"
|
[&>p]:text-foreground [&>p]:mb-6 [&>p]:leading-relaxed"
|
||||||
>
|
>
|
||||||
{@html sanitizedBody}
|
{@html sanitizedBody}
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -51,6 +51,9 @@
|
|||||||
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
||||||
import ChevronUp from '@lucide/svelte/icons/chevron-up';
|
import ChevronUp from '@lucide/svelte/icons/chevron-up';
|
||||||
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
||||||
|
import X from '@lucide/svelte/icons/x';
|
||||||
|
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
||||||
|
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
novelId?: string;
|
novelId?: string;
|
||||||
@@ -66,10 +69,22 @@
|
|||||||
let refreshError: string | null = $state(null);
|
let refreshError: string | null = $state(null);
|
||||||
let refreshSuccess = $state(false);
|
let refreshSuccess = $state(false);
|
||||||
|
|
||||||
|
// Image viewer state
|
||||||
|
type GalleryImage = {
|
||||||
|
src: string;
|
||||||
|
alt: string;
|
||||||
|
chapterId?: number;
|
||||||
|
chapterOrder?: number;
|
||||||
|
chapterName?: string;
|
||||||
|
isCover: boolean;
|
||||||
|
};
|
||||||
|
let viewerOpen = $state(false);
|
||||||
|
let viewerIndex = $state(0);
|
||||||
|
|
||||||
const DESCRIPTION_PREVIEW_LENGTH = 300;
|
const DESCRIPTION_PREVIEW_LENGTH = 300;
|
||||||
|
|
||||||
// Derived values
|
// Derived values
|
||||||
const coverSrc = $derived(novel?.coverImage?.newPath ?? novel?.coverImage?.originalPath);
|
const coverSrc = $derived(novel?.coverImage?.newPath);
|
||||||
const status = $derived(novel?.rawStatus ?? 'UNKNOWN');
|
const status = $derived(novel?.rawStatus ?? 'UNKNOWN');
|
||||||
const statusColor = $derived(statusColors[status]);
|
const statusColor = $derived(statusColors[status]);
|
||||||
const statusLabel = $derived(statusLabels[status]);
|
const statusLabel = $derived(statusLabels[status]);
|
||||||
@@ -102,6 +117,65 @@
|
|||||||
return lastUpdated.getTime() < sixHoursAgo;
|
return lastUpdated.getTime() < sixHoursAgo;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Gallery images - cover + chapter images
|
||||||
|
const galleryImages = $derived.by(() => {
|
||||||
|
const images: GalleryImage[] = [];
|
||||||
|
|
||||||
|
// Add cover image first
|
||||||
|
if (coverSrc && novel) {
|
||||||
|
images.push({ src: coverSrc, alt: `${novel.name} cover`, isCover: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add chapter images
|
||||||
|
for (const chapter of sortedChapters) {
|
||||||
|
for (const img of chapter.images ?? []) {
|
||||||
|
if (img.newPath) {
|
||||||
|
images.push({
|
||||||
|
src: img.newPath,
|
||||||
|
alt: `Image from ${chapter.name}`,
|
||||||
|
chapterId: chapter.id,
|
||||||
|
chapterOrder: chapter.order,
|
||||||
|
chapterName: chapter.name,
|
||||||
|
isCover: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return images;
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentImage = $derived(galleryImages[viewerIndex]);
|
||||||
|
|
||||||
|
// Image viewer functions
|
||||||
|
function openImageViewer(index: number) {
|
||||||
|
viewerIndex = index;
|
||||||
|
viewerOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeViewer() {
|
||||||
|
viewerOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextImage() {
|
||||||
|
if (galleryImages.length > 0) {
|
||||||
|
viewerIndex = (viewerIndex + 1) % galleryImages.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevImage() {
|
||||||
|
if (galleryImages.length > 0) {
|
||||||
|
viewerIndex = (viewerIndex - 1 + galleryImages.length) % galleryImages.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleViewerKeydown(e: KeyboardEvent) {
|
||||||
|
if (!viewerOpen) return;
|
||||||
|
if (e.key === 'Escape') closeViewer();
|
||||||
|
if (e.key === 'ArrowRight') nextImage();
|
||||||
|
if (e.key === 'ArrowLeft') prevImage();
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchNovel() {
|
async function fetchNovel() {
|
||||||
if (!novelId) {
|
if (!novelId) {
|
||||||
error = 'No novel ID provided';
|
error = 'No novel ID provided';
|
||||||
@@ -371,18 +445,17 @@
|
|||||||
Chapters
|
Chapters
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="comments"
|
value="gallery"
|
||||||
disabled
|
class="rounded-md data-[state=active]:bg-background data-[state=active]:shadow-sm px-3 py-1.5 text-sm font-medium transition-all"
|
||||||
class="rounded-md data-[state=active]:bg-background data-[state=active]:shadow-sm px-3 py-1.5 text-sm font-medium transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
>
|
>
|
||||||
Comments
|
Gallery
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="recommendations"
|
value="bookmarks"
|
||||||
disabled
|
disabled
|
||||||
class="rounded-md data-[state=active]:bg-background data-[state=active]:shadow-sm px-3 py-1.5 text-sm font-medium transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
class="rounded-md data-[state=active]:bg-background data-[state=active]:shadow-sm px-3 py-1.5 text-sm font-medium transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
Recommendations
|
Bookmarks
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -420,15 +493,32 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="comments" class="mt-0">
|
<TabsContent value="gallery" class="mt-0">
|
||||||
<p class="text-muted-foreground text-sm py-8 text-center">
|
{#if galleryImages.length === 0}
|
||||||
Comments coming soon.
|
<p class="text-muted-foreground text-sm py-4 text-center">
|
||||||
|
No images available.
|
||||||
</p>
|
</p>
|
||||||
|
{:else}
|
||||||
|
<div class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-2">
|
||||||
|
{#each galleryImages as image, index (image.src)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => openImageViewer(index)}
|
||||||
|
class="relative aspect-square overflow-hidden rounded-md bg-muted/50 hover:ring-2 ring-primary transition-all"
|
||||||
|
>
|
||||||
|
<img src={image.src} alt={image.alt} class="h-full w-full object-cover" />
|
||||||
|
{#if image.isCover}
|
||||||
|
<Badge class="absolute top-1 left-1 text-xs">Cover</Badge>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="recommendations" class="mt-0">
|
<TabsContent value="bookmarks" class="mt-0">
|
||||||
<p class="text-muted-foreground text-sm py-8 text-center">
|
<p class="text-muted-foreground text-sm py-8 text-center">
|
||||||
Recommendations coming soon.
|
Bookmarks coming soon.
|
||||||
</p>
|
</p>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -436,3 +526,72 @@
|
|||||||
</Card>
|
</Card>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Image Viewer Modal -->
|
||||||
|
{#if viewerOpen && currentImage}
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/90 backdrop-blur-sm"
|
||||||
|
onclick={closeViewer}
|
||||||
|
onkeydown={handleViewerKeydown}
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<!-- Close button -->
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class="absolute top-4 right-4 text-white hover:bg-white/10"
|
||||||
|
onclick={closeViewer}
|
||||||
|
>
|
||||||
|
<X class="h-6 w-6" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Navigation arrows -->
|
||||||
|
{#if galleryImages.length > 1}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class="absolute left-4 text-white hover:bg-white/10 h-12 w-12"
|
||||||
|
onclick={(e: MouseEvent) => { e.stopPropagation(); prevImage(); }}
|
||||||
|
>
|
||||||
|
<ChevronLeft class="h-8 w-8" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class="absolute right-4 top-1/2 -translate-y-1/2 text-white hover:bg-white/10 h-12 w-12"
|
||||||
|
onclick={(e: MouseEvent) => { e.stopPropagation(); nextImage(); }}
|
||||||
|
>
|
||||||
|
<ChevronRight class="h-8 w-8" />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Image container -->
|
||||||
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div class="flex flex-col items-center max-w-[90vw] max-h-[90vh]" onclick={(e: MouseEvent) => e.stopPropagation()}>
|
||||||
|
<img
|
||||||
|
src={currentImage.src}
|
||||||
|
alt={currentImage.alt}
|
||||||
|
class="max-w-full max-h-[80vh] object-contain rounded-lg"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Chapter link (if not cover) -->
|
||||||
|
{#if !currentImage.isCover && currentImage.chapterOrder}
|
||||||
|
<a
|
||||||
|
href="/novels/{novelId}/chapters/{currentImage.chapterOrder}"
|
||||||
|
class="text-white/80 hover:text-white text-sm inline-flex items-center gap-1 mt-3"
|
||||||
|
>
|
||||||
|
From: Ch. {currentImage.chapterOrder} - {currentImage.chapterName}
|
||||||
|
<ExternalLink class="h-3 w-3" />
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Image counter -->
|
||||||
|
<div class="text-white/60 text-sm mt-2">
|
||||||
|
{viewerIndex + 1} / {galleryImages.length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ export type ImageDto = {
|
|||||||
id: Scalars['UUID']['output'];
|
id: Scalars['UUID']['output'];
|
||||||
lastUpdatedTime: Scalars['Instant']['output'];
|
lastUpdatedTime: Scalars['Instant']['output'];
|
||||||
newPath: Maybe<Scalars['String']['output']>;
|
newPath: Maybe<Scalars['String']['output']>;
|
||||||
originalPath: Scalars['String']['output'];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ImageDtoFilterInput = {
|
export type ImageDtoFilterInput = {
|
||||||
@@ -124,7 +123,6 @@ export type ImageDtoFilterInput = {
|
|||||||
lastUpdatedTime?: InputMaybe<InstantFilterInput>;
|
lastUpdatedTime?: InputMaybe<InstantFilterInput>;
|
||||||
newPath?: InputMaybe<StringOperationFilterInput>;
|
newPath?: InputMaybe<StringOperationFilterInput>;
|
||||||
or?: InputMaybe<Array<ImageDtoFilterInput>>;
|
or?: InputMaybe<Array<ImageDtoFilterInput>>;
|
||||||
originalPath?: InputMaybe<StringOperationFilterInput>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ImageDtoSortInput = {
|
export type ImageDtoSortInput = {
|
||||||
@@ -132,7 +130,6 @@ export type ImageDtoSortInput = {
|
|||||||
id?: InputMaybe<SortEnumType>;
|
id?: InputMaybe<SortEnumType>;
|
||||||
lastUpdatedTime?: InputMaybe<SortEnumType>;
|
lastUpdatedTime?: InputMaybe<SortEnumType>;
|
||||||
newPath?: InputMaybe<SortEnumType>;
|
newPath?: InputMaybe<SortEnumType>;
|
||||||
originalPath?: InputMaybe<SortEnumType>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ImportNovelInput = {
|
export type ImportNovelInput = {
|
||||||
@@ -730,7 +727,7 @@ export type NovelQueryVariables = Exact<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type NovelQuery = { novels: { nodes: Array<{ id: any, name: string, description: string, url: string, rawLanguage: Language, rawStatus: NovelStatus, statusOverride: NovelStatus | null, externalId: string, createdTime: any, lastUpdatedTime: any, author: { id: any, name: string, externalUrl: string | null }, source: { id: any, name: string, key: string, url: string }, coverImage: { newPath: string | null } | null, tags: Array<{ id: any, key: string, displayName: string, tagType: TagType }>, chapters: Array<{ id: any, order: any, name: string, lastUpdatedTime: any }> }> | null } | null };
|
export type NovelQuery = { novels: { nodes: Array<{ id: any, name: string, description: string, url: string, rawLanguage: Language, rawStatus: NovelStatus, statusOverride: NovelStatus | null, externalId: string, createdTime: any, lastUpdatedTime: any, author: { id: any, name: string, externalUrl: string | null }, source: { id: any, name: string, key: string, url: string }, coverImage: { newPath: string | null } | null, tags: Array<{ id: any, key: string, displayName: string, tagType: TagType }>, chapters: Array<{ id: any, order: any, name: string, lastUpdatedTime: any, images: Array<{ id: any, newPath: string | null }> }> }> | null } | null };
|
||||||
|
|
||||||
export type NovelsQueryVariables = Exact<{
|
export type NovelsQueryVariables = Exact<{
|
||||||
first?: InputMaybe<Scalars['Int']['input']>;
|
first?: InputMaybe<Scalars['Int']['input']>;
|
||||||
@@ -744,5 +741,5 @@ export type NovelsQuery = { novels: { edges: Array<{ cursor: string, node: { id:
|
|||||||
|
|
||||||
export const ImportNovelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ImportNovel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImportNovelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importNovel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novelUpdateRequestedEvent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novelUrl"}}]}}]}}]}}]} as unknown as DocumentNode<ImportNovelMutation, ImportNovelMutationVariables>;
|
export const ImportNovelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ImportNovel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ImportNovelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importNovel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novelUpdateRequestedEvent"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novelUrl"}}]}}]}}]}}]} as unknown as DocumentNode<ImportNovelMutation, ImportNovelMutationVariables>;
|
||||||
export const GetChapterDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetChapter"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"novelId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UnsignedInt"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"chapterOrder"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UnsignedInt"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"novelId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"novelId"}}},{"kind":"Argument","name":{"kind":"Name","value":"chapterOrder"},"value":{"kind":"Variable","name":{"kind":"Name","value":"chapterOrder"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"order"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"revision"}},{"kind":"Field","name":{"kind":"Name","value":"createdTime"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"newPath"}}]}},{"kind":"Field","name":{"kind":"Name","value":"novelId"}},{"kind":"Field","name":{"kind":"Name","value":"novelName"}},{"kind":"Field","name":{"kind":"Name","value":"totalChapters"}},{"kind":"Field","name":{"kind":"Name","value":"prevChapterOrder"}},{"kind":"Field","name":{"kind":"Name","value":"nextChapterOrder"}}]}}]}}]} as unknown as DocumentNode<GetChapterQuery, GetChapterQueryVariables>;
|
export const GetChapterDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetChapter"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"novelId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UnsignedInt"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"chapterOrder"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UnsignedInt"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"novelId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"novelId"}}},{"kind":"Argument","name":{"kind":"Name","value":"chapterOrder"},"value":{"kind":"Variable","name":{"kind":"Name","value":"chapterOrder"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"order"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"revision"}},{"kind":"Field","name":{"kind":"Name","value":"createdTime"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"newPath"}}]}},{"kind":"Field","name":{"kind":"Name","value":"novelId"}},{"kind":"Field","name":{"kind":"Name","value":"novelName"}},{"kind":"Field","name":{"kind":"Name","value":"totalChapters"}},{"kind":"Field","name":{"kind":"Name","value":"prevChapterOrder"}},{"kind":"Field","name":{"kind":"Name","value":"nextChapterOrder"}}]}}]}}]} as unknown as DocumentNode<GetChapterQuery, GetChapterQueryVariables>;
|
||||||
export const NovelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Novel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UnsignedInt"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}]}},{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"rawLanguage"}},{"kind":"Field","name":{"kind":"Name","value":"rawStatus"}},{"kind":"Field","name":{"kind":"Name","value":"statusOverride"}},{"kind":"Field","name":{"kind":"Name","value":"externalId"}},{"kind":"Field","name":{"kind":"Name","value":"createdTime"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"externalUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"kind":"Field","name":{"kind":"Name","value":"coverImage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"newPath"}}]}},{"kind":"Field","name":{"kind":"Name","value":"tags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"tagType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"chapters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"order"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}}]}}]}}]}}]}}]} as unknown as DocumentNode<NovelQuery, NovelQueryVariables>;
|
export const NovelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Novel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UnsignedInt"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"id"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}}]}},{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"rawLanguage"}},{"kind":"Field","name":{"kind":"Name","value":"rawStatus"}},{"kind":"Field","name":{"kind":"Name","value":"statusOverride"}},{"kind":"Field","name":{"kind":"Name","value":"externalId"}},{"kind":"Field","name":{"kind":"Name","value":"createdTime"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"externalUrl"}}]}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"kind":"Field","name":{"kind":"Name","value":"coverImage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"newPath"}}]}},{"kind":"Field","name":{"kind":"Name","value":"tags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"tagType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"chapters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"order"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}},{"kind":"Field","name":{"kind":"Name","value":"images"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"newPath"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<NovelQuery, NovelQueryVariables>;
|
||||||
export const NovelsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Novels"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"where"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"NovelDtoFilterInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"Variable","name":{"kind":"Name","value":"where"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"coverImage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"newPath"}}]}},{"kind":"Field","name":{"kind":"Name","value":"rawStatus"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}},{"kind":"Field","name":{"kind":"Name","value":"chapters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"order"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"tags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]} as unknown as DocumentNode<NovelsQuery, NovelsQueryVariables>;
|
export const NovelsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Novels"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"first"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"where"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"NovelDtoFilterInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"novels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"first"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"where"},"value":{"kind":"Variable","name":{"kind":"Name","value":"where"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"coverImage"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"newPath"}}]}},{"kind":"Field","name":{"kind":"Name","value":"rawStatus"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedTime"}},{"kind":"Field","name":{"kind":"Name","value":"chapters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"order"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"tags"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]} as unknown as DocumentNode<NovelsQuery, NovelsQueryVariables>;
|
||||||
@@ -1,19 +1,36 @@
|
|||||||
import { Client, cacheExchange, fetchExchange } from '@urql/core';
|
import { Client, cacheExchange, fetchExchange } from '@urql/core';
|
||||||
|
import { authExchange } from '@urql/exchange-auth';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { user } from '../auth/authStore';
|
import { user, refreshToken } from '../auth/authStore';
|
||||||
|
|
||||||
export function createClient() {
|
export function createClient() {
|
||||||
return new Client({
|
return new Client({
|
||||||
url: import.meta.env.PUBLIC_GRAPHQL_URI,
|
url: import.meta.env.PUBLIC_GRAPHQL_URI,
|
||||||
exchanges: [cacheExchange, fetchExchange],
|
exchanges: [
|
||||||
fetchOptions: () => {
|
cacheExchange,
|
||||||
|
authExchange(async (utils) => ({
|
||||||
|
addAuthToOperation(operation) {
|
||||||
const currentUser = get(user);
|
const currentUser = get(user);
|
||||||
return {
|
if (!currentUser?.access_token) return operation;
|
||||||
headers: currentUser?.access_token
|
return utils.appendHeaders(operation, {
|
||||||
? { Authorization: `Bearer ${currentUser.access_token}` }
|
Authorization: `Bearer ${currentUser.access_token}`,
|
||||||
: {},
|
});
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
didAuthError(error) {
|
||||||
|
return error.graphQLErrors?.some(
|
||||||
|
(e) => e.extensions?.code === 'AUTH_NOT_AUTHENTICATED'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
async refreshAuth() {
|
||||||
|
const newUser = await refreshToken();
|
||||||
|
if (!newUser) {
|
||||||
|
// Refresh failed, redirect to login
|
||||||
|
window.location.href = '/';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
fetchExchange,
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ query Novel($id: UnsignedInt!) {
|
|||||||
order
|
order
|
||||||
name
|
name
|
||||||
lastUpdatedTime
|
lastUpdatedTime
|
||||||
|
images {
|
||||||
|
id
|
||||||
|
newPath
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,42 @@
|
|||||||
import DOMPurify from 'isomorphic-dompurify';
|
import DOMPurify from 'isomorphic-dompurify';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits plain text into paragraphs based on newlines.
|
||||||
|
* Double newlines create new paragraphs, single newlines become <br>.
|
||||||
|
* If the content already contains HTML block elements, returns as-is.
|
||||||
|
*/
|
||||||
|
function wrapInParagraphs(text: string): string {
|
||||||
|
// Check if content already has block-level HTML elements
|
||||||
|
const hasBlockElements = /<(p|div|h[1-6]|ul|ol|blockquote|pre|table|hr)[>\s]/i.test(text);
|
||||||
|
if (hasBlockElements) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split on double newlines (paragraph breaks)
|
||||||
|
const paragraphs = text.split(/\n\s*\n/);
|
||||||
|
|
||||||
|
return paragraphs
|
||||||
|
.map((para) => {
|
||||||
|
const trimmed = para.trim();
|
||||||
|
if (!trimmed) return '';
|
||||||
|
// Convert single newlines to <br> within paragraphs
|
||||||
|
const withBreaks = trimmed.replace(/\n/g, '<br>');
|
||||||
|
return `<p>${withBreaks}</p>`;
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitizes chapter HTML content with extended allowed tags.
|
* Sanitizes chapter HTML content with extended allowed tags.
|
||||||
* More permissive than the description sanitizer to support
|
* More permissive than the description sanitizer to support
|
||||||
* formatted novel content including headings, lists, and images.
|
* formatted novel content including headings, lists, and images.
|
||||||
|
* Also wraps plain text in paragraph tags for better browser translate support.
|
||||||
*/
|
*/
|
||||||
export function sanitizeChapterHtml(html: string): string {
|
export function sanitizeChapterHtml(html: string): string {
|
||||||
return DOMPurify.sanitize(html, {
|
// First wrap in paragraphs if needed, then sanitize
|
||||||
|
const wrapped = wrapInParagraphs(html);
|
||||||
|
return DOMPurify.sanitize(wrapped, {
|
||||||
ALLOWED_TAGS: [
|
ALLOWED_TAGS: [
|
||||||
// Basic formatting
|
// Basic formatting
|
||||||
'b',
|
'b',
|
||||||
|
|||||||
Reference in New Issue
Block a user