nuxt-seo-utils 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.mts +103 -0
- package/dist/module.d.ts +103 -0
- package/dist/module.json +13 -0
- package/dist/module.mjs +531 -0
- package/dist/runtime/nitro/middleware/redirectNitroOrigin.d.ts +2 -0
- package/dist/runtime/nitro/middleware/redirectNitroOrigin.js +18 -0
- package/dist/runtime/nitro/middleware/resolveImagesInPagesDir.dev.d.ts +2 -0
- package/dist/runtime/nitro/middleware/resolveImagesInPagesDir.dev.js +17 -0
- package/dist/runtime/nitro/tsconfig.json +3 -0
- package/dist/runtime/nuxt/composables/polyfills.d.ts +11 -0
- package/dist/runtime/nuxt/composables/polyfills.js +21 -0
- package/dist/runtime/nuxt/composables/useBreadcrumbItems.d.ts +95 -0
- package/dist/runtime/nuxt/composables/useBreadcrumbItems.js +98 -0
- package/dist/runtime/nuxt/logic/applyDefaults.d.ts +4 -0
- package/dist/runtime/nuxt/logic/applyDefaults.js +48 -0
- package/dist/runtime/nuxt/plugins/0.routeRules.server.d.ts +2 -0
- package/dist/runtime/nuxt/plugins/0.routeRules.server.js +21 -0
- package/dist/runtime/nuxt/plugins/1.absoluteImageUrls.server.d.ts +2 -0
- package/dist/runtime/nuxt/plugins/1.absoluteImageUrls.server.js +31 -0
- package/dist/runtime/nuxt/plugins/defaults.d.ts +2 -0
- package/dist/runtime/nuxt/plugins/defaults.js +20 -0
- package/dist/runtime/nuxt/plugins/defaultsWaitI18n.d.ts +2 -0
- package/dist/runtime/nuxt/plugins/defaultsWaitI18n.js +23 -0
- package/dist/runtime/nuxt/plugins/inferSeoMetaPlugin.d.ts +2 -0
- package/dist/runtime/nuxt/plugins/inferSeoMetaPlugin.js +9 -0
- package/dist/runtime/nuxt/plugins/siteConfig.d.ts +2 -0
- package/dist/runtime/nuxt/plugins/siteConfig.js +30 -0
- package/dist/runtime/nuxt/plugins/titles.d.ts +2 -0
- package/dist/runtime/nuxt/plugins/titles.js +29 -0
- package/dist/runtime/pure/breadcrumbs.d.ts +1 -0
- package/dist/runtime/pure/breadcrumbs.js +18 -0
- package/dist/runtime/types.d.ts +1 -0
- package/dist/runtime/types.js +0 -0
- package/dist/types.d.mts +1 -0
- package/dist/types.d.ts +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { NuxtLinkProps } from 'nuxt/app';
|
|
2
|
+
import type { MaybeRefOrGetter } from 'vue';
|
|
3
|
+
interface NuxtUIBreadcrumbItem extends NuxtLinkProps {
|
|
4
|
+
label: string;
|
|
5
|
+
labelClass?: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
iconClass?: string;
|
|
8
|
+
as?: string;
|
|
9
|
+
type?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
active?: boolean;
|
|
12
|
+
exact?: boolean;
|
|
13
|
+
exactQuery?: boolean;
|
|
14
|
+
exactMatch?: boolean;
|
|
15
|
+
inactiveClass?: string;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
export interface BreadcrumbProps {
|
|
19
|
+
/**
|
|
20
|
+
* Generate the breadcrumbs based on a different path than the current route.
|
|
21
|
+
*/
|
|
22
|
+
path?: MaybeRefOrGetter<string>;
|
|
23
|
+
/**
|
|
24
|
+
* The id of the breadcrumb list. It's recommended to provide a unique
|
|
25
|
+
* id when adding multiple breadcrumb lists to the same page.
|
|
26
|
+
*/
|
|
27
|
+
id?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Append additional breadcrumb items to the end of the list. This is applied
|
|
30
|
+
* after the `overrides` option.
|
|
31
|
+
*/
|
|
32
|
+
append?: BreadcrumbItemProps[];
|
|
33
|
+
/**
|
|
34
|
+
* Prepend additional breadcrumb items to the start of the list. This is applied
|
|
35
|
+
* after the `overrides` option.
|
|
36
|
+
*/
|
|
37
|
+
prepend?: BreadcrumbItemProps[];
|
|
38
|
+
/**
|
|
39
|
+
* Override any of the breadcrumb items based on the index.
|
|
40
|
+
*/
|
|
41
|
+
overrides?: (BreadcrumbItemProps | false | undefined)[];
|
|
42
|
+
/**
|
|
43
|
+
* Should the schema.org breadcrumb be generated.
|
|
44
|
+
* @default true
|
|
45
|
+
*/
|
|
46
|
+
schemaOrg?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* The Aria Label for the breadcrumbs.
|
|
49
|
+
* You shouldn't need to change this.
|
|
50
|
+
*
|
|
51
|
+
* @default 'Breadcrumbs'
|
|
52
|
+
*/
|
|
53
|
+
ariaLabel?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Should the current breadcrumb item be shown.
|
|
56
|
+
*
|
|
57
|
+
* @default false
|
|
58
|
+
*/
|
|
59
|
+
hideCurrent?: MaybeRefOrGetter<boolean>;
|
|
60
|
+
/**
|
|
61
|
+
* Should the root breadcrumb be shown.
|
|
62
|
+
*/
|
|
63
|
+
hideRoot?: MaybeRefOrGetter<boolean>;
|
|
64
|
+
}
|
|
65
|
+
export interface BreadcrumbItemProps extends NuxtUIBreadcrumbItem {
|
|
66
|
+
/** Whether the breadcrumb item represents the aria-current. */
|
|
67
|
+
current?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The type of current location the breadcrumb item represents, if `isCurrent` is true.
|
|
70
|
+
* @default 'page'
|
|
71
|
+
*/
|
|
72
|
+
ariaCurrent?: 'page' | 'step' | 'location' | 'date' | 'time' | boolean | 'true' | 'false';
|
|
73
|
+
to?: string;
|
|
74
|
+
ariaLabel?: string;
|
|
75
|
+
separator?: boolean | string;
|
|
76
|
+
class?: (string | string[] | undefined)[] | string;
|
|
77
|
+
/**
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
_props?: {
|
|
81
|
+
first: boolean;
|
|
82
|
+
last: boolean;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Generate an automatic breadcrumb list that helps users to navigate between pages.
|
|
87
|
+
*
|
|
88
|
+
* - Integrates with [Nuxt Schema.org](/schema-org/getting-started/installation) to generate [BreadcrumbList](https://schema.org/BreadcrumbList) structured data.
|
|
89
|
+
* - Integrates with [Nuxt I18n](https://i18n.nuxtjs.org/) to generate localized breadcrumbs.
|
|
90
|
+
*
|
|
91
|
+
* @see https://github.com/harlan-zw/nuxt-seo/blob/main/src/runtime/nuxt/composables/useBreadcrumbItems.ts
|
|
92
|
+
* @docs https://nuxtseo.com/nuxt-seo/api/breadcrumbs
|
|
93
|
+
*/
|
|
94
|
+
export declare function useBreadcrumbItems(options?: BreadcrumbProps): import("vue").ComputedRef<BreadcrumbItemProps[]>;
|
|
95
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
createSitePathResolver,
|
|
4
|
+
defineBreadcrumb,
|
|
5
|
+
toValue,
|
|
6
|
+
useI18n,
|
|
7
|
+
useRouter,
|
|
8
|
+
useSchemaOrg,
|
|
9
|
+
useSiteConfig
|
|
10
|
+
} from "#imports";
|
|
11
|
+
import { defu } from "defu";
|
|
12
|
+
import { fixSlashes } from "site-config-stack/urls";
|
|
13
|
+
import { withoutTrailingSlash } from "ufo";
|
|
14
|
+
import { pathBreadcrumbSegments } from "../../pure/breadcrumbs.js";
|
|
15
|
+
function withoutQuery(path) {
|
|
16
|
+
return path.split("?")[0];
|
|
17
|
+
}
|
|
18
|
+
function titleCase(s) {
|
|
19
|
+
return s.replaceAll("-", " ").replace(/\w\S*/g, (w) => w.charAt(0).toUpperCase() + w.substr(1).toLowerCase());
|
|
20
|
+
}
|
|
21
|
+
export function useBreadcrumbItems(options = {}) {
|
|
22
|
+
const router = useRouter();
|
|
23
|
+
const i18n = useI18n();
|
|
24
|
+
const siteResolver = createSitePathResolver({
|
|
25
|
+
canonical: true,
|
|
26
|
+
absolute: true
|
|
27
|
+
});
|
|
28
|
+
const siteConfig = useSiteConfig();
|
|
29
|
+
const items = computed(() => {
|
|
30
|
+
let rootNode = "/";
|
|
31
|
+
if (i18n) {
|
|
32
|
+
if (i18n.strategy === "prefix" || i18n.strategy !== "no_prefix" && toValue(i18n.defaultLocale) !== toValue(i18n.locale))
|
|
33
|
+
rootNode = `/${toValue(i18n.locale)}`;
|
|
34
|
+
}
|
|
35
|
+
const current = withoutQuery(withoutTrailingSlash(toValue(options.path || router.currentRoute.value?.path) || rootNode));
|
|
36
|
+
const overrides = options.overrides || [];
|
|
37
|
+
const segments = pathBreadcrumbSegments(current, rootNode).map((path, index) => {
|
|
38
|
+
let item = {
|
|
39
|
+
to: path
|
|
40
|
+
};
|
|
41
|
+
if (typeof overrides[index] !== "undefined") {
|
|
42
|
+
if (overrides[index] === false)
|
|
43
|
+
return false;
|
|
44
|
+
item = defu(overrides[index], item);
|
|
45
|
+
}
|
|
46
|
+
return item;
|
|
47
|
+
});
|
|
48
|
+
if (options.prepend)
|
|
49
|
+
segments.unshift(...options.prepend);
|
|
50
|
+
if (options.append)
|
|
51
|
+
segments.push(...options.append);
|
|
52
|
+
return segments.filter(Boolean).map((item) => {
|
|
53
|
+
const route = router.resolve(item.to)?.matched?.[0] || router.currentRoute.value;
|
|
54
|
+
const routeMeta = route?.meta || {};
|
|
55
|
+
const routeName = route ? String(route.name || route.path) : item.to === "/" ? "index" : "unknown";
|
|
56
|
+
let [name] = routeName.split("___");
|
|
57
|
+
if (name === "unknown" || name.includes("/"))
|
|
58
|
+
name = (item.to || "").split("/").pop() || "";
|
|
59
|
+
if (routeMeta.breadcrumb) {
|
|
60
|
+
item = {
|
|
61
|
+
...item,
|
|
62
|
+
...routeMeta.breadcrumb
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
item.label = item.label || routeMeta.breadcrumbTitle || routeMeta.title;
|
|
66
|
+
if (typeof item.label === "undefined") {
|
|
67
|
+
item.label = i18n.t(`breadcrumb.items.${name}.label`, name === "index" ? "Home" : titleCase(name), { missingWarn: false });
|
|
68
|
+
}
|
|
69
|
+
if (typeof item.ariaLabel === "undefined") {
|
|
70
|
+
item.ariaLabel = i18n.t(`breadcrumb.items.${name}.ariaLabel`, item.label, { missingWarn: false }) || item.label;
|
|
71
|
+
}
|
|
72
|
+
item.current = item.current || item.to === current;
|
|
73
|
+
if (toValue(options.hideCurrent) && item.current)
|
|
74
|
+
return false;
|
|
75
|
+
return item;
|
|
76
|
+
}).map((m) => {
|
|
77
|
+
if (m && m.to) {
|
|
78
|
+
m.to = fixSlashes(siteConfig.trailingSlash, m.to);
|
|
79
|
+
if (m.to === rootNode && toValue(options.hideRoot))
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return m;
|
|
83
|
+
}).filter(Boolean);
|
|
84
|
+
});
|
|
85
|
+
const schemaOrgEnabled = typeof options.schemaOrg === "undefined" ? true : options.schemaOrg;
|
|
86
|
+
if ((import.meta.dev || import.meta.server) && schemaOrgEnabled) {
|
|
87
|
+
useSchemaOrg([
|
|
88
|
+
defineBreadcrumb({
|
|
89
|
+
id: `#${options.id || "breadcrumb"}`,
|
|
90
|
+
itemListElement: computed(() => items.value.map((item) => ({
|
|
91
|
+
name: item.label || item.ariaLabel,
|
|
92
|
+
item: item.to ? siteResolver(item.to) : void 0
|
|
93
|
+
})))
|
|
94
|
+
})
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
return items;
|
|
98
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
createSitePathResolver,
|
|
4
|
+
useHead,
|
|
5
|
+
useRoute,
|
|
6
|
+
useRuntimeConfig,
|
|
7
|
+
useSeoMeta,
|
|
8
|
+
useSiteConfig
|
|
9
|
+
} from "#imports";
|
|
10
|
+
import { stringifyQuery } from "ufo";
|
|
11
|
+
export function applyDefaults(i18n) {
|
|
12
|
+
const { canonicalQueryWhitelist } = useRuntimeConfig().public["nuxt-seo"];
|
|
13
|
+
const siteConfig = useSiteConfig();
|
|
14
|
+
const route = useRoute();
|
|
15
|
+
const resolveUrl = createSitePathResolver({ withBase: true, absolute: true });
|
|
16
|
+
const canonicalUrl = computed(() => {
|
|
17
|
+
const { query } = route;
|
|
18
|
+
const url = resolveUrl(route.path || "/").value || route.path;
|
|
19
|
+
const filteredQuery = Object.fromEntries(
|
|
20
|
+
Object.entries(query).filter(([key]) => canonicalQueryWhitelist.includes(key))
|
|
21
|
+
);
|
|
22
|
+
return Object.keys(filteredQuery).length ? `${url}?${stringifyQuery(filteredQuery)}` : url;
|
|
23
|
+
});
|
|
24
|
+
const minimalPriority = {
|
|
25
|
+
// give nuxt.config values higher priority
|
|
26
|
+
tagPriority: 101
|
|
27
|
+
};
|
|
28
|
+
useHead({
|
|
29
|
+
htmlAttrs: { lang: i18n.locale },
|
|
30
|
+
templateParams: { site: siteConfig, siteName: siteConfig.name || "" },
|
|
31
|
+
titleTemplate: "%s %separator %siteName",
|
|
32
|
+
link: [{ rel: "canonical", href: () => canonicalUrl.value }]
|
|
33
|
+
}, minimalPriority);
|
|
34
|
+
const seoMeta = {
|
|
35
|
+
ogType: "website",
|
|
36
|
+
ogUrl: () => canonicalUrl.value,
|
|
37
|
+
ogLocale: () => i18n.locale.value,
|
|
38
|
+
ogSiteName: siteConfig.name
|
|
39
|
+
};
|
|
40
|
+
if (siteConfig.description)
|
|
41
|
+
seoMeta.description = siteConfig.description;
|
|
42
|
+
if (siteConfig.twitter) {
|
|
43
|
+
const id = siteConfig.twitter.startsWith("@") ? siteConfig.twitter : `@${siteConfig.twitter}`;
|
|
44
|
+
seoMeta.twitterCreator = id;
|
|
45
|
+
seoMeta.twitterSite = id;
|
|
46
|
+
}
|
|
47
|
+
useSeoMeta(seoMeta, minimalPriority);
|
|
48
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useRequestEvent } from "#imports";
|
|
2
|
+
import { unpackMeta } from "@unhead/shared";
|
|
3
|
+
import { injectHead } from "@unhead/vue";
|
|
4
|
+
import { defineNuxtPlugin } from "nuxt/app";
|
|
5
|
+
export default defineNuxtPlugin({
|
|
6
|
+
enforce: "post",
|
|
7
|
+
async setup() {
|
|
8
|
+
const head = injectHead();
|
|
9
|
+
if (!head)
|
|
10
|
+
return;
|
|
11
|
+
const event = useRequestEvent();
|
|
12
|
+
if (event.context._nitro.routeRules.head)
|
|
13
|
+
head.push(event.context._nitro.routeRules.head, { mode: "server" });
|
|
14
|
+
if (event.context._nitro.routeRules.seoMeta) {
|
|
15
|
+
const meta = unpackMeta({ ...event.context._nitro.routeRules.seoMeta });
|
|
16
|
+
head.push({
|
|
17
|
+
meta
|
|
18
|
+
}, { mode: "server" });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createSitePathResolver, unref } from "#imports";
|
|
2
|
+
import { injectHead } from "@unhead/vue";
|
|
3
|
+
import { defineNuxtPlugin } from "nuxt/app";
|
|
4
|
+
export default defineNuxtPlugin({
|
|
5
|
+
enforce: "post",
|
|
6
|
+
setup() {
|
|
7
|
+
const head = injectHead();
|
|
8
|
+
if (!head)
|
|
9
|
+
return;
|
|
10
|
+
const resolver = createSitePathResolver({
|
|
11
|
+
withBase: true,
|
|
12
|
+
absolute: true,
|
|
13
|
+
canonical: true
|
|
14
|
+
});
|
|
15
|
+
head.use({
|
|
16
|
+
hooks: {
|
|
17
|
+
"tags:resolve": async ({ tags }) => {
|
|
18
|
+
for (const tag of tags) {
|
|
19
|
+
if (tag.tag !== "meta")
|
|
20
|
+
continue;
|
|
21
|
+
if (tag.props.property !== "og:image:url" && tag.props.property !== "og:image" && tag.props.name !== "twitter:image")
|
|
22
|
+
continue;
|
|
23
|
+
if (typeof tag.props.content !== "string" || !tag.props.content.trim() || tag.props.content.startsWith("http") || tag.props.content.startsWith("//"))
|
|
24
|
+
continue;
|
|
25
|
+
tag.props.content = unref(resolver(tag.props.content));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineNuxtPlugin,
|
|
3
|
+
ref,
|
|
4
|
+
useSiteConfig
|
|
5
|
+
} from "#imports";
|
|
6
|
+
import { applyDefaults } from "../logic/applyDefaults.js";
|
|
7
|
+
export default defineNuxtPlugin({
|
|
8
|
+
name: "nuxt-seo:defaults",
|
|
9
|
+
order: 999,
|
|
10
|
+
env: {
|
|
11
|
+
islands: false
|
|
12
|
+
},
|
|
13
|
+
setup() {
|
|
14
|
+
const siteConfig = useSiteConfig();
|
|
15
|
+
const locale = ref(siteConfig.currentLocale || siteConfig.defaultLocale);
|
|
16
|
+
applyDefaults({
|
|
17
|
+
locale
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineNuxtPlugin, ref, useSiteConfig } from "#imports";
|
|
2
|
+
import { applyDefaults } from "../logic/applyDefaults.js";
|
|
3
|
+
export default defineNuxtPlugin({
|
|
4
|
+
name: "nuxt-seo:defaults",
|
|
5
|
+
env: {
|
|
6
|
+
islands: false
|
|
7
|
+
},
|
|
8
|
+
// we need to wait for the i18n plugin to run first
|
|
9
|
+
// @ts-expect-error dynamic
|
|
10
|
+
dependsOn: import.meta.server ? [
|
|
11
|
+
"nuxt-site-config:i18n"
|
|
12
|
+
] : [
|
|
13
|
+
"i18n:plugin"
|
|
14
|
+
],
|
|
15
|
+
setup(nuxtApp) {
|
|
16
|
+
const siteConfig = useSiteConfig();
|
|
17
|
+
const locale = ref(nuxtApp.$i18n?.locale?.value || siteConfig.currentLocale || siteConfig.defaultLocale);
|
|
18
|
+
nuxtApp.hook("i18n:localeSwitched", ({ newLocale }) => {
|
|
19
|
+
locale.value = newLocale;
|
|
20
|
+
});
|
|
21
|
+
applyDefaults({ locale });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { InferSeoMetaPlugin } from "@unhead/addons";
|
|
2
|
+
import { injectHead } from "@unhead/vue";
|
|
3
|
+
import { defineNuxtPlugin } from "nuxt/app";
|
|
4
|
+
export default defineNuxtPlugin(() => {
|
|
5
|
+
const head = injectHead();
|
|
6
|
+
if (!head)
|
|
7
|
+
return;
|
|
8
|
+
head.use(InferSeoMetaPlugin());
|
|
9
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useSiteConfig } from "#imports";
|
|
2
|
+
import { injectHead } from "@unhead/vue";
|
|
3
|
+
import { defineNuxtPlugin } from "nuxt/app";
|
|
4
|
+
export default defineNuxtPlugin(() => {
|
|
5
|
+
const head = injectHead();
|
|
6
|
+
if (!head)
|
|
7
|
+
return;
|
|
8
|
+
const siteConfig = useSiteConfig();
|
|
9
|
+
const input = {
|
|
10
|
+
meta: [],
|
|
11
|
+
templateParams: {
|
|
12
|
+
site: siteConfig,
|
|
13
|
+
// support legacy
|
|
14
|
+
siteUrl: siteConfig.url,
|
|
15
|
+
siteName: siteConfig.name
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
if (siteConfig.separator)
|
|
19
|
+
input.templateParams.separator = siteConfig.separator;
|
|
20
|
+
if (siteConfig.description) {
|
|
21
|
+
input.templateParams.siteDescription = siteConfig.description;
|
|
22
|
+
input.meta.push(
|
|
23
|
+
{
|
|
24
|
+
name: "description",
|
|
25
|
+
content: "%site.description"
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
head.push(input, { tagPriority: 150 });
|
|
30
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
defineNuxtPlugin,
|
|
4
|
+
useHead,
|
|
5
|
+
useRoute
|
|
6
|
+
} from "#imports";
|
|
7
|
+
import { titleCase } from "scule";
|
|
8
|
+
import { withoutTrailingSlash } from "ufo";
|
|
9
|
+
export default defineNuxtPlugin({
|
|
10
|
+
name: "nuxt-seo:fallback-titles",
|
|
11
|
+
env: {
|
|
12
|
+
islands: false
|
|
13
|
+
},
|
|
14
|
+
setup() {
|
|
15
|
+
const route = useRoute();
|
|
16
|
+
const title = computed(() => {
|
|
17
|
+
if (typeof route.meta?.title === "string")
|
|
18
|
+
return route.meta?.title;
|
|
19
|
+
const path = withoutTrailingSlash(route.path || "/");
|
|
20
|
+
const lastSegment = path.split("/").pop();
|
|
21
|
+
return lastSegment ? titleCase(lastSegment) : null;
|
|
22
|
+
});
|
|
23
|
+
const minimalPriority = {
|
|
24
|
+
// give nuxt.config values higher priority
|
|
25
|
+
tagPriority: 101
|
|
26
|
+
};
|
|
27
|
+
useHead({ title: () => title.value }, minimalPriority);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function pathBreadcrumbSegments(path: string, rootNode?: string): string[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { hasTrailingSlash, parseURL, stringifyParsedURL, withTrailingSlash } from "ufo";
|
|
2
|
+
export function pathBreadcrumbSegments(path, rootNode = "/") {
|
|
3
|
+
const startNode = parseURL(path);
|
|
4
|
+
const appendsTrailingSlash = hasTrailingSlash(startNode.pathname);
|
|
5
|
+
const stepNode = (node, nodes = []) => {
|
|
6
|
+
const fullPath = stringifyParsedURL(node);
|
|
7
|
+
const currentPathName = node.pathname || "/";
|
|
8
|
+
nodes.push(fullPath || "/");
|
|
9
|
+
if (currentPathName !== rootNode && currentPathName !== "/") {
|
|
10
|
+
node.pathname = currentPathName.substring(0, currentPathName.lastIndexOf("/"));
|
|
11
|
+
if (appendsTrailingSlash)
|
|
12
|
+
node.pathname = withTrailingSlash(node.pathname.substring(0, node.pathname.lastIndexOf("/")));
|
|
13
|
+
stepNode(node, nodes);
|
|
14
|
+
}
|
|
15
|
+
return nodes;
|
|
16
|
+
};
|
|
17
|
+
return stepNode(startNode).reverse();
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { Head, UseSeoMetaInput } from '@unhead/schema';
|
|
File without changes
|
package/dist/types.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type ModuleOptions, default } from './module.js'
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type ModuleOptions, default } from './module'
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nuxt-seo-utils",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "5.0.0",
|
|
5
|
+
"description": "SEO utilities to improve your Nuxt sites discoverability and shareability",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"funding": "https://github.com/sponsors/harlan-zw",
|
|
8
|
+
"homepage": "https://nuxtseo.com/docs/utils",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/harlan-zw/nuxt-seo-utils.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/harlan-zw/nuxt-seo-utils/issues"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/types.d.ts",
|
|
19
|
+
"import": "./dist/module.mjs",
|
|
20
|
+
"require": "./dist/module.cjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/module.cjs",
|
|
24
|
+
"types": "./dist/types.d.ts",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"build": {
|
|
29
|
+
"externals": [
|
|
30
|
+
"@unhead/shared",
|
|
31
|
+
"unhead"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@nuxt/kit": "^3.13.2",
|
|
36
|
+
"@unhead/addons": "^1.11.7",
|
|
37
|
+
"defu": "^6.1.4",
|
|
38
|
+
"escape-string-regexp": "^5.0.0",
|
|
39
|
+
"fast-glob": "^3.3.2",
|
|
40
|
+
"image-size": "^1.1.1",
|
|
41
|
+
"nuxt-site-config": "^2.2.18",
|
|
42
|
+
"nuxt-site-config-kit": "^2.2.18",
|
|
43
|
+
"pathe": "^1.1.2",
|
|
44
|
+
"scule": "^1.3.0",
|
|
45
|
+
"ufo": "^1.5.4"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@antfu/eslint-config": "3.7.3",
|
|
49
|
+
"@nuxt/module-builder": "^0.8.4",
|
|
50
|
+
"@nuxt/test-utils": "^3.14.2",
|
|
51
|
+
"@nuxt/ui": "^2.18.6",
|
|
52
|
+
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
|
53
|
+
"@nuxtjs/i18n": "^8.5.5",
|
|
54
|
+
"@unhead/shared": "^1.11.7",
|
|
55
|
+
"@vue/test-utils": "^2.4.6",
|
|
56
|
+
"bumpp": "^9.6.1",
|
|
57
|
+
"case-police": "^0.7.0",
|
|
58
|
+
"cheerio": "1.0.0",
|
|
59
|
+
"eslint": "^9.11.1",
|
|
60
|
+
"happy-dom": "^15.7.4",
|
|
61
|
+
"markdownlint-cli": "^0.42.0",
|
|
62
|
+
"nuxt": "^3.13.2",
|
|
63
|
+
"nuxt-content-twoslash": "^0.1.1",
|
|
64
|
+
"playwright-core": "^1.47.2",
|
|
65
|
+
"sass": "^1.79.4",
|
|
66
|
+
"typescript": "5.6.2",
|
|
67
|
+
"vitest": "^2.1.2"
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"lint": "eslint . --fix",
|
|
71
|
+
"stub": "nuxt-build-module build --stub && nuxt-module-build prepare",
|
|
72
|
+
"build": "pnpm stub && nuxt-module-build build",
|
|
73
|
+
"dev": "nuxi dev playground",
|
|
74
|
+
"dev:build": "nuxi build playground",
|
|
75
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
76
|
+
"release": "pnpm build && bumpp -x \"npx changelogen --output=CHANGELOG.log\" && pnpm publish",
|
|
77
|
+
"lint:docs": "markdownlint ./docs/content && case-police 'docs/content/**/*.md' *.md",
|
|
78
|
+
"lint:docs:fix": "markdownlint ./docs/content --fix && case-police 'docs/content/**/*.md' *.md --fix",
|
|
79
|
+
"twoslash:verify": "nuxt-content-twoslash verify --content-dir docs/content",
|
|
80
|
+
"test": "pnpm dev:prepare && vitest --run --exclude **/__runtime__ && pnpm test:runtime",
|
|
81
|
+
"test:runtime": "cd test/fixtures/basic && vitest --run"
|
|
82
|
+
}
|
|
83
|
+
}
|