mirror of
https://github.com/tabler/tabler.git
synced 2026-10-12 05:06:55 +04:00
Fix robots.txt, minified assets and dead links in the preview build (#3079)
This commit is contained in:
1 parent
39e73cea60
commit
084d2021d3
13 files changed
+80
-30
No files matched your search
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@tabler/docs": patch
|
||||||
|
"@tabler/preview": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixed docs breadcrumbs showing "Introduction", unresolved `site` expressions in the `.md` mirrors and the missing `play` and `rss` icons.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@tabler/preview": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixed `robots.txt`, minified assets in `BaseLayout`, the marketing menu link and the `markdown.html` redirect in the preview build.
|
||||||
@@ -231,7 +231,7 @@ Social buttons use the brand colors and icons of popular services, so a share or
|
|||||||
<a href="#" class="btn btn-instagram"> <Icon name="brand-instagram" /> Instagram </a>
|
<a href="#" class="btn btn-instagram"> <Icon name="brand-instagram" /> Instagram </a>
|
||||||
<a href="#" class="btn btn-pinterest"> <Icon name="brand-pinterest" /> Pinterest </a>
|
<a href="#" class="btn btn-pinterest"> <Icon name="brand-pinterest" /> Pinterest </a>
|
||||||
<a href="#" class="btn btn-vk"> <Icon name="brand-vk" /> VK </a>
|
<a href="#" class="btn btn-vk"> <Icon name="brand-vk" /> VK </a>
|
||||||
<a href="#" class="btn btn-rss"> <Icon name="brand-rss" /> RSS </a>
|
<a href="#" class="btn btn-rss"> <Icon name="rss" /> RSS </a>
|
||||||
<a href="#" class="btn btn-flickr"> <Icon name="brand-flickr" /> Flickr </a>
|
<a href="#" class="btn btn-flickr"> <Icon name="brand-flickr" /> Flickr </a>
|
||||||
<a href="#" class="btn btn-bitbucket"> <Icon name="brand-bitbucket" /> Bitbucket </a>
|
<a href="#" class="btn btn-bitbucket"> <Icon name="brand-bitbucket" /> Bitbucket </a>
|
||||||
<a href="#" class="btn btn-tabler"> <Icon name="brand-tabler" /> Tabler </a>
|
<a href="#" class="btn btn-tabler"> <Icon name="brand-tabler" /> Tabler </a>
|
||||||
|
|||||||
@@ -109,7 +109,9 @@ const pageSection = inSection('/ui')
|
|||||||
? 'Illustrations'
|
? 'Illustrations'
|
||||||
: inSection('/emails')
|
: inSection('/emails')
|
||||||
? 'Emails'
|
? 'Emails'
|
||||||
: '';
|
: inSection('/payments')
|
||||||
|
? 'Payments'
|
||||||
|
: '';
|
||||||
const metaTitle = seoTitle ?? title;
|
const metaTitle = seoTitle ?? title;
|
||||||
const metaDescription = seoDescription ?? description;
|
const metaDescription = seoDescription ?? description;
|
||||||
const siteName = pageSection ? `Tabler ${pageSection} Documentation` : 'Tabler Documentation';
|
const siteName = pageSection ? `Tabler ${pageSection} Documentation` : 'Tabler Documentation';
|
||||||
@@ -149,13 +151,15 @@ const linkedinUrl = 'https://www.linkedin.com/company/tabler-io';
|
|||||||
|
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
|
|
||||||
// JSON-LD breadcrumbs. Segment names come from the docs.json menu tree;
|
// JSON-LD breadcrumbs. Segment names come from the docs.json menu tree, except
|
||||||
// top-level sections are not in the menu, so they get explicit names.
|
// the top-level sections: the menu labels their index pages "Introduction",
|
||||||
|
// so they get explicit names that win over the menu.
|
||||||
const sectionNames: Record<string, string> = {
|
const sectionNames: Record<string, string> = {
|
||||||
'/ui': 'Tabler UI',
|
'/ui': 'Tabler UI',
|
||||||
'/icons': 'Tabler Icons',
|
'/icons': 'Tabler Icons',
|
||||||
'/illustrations': 'Tabler Illustrations',
|
'/illustrations': 'Tabler Illustrations',
|
||||||
'/emails': 'Tabler Emails',
|
'/emails': 'Tabler Emails',
|
||||||
|
'/payments': 'Tabler Payments',
|
||||||
};
|
};
|
||||||
type MenuNode = { title?: string; url?: string; children?: MenuNode[] };
|
type MenuNode = { title?: string; url?: string; children?: MenuNode[] };
|
||||||
const menuTitleByUrl = new Map<string, string>();
|
const menuTitleByUrl = new Map<string, string>();
|
||||||
@@ -190,7 +194,7 @@ const breadcrumbs = (() => {
|
|||||||
segments.forEach((segment, index) => {
|
segments.forEach((segment, index) => {
|
||||||
path += `/${segment}`;
|
path += `/${segment}`;
|
||||||
const isLast = index === segments.length - 1;
|
const isLast = index === segments.length - 1;
|
||||||
const name = isLast ? title : (menuTitleByUrl.get(path) ?? sectionNames[path] ?? segment);
|
const name = isLast ? title : (sectionNames[path] ?? menuTitleByUrl.get(path) ?? segment);
|
||||||
items.push({ name, path });
|
items.push({ name, path });
|
||||||
});
|
});
|
||||||
return items;
|
return items;
|
||||||
|
|||||||
+28
-7
@@ -23,6 +23,7 @@ import { site } from '@shared/lib/site'
|
|||||||
import { callouts } from '@components/callouts/index.ts'
|
import { callouts } from '@components/callouts/index.ts'
|
||||||
import docs from '@data/docs.json'
|
import docs from '@data/docs.json'
|
||||||
import packageManagers from '@data/package-managers.json'
|
import packageManagers from '@data/package-managers.json'
|
||||||
|
import payments from '@data/payments.json'
|
||||||
import { cdnCssTag, cdnJsTag, cdnPackageSnippet, cdnPluginSnippet } from './cdn-snippets.ts'
|
import { cdnCssTag, cdnJsTag, cdnPackageSnippet, cdnPluginSnippet } from './cdn-snippets.ts'
|
||||||
|
|
||||||
// Lazy raw imports, same as CodeDocs.astro — node:fs paths break once this is
|
// Lazy raw imports, same as CodeDocs.astro — node:fs paths break once this is
|
||||||
@@ -38,12 +39,30 @@ const fence = (code: string, lang = 'html') => `\`\`\`${lang}\n${code.trim()}\n\
|
|||||||
*/
|
*/
|
||||||
function resolveCodeTokens(snippet: string): string {
|
function resolveCodeTokens(snippet: string): string {
|
||||||
const tokens: Record<string, () => string> = {
|
const tokens: Record<string, () => string> = {
|
||||||
'${site.cdnUrl}': () => site.cdnUrl,
|
|
||||||
'${cdnCssTag()}': cdnCssTag,
|
'${cdnCssTag()}': cdnCssTag,
|
||||||
'${cdnJsTag()}': cdnJsTag,
|
'${cdnJsTag()}': cdnJsTag,
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(tokens).reduce((text, [token, resolve]) => text.replaceAll(token, resolve()), snippet)
|
return Object.entries(tokens)
|
||||||
|
.reduce((text, [token, resolve]) => text.replaceAll(token, resolve()), snippet)
|
||||||
|
.replace(/\$\{site\.(\w+)\}/g, (match, key: string) => siteValue(key) ?? match)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A string or number field of `site`, as the MDX pages read it; undefined for anything else. */
|
||||||
|
function siteValue(key: string): string | undefined {
|
||||||
|
const value = (site as Record<string, unknown>)[key]
|
||||||
|
return typeof value === 'string' || typeof value === 'number' ? String(value) : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSX expressions the pages use in prose — `{ site.iconsCount }`,
|
||||||
|
* `{ payments.length }` — and `${site.…}` inside template literals. MDX
|
||||||
|
* evaluates them in the browser; the markdown mirror has to do it here.
|
||||||
|
*/
|
||||||
|
function resolveExpressions(text: string): string {
|
||||||
|
return resolveCodeTokens(text)
|
||||||
|
.replace(/\{\s*site\.(\w+)\s*\}/g, (match, key: string) => siteValue(key) ?? match)
|
||||||
|
.replace(/\{\s*payments\.length\s*\}/g, String(payments.length))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Strip the common leading indentation from a block and trim blank edges. */
|
/** Strip the common leading indentation from a block and trim blank edges. */
|
||||||
@@ -199,12 +218,14 @@ export async function mdxToMarkdown(body: string, examples: (string | null)[] =
|
|||||||
// top-level imports are wiring, not content
|
// top-level imports are wiring, not content
|
||||||
text = text.replace(/^import\s+.+?from\s+['"][^'"]+['"];?[ \t]*$/gm, '')
|
text = text.replace(/^import\s+.+?from\s+['"][^'"]+['"];?[ \t]*$/gm, '')
|
||||||
|
|
||||||
// <Example> slots hold the markup the page is actually documenting
|
// <Example> slots hold the markup the page is actually documenting: either
|
||||||
const examplePattern = /<Example\b[^>]*>([\s\S]*?)<\/Example>/g
|
// wrapped in the tag, or passed as `html={\`…\`}` on a self-closing one (that
|
||||||
|
// template literal may contain `>`, so it is matched before the attribute scan)
|
||||||
|
const examplePattern = /<Example\b(?:[^>]*?html=\{`([\s\S]*?)`\})?[^>]*?(?:\/>|>([\s\S]*?)<\/Example>)/g
|
||||||
const useRendered = examples.length === (text.match(examplePattern)?.length ?? 0)
|
const useRendered = examples.length === (text.match(examplePattern)?.length ?? 0)
|
||||||
let exampleIndex = 0
|
let exampleIndex = 0
|
||||||
text = text.replace(examplePattern, (_match, inner: string) => {
|
text = text.replace(examplePattern, (_match, html: string | undefined, inner: string | undefined) => {
|
||||||
const snippet = (useRendered ? examples[exampleIndex++] : null) ?? dedent(inner)
|
const snippet = (useRendered ? examples[exampleIndex++] : null) ?? dedent(html ?? inner ?? '')
|
||||||
return snippet ? `\n${fence(snippet)}\n` : ''
|
return snippet ? `\n${fence(snippet)}\n` : ''
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -244,7 +265,7 @@ export async function mdxToMarkdown(body: string, examples: (string | null)[] =
|
|||||||
// MDX comments and leftover JSX expressions
|
// MDX comments and leftover JSX expressions
|
||||||
text = text.replace(/\{\/\*[\s\S]*?\*\/\}/g, '')
|
text = text.replace(/\{\/\*[\s\S]*?\*\/\}/g, '')
|
||||||
|
|
||||||
return restoreCode(text, code)
|
return resolveExpressions(restoreCode(text, code))
|
||||||
.replace(/\n{3,}/g, '\n\n')
|
.replace(/\n{3,}/g, '\n\n')
|
||||||
.trim()
|
.trim()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
---
|
---
|
||||||
|
// Legacy alias kept for old bookmarks and the downloadable package: the page
|
||||||
|
// moved to prose.html.
|
||||||
import RedirectLayout from '@shared/layouts/RedirectLayout.astro'
|
import RedirectLayout from '@shared/layouts/RedirectLayout.astro'
|
||||||
---
|
---
|
||||||
|
|
||||||
<RedirectLayout />
|
<RedirectLayout url="/prose.html" />
|
||||||
@@ -3,15 +3,17 @@ import { site } from '@shared/lib/site'
|
|||||||
|
|
||||||
export const prerender = true
|
export const prerender = true
|
||||||
|
|
||||||
// development, and crawling is allowed only in the preview environment.
|
// Vercel sets VERCEL_ENV to "production" on preview.tabler.io and to "preview"
|
||||||
|
// on branch deploys; it is unset locally. Only production may be crawled,
|
||||||
|
// and the sitemap URL is absolute outside development.
|
||||||
export const GET: APIRoute = () => {
|
export const GET: APIRoute = () => {
|
||||||
const environment = process.env.NODE_ENV || 'production'
|
const environment = process.env.NODE_ENV || 'production'
|
||||||
|
const isProduction = process.env.VERCEL_ENV === 'production'
|
||||||
const sitemapBase = environment !== 'development' ? site.previewUrl : ''
|
const sitemapBase = environment !== 'development' ? site.previewUrl : ''
|
||||||
const body = `Sitemap: ${sitemapBase}/sitemap.xml
|
const body = `Sitemap: ${sitemapBase}/sitemap.xml
|
||||||
|
|
||||||
|
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow:${environment === 'preview' ? '' : ' /'}
|
Disallow:${isProduction ? '' : ' /'}
|
||||||
`
|
`
|
||||||
|
|
||||||
return new Response(body, {
|
return new Response(body, {
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import { site } from '@shared/lib/site'
|
|||||||
|
|
||||||
export const prerender = true
|
export const prerender = true
|
||||||
|
|
||||||
// Index pages collapse to directory URLs (build.format 'file').
|
// build.format 'file' emits x/index.astro as x.html (the hosting serves no
|
||||||
|
// x/ directory), so nested index pages collapse to that file; the root index
|
||||||
|
// stays "/".
|
||||||
const pages = import.meta.glob('./**/*.astro')
|
const pages = import.meta.glob('./**/*.astro')
|
||||||
|
|
||||||
const urls = Object.keys(pages)
|
const urls = Object.keys(pages)
|
||||||
@@ -16,7 +18,7 @@ const urls = Object.keys(pages)
|
|||||||
})
|
})
|
||||||
.map((path) => {
|
.map((path) => {
|
||||||
if (path === 'index.html') return '/'
|
if (path === 'index.html') return '/'
|
||||||
if (path.endsWith('/index.html')) return `/${path.slice(0, -'index.html'.length)}`
|
if (path.endsWith('/index.html')) return `/${path.slice(0, -'/index.html'.length)}.html`
|
||||||
return `/${path}`
|
return `/${path}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import Icon from '@ui/Icon.astro'
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader title="Product Tour Example">
|
<CardHeader title="Product Tour Example">
|
||||||
<CardActions>
|
<CardActions>
|
||||||
<Button id="start-tour" text="Start Tour" icon="play" color="primary" element="button" />
|
<Button id="start-tour" text="Start Tour" icon="player-play" color="primary" element="button" />
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
|
|||||||
@@ -314,7 +314,7 @@
|
|||||||
},
|
},
|
||||||
"marketing": {
|
"marketing": {
|
||||||
"title": "Marketing",
|
"title": "Marketing",
|
||||||
"url": "marketing/index.html"
|
"url": "marketing.html"
|
||||||
},
|
},
|
||||||
"music": {
|
"music": {
|
||||||
"title": "Music",
|
"title": "Music",
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ const { title, pageLibs = [], bodyClass, rtl = false, base = '.', description, t
|
|||||||
// script runs, so a page keeps its variant until the visitor chooses otherwise.
|
// script runs, so a page keeps its variant until the visitor chooses otherwise.
|
||||||
const themeAttributes = themeDataAttributes(themeSettings)
|
const themeAttributes = themeDataAttributes(themeSettings)
|
||||||
|
|
||||||
// Development build: unminified assets, dev favicon.
|
// Development build: unminified assets, dev favicon (same rule as DocsLayout).
|
||||||
const environment = 'development'
|
const environment = process.env.NODE_ENV || 'production'
|
||||||
const min = environment === 'development' ? '' : '.min'
|
const isDevelopment = environment === 'development'
|
||||||
|
const min = isDevelopment ? '' : '.min'
|
||||||
// RTL swaps tabler/plugin stylesheets for their .rtl.css variants (demo stays LTR)
|
// RTL swaps tabler/plugin stylesheets for their .rtl.css variants (demo stays LTR)
|
||||||
const rtlSuffix = rtl ? '.rtl' : ''
|
const rtlSuffix = rtl ? '.rtl' : ''
|
||||||
|
|
||||||
@@ -55,8 +56,8 @@ const libJsFiles = (head: boolean) => pageLibEntries.filter(([, lib]) => Boolean
|
|||||||
<title>{title ? `${title} - ` : ''}{site.title} - {site.descriptionShort}</title>
|
<title>{title ? `${title} - ` : ''}{site.title} - {site.descriptionShort}</title>
|
||||||
<meta name="description" content={description ?? site.descriptionShort} />
|
<meta name="description" content={description ?? site.descriptionShort} />
|
||||||
|
|
||||||
<link rel="icon" href={`${base}/favicon-dev.ico`} type="image/x-icon" />
|
<link rel="icon" href={`${base}/favicon${isDevelopment ? '-dev' : ''}.ico`} type="image/x-icon" />
|
||||||
<link rel="shortcut icon" href={`${base}/favicon-dev.ico`} type="image/x-icon" />
|
<link rel="shortcut icon" href={`${base}/favicon${isDevelopment ? '-dev' : ''}.ico`} type="image/x-icon" />
|
||||||
<!-- END META -->
|
<!-- END META -->
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
+7
-2
@@ -24,8 +24,13 @@ const FILLER_PATH = /<path stroke="none" d="M0 0h24v24H0z" fill="none"\s*\/>/
|
|||||||
* aria-hidden="true" focusable="false" and the given classes.
|
* aria-hidden="true" focusable="false" and the given classes.
|
||||||
*/
|
*/
|
||||||
export function iconSvg(name: string, { filled = false, classes = 'icon' }: { filled?: boolean | undefined; classes?: string | undefined } = {}): string | undefined {
|
export function iconSvg(name: string, { filled = false, classes = 'icon' }: { filled?: boolean | undefined; classes?: string | undefined } = {}): string | undefined {
|
||||||
const icon = (icons as unknown as Record<string, IconRecord>)[name]
|
const icon = (icons as unknown as Record<string, IconRecord | undefined>)[name]
|
||||||
if (!icon) return undefined
|
if (!icon) {
|
||||||
|
// Icon.astro renders nothing for an unknown name, which is invisible in the
|
||||||
|
// page; say so in the build log instead of failing silently.
|
||||||
|
console.warn(`[svg] unknown Tabler icon "${name}"`)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
let svg = icon.svg?.[filled ? 'filled' : 'outline'] ?? ''
|
let svg = icon.svg?.[filled ? 'filled' : 'outline'] ?? ''
|
||||||
svg = svg.replace(FILLER_PATH, '')
|
svg = svg.replace(FILLER_PATH, '')
|
||||||
svg = svg.replace(/class="[^"]+"/, `aria-hidden="true" focusable="false" class="${classes}"`)
|
svg = svg.replace(/class="[^"]+"/, `aria-hidden="true" focusable="false" class="${classes}"`)
|
||||||
|
|||||||
+5
-3
@@ -4,24 +4,25 @@
|
|||||||
"tasks": {
|
"tasks": {
|
||||||
"build": {
|
"build": {
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.build/**", "$TURBO_ROOT$/.browserslistrc", "$TURBO_ROOT$/shared/banner/**"],
|
||||||
"outputs": ["dist/**"],
|
"outputs": ["dist/**"],
|
||||||
"cache": true
|
"cache": true
|
||||||
},
|
},
|
||||||
"@tabler/preview#build": {
|
"@tabler/preview#build": {
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared/**"],
|
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.build/**", "$TURBO_ROOT$/.browserslistrc", "$TURBO_ROOT$/shared/**", "!$TURBO_ROOT$/shared/.turbo/**", "!$TURBO_ROOT$/shared/.astro/**", "!$TURBO_ROOT$/shared/**/node_modules/**"],
|
||||||
"outputs": ["dist/**", "tmp-assets/**"],
|
"outputs": ["dist/**", "tmp-assets/**"],
|
||||||
"cache": true
|
"cache": true
|
||||||
},
|
},
|
||||||
"@tabler/docs#build": {
|
"@tabler/docs#build": {
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared/**"],
|
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.build/**", "$TURBO_ROOT$/.browserslistrc", "$TURBO_ROOT$/shared/**", "!$TURBO_ROOT$/shared/.turbo/**", "!$TURBO_ROOT$/shared/.astro/**", "!$TURBO_ROOT$/shared/**/node_modules/**"],
|
||||||
"outputs": ["dist/**", "tmp-assets/**"],
|
"outputs": ["dist/**", "tmp-assets/**"],
|
||||||
"cache": true
|
"cache": true
|
||||||
},
|
},
|
||||||
"@tabler/screenshots#build": {
|
"@tabler/screenshots#build": {
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared/**"],
|
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.build/**", "$TURBO_ROOT$/.browserslistrc", "$TURBO_ROOT$/shared/**", "!$TURBO_ROOT$/shared/.turbo/**", "!$TURBO_ROOT$/shared/.astro/**", "!$TURBO_ROOT$/shared/**/node_modules/**"],
|
||||||
"outputs": ["dist/**"],
|
"outputs": ["dist/**"],
|
||||||
"cache": true
|
"cache": true
|
||||||
},
|
},
|
||||||
@@ -66,6 +67,7 @@
|
|||||||
},
|
},
|
||||||
"type-check": {
|
"type-check": {
|
||||||
"dependsOn": ["^type-check"],
|
"dependsOn": ["^type-check"],
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared/**", "!$TURBO_ROOT$/shared/.turbo/**", "!$TURBO_ROOT$/shared/.astro/**", "!$TURBO_ROOT$/shared/**/node_modules/**"],
|
||||||
"cache": true
|
"cache": true
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
|
|||||||
Reference in new issue
Block a user