@autobusal/common 1.5.1 → 1.7.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/CHANGELOG.md +20 -0
- package/JsonLd.tsx +18 -0
- package/Meta.tsx +21 -2
- package/index.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
All notable changes to `@autobusal/common` are documented here. This project follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [1.7.0] - 2026-07-30
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **New `JsonLd` component** - renders a schema.org `<script type="application/ld+json">` block from a plain object, with `<` escaped to `\u003c` so a string value containing "</script>" can't break out of the tag. No hoisting needed (unlike Meta's title/meta/link) - Google explicitly supports structured data anywhere in the document, so this renders in place. First consumers: `@autobusal/providers`' `Preload.tsx` (sitewide Organization + WebSite) and `route-view`/magus's `BlogView.tsx` (BusTrip/BreadcrumbList, Article).
|
|
11
|
+
|
|
12
|
+
## [1.6.0] - 2026-07-30
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`Meta` now emits a canonical `<link>` and `og:url` on every page by
|
|
17
|
+
default**, even when the caller passes no `url` prop - previously `url`
|
|
18
|
+
was never passed at any of this component's ~136 call sites across both
|
|
19
|
+
apps, so no page anywhere had a canonical tag. Defaults to the current
|
|
20
|
+
path (`location.pathname`, via `react-router-dom`'s `useLocation`) with
|
|
21
|
+
the origin prepended - query strings are naturally excluded, so a search
|
|
22
|
+
results page with `?sfrom=X&sto=Y` or a paginated `?page=N` correctly
|
|
23
|
+
canonicalizes to its bare path instead of creating unbounded near-duplicate
|
|
24
|
+
URLs. `url` can still be passed explicitly to override.
|
|
25
|
+
|
|
6
26
|
## [1.5.1] - 2026-07-30
|
|
7
27
|
|
|
8
28
|
### Fixed
|
package/JsonLd.tsx
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
data: object
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
// Structured data (schema.org via JSON-LD) is explicitly fine anywhere in the
|
|
6
|
+
// document per Google's own guidance - no need to hoist into <head> the way
|
|
7
|
+
// Meta.tsx's title/meta/link tags are. Escaping `<` prevents any string value
|
|
8
|
+
// (an article title, a company name) that happens to contain "</script>"
|
|
9
|
+
// from breaking out of the script tag - `<` is a valid JSON escape that
|
|
10
|
+
// every JSON-LD parser (including Google's) correctly reads back as `<`.
|
|
11
|
+
const JsonLd = ({ data }: Props): JSX.Element => (
|
|
12
|
+
<script
|
|
13
|
+
type="application/ld+json"
|
|
14
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(data).replace(/</g, '\\u003c') }}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export default JsonLd;
|
package/Meta.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
|
+
import { useLocation } from 'react-router-dom';
|
|
2
3
|
|
|
3
4
|
interface Props {
|
|
4
5
|
title: string
|
|
@@ -52,8 +53,26 @@ const useSoleTitleOwnership = (fullTitle: string): void => {
|
|
|
52
53
|
});
|
|
53
54
|
};
|
|
54
55
|
|
|
56
|
+
// `url` was never passed at any of this component's ~136 call sites across
|
|
57
|
+
// both apps - every page shipped with no canonical tag at all, which is a
|
|
58
|
+
// real problem given the 9-segment /bus-lines search URLs and ?sfrom=/?sto=/
|
|
59
|
+
// ?page= parameters create effectively unbounded near-duplicate-content URL
|
|
60
|
+
// space. Rather than thread an explicit `url` through every single call site
|
|
61
|
+
// (easy to miss, easy to get subtly wrong per-page), default it to the
|
|
62
|
+
// current path - React Router's `location.pathname` naturally excludes the
|
|
63
|
+
// query string, so a search results page with ?sfrom=X&sto=Y canonicalizes
|
|
64
|
+
// to its bare /bus-lines/{from}/{to}/... path, and a static page's own path
|
|
65
|
+
// is already the correct canonical. Callers can still pass `url` explicitly
|
|
66
|
+
// to override (e.g. to canonicalize a paginated URL back to page 1).
|
|
67
|
+
const useCanonicalUrl = (explicit?: string): string => {
|
|
68
|
+
const location = useLocation();
|
|
69
|
+
|
|
70
|
+
return explicit ?? `${ window.location.origin }${ location.pathname }`;
|
|
71
|
+
};
|
|
72
|
+
|
|
55
73
|
const Meta = ({ title, keywords, description, image, url, type = 'website', noIndex = false, children }: Props): JSX.Element => {
|
|
56
74
|
const fullTitle = withBrand(title);
|
|
75
|
+
const canonicalUrl = useCanonicalUrl(url);
|
|
57
76
|
|
|
58
77
|
useSoleTitleOwnership(fullTitle);
|
|
59
78
|
|
|
@@ -67,14 +86,14 @@ const Meta = ({ title, keywords, description, image, url, type = 'website', noIn
|
|
|
67
86
|
{/* Standard Meta Tags */}
|
|
68
87
|
{ keywords && <meta name="keywords" content={ keywords } /> }
|
|
69
88
|
{ description && <meta name="description" content={ description } /> }
|
|
70
|
-
|
|
89
|
+
<link rel="canonical" href={ canonicalUrl } />
|
|
71
90
|
|
|
72
91
|
{/* Open Graph / Facebook */}
|
|
73
92
|
<meta property="og:type" content={ type } />
|
|
74
93
|
<meta property="og:title" content={ fullTitle } />
|
|
75
94
|
{ description && <meta property="og:description" content={ description } /> }
|
|
76
95
|
{ image && <meta property="og:image" content={ image } /> }
|
|
77
|
-
|
|
96
|
+
<meta property="og:url" content={ canonicalUrl } />
|
|
78
97
|
{ brand && <meta property="og:site_name" content={ brand } /> }
|
|
79
98
|
|
|
80
99
|
{/* Twitter */}
|
package/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import File from './File/File';
|
|
|
17
17
|
import Gender from './Gender';
|
|
18
18
|
import { General, Inline, Box, BoxMiddle, GeneralFull } from './Loading/Loading';
|
|
19
19
|
import IpLogs from './IpLogs/IpLogs';
|
|
20
|
+
import JsonLd from './JsonLd';
|
|
20
21
|
import Meta from './Meta';
|
|
21
22
|
import Modal from './Modal/Modal';
|
|
22
23
|
import NotificationIcon from './Notifications/Icon';
|
|
@@ -56,6 +57,7 @@ export {
|
|
|
56
57
|
GeneralFull,
|
|
57
58
|
Inline,
|
|
58
59
|
IpLogs,
|
|
60
|
+
JsonLd,
|
|
59
61
|
Meta,
|
|
60
62
|
Modal,
|
|
61
63
|
NotificationIcon,
|