Commit 55d771ae authored by Lê Bảo Hồng Đức's avatar Lê Bảo Hồng Đức

fix

parent 1d0d929b
...@@ -192,6 +192,9 @@ importers: ...@@ -192,6 +192,9 @@ importers:
axios: axios:
specifier: ^1.13.1 specifier: ^1.13.1
version: 1.13.1 version: 1.13.1
baseline-browser-mapping:
specifier: ^2.10.32
version: 2.10.32
eslint: eslint:
specifier: ^9 specifier: ^9
version: 9.38.0(jiti@2.6.1) version: 9.38.0(jiti@2.6.1)
...@@ -2019,8 +2022,9 @@ packages: ...@@ -2019,8 +2022,9 @@ packages:
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
baseline-browser-mapping@2.8.20: baseline-browser-mapping@2.10.32:
resolution: {integrity: sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==} resolution: {integrity: sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==}
engines: {node: '>=6.0.0'}
hasBin: true hasBin: true
brace-expansion@1.1.12: brace-expansion@1.1.12:
...@@ -5839,7 +5843,7 @@ snapshots: ...@@ -5839,7 +5843,7 @@ snapshots:
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
baseline-browser-mapping@2.8.20: {} baseline-browser-mapping@2.10.32: {}
brace-expansion@1.1.12: brace-expansion@1.1.12:
dependencies: dependencies:
...@@ -5856,7 +5860,7 @@ snapshots: ...@@ -5856,7 +5860,7 @@ snapshots:
browserslist@4.27.0: browserslist@4.27.0:
dependencies: dependencies:
baseline-browser-mapping: 2.8.20 baseline-browser-mapping: 2.10.32
caniuse-lite: 1.0.30001751 caniuse-lite: 1.0.30001751
electron-to-chromium: 1.5.243 electron-to-chromium: 1.5.243
node-releases: 2.0.26 node-releases: 2.0.26
......
...@@ -153,6 +153,16 @@ const normalizeLink = (value?: string | null, fallback = "/") => { ...@@ -153,6 +153,16 @@ const normalizeLink = (value?: string | null, fallback = "/") => {
return `/${trimmed}`; return `/${trimmed}`;
}; };
const buildPostLink = (path?: string | null, id?: string | null, fallback = "#") => {
const normalizedPath = normalizeLink(path, fallback);
const trimmedId = id?.trim() ?? "";
if (!trimmedId || normalizedPath === "#") return normalizedPath;
const params = new URLSearchParams({ id: trimmedId });
return `${normalizedPath}?${params.toString()}`;
};
const resolveAssetUrl = (value?: string | null) => { const resolveAssetUrl = (value?: string | null) => {
const trimmed = value?.trim(); const trimmed = value?.trim();
...@@ -224,7 +234,6 @@ const isVisibleNewsPost = (item: HomePostItem) => { ...@@ -224,7 +234,6 @@ const isVisibleNewsPost = (item: HomePostItem) => {
if (item.type && item.type !== "news") return false; if (item.type && item.type !== "news") return false;
if (item.isHidden) return false; if (item.isHidden) return false;
if (!item.isActive) return false; if (!item.isActive) return false;
if (item.status && item.status !== "published") return false;
return true; return true;
}; };
...@@ -246,7 +255,6 @@ function createCategoryPostsQuery(categoryId: string, pageSize: string) { ...@@ -246,7 +255,6 @@ function createCategoryPostsQuery(categoryId: string, pageSize: string) {
`category.id==${categoryId}`, `category.id==${categoryId}`,
"is_hidden==false", "is_hidden==false",
"is_active==true", "is_active==true",
"status==published",
"type==news", "type==news",
].join(","), ].join(","),
}); });
...@@ -265,7 +273,6 @@ function createEventCalendarQuery(currentMonth: Date) { ...@@ -265,7 +273,6 @@ function createEventCalendarQuery(currentMonth: Date) {
`registration_deadline<=${dayjs(monthEnd).format("YYYY-MM-DD HH:mm:ss")}`, `registration_deadline<=${dayjs(monthEnd).format("YYYY-MM-DD HH:mm:ss")}`,
"is_hidden==false", "is_hidden==false",
"is_active==true", "is_active==true",
"status==published",
"type==news", "type==news",
].join(","), ].join(","),
}); });
...@@ -281,7 +288,6 @@ async function fetchHomePosts() { ...@@ -281,7 +288,6 @@ async function fetchHomePosts() {
"is_featured==true", "is_featured==true",
"is_hidden==false", "is_hidden==false",
"is_active==true", "is_active==true",
"status==published",
"type==news", "type==news",
].join(","), ].join(","),
}); });
...@@ -346,8 +352,9 @@ async function fetchHomePosts() { ...@@ -346,8 +352,9 @@ async function fetchHomePosts() {
const thumbnailPath = item.thumbnail?.path ?? item.thumbnail?.original ?? null; const thumbnailPath = item.thumbnail?.path ?? item.thumbnail?.original ?? null;
const title = String(item.title ?? "").trim(); const title = String(item.title ?? "").trim();
const externalLink = normalizeLink( const externalLink = buildPostLink(
item.external_link || (title ? `/${title}` : undefined), item.external_link || (title ? `/${title}` : undefined),
item.id ? String(item.id) : "",
"#", "#",
); );
...@@ -560,8 +567,9 @@ export function useEventCalendarPosts(currentMonth: Date) { ...@@ -560,8 +567,9 @@ export function useEventCalendarPosts(currentMonth: Date) {
const thumbnailPath = item.thumbnail?.path ?? item.thumbnail?.original ?? null; const thumbnailPath = item.thumbnail?.path ?? item.thumbnail?.original ?? null;
const title = String(item.title ?? "").trim(); const title = String(item.title ?? "").trim();
const externalLink = normalizeLink( const externalLink = buildPostLink(
item.external_link || (title ? `/${title}` : undefined), item.external_link || (title ? `/${title}` : undefined),
item.id ? String(item.id) : "",
"#", "#",
); );
......
"use client"; "use client";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { notFound, useParams, useRouter } from "next/navigation"; import { notFound, useParams, useRouter, useSearchParams } from "next/navigation";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { Spinner } from "@/components/ui"; import { Spinner } from "@/components/ui";
import ArticlePage from "./templates/ArticlePage"; import ArticlePage from "./templates/ArticlePage";
...@@ -10,6 +10,7 @@ import CatalogPage from "./templates/CatalogPage"; ...@@ -10,6 +10,7 @@ import CatalogPage from "./templates/CatalogPage";
import InformationPage from "./templates/InformationPage"; import InformationPage from "./templates/InformationPage";
import { import {
fetchDynamicCategories, fetchDynamicCategories,
fetchDynamicPostById,
fetchDynamicPostByExternalLink, fetchDynamicPostByExternalLink,
fetchDynamicSinglePagePost, fetchDynamicSinglePagePost,
findDynamicCategoryByPath, findDynamicCategoryByPath,
...@@ -23,6 +24,9 @@ export default function DynamicPage() { ...@@ -23,6 +24,9 @@ export default function DynamicPage() {
const path = slug.join("/"); const path = slug.join("/");
const routePath = `/${path}`; const routePath = `/${path}`;
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams();
const postId = searchParams.get("id")?.trim() ?? "";
const preferredCategoryId = searchParams.get("categoryId")?.trim() ?? "";
const categoryQuery = useQuery({ const categoryQuery = useQuery({
queryKey: ["dynamic-categories"], queryKey: ["dynamic-categories"],
...@@ -30,21 +34,32 @@ export default function DynamicPage() { ...@@ -30,21 +34,32 @@ export default function DynamicPage() {
staleTime: 5 * 60 * 1000, staleTime: 5 * 60 * 1000,
}); });
const detailQuery = useQuery({
queryKey: ["dynamic-post-detail", routePath],
queryFn: () => fetchDynamicPostByExternalLink(routePath),
enabled: Boolean(routePath),
staleTime: 60 * 1000,
});
const matchedCategory = useMemo( const matchedCategory = useMemo(
() => findDynamicCategoryByPath(categoryQuery.data ?? [], routePath), () => findDynamicCategoryByPath(categoryQuery.data ?? [], routePath),
[categoryQuery.data, routePath], [categoryQuery.data, routePath],
); );
const detailQuery = useQuery({
queryKey: ["dynamic-post-detail", postId || routePath],
queryFn: () =>
postId
? fetchDynamicPostById(postId)
: fetchDynamicPostByExternalLink(routePath),
enabled:
(Boolean(postId) || Boolean(routePath)) &&
!categoryQuery.isLoading &&
(Boolean(postId) || !matchedCategory),
staleTime: 60 * 1000,
});
const resolvedCategory = useMemo( const resolvedCategory = useMemo(
() => matchedCategory ?? findMenuCategoryForPost(detailQuery.data ?? null, categoryQuery.data ?? []), () =>
[matchedCategory, detailQuery.data, categoryQuery.data], (preferredCategoryId
? categoryQuery.data?.find((item) => item.id === preferredCategoryId) ?? null
: null) ??
matchedCategory ??
findMenuCategoryForPost(detailQuery.data ?? null, categoryQuery.data ?? []),
[preferredCategoryId, matchedCategory, detailQuery.data, categoryQuery.data],
); );
const singlePageQuery = useQuery({ const singlePageQuery = useQuery({
......
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import ImageNext from "@/components/shared/image-next"; import ImageNext from "@/components/shared/image-next";
import AppEditorContent from "@/components/shared/editor-content";
import ListCategory from "@/components/base/list-category"; import ListCategory from "@/components/base/list-category";
import EventsCalendar from "@/app/(main)/(home)/components/events-calendar"; import EventsCalendar from "@/app/(main)/(home)/components/events-calendar";
import { buildDynamicCategoryMenu } from "./data"; import { buildDynamicCategoryMenu, findDisplayCategoryForPost } from "./data";
import StructuredPostContent from "./StructuredPostContent"; import StructuredPostContent from "./StructuredPostContent";
import type { DynamicCategoryRouteItem, DynamicPostItem } from "./types"; import type { DynamicCategoryRouteItem, DynamicPostItem } from "./types";
...@@ -22,7 +23,10 @@ export default function ArticleDetailPage({ ...@@ -22,7 +23,10 @@ export default function ArticleDetailPage({
const publishedDate = dayjs( const publishedDate = dayjs(
post.release_at ?? post.published_at ?? post.created_at, post.release_at ?? post.published_at ?? post.created_at,
).format("DD/MM/YYYY"); ).format("DD/MM/YYYY");
const primaryCategory = post.categories[0]?.name || category?.name || "Tin tức"; const primaryCategory =
findDisplayCategoryForPost(post, category, allCategories)?.name ||
category?.name ||
"Tin tức";
const categoryMenu = category ? buildDynamicCategoryMenu(category, allCategories) : []; const categoryMenu = category ? buildDynamicCategoryMenu(category, allCategories) : [];
return ( return (
...@@ -44,9 +48,9 @@ export default function ArticleDetailPage({ ...@@ -44,9 +48,9 @@ export default function ArticleDetailPage({
<div className="mt-3 h-[3px] w-16 rounded-full bg-[#f5a400]" /> <div className="mt-3 h-[3px] w-16 rounded-full bg-[#f5a400]" />
{post.summary ? ( {post.summary ? (
<p className="mt-5 max-w-4xl text-base font-semibold leading-7 text-[#374151] md:text-lg md:leading-8"> <div className="mt-5 max-w-4xl text-base font-semibold leading-7 text-[#374151] md:text-lg md:leading-8">
{post.summary} <AppEditorContent value={post.summary} />
</p> </div>
) : null} ) : null}
<div className="mt-7 rounded-3xl bg-white px-4 py-5 shadow-[0_18px_42px_rgba(17,24,39,0.06)] sm:px-8 sm:py-6 lg:px-10"> <div className="mt-7 rounded-3xl bg-white px-4 py-5 shadow-[0_18px_42px_rgba(17,24,39,0.06)] sm:px-8 sm:py-6 lg:px-10">
...@@ -59,13 +63,16 @@ export default function ArticleDetailPage({ ...@@ -59,13 +63,16 @@ export default function ArticleDetailPage({
<style jsx global>{` <style jsx global>{`
.article-detail-content { .article-detail-content {
color: #1f2937; color: #1f2937;
font-size: 16px;
line-height: 1.85; line-height: 1.85;
width: 100%;
max-width: 100%;
} }
.article-detail-content p, .article-detail-content p,
.article-detail-content div { .article-detail-content div {
margin: 0 0 18px; margin: 0 0 18px;
max-width: 100% !important;
box-sizing: border-box;
} }
.article-detail-content h1, .article-detail-content h1,
...@@ -80,17 +87,39 @@ export default function ArticleDetailPage({ ...@@ -80,17 +87,39 @@ export default function ArticleDetailPage({
line-height: 1.45; line-height: 1.45;
} }
.article-detail-content :is(p, div, span, li, a, strong, em, u, s) {
font-family: inherit;
}
.article-detail-content img { .article-detail-content img {
display: block; display: block;
width: 100%; width: 100% !important;
max-width: 100%; max-width: 100% !important;
height: auto; height: auto !important;
margin: 24px auto 10px; margin: 24px auto 10px;
border-radius: 14px; border-radius: 14px;
} }
.article-detail-content figure { .article-detail-content figure {
display: block !important;
width: 100% !important;
max-width: 100% !important;
margin: 28px 0; margin: 28px 0;
text-align: center;
}
.article-detail-content .article-content,
.article-detail-content .article-content_toc,
.article-detail-content table,
.article-detail-content iframe {
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box;
}
.article-detail-content table {
display: table;
table-layout: fixed;
} }
.article-detail-content figcaption, .article-detail-content figcaption,
......
...@@ -11,9 +11,11 @@ import { Button } from "@/components/ui/button"; ...@@ -11,9 +11,11 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import ListCategory from "@/components/base/list-category"; import ListCategory from "@/components/base/list-category";
import { import {
buildDynamicPostHref,
buildDynamicCategoryMenu, buildDynamicCategoryMenu,
buildPostFilters, buildVisibleNewsFilters,
fetchDynamicPostList, fetchDynamicPostList,
findDisplayCategoryForPost,
resolveDynamicPostImage, resolveDynamicPostImage,
stripHtml, stripHtml,
} from "./data"; } from "./data";
...@@ -84,12 +86,8 @@ export default function ArticlePage({ category, allCategories }: ArticlePageProp ...@@ -84,12 +86,8 @@ export default function ArticlePage({ category, allCategories }: ArticlePageProp
fetchDynamicPostList({ fetchDynamicPostList({
page, page,
pageSize, pageSize,
filters: buildPostFilters([ filters: buildVisibleNewsFilters([
`category.id==${category.id}`, `category.id==${category.id}`,
"is_hidden==false",
"is_active==true",
"status==published",
"type==news",
keyword ? `title@=${keyword}` : null, keyword ? `title@=${keyword}` : null,
]), ]),
}), }),
...@@ -134,10 +132,14 @@ export default function ArticlePage({ category, allCategories }: ArticlePageProp ...@@ -134,10 +132,14 @@ export default function ArticlePage({ category, allCategories }: ArticlePageProp
?.map((section) => section.content) ?.map((section) => section.content)
.join(" "); .join(" ");
const description = const description =
item.summary || stripHtml(item.summary) ||
stripHtml(item.content) || stripHtml(item.content) ||
stripHtml(fallbackDescription); stripHtml(fallbackDescription);
const primaryCategory = item.categories[0]; const primaryCategory = findDisplayCategoryForPost(
item,
category,
allCategories,
);
const tagIndex = categoryIndexMap.get(primaryCategory?.id ?? "") ?? index; const tagIndex = categoryIndexMap.get(primaryCategory?.id ?? "") ?? index;
const date = formatPostDate( const date = formatPostDate(
item.release_at ?? item.published_at ?? item.created_at, item.release_at ?? item.published_at ?? item.created_at,
...@@ -149,16 +151,16 @@ export default function ArticlePage({ category, allCategories }: ArticlePageProp ...@@ -149,16 +151,16 @@ export default function ArticlePage({ category, allCategories }: ArticlePageProp
className="border-b border-[#eceff3] pb-8 last:border-b-0" className="border-b border-[#eceff3] pb-8 last:border-b-0"
> >
<Link <Link
href={item.external_link} href={buildDynamicPostHref(item.external_link, item.id, category.id)}
className="group grid gap-5 sm:grid-cols-[250px_minmax(0,1fr)]" className="group grid gap-5 sm:grid-cols-[250px_minmax(0,1fr)]"
> >
<div className="overflow-hidden rounded-md bg-[#edf1f5]"> <div className="relative overflow-hidden rounded-md bg-[#edf1f5] aspect-[25/15] sm:aspect-[5/3]">
<ImageNext <ImageNext
src={resolveDynamicPostImage(item.thumbnail)} src={resolveDynamicPostImage(item.thumbnail)}
alt={item.title} alt={item.title}
width={520} width={520}
height={360} height={360}
className="h-[170px] w-full object-cover transition-transform duration-500 group-hover:scale-[1.03] sm:h-[150px]" className="absolute inset-0 h-full w-full object-cover transition-transform duration-500 group-hover:scale-[1.03]"
/> />
</div> </div>
......
...@@ -11,8 +11,9 @@ import { Button } from "@/components/ui/button"; ...@@ -11,8 +11,9 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import ListCategory from "@/components/base/list-category"; import ListCategory from "@/components/base/list-category";
import { import {
buildDynamicPostHref,
buildDynamicCategoryMenu, buildDynamicCategoryMenu,
buildPostFilters, buildVisibleNewsFilters,
fetchDynamicPostList, fetchDynamicPostList,
resolveDynamicPostImage, resolveDynamicPostImage,
} from "./data"; } from "./data";
...@@ -23,15 +24,6 @@ type CatalogPageProps = { ...@@ -23,15 +24,6 @@ type CatalogPageProps = {
allCategories: DynamicCategoryRouteItem[]; allCategories: DynamicCategoryRouteItem[];
}; };
const getCatalogImageClassName = (index: number) =>
index % 4 === 0
? "object-cover"
: index % 4 === 1
? "object-contain bg-[#f0f3f8]"
: index % 4 === 2
? "object-cover object-center"
: "object-contain bg-[#eef4fb]";
export default function CatalogPage({ category, allCategories }: CatalogPageProps) { export default function CatalogPage({ category, allCategories }: CatalogPageProps) {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const router = useRouter(); const router = useRouter();
...@@ -68,12 +60,8 @@ export default function CatalogPage({ category, allCategories }: CatalogPageProp ...@@ -68,12 +60,8 @@ export default function CatalogPage({ category, allCategories }: CatalogPageProp
fetchDynamicPostList({ fetchDynamicPostList({
page, page,
pageSize, pageSize,
filters: buildPostFilters([ filters: buildVisibleNewsFilters([
`category.id==${category.id}`, `category.id==${category.id}`,
"is_hidden==false",
"is_active==true",
"status==published",
"type==news",
keyword ? `title@=${keyword}` : null, keyword ? `title@=${keyword}` : null,
]), ]),
}), }),
...@@ -105,11 +93,11 @@ export default function CatalogPage({ category, allCategories }: CatalogPageProp ...@@ -105,11 +93,11 @@ export default function CatalogPage({ category, allCategories }: CatalogPageProp
<main className="order-2 min-w-0 xl:order-1 xl:flex-1"> <main className="order-2 min-w-0 xl:order-1 xl:flex-1">
{paginatedPosts.length ? ( {paginatedPosts.length ? (
<div className="grid grid-cols-2 gap-5 sm:grid-cols-3 xl:grid-cols-4 xl:gap-6"> <div className="grid grid-cols-2 gap-5 sm:grid-cols-3 xl:grid-cols-4 xl:gap-6">
{paginatedPosts.map((item, index) => { {paginatedPosts.map((item) => {
return ( return (
<Link <Link
key={item.id} key={item.id}
href={item.external_link} href={buildDynamicPostHref(item.external_link, item.id, category.id)}
className="group block" className="group block"
> >
<div className="overflow-hidden bg-white shadow-[0_10px_24px_rgba(17,24,39,0.08)]"> <div className="overflow-hidden bg-white shadow-[0_10px_24px_rgba(17,24,39,0.08)]">
...@@ -119,7 +107,7 @@ export default function CatalogPage({ category, allCategories }: CatalogPageProp ...@@ -119,7 +107,7 @@ export default function CatalogPage({ category, allCategories }: CatalogPageProp
alt={item.title} alt={item.title}
width={520} width={520}
height={693} height={693}
className={`h-full w-full transition-transform duration-500 group-hover:scale-[1.03] ${getCatalogImageClassName(index)}`} className="absolute inset-0 h-full w-full object-cover transition-transform duration-500 group-hover:scale-[1.03]"
/> />
</div> </div>
</div> </div>
......
...@@ -142,13 +142,16 @@ export default function InformationPage({ ...@@ -142,13 +142,16 @@ export default function InformationPage({
<style jsx global>{` <style jsx global>{`
.page-detail-content { .page-detail-content {
color: #1f2937; color: #1f2937;
font-size: 16px;
line-height: 1.85; line-height: 1.85;
width: 100%;
max-width: 100%;
} }
.page-detail-content p, .page-detail-content p,
.page-detail-content div { .page-detail-content div {
margin: 0 0 18px; margin: 0 0 18px;
max-width: 100% !important;
box-sizing: border-box;
} }
.page-detail-content h1, .page-detail-content h1,
...@@ -163,17 +166,39 @@ export default function InformationPage({ ...@@ -163,17 +166,39 @@ export default function InformationPage({
line-height: 1.45; line-height: 1.45;
} }
.page-detail-content :is(p, div, span, li, a, strong, em, u, s) {
font-family: inherit;
}
.page-detail-content img { .page-detail-content img {
display: block; display: block;
width: 100%; width: 100% !important;
max-width: 100%; max-width: 100% !important;
height: auto; height: auto !important;
margin: 24px auto 10px; margin: 24px auto 10px;
border-radius: 14px; border-radius: 14px;
} }
.page-detail-content figure { .page-detail-content figure {
display: block !important;
width: 100% !important;
max-width: 100% !important;
margin: 28px 0; margin: 28px 0;
text-align: center;
}
.page-detail-content .article-content,
.page-detail-content .article-content_toc,
.page-detail-content table,
.page-detail-content iframe {
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box;
}
.page-detail-content table {
display: table;
table-layout: fixed;
} }
.page-detail-content figcaption, .page-detail-content figcaption,
......
...@@ -66,8 +66,101 @@ function normalizeCaptionShortcodes(html: string) { ...@@ -66,8 +66,101 @@ function normalizeCaptionShortcodes(html: string) {
}); });
} }
function normalizeImportedLayout(html: string) {
if (typeof window === "undefined" || !html.trim()) return html;
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
const mediaLayoutSelectors = [
".article-content",
".article-content_toc",
"figure",
"figcaption",
"img",
"table",
"iframe",
];
document.body.querySelectorAll<HTMLElement>(mediaLayoutSelectors.join(",")).forEach((element) => {
element.style.removeProperty("width");
element.style.removeProperty("max-width");
element.style.removeProperty("min-width");
if (element.tagName === "IMG") {
element.style.removeProperty("height");
element.style.removeProperty("max-height");
element.style.removeProperty("min-height");
element.style.removeProperty("aspect-ratio");
element.style.setProperty("display", "block");
element.style.setProperty("width", "100%");
element.style.setProperty("max-width", "100%");
element.style.setProperty("height", "auto");
element.removeAttribute("width");
element.removeAttribute("height");
}
if (element.tagName === "FIGURE") {
element.style.setProperty("display", "block");
element.style.setProperty("margin", "1.75rem 0");
element.style.setProperty("width", "100%");
element.style.setProperty("max-width", "100%");
element.style.setProperty("text-align", "center");
}
if (element.classList.contains("article-content") || element.classList.contains("article-content_toc")) {
element.style.setProperty("display", "block");
element.style.setProperty("width", "100%");
element.style.setProperty("max-width", "100%");
element.style.setProperty("overflow", "hidden");
}
});
document.body.querySelectorAll<HTMLImageElement>("img").forEach((image) => {
let current = image.parentElement;
while (
current &&
current !== document.body &&
!current.classList.contains("article-content") &&
!current.classList.contains("article-content_toc")
) {
current.style.removeProperty("width");
current.style.removeProperty("max-width");
current.style.removeProperty("min-width");
current.style.removeProperty("height");
current.style.removeProperty("max-height");
current.style.removeProperty("min-height");
current.style.removeProperty("float");
current.style.removeProperty("left");
current.style.removeProperty("right");
if (current.tagName !== "FIGCAPTION") {
current.style.setProperty("max-width", "100%");
current.style.setProperty("box-sizing", "border-box");
}
if (current.tagName === "DIV" || current.tagName === "P" || current.tagName === "FIGURE") {
current.style.setProperty("display", "block");
current.style.setProperty("width", "100%");
}
current = current.parentElement;
}
});
document.body
.querySelectorAll<HTMLElement>(".article-content, .article-content_toc, figure, img, table, iframe")
.forEach((element) => {
element.style.removeProperty("float");
element.style.removeProperty("left");
element.style.removeProperty("right");
});
return document.body.innerHTML;
}
function renderStructuredHtml(html: string) { function renderStructuredHtml(html: string) {
return parse(normalizeCaptionShortcodes(html)); return parse(normalizeImportedLayout(normalizeCaptionShortcodes(html)));
} }
export default function StructuredPostContent({ post }: StructuredPostContentProps) { export default function StructuredPostContent({ post }: StructuredPostContentProps) {
......
...@@ -85,6 +85,14 @@ type PostListResponse = { ...@@ -85,6 +85,14 @@ type PostListResponse = {
}; };
}; };
type PostDetailResponse = RawPostItem;
type PostDetailEnvelope = {
responseData?: RawPostItem | null;
data?: {
responseData?: RawPostItem | null;
} | null;
};
export type DynamicPostListResult = { export type DynamicPostListResult = {
count: number; count: number;
page: number; page: number;
...@@ -99,6 +107,44 @@ const normalizePath = (value?: string | null) => { ...@@ -99,6 +107,44 @@ const normalizePath = (value?: string | null) => {
return `/${trimmed.replace(/^\/+|\/+$/g, "")}`; return `/${trimmed.replace(/^\/+|\/+$/g, "")}`;
}; };
export const buildDynamicPostHref = (
path?: string | null,
id?: string | null,
categoryId?: string | null,
) => {
const normalizedPath = normalizePath(path);
const trimmedId = id?.trim() ?? "";
const trimmedCategoryId = categoryId?.trim() ?? "";
if ((!trimmedId && !trimmedCategoryId) || normalizedPath === "/") {
return normalizedPath;
}
const params = new URLSearchParams();
if (trimmedId) {
params.set("id", trimmedId);
}
if (trimmedCategoryId) {
params.set("categoryId", trimmedCategoryId);
}
return `${normalizedPath}?${params.toString()}`;
};
const getSlugFromPath = (value?: string | null) => {
const normalizedPath = normalizePath(value);
const segments = normalizedPath.split("/").filter(Boolean);
const lastSegment = segments.at(-1);
if (!lastSegment) return "";
try {
return decodeURIComponent(lastSegment).trim();
} catch {
return lastSegment.trim();
}
};
const normalizeCategoryType = (value?: string | null): DynamicCategoryType | null => { const normalizeCategoryType = (value?: string | null): DynamicCategoryType | null => {
if (value === "category" || value === "page" || value === "news") return value; if (value === "category" || value === "page" || value === "news") return value;
return null; return null;
...@@ -195,6 +241,16 @@ const buildPostFilters = (filters: Array<string | null | undefined>) => ...@@ -195,6 +241,16 @@ const buildPostFilters = (filters: Array<string | null | undefined>) =>
.filter(Boolean) .filter(Boolean)
.join(","); .join(",");
export const buildVisibleNewsFilters = (
filters: Array<string | null | undefined> = [],
) =>
buildPostFilters([
...filters,
"is_hidden==false",
"is_active==true",
"type==news",
]);
export async function fetchDynamicCategories(): Promise<DynamicCategoryRouteItem[]> { export async function fetchDynamicCategories(): Promise<DynamicCategoryRouteItem[]> {
const response = await useCustomClient<CategoryListResponse>( const response = await useCustomClient<CategoryListResponse>(
"/category?page=1&pageSize=200&sortField=sort_order&sortOrder=ASC", "/category?page=1&pageSize=200&sortField=sort_order&sortOrder=ASC",
...@@ -255,16 +311,57 @@ export async function fetchDynamicPostList(params: { ...@@ -255,16 +311,57 @@ export async function fetchDynamicPostList(params: {
}; };
} }
export async function fetchDynamicPostById(id: string) {
const normalizedId = id.trim();
if (!normalizedId) return null;
const listResult = await fetchDynamicPostList({
page: 1,
pageSize: 1,
filters: buildVisibleNewsFilters([`id==${normalizedId}`]),
}).catch(() => null);
if (listResult?.rows[0]) {
return listResult.rows[0];
}
const newsResponse = await useCustomClient<PostDetailEnvelope>(`/news/${normalizedId}`).catch(() => null);
const newsItem = newsResponse?.responseData ?? newsResponse?.data?.responseData ?? null;
if (newsItem) {
const post = mapPost(newsItem);
return post.id && post.title ? post : null;
}
const postResponse = await useCustomClient<PostDetailResponse>(`/post/${normalizedId}`).catch(() => null);
if (!postResponse) return null;
const post = mapPost(postResponse);
return post.id && post.title ? post : null;
}
export async function fetchDynamicPostByExternalLink(path: string) { export async function fetchDynamicPostByExternalLink(path: string) {
const normalizedPath = normalizePath(path);
const slug = getSlugFromPath(normalizedPath);
if (slug) {
const slugResult = await fetchDynamicPostList({
page: 1,
pageSize: 1,
filters: buildVisibleNewsFilters([`slug==${slug}`]),
});
if (slugResult.rows[0]) {
return slugResult.rows[0];
}
}
const result = await fetchDynamicPostList({ const result = await fetchDynamicPostList({
page: 1, page: 1,
pageSize: 1, pageSize: 1,
filters: buildPostFilters([ filters: buildVisibleNewsFilters([`external_link==${normalizedPath}`]),
`external_link==${normalizePath(path)}`,
"is_hidden==false",
"is_active==true",
"status==published",
]),
}); });
return result.rows[0] ?? null; return result.rows[0] ?? null;
...@@ -307,6 +404,51 @@ export function findMenuCategoryForPost( ...@@ -307,6 +404,51 @@ export function findMenuCategoryForPost(
return null; return null;
} }
export function findDisplayCategoryForPost(
post: DynamicPostItem | null,
activeCategory: DynamicCategoryRouteItem | null,
categories: DynamicCategoryRouteItem[] = [],
) {
if (!post) return null;
if (activeCategory) {
const matchedPostCategory =
post.categories.find((item) => item.id === activeCategory.id) ??
post.categories.find((item) => normalizePath(item.url) === normalizePath(activeCategory.url));
if (matchedPostCategory) {
return {
id: matchedPostCategory.id,
name: matchedPostCategory.name,
url: normalizePath(matchedPostCategory.url),
type: matchedPostCategory.type,
};
}
const matchedTreeCategory = categories.find((item) => item.id === activeCategory.id);
if (matchedTreeCategory) {
return {
id: matchedTreeCategory.id,
name: matchedTreeCategory.name,
url: normalizePath(matchedTreeCategory.url),
type: matchedTreeCategory.type,
};
}
}
const firstCategory = post.categories[0];
if (firstCategory) {
return {
id: firstCategory.id,
name: firstCategory.name,
url: normalizePath(firstCategory.url),
type: firstCategory.type,
};
}
return null;
}
export function buildDynamicCategoryMenu( export function buildDynamicCategoryMenu(
activeCategory: DynamicCategoryRouteItem | null, activeCategory: DynamicCategoryRouteItem | null,
categories: DynamicCategoryRouteItem[], categories: DynamicCategoryRouteItem[],
......
...@@ -7,6 +7,7 @@ import Link from "next/link"; ...@@ -7,6 +7,7 @@ import Link from "next/link";
import { useCustomClient } from "@/api/mutator/custom-client"; import { useCustomClient } from "@/api/mutator/custom-client";
import ImageNext from "@/components/shared/image-next"; import ImageNext from "@/components/shared/image-next";
import { fetchClientVideos } from "@/lib/api/videos"; import { fetchClientVideos } from "@/lib/api/videos";
import { buildDynamicPostHref, buildVisibleNewsFilters } from "../data";
import StructuredPostContent from "../StructuredPostContent"; import StructuredPostContent from "../StructuredPostContent";
import type { DynamicPostItem } from "../types"; import type { DynamicPostItem } from "../types";
...@@ -87,13 +88,7 @@ export default function AboutVcciHcmPage({ ...@@ -87,13 +88,7 @@ export default function AboutVcciHcmPage({
pageSize: "3", pageSize: "3",
sortField: "release_at", sortField: "release_at",
sortOrder: "desc", sortOrder: "desc",
filters: [ filters: buildVisibleNewsFilters([`category.id==${TIN_VCCI_CATEGORY_ID}`]),
`category.id==${TIN_VCCI_CATEGORY_ID}`,
"is_hidden==false",
"is_active==true",
"status==published",
"type==news",
].join(","),
}); });
const response = await useCustomClient<TinVcciApiEnvelope>(`/post?${query.toString()}`); const response = await useCustomClient<TinVcciApiEnvelope>(`/post?${query.toString()}`);
...@@ -101,7 +96,7 @@ export default function AboutVcciHcmPage({ ...@@ -101,7 +96,7 @@ export default function AboutVcciHcmPage({
return (response.responseData?.rows ?? []).map((item) => ({ return (response.responseData?.rows ?? []).map((item) => ({
id: String(item.id ?? ""), id: String(item.id ?? ""),
title: String(item.title ?? "").trim(), title: String(item.title ?? "").trim(),
externalLink: item.external_link?.trim() || "#", externalLink: buildDynamicPostHref(item.external_link?.trim() || "#", item.id ? String(item.id) : ""),
publishedAt: String(item.published_at ?? item.release_at ?? item.created_at ?? ""), publishedAt: String(item.published_at ?? item.release_at ?? item.created_at ?? ""),
thumbnailUrl: thumbnailUrl:
item.thumbnail?.url?.trim() || item.thumbnail?.url?.trim() ||
...@@ -133,7 +128,7 @@ export default function AboutVcciHcmPage({ ...@@ -133,7 +128,7 @@ export default function AboutVcciHcmPage({
) : null} ) : null}
<div className="mt-7 rounded-3xl bg-white px-5 py-6 shadow-[0_18px_42px_rgba(17,24,39,0.06)] sm:px-8 lg:px-10"> <div className="mt-7 rounded-3xl bg-white px-5 py-6 shadow-[0_18px_42px_rgba(17,24,39,0.06)] sm:px-8 lg:px-10">
<div className="page-detail-content prose tiptap max-w-none overflow-hidden"> <div className="about-vcci-page-content page-detail-content prose tiptap max-w-none overflow-hidden">
<StructuredPostContent post={post} /> <StructuredPostContent post={post} />
</div> </div>
</div> </div>
...@@ -154,6 +149,24 @@ export default function AboutVcciHcmPage({ ...@@ -154,6 +149,24 @@ export default function AboutVcciHcmPage({
</aside> </aside>
</section> </section>
<style jsx global>{`
.about-vcci-page-content figure {
width: 100% !important;
max-width: 100% !important;
margin: 28px 0 !important;
text-align: center;
}
.about-vcci-page-content img {
width: 100% !important;
max-width: 100% !important;
height: auto !important;
margin-left: auto !important;
margin-right: auto !important;
object-fit: contain;
}
`}</style>
<section className="mt-10 space-y-10 md:mt-12 md:space-y-12"> <section className="mt-10 space-y-10 md:mt-12 md:space-y-12">
<div> <div>
<div className="text-center"> <div className="text-center">
...@@ -303,7 +316,7 @@ export default function AboutVcciHcmPage({ ...@@ -303,7 +316,7 @@ export default function AboutVcciHcmPage({
</Link> </Link>
</div> </div>
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-3"> <div className="grid pb-6 gap-5 md:grid-cols-2 xl:grid-cols-3">
{tinVcciItems.map((item) => ( {tinVcciItems.map((item) => (
<Link <Link
key={item.id} key={item.id}
......
...@@ -10,7 +10,8 @@ import { Button } from "@/components/ui/button"; ...@@ -10,7 +10,8 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Spinner } from "@components/ui/spinner"; import { Spinner } from "@components/ui/spinner";
import { import {
buildPostFilters, buildDynamicPostHref,
buildVisibleNewsFilters,
fetchDynamicPostList, fetchDynamicPostList,
resolveDynamicPostImage, resolveDynamicPostImage,
stripHtml, stripHtml,
...@@ -46,14 +47,14 @@ function SearchResultItem({ item, index }: { item: DynamicPostItem; index: numbe ...@@ -46,14 +47,14 @@ function SearchResultItem({ item, index }: { item: DynamicPostItem; index: numbe
?.map((section) => section.content) ?.map((section) => section.content)
.join(" "); .join(" ");
const description = const description =
item.summary || stripHtml(item.content) || stripHtml(fallbackDescription); stripHtml(item.summary) || stripHtml(item.content) || stripHtml(fallbackDescription);
const date = formatPostDate(item.release_at || item.published_at || item.created_at); const date = formatPostDate(item.release_at || item.published_at || item.created_at);
const categoryName = item.categories[0]?.name || "Tin tức"; const categoryName = item.categories[0]?.name || "Tin tức";
return ( return (
<article className="border-b border-[#eceff3] pb-8 last:border-b-0"> <article className="border-b border-[#eceff3] pb-8 last:border-b-0">
<Link <Link
href={item.external_link || "#"} href={buildDynamicPostHref(item.external_link, item.id)}
className="group grid gap-5 sm:grid-cols-[250px_minmax(0,1fr)]" className="group grid gap-5 sm:grid-cols-[250px_minmax(0,1fr)]"
> >
<div className="overflow-hidden rounded-md bg-[#edf1f5]"> <div className="overflow-hidden rounded-md bg-[#edf1f5]">
...@@ -104,11 +105,7 @@ function SearchContent() { ...@@ -104,11 +105,7 @@ function SearchContent() {
fetchDynamicPostList({ fetchDynamicPostList({
page, page,
pageSize, pageSize,
filters: buildPostFilters([ filters: buildVisibleNewsFilters([
"is_hidden==false",
"is_active==true",
"status==published",
"type==news",
query ? `title@=${query}` : null, query ? `title@=${query}` : null,
]), ]),
}), }),
......
...@@ -22,6 +22,79 @@ interface AdminRichTextEditorProps { ...@@ -22,6 +22,79 @@ interface AdminRichTextEditorProps {
readOnly?: boolean; readOnly?: boolean;
} }
const FONT_FAMILY_OPTIONS = {
"Arial, Helvetica, sans-serif": "Arial",
"Tahoma, Geneva, sans-serif": "Tahoma",
"Verdana, Geneva, sans-serif": "Verdana",
"'Times New Roman', Times, serif": "Times New Roman",
"Georgia, serif": "Georgia",
"'Courier New', Courier, monospace": "Courier New",
"'Trebuchet MS', Helvetica, sans-serif": "Trebuchet MS",
} as const;
const FONT_SIZE_OPTIONS = {
"12px": "12",
"14px": "14",
"16px": "16",
"18px": "18",
"20px": "20",
"24px": "24",
"28px": "28",
"32px": "32",
"36px": "36",
"42px": "42",
} as const;
const IMPORTED_LAYOUT_SELECTORS = [
".article-content",
".article-content_toc",
"figure",
"figcaption",
"img",
"table",
"iframe",
];
function normalizeImportedHtml(value: string) {
if (typeof window === "undefined" || !value.trim()) return value;
const parser = new DOMParser();
const document = parser.parseFromString(value, "text/html");
document.body.querySelectorAll<HTMLElement>(IMPORTED_LAYOUT_SELECTORS.join(",")).forEach((element) => {
element.style.removeProperty("width");
element.style.removeProperty("max-width");
element.style.removeProperty("min-width");
element.style.removeProperty("float");
element.style.removeProperty("left");
element.style.removeProperty("right");
if (element.tagName === "IMG") {
element.style.setProperty("display", "block");
element.style.setProperty("width", "100%");
element.style.setProperty("max-width", "100%");
element.style.setProperty("height", "auto");
element.removeAttribute("width");
element.removeAttribute("height");
}
if (element.tagName === "FIGURE") {
element.style.setProperty("display", "block");
element.style.setProperty("margin", "1.5rem 0");
element.style.setProperty("width", "100%");
element.style.setProperty("max-width", "100%");
}
if (element.classList.contains("article-content") || element.classList.contains("article-content_toc")) {
element.style.setProperty("width", "100%");
element.style.setProperty("max-width", "100%");
element.style.setProperty("overflow", "hidden");
}
});
return document.body.innerHTML;
}
export function AdminRichTextEditor({ export function AdminRichTextEditor({
value, value,
onChange, onChange,
...@@ -97,6 +170,17 @@ export function AdminRichTextEditor({ ...@@ -97,6 +170,17 @@ export function AdminRichTextEditor({
defaultActionOnPaste: "insert_as_html", defaultActionOnPaste: "insert_as_html",
enter: "p", enter: "p",
showPlaceholder: false, showPlaceholder: false,
toolbarAdaptive: false,
toolbarInlineForSelection: true,
showXPathInStatusbar: false,
controls: {
font: {
list: FONT_FAMILY_OPTIONS,
},
fontsize: {
list: FONT_SIZE_OPTIONS,
},
},
}), }),
[minHeight, placeholder, readOnly], [minHeight, placeholder, readOnly],
); );
...@@ -117,6 +201,17 @@ export function AdminRichTextEditor({ ...@@ -117,6 +201,17 @@ export function AdminRichTextEditor({
padding: 10px; padding: 10px;
} }
.admin-rich-text-editor .jodit-toolbar-editor-collection {
border-radius: 0.9rem;
border: 1px solid rgba(6, 62, 142, 0.15);
box-shadow: 0 18px 34px rgba(17, 24, 39, 0.12);
overflow: hidden;
}
.admin-rich-text-editor .jodit-toolbar-editor-collection .jodit-toolbar__box {
padding: 8px;
}
.admin-rich-text-editor .jodit-workplace { .admin-rich-text-editor .jodit-workplace {
min-height: ${minHeight}px; min-height: ${minHeight}px;
} }
...@@ -134,11 +229,28 @@ export function AdminRichTextEditor({ ...@@ -134,11 +229,28 @@ export function AdminRichTextEditor({
} }
.admin-rich-text-editor .jodit-wysiwyg img { .admin-rich-text-editor .jodit-wysiwyg img {
max-width: 100%; display: block;
width: 100% !important;
max-width: 100% !important;
height: auto; height: auto;
border-radius: 0.75rem; border-radius: 0.75rem;
} }
.admin-rich-text-editor .jodit-wysiwyg .article-content,
.admin-rich-text-editor .jodit-wysiwyg .article-content_toc,
.admin-rich-text-editor .jodit-wysiwyg figure,
.admin-rich-text-editor .jodit-wysiwyg table,
.admin-rich-text-editor .jodit-wysiwyg iframe {
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box;
}
.admin-rich-text-editor .jodit-wysiwyg figure {
display: block !important;
margin: 1.5rem 0 !important;
}
.admin-rich-text-editor .jodit-placeholder { .admin-rich-text-editor .jodit-placeholder {
color: #374151 !important; color: #374151 !important;
} }
...@@ -149,7 +261,7 @@ export function AdminRichTextEditor({ ...@@ -149,7 +261,7 @@ export function AdminRichTextEditor({
ref={editor} ref={editor}
value={value} value={value}
config={config} config={config}
onBlur={(nextContent) => onChange(nextContent)} onBlur={(nextContent) => onChange(normalizeImportedHtml(nextContent))}
onChange={() => undefined} onChange={() => undefined}
/> />
</div> </div>
......
...@@ -115,7 +115,7 @@ const CommandItem = React.forwardRef< ...@@ -115,7 +115,7 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item <CommandPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "relative flex cursor-pointer gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
className className
)} )}
{...props} {...props}
......
...@@ -27,7 +27,7 @@ const ContextMenuSubTrigger = React.forwardRef< ...@@ -27,7 +27,7 @@ const ContextMenuSubTrigger = React.forwardRef<
<ContextMenuPrimitive.SubTrigger <ContextMenuPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", "flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8", inset && "pl-8",
className className
)} )}
...@@ -80,7 +80,7 @@ const ContextMenuItem = React.forwardRef< ...@@ -80,7 +80,7 @@ const ContextMenuItem = React.forwardRef<
<ContextMenuPrimitive.Item <ContextMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8", inset && "pl-8",
className className
)} )}
...@@ -96,7 +96,7 @@ const ContextMenuCheckboxItem = React.forwardRef< ...@@ -96,7 +96,7 @@ const ContextMenuCheckboxItem = React.forwardRef<
<ContextMenuPrimitive.CheckboxItem <ContextMenuPrimitive.CheckboxItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
checked={checked} checked={checked}
...@@ -120,7 +120,7 @@ const ContextMenuRadioItem = React.forwardRef< ...@@ -120,7 +120,7 @@ const ContextMenuRadioItem = React.forwardRef<
<ContextMenuPrimitive.RadioItem <ContextMenuPrimitive.RadioItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
{...props} {...props}
......
...@@ -27,7 +27,7 @@ const DropdownMenuSubTrigger = React.forwardRef< ...@@ -27,7 +27,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8", inset && "pl-8",
className className
)} )}
...@@ -84,7 +84,7 @@ const DropdownMenuItem = React.forwardRef< ...@@ -84,7 +84,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0", "relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8", inset && "pl-8",
className className
)} )}
...@@ -100,7 +100,7 @@ const DropdownMenuCheckboxItem = React.forwardRef< ...@@ -100,7 +100,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
checked={checked} checked={checked}
...@@ -124,7 +124,7 @@ const DropdownMenuRadioItem = React.forwardRef< ...@@ -124,7 +124,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
{...props} {...props}
......
...@@ -58,7 +58,7 @@ const MenubarTrigger = React.forwardRef< ...@@ -58,7 +58,7 @@ const MenubarTrigger = React.forwardRef<
<MenubarPrimitive.Trigger <MenubarPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", "flex cursor-pointer select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
className className
)} )}
{...props} {...props}
...@@ -75,7 +75,7 @@ const MenubarSubTrigger = React.forwardRef< ...@@ -75,7 +75,7 @@ const MenubarSubTrigger = React.forwardRef<
<MenubarPrimitive.SubTrigger <MenubarPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", "flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8", inset && "pl-8",
className className
)} )}
...@@ -136,7 +136,7 @@ const MenubarItem = React.forwardRef< ...@@ -136,7 +136,7 @@ const MenubarItem = React.forwardRef<
<MenubarPrimitive.Item <MenubarPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8", inset && "pl-8",
className className
)} )}
...@@ -152,7 +152,7 @@ const MenubarCheckboxItem = React.forwardRef< ...@@ -152,7 +152,7 @@ const MenubarCheckboxItem = React.forwardRef<
<MenubarPrimitive.CheckboxItem <MenubarPrimitive.CheckboxItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
checked={checked} checked={checked}
...@@ -175,7 +175,7 @@ const MenubarRadioItem = React.forwardRef< ...@@ -175,7 +175,7 @@ const MenubarRadioItem = React.forwardRef<
<MenubarPrimitive.RadioItem <MenubarPrimitive.RadioItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
{...props} {...props}
......
...@@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef< ...@@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger <SelectPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", "flex h-9 w-full cursor-pointer items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className className
)} )}
{...props} {...props}
...@@ -39,7 +39,7 @@ const SelectScrollUpButton = React.forwardRef< ...@@ -39,7 +39,7 @@ const SelectScrollUpButton = React.forwardRef<
<SelectPrimitive.ScrollUpButton <SelectPrimitive.ScrollUpButton
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default items-center justify-center py-1", "flex cursor-pointer items-center justify-center py-1",
className className
)} )}
{...props} {...props}
...@@ -56,7 +56,7 @@ const SelectScrollDownButton = React.forwardRef< ...@@ -56,7 +56,7 @@ const SelectScrollDownButton = React.forwardRef<
<SelectPrimitive.ScrollDownButton <SelectPrimitive.ScrollDownButton
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default items-center justify-center py-1", "flex cursor-pointer items-center justify-center py-1",
className className
)} )}
{...props} {...props}
...@@ -118,7 +118,7 @@ const SelectItem = React.forwardRef< ...@@ -118,7 +118,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item <SelectPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
{...props} {...props}
......
...@@ -22,6 +22,25 @@ ...@@ -22,6 +22,25 @@
font-family: var(--default-font-family); font-family: var(--default-font-family);
} }
:where(
button,
[type='button'],
[type='reset'],
[type='submit'],
[role='button'],
a[href],
label[for],
summary,
select,
option,
input[type='checkbox'],
input[type='radio'],
input[type='file'],
input[type='image']
):not(:disabled, [aria-disabled='true']) {
cursor: pointer;
}
/* COLOR */ /* COLOR */
:root { :root {
/* Default background color of <body />...etc */ /* Default background color of <body />...etc */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment