;
}
-export const TableOfContents = ({
- text,
- title,
- onContentClicked,
-}: TableOfContentsProps): JSX.Element => {
+export const TableOfContents = ({ toc, onContentClicked }: TableOfContentsProps): JSX.Element => {
const { format } = useFormat();
- const toc = getTocFromMarkdawn(preprocessMarkDawn(text), title);
return (
<>
@@ -435,7 +429,14 @@ const markdawnHeadersParser = (
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
-const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
+export const getTocFromMarkdawn = (
+ markdawn: string | null | undefined,
+ title?: string
+): TocInterface | undefined => {
+ if (isUndefined(markdawn)) return undefined;
+
+ const text = preprocessMarkDawn(markdawn);
+
const toc: TocInterface = {
title: title ?? "Return to top",
slug: slugify(title),
@@ -522,5 +523,6 @@ const getTocFromMarkdawn = (text: string, title?: string): TocInterface => {
}
});
+ if (toc.children.length === 0) return undefined;
return toc;
};
diff --git a/src/components/Panels/SearchPopup.tsx b/src/components/Panels/SearchPopup.tsx
index 4942154..0ad0aec 100644
--- a/src/components/Panels/SearchPopup.tsx
+++ b/src/components/Panels/SearchPopup.tsx
@@ -14,6 +14,7 @@ import {
MeiliLibraryItem,
MeiliPost,
MeiliVideo,
+ MeiliWeapon,
MeiliWikiPage,
} from "shared/meilisearch-graphql-typings/meiliTypes";
import { getVideoThumbnailURL } from "helpers/videos";
@@ -43,6 +44,7 @@ export const SearchPopup = (): JSX.Element => {
const [videos, setVideos] = useState>();
const [posts, setPosts] = useState>();
const [wikiPages, setWikiPages] = useState>();
+ const [weapons, setWeapons] = useState>();
useEffect(() => {
const fetchLibraryItems = async () => {
@@ -129,6 +131,25 @@ export const SearchPopup = (): JSX.Element => {
setPosts(searchResult);
};
+ const fetchWeapons = async () => {
+ const searchResult = await meiliSearch(MeiliIndices.WEAPON, query, {
+ limit: SEARCH_LIMIT,
+ attributesToRetrieve: ["*"],
+ attributesToHighlight: ["translations.description", "translations.names"],
+ attributesToCrop: ["translations.description"],
+ sort: ["slug:asc"],
+ });
+ searchResult.hits = searchResult.hits.map((item) => {
+ if (Object.keys(item._matchesPosition).some((match) => match.startsWith("translations"))) {
+ item._formatted.translations = filterDefined(item._formatted.translations).filter(
+ (translation) => JSON.stringify(translation).includes("")
+ );
+ }
+ return item;
+ });
+ setWeapons(searchResult);
+ };
+
const fetchWikiPages = async () => {
const searchResult = await meiliSearch(MeiliIndices.WIKI_PAGE, query, {
limit: SEARCH_LIMIT,
@@ -160,12 +181,14 @@ export const SearchPopup = (): JSX.Element => {
setContents(undefined);
setVideos(undefined);
setPosts(undefined);
+ setWeapons(undefined);
} else {
fetchWikiPages();
fetchLibraryItems();
fetchContents();
fetchVideos();
fetchPosts();
+ fetchWeapons();
}
}, [query]);
@@ -411,6 +434,48 @@ export const SearchPopup = (): JSX.Element => {
)}
+
+ {isDefined(weapons) && (
+
+
+ {weapons.hits.map((item) => (
+ ({
+ language: language.data.attributes.code,
+ title: primaryName,
+ subtitle: aliases.join("・"),
+ description: containsHighlight(description) ? description : undefined,
+ })
+ )}
+ fallback={{ title: prettySlug(item.slug) }}
+ thumbnail={item.thumbnail?.data?.attributes}
+ thumbnailAspectRatio="1/1"
+ thumbnailForceAspectRatio
+ thumbnailFitMethod="contain"
+ keepInfoVisible
+ topChips={
+ item.type?.data?.attributes?.slug
+ ? [prettySlug(item.type.data.attributes.slug)]
+ : undefined
+ }
+ bottomChips={filterHasAttributes(item.categories, [
+ "attributes.short",
+ ] as const).map((category) => category.attributes.short)}
+ />
+ ))}
+
+
+ )}
);
@@ -442,7 +507,7 @@ const SearchResultSection = ({
className="grid grid-cols-[auto_1fr] place-items-center gap-6 px-6 py-4"
href={href}
onClick={() => setSearchOpened(false)}>
-
{title}
{isDefined(totalHits) && totalHits > SEARCH_LIMIT && (
diff --git a/src/components/PostPage.tsx b/src/components/PostPage.tsx
index cd793da..8999de7 100644
--- a/src/components/PostPage.tsx
+++ b/src/components/PostPage.tsx
@@ -2,7 +2,7 @@ import { Fragment, useCallback } from "react";
import { AppLayout, AppLayoutRequired } from "./AppLayout";
import { Chip } from "./Chip";
import { HorizontalLine } from "./HorizontalLine";
-import { Markdawn, TableOfContents } from "./Markdown/Markdawn";
+import { getTocFromMarkdawn, Markdawn, TableOfContents } from "./Markdown/Markdawn";
import { ReturnButton } from "./PanelComponents/ReturnButton";
import { ContentPanel } from "./Containers/ContentPanel";
import { SubPanel } from "./Containers/SubPanel";
@@ -70,56 +70,55 @@ export const PostPage = ({
const title = selectedTranslation?.title ?? prettySlug(post.slug);
const excerpt = selectedTranslation?.excerpt ?? "";
- const subPanel = (
-
-
- {[
- returnHref && returnTitle && !is1ColumnLayout && (
-
- ),
+ const toc = getTocFromMarkdawn(body, title);
- displayCredits && (
- <>
- {selectedTranslation && (
-
-
{format("status")}:
+ const subPanelElems = [
+ returnHref && returnTitle && !is1ColumnLayout && (
+
+ ),
-
-
-
-
+ displayCredits && (
+ <>
+ {selectedTranslation && (
+
+
{format("status")}:
+
+
+
+
+
+ )}
+
+ {post.authors && post.authors.data.length > 0 && (
+
+
{"Authors"}:
+
+ {filterHasAttributes(post.authors.data, ["id", "attributes"] as const).map(
+ (author) => (
+
+
+
+ )
)}
+
+
+ )}
+ >
+ ),
- {post.authors && post.authors.data.length > 0 && (
-
-
{"Authors"}:
-
- {filterHasAttributes(post.authors.data, ["id", "attributes"] as const).map(
- (author) => (
-
-
-
- )
- )}
-
-
- )}
- >
- ),
+ displayToc && isDefined(toc) && (
+ setSubPanelOpened(false)} />
+ ),
+ ];
- displayToc && (
- setSubPanelOpened(false)}
- />
- ),
- ]}
-
-
- );
+ const subPanel =
+ subPanelElems.filter(Boolean).length > 0 ? (
+
+ {subPanelElems}
+
+ ) : undefined;
const contentPanel = (
diff --git a/src/components/PreviewCard.tsx b/src/components/PreviewCard.tsx
index 1e1fe09..5b7708d 100644
--- a/src/components/PreviewCard.tsx
+++ b/src/components/PreviewCard.tsx
@@ -24,6 +24,7 @@ interface Props {
thumbnail?: UploadImageFragment | string | null | undefined;
thumbnailAspectRatio?: string;
thumbnailForceAspectRatio?: boolean;
+ thumbnailFitMethod?: "contain" | "cover";
thumbnailRounded?: boolean;
href: string;
pre_title?: string | null | undefined;
@@ -60,6 +61,7 @@ export const PreviewCard = ({
thumbnail,
thumbnailAspectRatio = "4/3",
thumbnailForceAspectRatio = false,
+ thumbnailFitMethod = "cover",
thumbnailRounded = true,
pre_title,
title,
@@ -133,7 +135,12 @@ export const PreviewCard = ({
thumbnailRounded,
cIf(keepInfoVisible, "rounded-t-md", "rounded-md notHoverable:rounded-b-none")
),
- cIf(thumbnailForceAspectRatio, "h-full w-full object-cover")
+ cIf(thumbnailForceAspectRatio, "h-full w-full"),
+ cIf(
+ thumbnailForceAspectRatio && thumbnailFitMethod === "contain",
+ "object-contain",
+ "object-cover"
+ )
)}
src={thumbnail}
quality={ImageQuality.Medium}
diff --git a/src/graphql/icuParams.ts b/src/graphql/icuParams.ts
index c3b1216..555bab1 100644
--- a/src/graphql/icuParams.ts
+++ b/src/graphql/icuParams.ts
@@ -179,4 +179,8 @@ export interface ICUParams {
subitem_of_x: { x: Date | boolean | number | string };
variant_of_x: { x: Date | boolean | number | string };
dark_mode_extension_warning: never;
+ weapon: { count: number };
+ weapons_description: never;
+ level_x: { x: Date | boolean | number | string };
+ story_x: { x: Date | boolean | number | string };
}
diff --git a/src/graphql/operations/getWeapon.graphql b/src/graphql/operations/getWeapon.graphql
new file mode 100644
index 0000000..20565fa
--- /dev/null
+++ b/src/graphql/operations/getWeapon.graphql
@@ -0,0 +1,92 @@
+query getWeapon($slug: String, $language_code: String) {
+ weaponStories(filters: { slug: { eq: $slug } }) {
+ data {
+ attributes {
+ ...sharedWeaponFragment
+ stories(pagination: { limit: -1 }) {
+ id
+ categories(pagination: { limit: -1 }) {
+ data {
+ id
+ attributes {
+ name
+ short
+ }
+ }
+ }
+ translations(pagination: { limit: -1 }) {
+ id
+ description
+ level_1
+ level_2
+ level_3
+ level_4
+ status
+ language {
+ data {
+ attributes {
+ code
+ }
+ }
+ }
+ }
+ }
+ weapon_group {
+ data {
+ attributes {
+ slug
+ weapons(pagination: { limit: -1 }, filters: { slug: { ne: $slug } }) {
+ data {
+ id
+ attributes {
+ ...sharedWeaponFragment
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+fragment sharedWeaponFragment on WeaponStory {
+ type {
+ data {
+ id
+ attributes {
+ slug
+ translations(filters: { language: { code: { eq: $language_code } } }) {
+ name
+ language {
+ data {
+ attributes {
+ code
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ name(pagination: { limit: -1 }) {
+ id
+ name
+ language {
+ data {
+ attributes {
+ code
+ }
+ }
+ }
+ }
+ slug
+ thumbnail {
+ data {
+ attributes {
+ ...uploadImage
+ }
+ }
+ }
+}
diff --git a/src/graphql/operations/getWeaponsSlugs.graphql b/src/graphql/operations/getWeaponsSlugs.graphql
new file mode 100644
index 0000000..bcd22c4
--- /dev/null
+++ b/src/graphql/operations/getWeaponsSlugs.graphql
@@ -0,0 +1,10 @@
+query getWeaponsSlugs {
+ weaponStories(pagination: { limit: -1 }) {
+ data {
+ id
+ attributes {
+ slug
+ }
+ }
+ }
+}
diff --git a/src/graphql/operations/local-data/localDataGetWebsiteInterfaces.graphql b/src/graphql/operations/local-data/localDataGetWebsiteInterfaces.graphql
index d26bf19..0a519e5 100644
--- a/src/graphql/operations/local-data/localDataGetWebsiteInterfaces.graphql
+++ b/src/graphql/operations/local-data/localDataGetWebsiteInterfaces.graphql
@@ -186,6 +186,10 @@ query localDataGetWebsiteInterfaces {
subitem_of_x
variant_of_x
dark_mode_extension_warning
+ weapon
+ weapons_description
+ level_x
+ story_x
}
}
}
diff --git a/src/hooks/useFormat.ts b/src/hooks/useFormat.ts
index 1285b8f..ae72fe5 100644
--- a/src/hooks/useFormat.ts
+++ b/src/hooks/useFormat.ts
@@ -59,7 +59,13 @@ export const useFormat = (): {
if (isDefinedAndNotEmpty(result)) {
return result;
}
- return new IntlMessageFormat(fallbackLangui[key] ?? "").format(processedValues).toString();
+ const fallback = new IntlMessageFormat(fallbackLangui[key] ?? "")
+ .format(processedValues)
+ .toString();
+ if (isDefinedAndNotEmpty(fallback)) {
+ return fallback;
+ }
+ return key;
},
[langui, fallbackLangui]
);
diff --git a/src/hooks/useReaderSettings.ts b/src/hooks/useReaderSettings.ts
index ac58a21..ecbee34 100644
--- a/src/hooks/useReaderSettings.ts
+++ b/src/hooks/useReaderSettings.ts
@@ -17,10 +17,10 @@ interface ReaderSettings extends FilterSettings {
const DEFAULT_READER_SETTINGS: ReaderSettings = {
bookFold: true,
- lighting: true,
+ lighting: false,
paperTexture: true,
teint: 0.1,
- dropShadow: true,
+ dropShadow: false,
pageQuality: ImageQuality.Large,
isSidePagesEnabled: true,
};
diff --git a/src/pages/contents/[slug].tsx b/src/pages/contents/[slug].tsx
index b82d96e..832f20a 100644
--- a/src/pages/contents/[slug].tsx
+++ b/src/pages/contents/[slug].tsx
@@ -4,7 +4,7 @@ import naturalCompare from "string-natural-compare";
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
import { Chip } from "components/Chip";
import { PreviewCardCTAs } from "components/Library/PreviewCardCTAs";
-import { Markdawn, TableOfContents } from "components/Markdown/Markdawn";
+import { getTocFromMarkdawn, Markdawn, TableOfContents } from "components/Markdown/Markdawn";
import { TranslatedReturnButton } from "components/PanelComponents/ReturnButton";
import { ContentPanel } from "components/Containers/ContentPanel";
import { SubPanel } from "components/Containers/SubPanel";
@@ -91,6 +91,15 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
},
};
+ const toc = getTocFromMarkdawn(
+ selectedTranslation?.text_set?.text,
+ prettyInlineTitle(
+ selectedTranslation?.pre_title,
+ selectedTranslation?.title,
+ selectedTranslation?.subtitle
+ )
+ );
+
const subPanel = (
@@ -191,17 +200,7 @@ const Content = ({ content, ...otherProps }: Props): JSX.Element => {
),
- selectedTranslation?.text_set?.text && (
-