diff --git a/README.md b/README.md index a268b55..32e7f58 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ ![GitHub file size in bytes](https://img.shields.io/github/size/felix-berlin/astro-breadcrumbs/src/Breadcrumbs.astro?label=component%20size&logo=astro&style=flat-square) ![GitHub file size in bytes](https://img.shields.io/github/size/felix-berlin/astro-breadcrumbs/src/breadcrumbs.css?label=CSS%20Size&logo=CSS3&style=flat-square) ![GitHub file size in bytes](https://img.shields.io/github/size/felix-berlin/astro-breadcrumbs/src/breadcrumbs.scss?label=SCSS%20Size&logo=SASS&style=flat-square) +![GitHub file size in bytes](https://img.shields.io/github/size/felix-berlin/astro-breadcrumbs/src/lib/truncated.ts?label=Client%20JS%20size&logo=JS&style=flat-square) ![Dependent repos (via libraries.io)](https://img.shields.io/librariesio/dependent-repos/npm/astro-breadcrumbs?style=flat-square) Well configurable breadcrumb component for [Astro](https://astro.build/). diff --git a/demo/astro.config.mjs b/demo/astro.config.mjs index 83d83ea..5d05d88 100644 --- a/demo/astro.config.mjs +++ b/demo/astro.config.mjs @@ -1,8 +1,10 @@ -import { defineConfig } from 'astro/config'; +import { defineConfig } from "astro/config"; import mdx from "@astrojs/mdx"; // https://astro.build/config export default defineConfig({ - integrations: [mdx()] -}); \ No newline at end of file + base: "/astro-breadcrumbs", + trailingSlash: "always", + integrations: [mdx()], +}); diff --git a/demo/src/pages/en/category/astro/one.astro b/demo/src/pages/en/category/astro/one.astro new file mode 100644 index 0000000..75abb1f --- /dev/null +++ b/demo/src/pages/en/category/astro/one.astro @@ -0,0 +1,20 @@ +--- +import Layout from "../../../../layouts/Layout.astro"; +import Breadcrumbs from "../../../../../../src/Breadcrumbs.astro"; +--- + + + + + Example 7 - With trailing slash + + + + + diff --git a/demo/src/pages/index.astro b/demo/src/pages/index.astro index ec0a822..3c2c453 100644 --- a/demo/src/pages/index.astro +++ b/demo/src/pages/index.astro @@ -1,71 +1,78 @@ --- import Layout from "../layouts/Layout.astro"; import Card from "../components/Card.astro"; +import Breadcrumbs from "../../../src/Breadcrumbs.astro"; --- - - Welcome to Astro Breadcrumbs - - Check out the src/pages/en/category/astro/page.astro directory - to see the code example. - - - - - - + + Welcome to Astro Breadcrumbs + + + Check out the src/pages/en/category/astro/page.astro directory + to see the code example. + + + + + + + diff --git a/src/Breadcrumbs.astro b/src/Breadcrumbs.astro index 212a7bb..7e949ea 100644 --- a/src/Breadcrumbs.astro +++ b/src/Breadcrumbs.astro @@ -8,8 +8,6 @@ export interface BreadcrumbsProps { ariaLabel?: string; crumbs?: Array; schemaJsonScript?: boolean; - baseUrl?: string; - trailingSlash?: boolean; ellipsisAriaLabel?: string; truncated?: boolean; } @@ -26,25 +24,21 @@ const { ariaLabel = "breadcrumbs", crumbs = [], schemaJsonScript = true, - baseUrl, - trailingSlash = false, ellipsisAriaLabel = "Show hidden navigation", truncated = false, } = Astro.props as BreadcrumbsProps; -const hasBaseUrl = !!baseUrl?.length; const paths = Astro.url.pathname.split("/").filter((crumb: any) => crumb); +const hasTrailingSlash = Astro.url.pathname.endsWith("/"); const pathLength = paths?.length; const UUID = crypto.randomUUID(); -const parts = generateCrumbs( - baseUrl, +const parts = generateCrumbs({ crumbs, paths, - hasBaseUrl, - trailingSlash, indexText, -); + hasTrailingSlash, +}); --- { - console.warn( - "\x1b[43m", - "Astro Breadcrumbs", - "\x1b[0m", - "\x1b[33m", - message, - "\x1b[0m", - ); -}; - -/** - * Check if a URL is valid. - * - * @param url - */ -export const isValidUrl = (url: string | undefined): boolean => { - if (!url) return false; - if (!URL.canParse(url)) return false; - - const urlToCheck = new URL(url); - - const checkProtocol = - urlToCheck.protocol === "http:" || urlToCheck.protocol === "https:"; - - if (!checkProtocol) coloredWarnLog("Astro Breadcrumbs"); - - return checkProtocol; -}; - -/** - * Check if a URL ends with a slash. - * - * @param url - */ -export const endsWithSlash = (url: string | undefined): boolean => { - if (!url) return false; - - return url.endsWith("/"); -}; - -/** - * Get the base URL. - * If the base URL is not valid, return a relative URL. - * - * @returns string | undefined | null - * - */ -export const getBaseUrl = ( - baseUrl: BreadcrumbsProps["baseUrl"], - hasBaseUrl: boolean, -): string | undefined | null => { - if (endsWithSlash(baseUrl) && hasBaseUrl) { - coloredWarnLog( - "Base URL should not have a trailing slash. Falling back to relative URL.", - ); - - return null; - } - if (!isValidUrl(baseUrl) && hasBaseUrl) { - coloredWarnLog("Base URL is not valid. Falling back to relative URL."); - - return null; - } - - return baseUrl; -}; - -/** - * Get the first breadcrumb. - */ -export const getFirstCrumb = ( - trailingSlash: BreadcrumbsProps["trailingSlash"], - baseUrl: BreadcrumbsProps["baseUrl"], - hasBaseUrl: boolean, -): string => { - if (trailingSlash && getBaseUrl(baseUrl, hasBaseUrl)) - return `${getBaseUrl(baseUrl, hasBaseUrl)}/`; - - return getBaseUrl(baseUrl, hasBaseUrl) ?? "/"; -}; diff --git a/src/lib/generateCrumbs.ts b/src/lib/generateCrumbs.ts index c4ce713..ed679a0 100644 --- a/src/lib/generateCrumbs.ts +++ b/src/lib/generateCrumbs.ts @@ -1,66 +1,68 @@ -import { getBaseUrl, getFirstCrumb } from "./crumbUtils.ts"; import type { BreadcrumbItem, BreadcrumbsProps } from "../Breadcrumbs.astro"; -export const generateCrumbs = ( - baseUrl: string | undefined, - crumbs: BreadcrumbsProps["crumbs"], - paths: string[], - hasBaseUrl: boolean, - trailingSlash: BreadcrumbsProps["trailingSlash"], - indexText: BreadcrumbsProps["indexText"], -) => { - let parts: Array = []; +type GenerateCrumbs = { + crumbs: BreadcrumbsProps["crumbs"]; + paths: string[]; + indexText: BreadcrumbsProps["indexText"]; + hasTrailingSlash: boolean; +}; + +export const generateCrumbs = ({ + crumbs, + paths, + indexText, + hasTrailingSlash, +}: GenerateCrumbs) => { /** - * If no crumbs are passed, generate them dynamically. + * If crumbs are passed, use them. */ - if (crumbs?.length === 0) { - /** - * Array of breadcrumb items. - * The first item is the index page. - */ - parts = [ - { - text: indexText!, - href: getFirstCrumb(trailingSlash, baseUrl, hasBaseUrl), - }, - ]; + if (crumbs && crumbs?.length > 0) { + return crumbs; + } + + const parts: Array = []; + const baseUrl = import.meta.env.BASE_URL; + const hasBaseUrl = baseUrl !== "/"; - /** - * Loop through the paths and create a breadcrumb item for each. - */ - paths.forEach((text: string, index: number) => { - const generateHref = `/${paths.slice(0, index + 1).join("/")}`; - const chooseBaseUrl = getBaseUrl(baseUrl, hasBaseUrl) - ? getBaseUrl(baseUrl, hasBaseUrl) + generateHref - : generateHref; - const addTrailingSlash = trailingSlash - ? `${chooseBaseUrl}/` - : chooseBaseUrl; - const finalHref = addTrailingSlash; + /** + * Loop through the paths and create a breadcrumb item for each. + */ + paths.forEach((text: string, index: number) => { + const generateHref = `/${paths.slice(0, index + 1).join("/")}`; + const addTrailingSlash = hasTrailingSlash + ? `${generateHref}/` + : generateHref; + const finalHref = addTrailingSlash; - // strip out any file extensions - const matches = text.match(/^(.+?)(\.[a-z0-9]+)?\/?$/i); + // strip out any file extensions + const matches = text.match(/^(.+?)(\.[a-z0-9]+)?\/?$/i); - if (matches && matches[2]) { - text = matches[1]; - } + if (matches && matches[2]) { + text = matches[1]; + } - parts = [ - ...parts, - { - text: text.replace(/[-_]/g, " "), - href: finalHref, - }, - ]; + parts.push({ + text: text, + href: finalHref, }); - } + }); /** - * If crumbs are passed, use them. + * If there is NO base URL, the index item is missing. + * Add it to the start of the array. */ - if (crumbs && crumbs?.length > 0) { - parts = crumbs; + if (!hasBaseUrl) { + parts.unshift({ text: indexText!, href: baseUrl }); } + /** + * If there is a base URL, the index item is present. + * Modify the first item to use the index page text. + */ + parts[0] = { + text: indexText!, + href: parts[0]?.href, + }; + return parts; }; diff --git a/tests/unit/coloredWarnLog.test.ts b/tests/unit/coloredWarnLog.test.ts deleted file mode 100644 index 21d7461..0000000 --- a/tests/unit/coloredWarnLog.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { coloredWarnLog } from "../../src/lib/crumbUtils.ts"; -import { test, vi } from "vitest"; - -test("coloredWarnLog should log warning message", () => { - const consoleSpy = vi.spyOn(console, "warn").mockImplementation(); - - const message = "Test warning message"; - coloredWarnLog(message); - - expect(consoleSpy).toHaveBeenCalledWith( - "\x1b[43m", - "Astro Breadcrumbs", - "\x1b[0m", - "\x1b[33m", - message, - "\x1b[0m", - ); - - consoleSpy.mockRestore(); -}); diff --git a/tests/unit/endsWithSlash.test.ts b/tests/unit/endsWithSlash.test.ts deleted file mode 100644 index 40918da..0000000 --- a/tests/unit/endsWithSlash.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { endsWithSlash } from "../../src/lib/crumbUtils.ts"; -import { test } from "vitest"; - -test("endsWithSlash", () => { - // Test with a URL that ends with a slash - let result = endsWithSlash("https://www.example.com/"); - expect(result).toBe(true); - - // Test with a URL that does not end with a slash - result = endsWithSlash("https://www.example.com"); - expect(result).toBe(false); - - // Test with a non-string value - result = endsWithSlash(undefined); - expect(result).toBe(false); -}); diff --git a/tests/unit/generateCrumbs.test.ts b/tests/unit/generateCrumbs.test.ts index fdacf63..1874e09 100644 --- a/tests/unit/generateCrumbs.test.ts +++ b/tests/unit/generateCrumbs.test.ts @@ -2,57 +2,39 @@ import { generateCrumbs } from "../../src/lib/generateCrumbs.ts"; import { test } from "vitest"; test("generateCrumbs - no crumbs passed", () => { - const baseUrl = "https://example.com"; - const crumbs = []; - const paths = ["path1", "path2"]; - const hasBaseUrl = true; - const trailingSlash = true; - const indexText = "Home"; - - const result = generateCrumbs( - baseUrl, - crumbs, - paths, - hasBaseUrl, - trailingSlash, - indexText, - ); + const result = generateCrumbs({ + crumbs: [], + paths: ["path1", "path2"], + indexText: "Home", + hasTrailingSlash: true, + }); // Check if the first crumb is the index page expect(result[0]).toEqual({ text: "Home", - href: "https://example.com/", + href: "/", }); // Check if the second crumb is correct expect(result[1]).toEqual({ text: "path1", - href: "https://example.com/path1/", + href: "/path1/", }); // Check if the third crumb is correct expect(result[2]).toEqual({ text: "path2", - href: "https://example.com/path1/path2/", + href: "/path1/path2/", }); }); test("generateCrumbs - with crumbs passed", () => { - const baseUrl = "https://example.com"; - const crumbs = [{ text: "Custom Crumb", href: "/custom-crumb" }]; - const paths = ["path1", "path2"]; - const hasBaseUrl = true; - const trailingSlash = true; - const indexText = "Home"; - - const result = generateCrumbs( - baseUrl, - crumbs, - paths, - hasBaseUrl, - trailingSlash, - indexText, - ); + const result = generateCrumbs({ + crumbs: [{ text: "Custom Crumb", href: "/custom-crumb" }], + paths: ["path1", "path2"], + indexText: "Home", + hasTrailingSlash: true, + }); // Check if the first crumb is the custom crumb expect(result[0]).toEqual({ @@ -64,146 +46,83 @@ test("generateCrumbs - with crumbs passed", () => { expect(result[1]).toEqual(undefined); }); -test("generateCrumbs - no baseUrl provided", () => { - const baseUrl = undefined; - const crumbs = []; - const paths = ["path1", "path2"]; - const hasBaseUrl = true; - const trailingSlash = true; - const indexText = "Home"; - - const result = generateCrumbs( - baseUrl, - crumbs, - paths, - hasBaseUrl, - trailingSlash, - indexText, - ); - - // Check if the first crumb is the index page - expect(result[0]).toEqual({ - text: "Home", - href: "/", - }); - - // Check if the second crumb is correct - expect(result[1]).toEqual({ - text: "path1", - href: "/path1/", - }); - - // Check if the third crumb is correct - expect(result[2]).toEqual({ - text: "path2", - href: "/path1/path2/", - }); -}); - test("generateCrumbs - no trailing slash", () => { - const baseUrl = "https://example.com"; - const crumbs = []; - const paths = ["path1", "path2"]; - const hasBaseUrl = true; - const trailingSlash = false; - const indexText = "Home"; - - const result = generateCrumbs( - baseUrl, - crumbs, - paths, - hasBaseUrl, - trailingSlash, - indexText, - ); + const result = generateCrumbs({ + crumbs: [], + paths: ["path1", "path2"], + indexText: "Home", + hasTrailingSlash: false, + }); // Check if the first crumb is the index page expect(result[0]).toEqual({ text: "Home", - href: "https://example.com", + href: "/", }); // Check if the second crumb is correct expect(result[1]).toEqual({ text: "path1", - href: "https://example.com/path1", + href: "/path1", }); // Check if the third crumb is correct expect(result[2]).toEqual({ text: "path2", - href: "https://example.com/path1/path2", + href: "/path1/path2", }); }); test("generateCrumbs - paths with file extensions", () => { - const baseUrl = "https://example.com"; - const crumbs = []; - const paths = ["path1.html", "path2.js"]; - const hasBaseUrl = true; - const trailingSlash = true; - const indexText = "Home"; - - const result = generateCrumbs( - baseUrl, - crumbs, - paths, - hasBaseUrl, - trailingSlash, - indexText, - ); + const result = generateCrumbs({ + crumbs: [], + paths: ["path1.html", "path2.js"], + indexText: "Home", + hasTrailingSlash: true, + }); // Check if the first crumb is the index page expect(result[0]).toEqual({ text: "Home", - href: "https://example.com/", + href: "/", }); // Check if the second crumb is correct expect(result[1]).toEqual({ text: "path1", - href: "https://example.com/path1.html/", + href: "/path1.html/", }); // Check if the third crumb is correct expect(result[2]).toEqual({ text: "path2", - href: "https://example.com/path1.html/path2.js/", + href: "/path1.html/path2.js/", }); }); test("generateCrumbs - paths with hyphens and underscores", () => { - const baseUrl = "https://example.com"; - const crumbs = []; - const paths = ["path-1", "path_2"]; - const hasBaseUrl = true; - const trailingSlash = true; - const indexText = "Home"; - - const result = generateCrumbs( - baseUrl, - crumbs, - paths, - hasBaseUrl, - trailingSlash, - indexText, - ); + const result = generateCrumbs({ + crumbs: [], + paths: ["path-1", "path_2"], + indexText: "Home", + hasTrailingSlash: true, + }); // Check if the first crumb is the index page expect(result[0]).toEqual({ text: "Home", - href: "https://example.com/", + href: "/", }); // Check if the second crumb is correct expect(result[1]).toEqual({ - text: "path 1", - href: "https://example.com/path-1/", + text: "path-1", + href: "/path-1/", }); // Check if the third crumb is correct expect(result[2]).toEqual({ - text: "path 2", - href: "https://example.com/path-1/path_2/", + text: "path_2", + href: "/path-1/path_2/", }); }); diff --git a/tests/unit/getBaseUrl.test.ts b/tests/unit/getBaseUrl.test.ts deleted file mode 100644 index 5abecdb..0000000 --- a/tests/unit/getBaseUrl.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { getBaseUrl } from "../../src/lib/crumbUtils.ts"; -import { test } from "vitest"; - -test("getBaseUrl", () => { - // Test with a base URL that ends with a slash - let result = getBaseUrl("https://www.example.com/", 21); - expect(result).toBe(null); - - // Test with a base URL that is not valid - result = getBaseUrl("ftp://www.example.com", 21); - expect(result).toBe(null); - - // Test with a valid base URL - result = getBaseUrl("https://www.example.com", 21); - expect(result).toBe("https://www.example.com"); -}); diff --git a/tests/unit/getFirstCrumb.test.ts b/tests/unit/getFirstCrumb.test.ts deleted file mode 100644 index 40d9716..0000000 --- a/tests/unit/getFirstCrumb.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { getFirstCrumb } from "../../src/lib/crumbUtils.ts"; -import { test } from "vitest"; - -test("getFirstCrumb", () => { - // Test with trailingSlash true and valid baseUrl - let result = getFirstCrumb(true, "https://www.example.com", 1); - expect(result).toBe("https://www.example.com/"); - - // Test with trailingSlash false and valid baseUrl - result = getFirstCrumb(false, "https://www.example.com", 1); - expect(result).toBe("https://www.example.com"); - - // Test with invalid baseUrl - result = getFirstCrumb(true, "ftp://www.example.com", 1); - expect(result).toBe("/"); -}); diff --git a/tests/unit/isValidUrl.test.ts b/tests/unit/isValidUrl.test.ts deleted file mode 100644 index 934a79b..0000000 --- a/tests/unit/isValidUrl.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { isValidUrl } from "../../src/lib/crumbUtils.ts"; -import { test } from "vitest"; - -test("isValidUrl", () => { - // Test with a valid URL - let result = isValidUrl("https://www.example.com"); - expect(result).toBe(true); - - // Test with an invalid URL - result = isValidUrl("ftp://www.example.com"); - expect(result).toBe(false); - - // Test with a non-string value - result = isValidUrl(undefined); - expect(result).toBe(false); - - // Test with a URL without a protocol - result = isValidUrl("www.example.com"); - expect(result).toBe(false); -});
- Check out the src/pages/en/category/astro/page.astro directory - to see the code example. -
src/pages/en/category/astro/page.astro
+ Check out the src/pages/en/category/astro/page.astro directory + to see the code example. +