seo:gallery - image keywords
This commit is contained in:
parent
8c9192e27a
commit
3f9798abca
File diff suppressed because one or more lines are too long
@ -23,8 +23,6 @@ export interface GalleryImage {
|
||||
name?: string
|
||||
url?: string
|
||||
src: string
|
||||
thumb?: string
|
||||
responsive?: string
|
||||
meta?: Meta
|
||||
keywords?: string
|
||||
description?: string
|
||||
|
||||
@ -10,7 +10,6 @@ import { resolve } from '@polymech/commons'
|
||||
import { files } from '@polymech/commons'
|
||||
import { sync as exists } from '@polymech/fs/exists'
|
||||
import { sync as read } from '@polymech/fs/read'
|
||||
|
||||
import { logger } from '@/base/index.js'
|
||||
|
||||
import { removeArrayValues, removeArrays, removeBufferValues, removeEmptyObjects } from '@/base/objects.js'
|
||||
@ -58,38 +57,6 @@ export const image_url = async (src, fallback = DEFAULT_IMAGE_URL) => {
|
||||
return safeSrc
|
||||
}
|
||||
|
||||
const default_image = () => {
|
||||
const url = image_url(DEFAULT_IMAGE_URL)
|
||||
return {
|
||||
name: "none",
|
||||
src: url,
|
||||
meta: {
|
||||
format: '',
|
||||
width: 0,
|
||||
height: 0,
|
||||
space: '',
|
||||
channels: 0,
|
||||
depth: 0,
|
||||
density: 0,
|
||||
chromaSubsampling: '',
|
||||
isProgressive: false,
|
||||
resolutionUnit: 0,
|
||||
hasProfile: false,
|
||||
hasAlpha: false,
|
||||
orientation: 0,
|
||||
exif: {},
|
||||
json: {},
|
||||
markdown: ""
|
||||
},
|
||||
keywords: [],
|
||||
description: '',
|
||||
alt: '',
|
||||
width: 0,
|
||||
height: 0,
|
||||
title: '',
|
||||
gps: { lon: '', lat: '' }
|
||||
}
|
||||
}
|
||||
export const gallery = async (
|
||||
assetPath,
|
||||
product): Promise<GalleryImage[] | undefined> => {
|
||||
@ -100,12 +67,12 @@ export const gallery = async (
|
||||
|
||||
const productConfig: any = read(PRODUCT_CONFIG(product), "json")
|
||||
if (!productConfig) {
|
||||
logger.warn(`ProductGallery : Product ${product} config not found !`)
|
||||
logger.warn(`item gallery : item ${product} config not found !`)
|
||||
return
|
||||
}
|
||||
const mediaPath = `${root}/${product}/${assetPath}/`
|
||||
if (!exists(mediaPath)) {
|
||||
logger.warn(`ProductGallery : Product ${product} media path not found ${mediaPath}!`)
|
||||
logger.warn(`item gallery : item ${product} media path not found ${mediaPath}!`)
|
||||
return []
|
||||
}
|
||||
const galleryGlob = (productConfig.gallery || {})[assetSlug]?.glob || IMAGES_GLOB
|
||||
@ -125,14 +92,12 @@ export const gallery = async (
|
||||
}
|
||||
galleryFiles = default_sort(galleryFiles)
|
||||
return await pMap(galleryFiles, async (file: string) => {
|
||||
|
||||
const parts = path.parse(file)
|
||||
const filePath = path.join(mediaPath, file)
|
||||
const meta_path_json = `${mediaPath}/${parts.name}.json`
|
||||
const meta_json = exists(meta_path_json) ? read(meta_path_json, "json") as MetaJSON : { alt: "", keywords: "", title: "", description: "" }
|
||||
const meta_path_md = `${mediaPath}/${parts.name}.md`
|
||||
const meta_markdown = exists(meta_path_md) ? read(meta_path_md, "string") as string : "" as string
|
||||
|
||||
let imageMeta: any = await loadImage(filePath)
|
||||
let exifRaw: any = null
|
||||
try {
|
||||
@ -141,8 +106,7 @@ export const gallery = async (
|
||||
logger.error(`ProductGallery : Error loading exif data for ${filePath}`)
|
||||
exifRaw = {}
|
||||
}
|
||||
|
||||
const keywords = exifRaw?.['LastKeywordXMP']?.description || exifRaw?.iptc?.Keywords?.description || ''
|
||||
const keywords = exifRaw?.['LastKeywordXMP']?.description || exifRaw?.iptc?.Keywords?.description || ''.split(',').map((k) => k.trim())
|
||||
const exifDescription = exifRaw?.['ImageDescription']?.description || ''
|
||||
const width = exifRaw?.['Image Width']?.value
|
||||
const height = exifRaw?.['Image Height']?.value
|
||||
@ -150,7 +114,7 @@ export const gallery = async (
|
||||
const lat = exifRaw?.['GPSLatitude']?.description
|
||||
|
||||
const title = exifRaw?.title?.description || meta_json.title || ''
|
||||
const description = meta_json.description || meta_markdown || exifDescription || exifRaw?.iptc?.['Caption/Abstract'].description || ''
|
||||
const alt = meta_json.description || meta_markdown || exifDescription || exifRaw?.iptc?.['Caption/Abstract'].description || ''
|
||||
imageMeta.exif = exifRaw
|
||||
imageMeta = removeBufferValues(imageMeta)
|
||||
imageMeta = removeArrayValues(imageMeta)
|
||||
@ -161,8 +125,6 @@ export const gallery = async (
|
||||
delete imageMeta.exif.icc
|
||||
delete imageMeta.exif.xmp
|
||||
delete imageMeta.exif.iptc
|
||||
const keywordsTranslated = ''
|
||||
|
||||
const assetUrl = (filePath) => {
|
||||
return sanitizeUri(ITEM_ASSET_URL(
|
||||
{
|
||||
@ -178,8 +140,6 @@ export const gallery = async (
|
||||
name: path.parse(file).name,
|
||||
url: await image_url(assetUrl(file)),
|
||||
src: await image_url(assetUrl(file)),
|
||||
thumb: assetUrl(`/${parts.name}-thumb.webp`),
|
||||
responsive: assetUrl(`/webp/${parts.name}.webp`),
|
||||
meta: {
|
||||
format: imageMeta.format,
|
||||
width: imageMeta.width,
|
||||
@ -198,15 +158,14 @@ export const gallery = async (
|
||||
json: meta_json as MetaJSON,
|
||||
markdown: meta_markdown as string
|
||||
},
|
||||
keywords: keywords.split(',').map((k) => k.trim()),
|
||||
description,
|
||||
alt: `${description} - ${keywordsTranslated || ''}`,
|
||||
keywords,
|
||||
description: alt,
|
||||
alt,
|
||||
width,
|
||||
height,
|
||||
title,
|
||||
gps: { lon, lat }
|
||||
}
|
||||
|
||||
return ret
|
||||
})
|
||||
}
|
||||
@ -1,45 +1,42 @@
|
||||
import { unified } from "unified";
|
||||
import remarkParse from "remark-parse";
|
||||
import remarkRehype from "remark-rehype";
|
||||
import rehypeStringify from "rehype-stringify";
|
||||
import { createComponent } from "astro/runtime/server/astro-component.js";
|
||||
import { renderTemplate, unescapeHTML } from "astro/runtime/server/index.js";
|
||||
|
||||
import { findUp } from 'find-up'
|
||||
import { createLogger } from '@polymech/log'
|
||||
import { parse, IProfile, IComponentConfig } from '@polymech/commons'
|
||||
import { renderMarkup } from "@/model/component.js";
|
||||
import { IComponentConfig } from '@polymech/commons'
|
||||
import { sync as read } from '@polymech/fs/read'
|
||||
import { sync as exists } from '@polymech/fs/exists'
|
||||
|
||||
import {
|
||||
LOGGING_NAMESPACE,
|
||||
OSRL_ENV,
|
||||
OSRL_PRODUCT_PROFILE,
|
||||
PRODUCT_ROOT,
|
||||
I18N_SOURCE_LANGUAGE
|
||||
} from 'config/config.js'
|
||||
import { I18N_SOURCE_LANGUAGE } from 'config/config.js'
|
||||
|
||||
import { translate } from '@/base/i18n.js'
|
||||
import { item_defaults } from '@/base/index.js'
|
||||
|
||||
import config from "../app/config.json" with { "type": "json" }
|
||||
|
||||
export const item_keywords = async (item: IComponentConfig, locale: string = I18N_SOURCE_LANGUAGE) => {
|
||||
let system_keywords = ""
|
||||
const keywords = (keywords: string) => keywords.split(',').map(k => k.trim()).filter(Boolean);
|
||||
|
||||
const unique = (...keywordGroups: string[][]) => {
|
||||
return Array.from(new Set(keywordGroups.flat()))
|
||||
};
|
||||
|
||||
export const site_keywords = async (locale: string = I18N_SOURCE_LANGUAGE) => {
|
||||
const configKeywords = keywords(config.metadata.keywords || "");
|
||||
const allKeywords = unique(configKeywords)
|
||||
const system_keywords = await translate(allKeywords.join(','), I18N_SOURCE_LANGUAGE, locale);
|
||||
const keywordsArray = keywords(system_keywords)
|
||||
return keywordsArray.join(',');
|
||||
};
|
||||
|
||||
export const item_keywords = async (item: IComponentConfig | null, locale: string = I18N_SOURCE_LANGUAGE) => {
|
||||
if (!item) {
|
||||
return (await site_keywords(locale))
|
||||
}
|
||||
|
||||
let system_keywords = "";
|
||||
if (item.PRODUCT_ROOT) {
|
||||
const defaultsJson = await item_defaults(item.PRODUCT_ROOT);
|
||||
let defaults: Record<string, string> = defaultsJson ? read(defaultsJson, 'json') as Record<string, string> || {} : {}
|
||||
const defaultsKeywords = (defaults.keywords || "").split(',').map(k => k.trim()).filter(Boolean)
|
||||
const configKeywords = (config.metadata.keywords || "").split(',').map(k => k.trim()).filter(Boolean)
|
||||
const itemKeywords = (item.keywords || "").split(',').map(k => k.trim()).filter(Boolean)
|
||||
const allKeywords = Array.from(new Set([
|
||||
...defaultsKeywords,
|
||||
...configKeywords,
|
||||
...itemKeywords
|
||||
])).join(',');
|
||||
system_keywords = await translate(allKeywords, I18N_SOURCE_LANGUAGE, locale)
|
||||
const defaults: Record<string, string> = defaultsJson ? read(defaultsJson, 'json') as Record<string, string> || {} : {}
|
||||
const defaultsKeywords = keywords(defaults.keywords || "")
|
||||
const configKeywords = await site_keywords(locale)
|
||||
const itemKeywords = keywords(item.keywords || "")
|
||||
const allKeywords = unique(defaultsKeywords, configKeywords.split(','), itemKeywords)
|
||||
system_keywords = await translate(allKeywords.join(''), I18N_SOURCE_LANGUAGE, locale)
|
||||
}
|
||||
const keywordsArray = [...new Set([item.name, ...system_keywords.split(',')])]
|
||||
const keywords = keywordsArray.join(',')
|
||||
return keywords
|
||||
}
|
||||
const keywordsArray = unique([item.name], keywords(system_keywords))
|
||||
return keywordsArray.join(',')
|
||||
};
|
||||
|
||||
@ -1,21 +1,27 @@
|
||||
---
|
||||
import { Img } from "imagetools/components";
|
||||
import Translate from "@/components/polymech/i18n.astro"
|
||||
import { translate } from "@/base/i18n";
|
||||
import { createMarkdownComponent, markdownToHtml } from "@/base/index.js";
|
||||
|
||||
import { translate } from "@/base/i18n"
|
||||
import { item_keywords, site_keywords } from '@/base/seo.js'
|
||||
|
||||
import pMap from 'p-map'
|
||||
|
||||
import { createMarkdownComponent, markdownToHtml } from "@/base/index.js"
|
||||
import { I18N_SOURCE_LANGUAGE, IMAGE_SETTINGS } from "config/config.js"
|
||||
import { IComponentConfig } from "@polymech/commons"
|
||||
interface Image {
|
||||
alt: string
|
||||
src: string
|
||||
title?: string
|
||||
description?: string
|
||||
item?:IComponentConfig
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
images: Image[];
|
||||
id?: string;
|
||||
siteKeywords?: string[];
|
||||
item?: IComponentConfig;
|
||||
gallerySettings?: {
|
||||
SIZES_REGULAR?: string;
|
||||
SIZES_THUMB?: string;
|
||||
@ -31,47 +37,24 @@ export interface Props {
|
||||
SHOW_DESCRIPTION?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const { images, gallerySettings = {}, lightboxSettings = {}, siteKeywords = [] } = Astro.props;
|
||||
|
||||
const { images, gallerySettings = {}, lightboxSettings = {}, item } = Astro.props;
|
||||
const mergedGallerySettings = {
|
||||
SIZES_REGULAR: gallerySettings.SIZES_REGULAR || IMAGE_SETTINGS.GALLERY.SIZES_REGULAR,
|
||||
SIZES_THUMB: gallerySettings.SIZES_THUMB || IMAGE_SETTINGS.GALLERY.SIZES_THUMB,
|
||||
SHOW_TITLE: gallerySettings.SHOW_TITLE ?? IMAGE_SETTINGS.GALLERY.SHOW_TITLE,
|
||||
SHOW_DESCRIPTION: gallerySettings.SHOW_DESCRIPTION ?? IMAGE_SETTINGS.GALLERY.SHOW_DESCRIPTION,
|
||||
};
|
||||
|
||||
}
|
||||
const mergedLightboxSettings = {
|
||||
SIZES_LARGE: lightboxSettings.SIZES_LARGE || IMAGE_SETTINGS.LIGHTBOX.SIZES_LARGE,
|
||||
SHOW_TITLE: lightboxSettings.SHOW_TITLE ?? IMAGE_SETTINGS.LIGHTBOX.SHOW_TITLE,
|
||||
SHOW_DESCRIPTION: lightboxSettings.SHOW_DESCRIPTION ?? IMAGE_SETTINGS.LIGHTBOX.SHOW_DESCRIPTION,
|
||||
};
|
||||
const locale = Astro.currentLocale || "en";
|
||||
|
||||
// Function to augment alt text with keywords
|
||||
const augmentAltText = (altText, keywords) => {
|
||||
if (!keywords || keywords.length === 0) return altText;
|
||||
|
||||
// Filter out keywords that are already in the alt text
|
||||
const filteredKeywords = keywords.filter(keyword =>
|
||||
keyword && keyword.trim() !== '' && !altText.toLowerCase().includes(keyword.toLowerCase())
|
||||
);
|
||||
|
||||
// If no keywords to add, return original alt text
|
||||
if (filteredKeywords.length === 0) return altText;
|
||||
|
||||
// Add keywords to alt text
|
||||
return `${altText} (${filteredKeywords.join(', ')})`;
|
||||
};
|
||||
|
||||
// Pre-calculate translated alt text for all images with augmented keywords
|
||||
const translatedAltTexts = await Promise.all(
|
||||
images.map(async (image) => {
|
||||
// Augment alt text with site keywords before translation
|
||||
const augmentedAltText = augmentAltText(image.alt, siteKeywords);
|
||||
return await translate(augmentedAltText, I18N_SOURCE_LANGUAGE, locale);
|
||||
})
|
||||
);
|
||||
}
|
||||
const locale = Astro.currentLocale || I18N_SOURCE_LANGUAGE
|
||||
const alt = async (altText) => `${altText} - ${await item_keywords(item, locale)}`
|
||||
const alt_translated = await pMap(images, async (image) => {
|
||||
const augmentedAltText = await alt(image.alt)
|
||||
return await translate(augmentedAltText, I18N_SOURCE_LANGUAGE, locale)
|
||||
}, { concurrency: 1 })
|
||||
|
||||
---
|
||||
|
||||
@ -145,7 +128,7 @@ const translatedAltTexts = await Promise.all(
|
||||
<div x-show={`currentIndex === ${index}`} key={index} class="w-full h-full flex items-center justify-center">
|
||||
<Img
|
||||
src={image.src}
|
||||
alt={translatedAltTexts[index]}
|
||||
alt={alt_translated[index]}
|
||||
objectFit="contain"
|
||||
format="avif"
|
||||
placeholder="blurred"
|
||||
@ -185,7 +168,7 @@ const translatedAltTexts = await Promise.all(
|
||||
placeholder="blurred"
|
||||
sizes={mergedGallerySettings.SIZES_THUMB}
|
||||
class="w-32 h-32 p-1 object-contain rounded hover:ring-2 hover:ring-blue-500"
|
||||
alt={translatedAltTexts[index]}
|
||||
alt={alt_translated[index]}
|
||||
attributes={{
|
||||
img: {
|
||||
class: "w-32 h-32 rounded-lg hover:ring-2 hover:ring-blue-500 thumbnail-img aspect-square",
|
||||
@ -215,7 +198,7 @@ const translatedAltTexts = await Promise.all(
|
||||
<div x-show={`currentIndex === ${index}`} key={index}>
|
||||
<Img
|
||||
src={image.src}
|
||||
alt={translatedAltTexts[index]}
|
||||
alt={alt_translated[index]}
|
||||
placeholder="blurred"
|
||||
format="avif"
|
||||
objectFit="contain"
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
import { slugify } from "@/base/strings.js";
|
||||
export interface Props {
|
||||
title: string;
|
||||
class?: string;
|
||||
}
|
||||
const { title = "title" } = Astro.props;
|
||||
const { title = "title", class:className = "" } = Astro.props;
|
||||
const slug = `${slugify(title)}-tab-content`;
|
||||
const label = `${slug}-tab`;
|
||||
const view = `${slugify(title)}-view`;
|
||||
@ -14,6 +15,4 @@ const view = `${slugify(title)}-view`;
|
||||
id={view}
|
||||
role="tabpanel"
|
||||
aria-labelledby={label}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
> <slot /></div>
|
||||
|
||||
@ -33,9 +33,9 @@ import {
|
||||
isRTL,
|
||||
} from "config/config.js";
|
||||
|
||||
const { frontmatter: data, ...rest } = Astro.props;
|
||||
const { frontmatter: item, ...rest } = Astro.props;
|
||||
const content = await translate(
|
||||
data.content || "",
|
||||
item.content || "",
|
||||
I18N_SOURCE_LANGUAGE,
|
||||
Astro.currentLocale,
|
||||
);
|
||||
@ -45,7 +45,7 @@ const str_debug =
|
||||
"```json\n" +
|
||||
JSON.stringify(
|
||||
{
|
||||
...data,
|
||||
...item,
|
||||
config: null,
|
||||
},
|
||||
null,
|
||||
@ -56,7 +56,7 @@ const str_debug =
|
||||
const Content_Debug = await createMarkdownComponent(str_debug);
|
||||
---
|
||||
|
||||
<BaseLayout frontmatter={data} description={data.description} {...rest}>
|
||||
<BaseLayout frontmatter={item} description={item.description} {...rest}>
|
||||
<Wrapper>
|
||||
<section>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-2 just xl:grid-cols-2 gap-2">
|
||||
@ -66,11 +66,11 @@ const Content_Debug = await createMarkdownComponent(str_debug);
|
||||
<h1
|
||||
class="text-neutral-500 font-mono font-semibold mb-2 text-2xl"
|
||||
>
|
||||
<span><Translate>{`${data.title}`}</Translate></span>
|
||||
<span><Translate>{`${item.title}`}</Translate></span>
|
||||
{
|
||||
isRTL(Astro.currentLocale) && (
|
||||
<div class="text-neutral-500 font-mono font-semibold mb-2">
|
||||
"{data.title}"
|
||||
"{item.title}"
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -82,12 +82,12 @@ const Content_Debug = await createMarkdownComponent(str_debug);
|
||||
<div class="gap-2 flex flex-col h-full justify-end">
|
||||
{
|
||||
SHOW_3D_PREVIEW &&
|
||||
data.Preview3d &&
|
||||
data.cad &&
|
||||
data.cad[0] &&
|
||||
data.cad[0][".html"] && (
|
||||
item.Preview3d &&
|
||||
item.cad &&
|
||||
item.cad[0] &&
|
||||
item.cad[0][".html"] && (
|
||||
<a
|
||||
href={data.cad[0][".html"]}
|
||||
href={item.cad[0][".html"]}
|
||||
title="link to your page"
|
||||
aria-label="your label"
|
||||
class="relative group overflow-hidden pl-4 font-mono h-14 flex space-x-6 items-center bg-white hover:bg-neutral-200 duration-300 rounded-xl w-full justify-between"
|
||||
@ -136,9 +136,9 @@ const Content_Debug = await createMarkdownComponent(str_debug);
|
||||
)
|
||||
}
|
||||
{
|
||||
SHOW_CHECKOUT && data.checkout && (
|
||||
SHOW_CHECKOUT && item.checkout && (
|
||||
<a
|
||||
href={data.checkout}
|
||||
href={item.checkout}
|
||||
title="link to your page"
|
||||
aria-label="your label"
|
||||
class="relative group overflow-hidden pl-4 font-mono h-14 flex space-x-6 items-center bg-orange-500 hover:bg-black duration-300 rounded-xl w-full justify-between"
|
||||
@ -195,7 +195,7 @@ const Content_Debug = await createMarkdownComponent(str_debug);
|
||||
<Translate>License</Translate>
|
||||
</h3>
|
||||
<p class="text-neutral-500 mt-4 text-sm">
|
||||
{data.license || DEFAULT_LICENSE}
|
||||
{item.license || DEFAULT_LICENSE}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -209,8 +209,9 @@ const Content_Debug = await createMarkdownComponent(str_debug);
|
||||
style="height: 100%; width: 100%;"
|
||||
>
|
||||
<GalleryK
|
||||
images={data.assets.renderings}
|
||||
images={item.assets.renderings}
|
||||
gallerySettings={{ SHOW_TITLE: false }}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@ -219,11 +220,11 @@ const Content_Debug = await createMarkdownComponent(str_debug);
|
||||
</section>
|
||||
|
||||
{
|
||||
data.assets.showcase && data.assets.showcase.length > 0 && (
|
||||
item.assets.showcase && item.assets.showcase.length > 0 && (
|
||||
<section>
|
||||
<div class="mb-2 md:mb-16 mt-0 md:mt-16 p-2 md:p-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<GalleryK
|
||||
images={data.assets.showcase}
|
||||
images={item.assets.showcase}
|
||||
lightboxSettings={{
|
||||
SHOW_TITLE: false,
|
||||
SHOW_DESCRIPTION: false,
|
||||
@ -233,6 +234,7 @@ const Content_Debug = await createMarkdownComponent(str_debug);
|
||||
SHOW_TITLE: false,
|
||||
SHOW_DESCRIPTION: false,
|
||||
}}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
@ -260,28 +262,28 @@ const Content_Debug = await createMarkdownComponent(str_debug);
|
||||
<div id="default-styled-tab-content">
|
||||
<TabContent title="Overview" class="content">
|
||||
{
|
||||
SHOW_README && data.readme && (
|
||||
<Readme markdown={data.readme} data={data} />
|
||||
SHOW_README && item.readme && (
|
||||
<Readme markdown={item.readme} data={item} />
|
||||
)
|
||||
}
|
||||
</TabContent>
|
||||
<TabContent title="Specs" class="bg-white rounded-xl dark:bg-gray-800 font-mono">
|
||||
<Specs frontmatter={data} />
|
||||
<Specs frontmatter={item} />
|
||||
</TabContent>
|
||||
<TabContent title="Gallery" class="p-0 md:p-4 rounded-lg bg-white">
|
||||
<GalleryK images={data.assets.gallery} />
|
||||
<GalleryK images={item.assets.gallery} item={item} />
|
||||
</TabContent>
|
||||
{
|
||||
SHOW_SAMPLES && (
|
||||
<TabContent title="Samples" class="p-4 bg-white rounded-xl dark:bg-gray-800">
|
||||
<GalleryK images={data.assets.samples} />
|
||||
<GalleryK images={item.assets.samples} item={item} />
|
||||
</TabContent>
|
||||
)
|
||||
}
|
||||
{
|
||||
SHOW_RESOURCES && (
|
||||
<TabContent title="Resources" class="p-4 bg-white rounded-xl dark:bg-gray-800">
|
||||
<Resources frontmatter={data} />
|
||||
<Resources frontmatter={item} />
|
||||
</TabContent>
|
||||
)
|
||||
}
|
||||
@ -315,7 +317,7 @@ const Content_Debug = await createMarkdownComponent(str_debug);
|
||||
);
|
||||
if (tabTrigger) {
|
||||
setTimeout(() => {
|
||||
tabTrigger.click();
|
||||
(tabTrigger as HTMLElement).click();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user