react-client-seo 1.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 +288 -0
- package/dist/Seo.d.ts +22 -0
- package/dist/Seo.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +197 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/useSeo.d.ts +25 -0
- package/dist/useSeo.d.ts.map +1 -0
- package/dist/utils.d.ts +47 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# react-client-seo
|
|
2
|
+
|
|
3
|
+
A lightweight, production-ready client-side SEO renderer for React and Vite applications. No server-side rendering required!
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Lightweight** - No heavy dependencies
|
|
8
|
+
- ✅ **Client-side only** - Works with React and Vite apps
|
|
9
|
+
- ✅ **TypeScript support** - Full type definitions included
|
|
10
|
+
- ✅ **React 17+ & 18+** - Compatible with both versions
|
|
11
|
+
- ✅ **Component & Hook APIs** - Use `<Seo />` component or `useSeo()` hook
|
|
12
|
+
- ✅ **Auto-updates** - Automatically creates/updates tags, avoids duplicates
|
|
13
|
+
- ✅ **Comprehensive** - Supports title, meta tags, Open Graph, Twitter Cards, JSON-LD, and custom meta tags
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install react-client-seo
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
yarn add react-client-seo
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add react-client-seo
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Peer Dependencies
|
|
30
|
+
|
|
31
|
+
- `react` >= 17.0.0
|
|
32
|
+
- `react-dom` >= 17.0.0
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Component API
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { Seo } from 'react-client-seo';
|
|
40
|
+
|
|
41
|
+
function App() {
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
<Seo
|
|
45
|
+
title="My Page Title"
|
|
46
|
+
description="This is a great page about React SEO"
|
|
47
|
+
keywords={['react', 'seo', 'meta tags']}
|
|
48
|
+
canonical="https://example.com/page"
|
|
49
|
+
/>
|
|
50
|
+
<div>Your content here</div>
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Hook API
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { useSeo } from 'react-client-seo';
|
|
60
|
+
import { useEffect } from 'react';
|
|
61
|
+
|
|
62
|
+
function MyComponent() {
|
|
63
|
+
const { updateSeo } = useSeo();
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
updateSeo({
|
|
67
|
+
title: 'My Page Title',
|
|
68
|
+
description: 'Page description',
|
|
69
|
+
keywords: 'react, seo',
|
|
70
|
+
});
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
return <div>Content</div>;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Examples
|
|
78
|
+
|
|
79
|
+
### Basic Usage
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<Seo
|
|
83
|
+
title="Home Page"
|
|
84
|
+
description="Welcome to our website"
|
|
85
|
+
keywords="home, welcome, website"
|
|
86
|
+
/>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Open Graph Tags
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
<Seo
|
|
93
|
+
title="Article Title"
|
|
94
|
+
description="Article description"
|
|
95
|
+
ogImage="https://example.com/image.jpg"
|
|
96
|
+
ogType="article"
|
|
97
|
+
ogUrl="https://example.com/article"
|
|
98
|
+
ogSiteName="My Website"
|
|
99
|
+
ogLocale="en_US"
|
|
100
|
+
/>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Twitter Cards
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<Seo
|
|
107
|
+
title="Article Title"
|
|
108
|
+
description="Article description"
|
|
109
|
+
twitterCard="summary_large_image"
|
|
110
|
+
twitterSite="@username"
|
|
111
|
+
twitterCreator="@author"
|
|
112
|
+
twitterImage="https://example.com/image.jpg"
|
|
113
|
+
twitterImageAlt="Article image"
|
|
114
|
+
/>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### JSON-LD Structured Data
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<Seo
|
|
121
|
+
title="Product Page"
|
|
122
|
+
description="Amazing product"
|
|
123
|
+
jsonLd={{
|
|
124
|
+
"@context": "https://schema.org",
|
|
125
|
+
"@type": "Product",
|
|
126
|
+
"name": "Amazing Product",
|
|
127
|
+
"description": "This is an amazing product",
|
|
128
|
+
"image": "https://example.com/product.jpg",
|
|
129
|
+
"offers": {
|
|
130
|
+
"@type": "Offer",
|
|
131
|
+
"price": "99.99",
|
|
132
|
+
"priceCurrency": "USD"
|
|
133
|
+
}
|
|
134
|
+
}}
|
|
135
|
+
/>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Advanced Open Graph with Custom Properties
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<Seo
|
|
142
|
+
title="Article"
|
|
143
|
+
openGraph={{
|
|
144
|
+
title: "Custom OG Title",
|
|
145
|
+
description: "Custom OG description",
|
|
146
|
+
type: "article",
|
|
147
|
+
url: "https://example.com/article",
|
|
148
|
+
image: "https://example.com/image.jpg",
|
|
149
|
+
"og:article:author": "John Doe",
|
|
150
|
+
"og:article:published_time": "2024-01-01T00:00:00Z",
|
|
151
|
+
}}
|
|
152
|
+
/>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Custom Meta Tags
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
<Seo
|
|
159
|
+
title="Page Title"
|
|
160
|
+
customMeta={[
|
|
161
|
+
{ name: "author", content: "John Doe" },
|
|
162
|
+
{ name: "robots", content: "index, follow" },
|
|
163
|
+
{ property: "custom:property", content: "value" },
|
|
164
|
+
{ httpEquiv: "X-UA-Compatible", content: "IE=edge" },
|
|
165
|
+
]}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Complete Example
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { Seo } from 'react-client-seo';
|
|
173
|
+
|
|
174
|
+
function ProductPage({ product }) {
|
|
175
|
+
return (
|
|
176
|
+
<>
|
|
177
|
+
<Seo
|
|
178
|
+
title={`${product.name} - My Store`}
|
|
179
|
+
description={product.description}
|
|
180
|
+
keywords={product.tags}
|
|
181
|
+
canonical={`https://mystore.com/products/${product.id}`}
|
|
182
|
+
ogImage={product.image}
|
|
183
|
+
ogType="product"
|
|
184
|
+
ogUrl={`https://mystore.com/products/${product.id}`}
|
|
185
|
+
ogSiteName="My Store"
|
|
186
|
+
twitterCard="summary_large_image"
|
|
187
|
+
twitterSite="@mystore"
|
|
188
|
+
twitterImage={product.image}
|
|
189
|
+
jsonLd={{
|
|
190
|
+
"@context": "https://schema.org",
|
|
191
|
+
"@type": "Product",
|
|
192
|
+
"name": product.name,
|
|
193
|
+
"description": product.description,
|
|
194
|
+
"image": product.image,
|
|
195
|
+
"offers": {
|
|
196
|
+
"@type": "Offer",
|
|
197
|
+
"price": product.price,
|
|
198
|
+
"priceCurrency": "USD",
|
|
199
|
+
"availability": "https://schema.org/InStock"
|
|
200
|
+
}
|
|
201
|
+
}}
|
|
202
|
+
customMeta={[
|
|
203
|
+
{ name: "robots", content: "index, follow" },
|
|
204
|
+
]}
|
|
205
|
+
/>
|
|
206
|
+
<div>
|
|
207
|
+
<h1>{product.name}</h1>
|
|
208
|
+
<p>{product.description}</p>
|
|
209
|
+
</div>
|
|
210
|
+
</>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## API Reference
|
|
216
|
+
|
|
217
|
+
### `<Seo />` Component Props
|
|
218
|
+
|
|
219
|
+
| Prop | Type | Description |
|
|
220
|
+
|------|------|-------------|
|
|
221
|
+
| `title` | `string` | Page title |
|
|
222
|
+
| `description` | `string` | Meta description |
|
|
223
|
+
| `keywords` | `string \| string[]` | Meta keywords (comma-separated string or array) |
|
|
224
|
+
| `canonical` | `string` | Canonical URL |
|
|
225
|
+
| `ogImage` | `string` | Open Graph image URL |
|
|
226
|
+
| `ogType` | `string` | Open Graph type (e.g., "article", "website") |
|
|
227
|
+
| `ogUrl` | `string` | Open Graph URL |
|
|
228
|
+
| `ogTitle` | `string` | Open Graph title (defaults to `title`) |
|
|
229
|
+
| `ogDescription` | `string` | Open Graph description (defaults to `description`) |
|
|
230
|
+
| `ogSiteName` | `string` | Open Graph site name |
|
|
231
|
+
| `ogLocale` | `string` | Open Graph locale |
|
|
232
|
+
| `twitterCard` | `'summary' \| 'summary_large_image' \| 'app' \| 'player'` | Twitter Card type |
|
|
233
|
+
| `twitterSite` | `string` | Twitter site handle |
|
|
234
|
+
| `twitterCreator` | `string` | Twitter creator handle |
|
|
235
|
+
| `twitterTitle` | `string` | Twitter title (defaults to `title`) |
|
|
236
|
+
| `twitterDescription` | `string` | Twitter description (defaults to `description`) |
|
|
237
|
+
| `twitterImage` | `string` | Twitter image URL |
|
|
238
|
+
| `twitterImageAlt` | `string` | Twitter image alt text |
|
|
239
|
+
| `jsonLd` | `object \| object[]` | JSON-LD structured data |
|
|
240
|
+
| `customMeta` | `CustomMeta[]` | Custom meta tags |
|
|
241
|
+
| `openGraph` | `OpenGraphMeta` | Additional Open Graph properties |
|
|
242
|
+
| `twitter` | `TwitterCardMeta` | Additional Twitter Card properties |
|
|
243
|
+
|
|
244
|
+
### `useSeo()` Hook
|
|
245
|
+
|
|
246
|
+
Returns an object with:
|
|
247
|
+
|
|
248
|
+
- `updateSeo(props: SeoProps)` - Update SEO tags
|
|
249
|
+
- `clearSeo()` - Clear managed SEO tags (canonical, JSON-LD)
|
|
250
|
+
|
|
251
|
+
## TypeScript
|
|
252
|
+
|
|
253
|
+
Full TypeScript support is included. Import types as needed:
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import type { SeoProps, OpenGraphMeta, TwitterCardMeta, CustomMeta, JsonLd } from 'react-client-seo';
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## How It Works
|
|
260
|
+
|
|
261
|
+
- The component/hook automatically creates meta tags if they don't exist
|
|
262
|
+
- Updates existing tags if they're already present
|
|
263
|
+
- Avoids duplicate tags
|
|
264
|
+
- Cleans up JSON-LD scripts on unmount (component API)
|
|
265
|
+
- Works entirely client-side - no SSR required
|
|
266
|
+
|
|
267
|
+
## Publishing to NPM
|
|
268
|
+
|
|
269
|
+
1. Update version in `package.json`
|
|
270
|
+
2. Build the package:
|
|
271
|
+
```bash
|
|
272
|
+
npm run build
|
|
273
|
+
```
|
|
274
|
+
3. Publish:
|
|
275
|
+
```bash
|
|
276
|
+
npm publish
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
The `prepare` script automatically runs before publishing, ensuring the package is built.
|
|
280
|
+
|
|
281
|
+
## License
|
|
282
|
+
|
|
283
|
+
MIT
|
|
284
|
+
|
|
285
|
+
## Contributing
|
|
286
|
+
|
|
287
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
288
|
+
|
package/dist/Seo.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SeoProps } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SEO Component
|
|
5
|
+
*
|
|
6
|
+
* Renders and updates SEO meta tags in the document head.
|
|
7
|
+
* Returns null (no UI rendering).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* <Seo
|
|
12
|
+
* title="My Page Title"
|
|
13
|
+
* description="Page description"
|
|
14
|
+
* keywords={['react', 'seo']}
|
|
15
|
+
* canonical="https://example.com/page"
|
|
16
|
+
* ogImage="https://example.com/image.jpg"
|
|
17
|
+
* jsonLd={{ "@context": "https://schema.org", "@type": "WebPage" }}
|
|
18
|
+
* />
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function Seo({ title, description, keywords, canonical, ogImage, ogType, ogUrl, ogTitle, ogDescription, ogSiteName, ogLocale, twitterCard, twitterSite, twitterCreator, twitterTitle, twitterDescription, twitterImage, twitterImageAlt, jsonLd, customMeta, openGraph, twitter, }: SeoProps): null;
|
|
22
|
+
//# sourceMappingURL=Seo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Seo.d.ts","sourceRoot":"","sources":["../src/Seo.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAcxC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,GAAG,CAAC,EAClB,KAAK,EACL,WAAW,EACX,QAAQ,EACR,SAAS,EACT,OAAO,EACP,MAAM,EACN,KAAK,EACL,OAAO,EACP,aAAa,EACb,UAAU,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,MAAM,EACN,UAAU,EACV,SAAS,EACT,OAAO,GACR,EAAE,QAAQ,QAgIV"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const W=require("react");function T(e,t){const i=`meta[${e}="${t}"]`;let n=document.querySelector(i);return n||(n=document.createElement("meta"),n.setAttribute(e,t),document.head.appendChild(n)),n}function c(e,t){if(!t)return;T("name",e).setAttribute("content",t)}function f(e,t){if(t==null||t==="")return;T("property",e).setAttribute("content",String(t))}function $(e){e&&(document.title=e)}function B(e){if(!e)return;let t=document.querySelector('link[rel="canonical"]');t||(t=document.createElement("link"),t.setAttribute("rel","canonical"),document.head.appendChild(t)),t.setAttribute("href",e)}function G(){const e=document.querySelector('link[rel="canonical"]');e&&e.remove()}function H(e){e&&(e.title&&f("og:title",e.title),e.description&&f("og:description",e.description),e.type&&f("og:type",e.type),e.url&&f("og:url",e.url),e.image&&f("og:image",e.image),e.imageWidth&&f("og:image:width",e.imageWidth),e.imageHeight&&f("og:image:height",e.imageHeight),e.imageAlt&&f("og:image:alt",e.imageAlt),e.siteName&&f("og:site_name",e.siteName),e.locale&&f("og:locale",e.locale),Object.keys(e).forEach(t=>{if(t.startsWith("og:")&&t!=="og:title"&&t!=="og:description"&&t!=="og:type"&&t!=="og:url"&&t!=="og:image"&&t!=="og:imageWidth"&&t!=="og:imageHeight"&&t!=="og:imageAlt"&&t!=="og:siteName"&&t!=="og:locale"){const i=e[t];i!=null&&i!==""&&f(t,i)}}))}function L(e){e&&(e.card&&c("twitter:card",e.card),e.site&&c("twitter:site",e.site),e.creator&&c("twitter:creator",e.creator),e.title&&c("twitter:title",e.title),e.description&&c("twitter:description",e.description),e.image&&c("twitter:image",e.image),e.imageAlt&&c("twitter:image:alt",e.imageAlt),Object.keys(e).forEach(t=>{if(t.startsWith("twitter:")&&t!=="twitter:card"&&t!=="twitter:site"&&t!=="twitter:creator"&&t!=="twitter:title"&&t!=="twitter:description"&&t!=="twitter:image"&&t!=="twitter:imageAlt"){const i=e[t];i!=null&&i!==""&&c(t,String(i))}}))}function x(e){!e||!Array.isArray(e)||e.forEach(t=>{if(!t.content)return;let i=null;if(t.name)i=T("name",t.name);else if(t.property)i=T("property",t.property);else if(t.httpEquiv){const n=`meta[http-equiv="${t.httpEquiv}"]`;i=document.querySelector(n),i||(i=document.createElement("meta"),i.setAttribute("http-equiv",t.httpEquiv),document.head.appendChild(i))}else if(t.charset){let n=document.querySelector("meta[charset]");n||(n=document.createElement("meta"),n.setAttribute("charset",t.charset),document.head.insertBefore(n,document.head.firstChild));return}i&&i.setAttribute("content",t.content)})}function M(e,t){const i=document.createElement("script");i.type="application/ld+json";{i.id=t;const n=document.getElementById(t);n&&n.remove()}return i.textContent=JSON.stringify(e),document.head.appendChild(i),()=>{i.remove()}}function K(e){const t=document.getElementById(e);t&&t.getAttribute("type")==="application/ld+json"&&t.remove()}function P(e){return e?Array.isArray(e)?e.join(", "):e:""}const z="react-client-seo-jsonld";function F({title:e,description:t,keywords:i,canonical:n,ogImage:r,ogType:C,ogUrl:u,ogTitle:d,ogDescription:m,ogSiteName:p,ogLocale:h,twitterCard:g,twitterSite:A,twitterCreator:b,twitterTitle:S,twitterDescription:v,twitterImage:E,twitterImageAlt:y,jsonLd:j,customMeta:O,openGraph:N,twitter:D}){return W.useEffect(()=>{e&&$(e),t&&c("description",t);const J=P(i);J&&c("keywords",J),n&&B(n);const o={...N};(d||e)&&(o.title=d||e),(m||t)&&(o.description=m||t),C&&(o.type=C),(u||n)&&(o.url=u||n),r&&(o.image=r),p&&(o.siteName=p),h&&(o.locale=h),Object.keys(o).length>0&&H(o);const a={...D};g&&(a.card=g),A&&(a.site=A),b&&(a.creator=b),(S||e)&&(a.title=S||e),(v||t)&&(a.description=v||t),(E||r)&&(a.image=E||r),y&&(a.imageAlt=y),Object.keys(a).length>0&&L(a),O&&x(O);let q=null;return j&&(q=M(j,z)),()=>{q&&q()}},[e,t,i,n,r,C,u,d,m,p,h,g,A,b,S,v,E,y,j,O,N,D]),null}const _="react-client-seo-jsonld";function Q(){const e=W.useCallback(i=>{const{title:n,description:r,keywords:C,canonical:u,ogImage:d,ogType:m,ogUrl:p,ogTitle:h,ogDescription:g,ogSiteName:A,ogLocale:b,twitterCard:S,twitterSite:v,twitterCreator:E,twitterTitle:y,twitterDescription:j,twitterImage:O,twitterImageAlt:N,jsonLd:D,customMeta:J,openGraph:o,twitter:a}=i;n&&$(n),r&&c("description",r);const q=P(C);q&&c("keywords",q),u&&B(u);const l={...o};(h||n)&&(l.title=h||n),(g||r)&&(l.description=g||r),m&&(l.type=m),(p||u)&&(l.url=p||u),d&&(l.image=d),A&&(l.siteName=A),b&&(l.locale=b),Object.keys(l).length>0&&H(l);const s={...a};S&&(s.card=S),v&&(s.site=v),E&&(s.creator=E),(y||n)&&(s.title=y||n),(j||r)&&(s.description=j||r),(O||d)&&(s.image=O||d),N&&(s.imageAlt=N),Object.keys(s).length>0&&L(s),J&&x(J),D&&M(D,_)},[]),t=W.useCallback(()=>{G(),K(_)},[]);return{updateSeo:e,clearSeo:t}}exports.Seo=F;exports.useSeo=Q;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/utils.ts","../src/Seo.tsx","../src/useSeo.ts"],"sourcesContent":["import type { CustomMeta, JsonLd, OpenGraphMeta, TwitterCardMeta } from './types';\r\n\r\n/**\r\n * Get or create a meta tag element\r\n */\r\nfunction getOrCreateMetaTag(attribute: 'name' | 'property', value: string): HTMLMetaElement {\r\n const selector = `meta[${attribute}=\"${value}\"]`;\r\n let element = document.querySelector<HTMLMetaElement>(selector);\r\n\r\n if (!element) {\r\n element = document.createElement('meta');\r\n element.setAttribute(attribute, value);\r\n document.head.appendChild(element);\r\n }\r\n\r\n return element;\r\n}\r\n\r\n/**\r\n * Set a meta tag by name\r\n */\r\nexport function setMetaTag(name: string, content: string): void {\r\n if (!content) return;\r\n\r\n const element = getOrCreateMetaTag('name', name);\r\n element.setAttribute('content', content);\r\n}\r\n\r\n/**\r\n * Set a meta tag by property (for Open Graph)\r\n */\r\nexport function setMetaProperty(property: string, content: string | number): void {\r\n if (content === undefined || content === null || content === '') return;\r\n\r\n const element = getOrCreateMetaTag('property', property);\r\n element.setAttribute('content', String(content));\r\n}\r\n\r\n/**\r\n * Set the page title\r\n */\r\nexport function setTitle(title: string): void {\r\n if (!title) return;\r\n document.title = title;\r\n}\r\n\r\n/**\r\n * Set canonical URL\r\n */\r\nexport function setCanonical(url: string): void {\r\n if (!url) return;\r\n\r\n let link = document.querySelector<HTMLLinkElement>('link[rel=\"canonical\"]');\r\n if (!link) {\r\n link = document.createElement('link');\r\n link.setAttribute('rel', 'canonical');\r\n document.head.appendChild(link);\r\n }\r\n link.setAttribute('href', url);\r\n}\r\n\r\n/**\r\n * Remove canonical URL\r\n */\r\nexport function removeCanonical(): void {\r\n const link = document.querySelector<HTMLLinkElement>('link[rel=\"canonical\"]');\r\n if (link) {\r\n link.remove();\r\n }\r\n}\r\n\r\n/**\r\n * Set Open Graph meta tags\r\n */\r\nexport function setOpenGraphTags(og: OpenGraphMeta): void {\r\n if (!og) return;\r\n\r\n // Standard Open Graph properties\r\n if (og.title) setMetaProperty('og:title', og.title);\r\n if (og.description) setMetaProperty('og:description', og.description);\r\n if (og.type) setMetaProperty('og:type', og.type);\r\n if (og.url) setMetaProperty('og:url', og.url);\r\n if (og.image) setMetaProperty('og:image', og.image);\r\n if (og.imageWidth) setMetaProperty('og:image:width', og.imageWidth);\r\n if (og.imageHeight) setMetaProperty('og:image:height', og.imageHeight);\r\n if (og.imageAlt) setMetaProperty('og:image:alt', og.imageAlt);\r\n if (og.siteName) setMetaProperty('og:site_name', og.siteName);\r\n if (og.locale) setMetaProperty('og:locale', og.locale);\r\n\r\n // Handle additional custom og:* properties\r\n Object.keys(og).forEach((key) => {\r\n if (key.startsWith('og:') && key !== 'og:title' && key !== 'og:description' && \r\n key !== 'og:type' && key !== 'og:url' && key !== 'og:image' && \r\n key !== 'og:imageWidth' && key !== 'og:imageHeight' && \r\n key !== 'og:imageAlt' && key !== 'og:siteName' && key !== 'og:locale') {\r\n const value = og[key as keyof OpenGraphMeta];\r\n if (value !== undefined && value !== null && value !== '') {\r\n setMetaProperty(key, value as string | number);\r\n }\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Set Twitter Card meta tags\r\n */\r\nexport function setTwitterCardTags(twitter: TwitterCardMeta): void {\r\n if (!twitter) return;\r\n\r\n if (twitter.card) setMetaTag('twitter:card', twitter.card);\r\n if (twitter.site) setMetaTag('twitter:site', twitter.site);\r\n if (twitter.creator) setMetaTag('twitter:creator', twitter.creator);\r\n if (twitter.title) setMetaTag('twitter:title', twitter.title);\r\n if (twitter.description) setMetaTag('twitter:description', twitter.description);\r\n if (twitter.image) setMetaTag('twitter:image', twitter.image);\r\n if (twitter.imageAlt) setMetaTag('twitter:image:alt', twitter.imageAlt);\r\n\r\n // Handle additional custom twitter:* properties\r\n Object.keys(twitter).forEach((key) => {\r\n if (key.startsWith('twitter:') && key !== 'twitter:card' && key !== 'twitter:site' && \r\n key !== 'twitter:creator' && key !== 'twitter:title' && \r\n key !== 'twitter:description' && key !== 'twitter:image' && \r\n key !== 'twitter:imageAlt') {\r\n const value = twitter[key as keyof TwitterCardMeta];\r\n if (value !== undefined && value !== null && value !== '') {\r\n setMetaTag(key, String(value));\r\n }\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Set custom meta tags\r\n */\r\nexport function setCustomMetaTags(customMeta: CustomMeta[]): void {\r\n if (!customMeta || !Array.isArray(customMeta)) return;\r\n\r\n customMeta.forEach((meta) => {\r\n if (!meta.content) return;\r\n\r\n let element: HTMLMetaElement | null = null;\r\n\r\n if (meta.name) {\r\n element = getOrCreateMetaTag('name', meta.name);\r\n } else if (meta.property) {\r\n element = getOrCreateMetaTag('property', meta.property);\r\n } else if (meta.httpEquiv) {\r\n const selector = `meta[http-equiv=\"${meta.httpEquiv}\"]`;\r\n element = document.querySelector<HTMLMetaElement>(selector);\r\n if (!element) {\r\n element = document.createElement('meta');\r\n element.setAttribute('http-equiv', meta.httpEquiv);\r\n document.head.appendChild(element);\r\n }\r\n } else if (meta.charset) {\r\n let charsetElement = document.querySelector<HTMLMetaElement>('meta[charset]');\r\n if (!charsetElement) {\r\n charsetElement = document.createElement('meta');\r\n charsetElement.setAttribute('charset', meta.charset);\r\n document.head.insertBefore(charsetElement, document.head.firstChild);\r\n }\r\n return;\r\n }\r\n\r\n if (element) {\r\n element.setAttribute('content', meta.content);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Inject JSON-LD structured data\r\n */\r\nexport function injectJsonLd(data: JsonLd, id?: string): () => void {\r\n const script = document.createElement('script');\r\n script.type = 'application/ld+json';\r\n if (id) {\r\n script.id = id;\r\n // Remove existing script with same id\r\n const existing = document.getElementById(id);\r\n if (existing) {\r\n existing.remove();\r\n }\r\n }\r\n script.textContent = JSON.stringify(data);\r\n document.head.appendChild(script);\r\n\r\n // Return cleanup function\r\n return () => {\r\n script.remove();\r\n };\r\n}\r\n\r\n/**\r\n * Remove JSON-LD script by id\r\n */\r\nexport function removeJsonLd(id: string): void {\r\n const script = document.getElementById(id);\r\n if (script && script.getAttribute('type') === 'application/ld+json') {\r\n script.remove();\r\n }\r\n}\r\n\r\n/**\r\n * Format keywords as string\r\n */\r\nexport function formatKeywords(keywords: string | string[] | undefined): string {\r\n if (!keywords) return '';\r\n if (Array.isArray(keywords)) {\r\n return keywords.join(', ');\r\n }\r\n return keywords;\r\n}\r\n\r\n","import { useEffect } from 'react';\r\nimport type { SeoProps } from './types';\r\nimport {\r\n setTitle,\r\n setMetaTag,\r\n setCanonical,\r\n setOpenGraphTags,\r\n setTwitterCardTags,\r\n setCustomMetaTags,\r\n injectJsonLd,\r\n formatKeywords,\r\n} from './utils';\r\n\r\nconst JSON_LD_ID = 'react-client-seo-jsonld';\r\n\r\n/**\r\n * SEO Component\r\n * \r\n * Renders and updates SEO meta tags in the document head.\r\n * Returns null (no UI rendering).\r\n * \r\n * @example\r\n * ```tsx\r\n * <Seo\r\n * title=\"My Page Title\"\r\n * description=\"Page description\"\r\n * keywords={['react', 'seo']}\r\n * canonical=\"https://example.com/page\"\r\n * ogImage=\"https://example.com/image.jpg\"\r\n * jsonLd={{ \"@context\": \"https://schema.org\", \"@type\": \"WebPage\" }}\r\n * />\r\n * ```\r\n */\r\nexport function Seo({\r\n title,\r\n description,\r\n keywords,\r\n canonical,\r\n ogImage,\r\n ogType,\r\n ogUrl,\r\n ogTitle,\r\n ogDescription,\r\n ogSiteName,\r\n ogLocale,\r\n twitterCard,\r\n twitterSite,\r\n twitterCreator,\r\n twitterTitle,\r\n twitterDescription,\r\n twitterImage,\r\n twitterImageAlt,\r\n jsonLd,\r\n customMeta,\r\n openGraph,\r\n twitter,\r\n}: SeoProps) {\r\n useEffect(() => {\r\n // Set title\r\n if (title) {\r\n setTitle(title);\r\n }\r\n\r\n // Set description\r\n if (description) {\r\n setMetaTag('description', description);\r\n }\r\n\r\n // Set keywords\r\n const keywordsStr = formatKeywords(keywords);\r\n if (keywordsStr) {\r\n setMetaTag('keywords', keywordsStr);\r\n }\r\n\r\n // Set canonical URL\r\n if (canonical) {\r\n setCanonical(canonical);\r\n }\r\n\r\n // Build Open Graph object\r\n const ogData: any = {\r\n ...openGraph,\r\n };\r\n\r\n if (ogTitle || title) {\r\n ogData.title = ogTitle || title;\r\n }\r\n if (ogDescription || description) {\r\n ogData.description = ogDescription || description;\r\n }\r\n if (ogType) {\r\n ogData.type = ogType;\r\n }\r\n if (ogUrl || canonical) {\r\n ogData.url = ogUrl || canonical;\r\n }\r\n if (ogImage) {\r\n ogData.image = ogImage;\r\n }\r\n if (ogSiteName) {\r\n ogData.siteName = ogSiteName;\r\n }\r\n if (ogLocale) {\r\n ogData.locale = ogLocale;\r\n }\r\n\r\n if (Object.keys(ogData).length > 0) {\r\n setOpenGraphTags(ogData);\r\n }\r\n\r\n // Build Twitter Card object\r\n const twitterData: any = {\r\n ...twitter,\r\n };\r\n\r\n if (twitterCard) {\r\n twitterData.card = twitterCard;\r\n }\r\n if (twitterSite) {\r\n twitterData.site = twitterSite;\r\n }\r\n if (twitterCreator) {\r\n twitterData.creator = twitterCreator;\r\n }\r\n if (twitterTitle || title) {\r\n twitterData.title = twitterTitle || title;\r\n }\r\n if (twitterDescription || description) {\r\n twitterData.description = twitterDescription || description;\r\n }\r\n if (twitterImage || ogImage) {\r\n twitterData.image = twitterImage || ogImage;\r\n }\r\n if (twitterImageAlt) {\r\n twitterData.imageAlt = twitterImageAlt;\r\n }\r\n\r\n if (Object.keys(twitterData).length > 0) {\r\n setTwitterCardTags(twitterData);\r\n }\r\n\r\n // Set custom meta tags\r\n if (customMeta) {\r\n setCustomMetaTags(customMeta);\r\n }\r\n\r\n // Inject JSON-LD\r\n let cleanupJsonLd: (() => void) | null = null;\r\n if (jsonLd) {\r\n cleanupJsonLd = injectJsonLd(jsonLd, JSON_LD_ID);\r\n }\r\n\r\n // Cleanup function\r\n return () => {\r\n if (cleanupJsonLd) {\r\n cleanupJsonLd();\r\n }\r\n };\r\n }, [\r\n title,\r\n description,\r\n keywords,\r\n canonical,\r\n ogImage,\r\n ogType,\r\n ogUrl,\r\n ogTitle,\r\n ogDescription,\r\n ogSiteName,\r\n ogLocale,\r\n twitterCard,\r\n twitterSite,\r\n twitterCreator,\r\n twitterTitle,\r\n twitterDescription,\r\n twitterImage,\r\n twitterImageAlt,\r\n jsonLd,\r\n customMeta,\r\n openGraph,\r\n twitter,\r\n ]);\r\n\r\n return null;\r\n}\r\n\r\n","import { useCallback } from 'react';\r\nimport type { SeoProps, UseSeoReturn } from './types';\r\nimport {\r\n setTitle,\r\n setMetaTag,\r\n setCanonical,\r\n setOpenGraphTags,\r\n setTwitterCardTags,\r\n setCustomMetaTags,\r\n injectJsonLd,\r\n formatKeywords,\r\n removeCanonical,\r\n removeJsonLd,\r\n} from './utils';\r\n\r\nconst JSON_LD_ID = 'react-client-seo-jsonld';\r\n\r\n/**\r\n * useSeo Hook\r\n * \r\n * Hook-based API for managing SEO meta tags.\r\n * \r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const { updateSeo } = useSeo();\r\n * \r\n * useEffect(() => {\r\n * updateSeo({\r\n * title: 'My Page',\r\n * description: 'Page description',\r\n * });\r\n * }, []);\r\n * \r\n * return <div>Content</div>;\r\n * }\r\n * ```\r\n */\r\nexport function useSeo(): UseSeoReturn {\r\n const updateSeo = useCallback((props: SeoProps) => {\r\n const {\r\n title,\r\n description,\r\n keywords,\r\n canonical,\r\n ogImage,\r\n ogType,\r\n ogUrl,\r\n ogTitle,\r\n ogDescription,\r\n ogSiteName,\r\n ogLocale,\r\n twitterCard,\r\n twitterSite,\r\n twitterCreator,\r\n twitterTitle,\r\n twitterDescription,\r\n twitterImage,\r\n twitterImageAlt,\r\n jsonLd,\r\n customMeta,\r\n openGraph,\r\n twitter,\r\n } = props;\r\n\r\n // Set title\r\n if (title) {\r\n setTitle(title);\r\n }\r\n\r\n // Set description\r\n if (description) {\r\n setMetaTag('description', description);\r\n }\r\n\r\n // Set keywords\r\n const keywordsStr = formatKeywords(keywords);\r\n if (keywordsStr) {\r\n setMetaTag('keywords', keywordsStr);\r\n }\r\n\r\n // Set canonical URL\r\n if (canonical) {\r\n setCanonical(canonical);\r\n }\r\n\r\n // Build Open Graph object\r\n const ogData: any = {\r\n ...openGraph,\r\n };\r\n\r\n if (ogTitle || title) {\r\n ogData.title = ogTitle || title;\r\n }\r\n if (ogDescription || description) {\r\n ogData.description = ogDescription || description;\r\n }\r\n if (ogType) {\r\n ogData.type = ogType;\r\n }\r\n if (ogUrl || canonical) {\r\n ogData.url = ogUrl || canonical;\r\n }\r\n if (ogImage) {\r\n ogData.image = ogImage;\r\n }\r\n if (ogSiteName) {\r\n ogData.siteName = ogSiteName;\r\n }\r\n if (ogLocale) {\r\n ogData.locale = ogLocale;\r\n }\r\n\r\n if (Object.keys(ogData).length > 0) {\r\n setOpenGraphTags(ogData);\r\n }\r\n\r\n // Build Twitter Card object\r\n const twitterData: any = {\r\n ...twitter,\r\n };\r\n\r\n if (twitterCard) {\r\n twitterData.card = twitterCard;\r\n }\r\n if (twitterSite) {\r\n twitterData.site = twitterSite;\r\n }\r\n if (twitterCreator) {\r\n twitterData.creator = twitterCreator;\r\n }\r\n if (twitterTitle || title) {\r\n twitterData.title = twitterTitle || title;\r\n }\r\n if (twitterDescription || description) {\r\n twitterData.description = twitterDescription || description;\r\n }\r\n if (twitterImage || ogImage) {\r\n twitterData.image = twitterImage || ogImage;\r\n }\r\n if (twitterImageAlt) {\r\n twitterData.imageAlt = twitterImageAlt;\r\n }\r\n\r\n if (Object.keys(twitterData).length > 0) {\r\n setTwitterCardTags(twitterData);\r\n }\r\n\r\n // Set custom meta tags\r\n if (customMeta) {\r\n setCustomMetaTags(customMeta);\r\n }\r\n\r\n // Inject JSON-LD\r\n if (jsonLd) {\r\n injectJsonLd(jsonLd, JSON_LD_ID);\r\n }\r\n }, []);\r\n\r\n const clearSeo = useCallback(() => {\r\n // Note: We don't clear title, description, keywords as they might be set by other means\r\n // Only clear what we manage\r\n removeCanonical();\r\n removeJsonLd(JSON_LD_ID);\r\n }, []);\r\n\r\n return {\r\n updateSeo,\r\n clearSeo,\r\n };\r\n}\r\n\r\n"],"names":["getOrCreateMetaTag","attribute","value","selector","element","setMetaTag","name","content","setMetaProperty","property","setTitle","title","setCanonical","url","link","removeCanonical","setOpenGraphTags","og","key","setTwitterCardTags","twitter","setCustomMetaTags","customMeta","meta","charsetElement","injectJsonLd","data","id","script","existing","removeJsonLd","formatKeywords","keywords","JSON_LD_ID","Seo","description","canonical","ogImage","ogType","ogUrl","ogTitle","ogDescription","ogSiteName","ogLocale","twitterCard","twitterSite","twitterCreator","twitterTitle","twitterDescription","twitterImage","twitterImageAlt","jsonLd","openGraph","useEffect","keywordsStr","ogData","twitterData","cleanupJsonLd","useSeo","updateSeo","useCallback","props","clearSeo"],"mappings":"yGAKA,SAASA,EAAmBC,EAAgCC,EAAgC,CAC1F,MAAMC,EAAW,QAAQF,CAAS,KAAKC,CAAK,KAC5C,IAAIE,EAAU,SAAS,cAA+BD,CAAQ,EAE9D,OAAKC,IACHA,EAAU,SAAS,cAAc,MAAM,EACvCA,EAAQ,aAAaH,EAAWC,CAAK,EACrC,SAAS,KAAK,YAAYE,CAAO,GAG5BA,CACT,CAKO,SAASC,EAAWC,EAAcC,EAAuB,CAC9D,GAAI,CAACA,EAAS,OAEEP,EAAmB,OAAQM,CAAI,EACvC,aAAa,UAAWC,CAAO,CACzC,CAKO,SAASC,EAAgBC,EAAkBF,EAAgC,CAChF,GAA6BA,GAAY,MAAQA,IAAY,GAAI,OAEjDP,EAAmB,WAAYS,CAAQ,EAC/C,aAAa,UAAW,OAAOF,CAAO,CAAC,CACjD,CAKO,SAASG,EAASC,EAAqB,CACvCA,IACL,SAAS,MAAQA,EACnB,CAKO,SAASC,EAAaC,EAAmB,CAC9C,GAAI,CAACA,EAAK,OAEV,IAAIC,EAAO,SAAS,cAA+B,uBAAuB,EACrEA,IACHA,EAAO,SAAS,cAAc,MAAM,EACpCA,EAAK,aAAa,MAAO,WAAW,EACpC,SAAS,KAAK,YAAYA,CAAI,GAEhCA,EAAK,aAAa,OAAQD,CAAG,CAC/B,CAKO,SAASE,GAAwB,CACtC,MAAMD,EAAO,SAAS,cAA+B,uBAAuB,EACxEA,GACFA,EAAK,OAAA,CAET,CAKO,SAASE,EAAiBC,EAAyB,CACnDA,IAGDA,EAAG,OAAOT,EAAgB,WAAYS,EAAG,KAAK,EAC9CA,EAAG,aAAaT,EAAgB,iBAAkBS,EAAG,WAAW,EAChEA,EAAG,MAAMT,EAAgB,UAAWS,EAAG,IAAI,EAC3CA,EAAG,KAAKT,EAAgB,SAAUS,EAAG,GAAG,EACxCA,EAAG,OAAOT,EAAgB,WAAYS,EAAG,KAAK,EAC9CA,EAAG,YAAYT,EAAgB,iBAAkBS,EAAG,UAAU,EAC9DA,EAAG,aAAaT,EAAgB,kBAAmBS,EAAG,WAAW,EACjEA,EAAG,UAAUT,EAAgB,eAAgBS,EAAG,QAAQ,EACxDA,EAAG,UAAUT,EAAgB,eAAgBS,EAAG,QAAQ,EACxDA,EAAG,QAAQT,EAAgB,YAAaS,EAAG,MAAM,EAGrD,OAAO,KAAKA,CAAE,EAAE,QAASC,GAAQ,CAC/B,GAAIA,EAAI,WAAW,KAAK,GAAKA,IAAQ,YAAcA,IAAQ,kBACvDA,IAAQ,WAAaA,IAAQ,UAAYA,IAAQ,YACjDA,IAAQ,iBAAmBA,IAAQ,kBACnCA,IAAQ,eAAiBA,IAAQ,eAAiBA,IAAQ,YAAa,CACzE,MAAMhB,EAAQe,EAAGC,CAA0B,EAChBhB,GAAU,MAAQA,IAAU,IACrDM,EAAgBU,EAAKhB,CAAwB,CAEjD,CACF,CAAC,EACH,CAKO,SAASiB,EAAmBC,EAAgC,CAC5DA,IAEDA,EAAQ,MAAMf,EAAW,eAAgBe,EAAQ,IAAI,EACrDA,EAAQ,MAAMf,EAAW,eAAgBe,EAAQ,IAAI,EACrDA,EAAQ,SAASf,EAAW,kBAAmBe,EAAQ,OAAO,EAC9DA,EAAQ,OAAOf,EAAW,gBAAiBe,EAAQ,KAAK,EACxDA,EAAQ,aAAaf,EAAW,sBAAuBe,EAAQ,WAAW,EAC1EA,EAAQ,OAAOf,EAAW,gBAAiBe,EAAQ,KAAK,EACxDA,EAAQ,UAAUf,EAAW,oBAAqBe,EAAQ,QAAQ,EAGtE,OAAO,KAAKA,CAAO,EAAE,QAASF,GAAQ,CACpC,GAAIA,EAAI,WAAW,UAAU,GAAKA,IAAQ,gBAAkBA,IAAQ,gBAChEA,IAAQ,mBAAqBA,IAAQ,iBACrCA,IAAQ,uBAAyBA,IAAQ,iBACzCA,IAAQ,mBAAoB,CAC9B,MAAMhB,EAAQkB,EAAQF,CAA4B,EACvBhB,GAAU,MAAQA,IAAU,IACrDG,EAAWa,EAAK,OAAOhB,CAAK,CAAC,CAEjC,CACF,CAAC,EACH,CAKO,SAASmB,EAAkBC,EAAgC,CAC5D,CAACA,GAAc,CAAC,MAAM,QAAQA,CAAU,GAE5CA,EAAW,QAASC,GAAS,CAC3B,GAAI,CAACA,EAAK,QAAS,OAEnB,IAAInB,EAAkC,KAEtC,GAAImB,EAAK,KACPnB,EAAUJ,EAAmB,OAAQuB,EAAK,IAAI,UACrCA,EAAK,SACdnB,EAAUJ,EAAmB,WAAYuB,EAAK,QAAQ,UAC7CA,EAAK,UAAW,CACzB,MAAMpB,EAAW,oBAAoBoB,EAAK,SAAS,KACnDnB,EAAU,SAAS,cAA+BD,CAAQ,EACrDC,IACHA,EAAU,SAAS,cAAc,MAAM,EACvCA,EAAQ,aAAa,aAAcmB,EAAK,SAAS,EACjD,SAAS,KAAK,YAAYnB,CAAO,EAErC,SAAWmB,EAAK,QAAS,CACvB,IAAIC,EAAiB,SAAS,cAA+B,eAAe,EACvEA,IACHA,EAAiB,SAAS,cAAc,MAAM,EAC9CA,EAAe,aAAa,UAAWD,EAAK,OAAO,EACnD,SAAS,KAAK,aAAaC,EAAgB,SAAS,KAAK,UAAU,GAErE,MACF,CAEIpB,GACFA,EAAQ,aAAa,UAAWmB,EAAK,OAAO,CAEhD,CAAC,CACH,CAKO,SAASE,EAAaC,EAAcC,EAAyB,CAClE,MAAMC,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,KAAO,sBACN,CACNA,EAAO,GAAKD,EAEZ,MAAME,EAAW,SAAS,eAAeF,CAAE,EACvCE,GACFA,EAAS,OAAA,CAEb,CACA,OAAAD,EAAO,YAAc,KAAK,UAAUF,CAAI,EACxC,SAAS,KAAK,YAAYE,CAAM,EAGzB,IAAM,CACXA,EAAO,OAAA,CACT,CACF,CAKO,SAASE,EAAaH,EAAkB,CAC7C,MAAMC,EAAS,SAAS,eAAeD,CAAE,EACrCC,GAAUA,EAAO,aAAa,MAAM,IAAM,uBAC5CA,EAAO,OAAA,CAEX,CAKO,SAASG,EAAeC,EAAiD,CAC9E,OAAKA,EACD,MAAM,QAAQA,CAAQ,EACjBA,EAAS,KAAK,IAAI,EAEpBA,EAJe,EAKxB,CCvMA,MAAMC,EAAa,0BAoBZ,SAASC,EAAI,CAClB,MAAAvB,EACA,YAAAwB,EACA,SAAAH,EACA,UAAAI,EACA,QAAAC,EACA,OAAAC,EACA,MAAAC,EACA,QAAAC,EACA,cAAAC,EACA,WAAAC,EACA,SAAAC,EACA,YAAAC,EACA,YAAAC,EACA,eAAAC,EACA,aAAAC,EACA,mBAAAC,EACA,aAAAC,EACA,gBAAAC,EACA,OAAAC,EACA,WAAA7B,EACA,UAAA8B,EACA,QAAAhC,CACF,EAAa,CACXiC,OAAAA,EAAAA,UAAU,IAAM,CAEV1C,GACFD,EAASC,CAAK,EAIZwB,GACF9B,EAAW,cAAe8B,CAAW,EAIvC,MAAMmB,EAAcvB,EAAeC,CAAQ,EACvCsB,GACFjD,EAAW,WAAYiD,CAAW,EAIhClB,GACFxB,EAAawB,CAAS,EAIxB,MAAMmB,EAAc,CAClB,GAAGH,CAAA,GAGDZ,GAAW7B,KACb4C,EAAO,MAAQf,GAAW7B,IAExB8B,GAAiBN,KACnBoB,EAAO,YAAcd,GAAiBN,GAEpCG,IACFiB,EAAO,KAAOjB,IAEZC,GAASH,KACXmB,EAAO,IAAMhB,GAASH,GAEpBC,IACFkB,EAAO,MAAQlB,GAEbK,IACFa,EAAO,SAAWb,GAEhBC,IACFY,EAAO,OAASZ,GAGd,OAAO,KAAKY,CAAM,EAAE,OAAS,GAC/BvC,EAAiBuC,CAAM,EAIzB,MAAMC,EAAmB,CACvB,GAAGpC,CAAA,EAGDwB,IACFY,EAAY,KAAOZ,GAEjBC,IACFW,EAAY,KAAOX,GAEjBC,IACFU,EAAY,QAAUV,IAEpBC,GAAgBpC,KAClB6C,EAAY,MAAQT,GAAgBpC,IAElCqC,GAAsBb,KACxBqB,EAAY,YAAcR,GAAsBb,IAE9Cc,GAAgBZ,KAClBmB,EAAY,MAAQP,GAAgBZ,GAElCa,IACFM,EAAY,SAAWN,GAGrB,OAAO,KAAKM,CAAW,EAAE,OAAS,GACpCrC,EAAmBqC,CAAW,EAI5BlC,GACFD,EAAkBC,CAAU,EAI9B,IAAImC,EAAqC,KACzC,OAAIN,IACFM,EAAgBhC,EAAa0B,EAAQlB,CAAU,GAI1C,IAAM,CACPwB,GACFA,EAAA,CAEJ,CACF,EAAG,CACD9C,EACAwB,EACAH,EACAI,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACA7B,EACA8B,EACAhC,CAAA,CACD,EAEM,IACT,CCzKA,MAAMa,EAAa,0BAuBZ,SAASyB,GAAuB,CACrC,MAAMC,EAAYC,cAAaC,GAAoB,CACjD,KAAM,CACJ,MAAAlD,EACA,YAAAwB,EACA,SAAAH,EACA,UAAAI,EACA,QAAAC,EACA,OAAAC,EACA,MAAAC,EACA,QAAAC,EACA,cAAAC,EACA,WAAAC,EACA,SAAAC,EACA,YAAAC,EACA,YAAAC,EACA,eAAAC,EACA,aAAAC,EACA,mBAAAC,EACA,aAAAC,EACA,gBAAAC,EACA,OAAAC,EACA,WAAA7B,EACA,UAAA8B,EACA,QAAAhC,CAAA,EACEyC,EAGAlD,GACFD,EAASC,CAAK,EAIZwB,GACF9B,EAAW,cAAe8B,CAAW,EAIvC,MAAMmB,EAAcvB,EAAeC,CAAQ,EACvCsB,GACFjD,EAAW,WAAYiD,CAAW,EAIhClB,GACFxB,EAAawB,CAAS,EAIxB,MAAMmB,EAAc,CAClB,GAAGH,CAAA,GAGDZ,GAAW7B,KACb4C,EAAO,MAAQf,GAAW7B,IAExB8B,GAAiBN,KACnBoB,EAAO,YAAcd,GAAiBN,GAEpCG,IACFiB,EAAO,KAAOjB,IAEZC,GAASH,KACXmB,EAAO,IAAMhB,GAASH,GAEpBC,IACFkB,EAAO,MAAQlB,GAEbK,IACFa,EAAO,SAAWb,GAEhBC,IACFY,EAAO,OAASZ,GAGd,OAAO,KAAKY,CAAM,EAAE,OAAS,GAC/BvC,EAAiBuC,CAAM,EAIzB,MAAMC,EAAmB,CACvB,GAAGpC,CAAA,EAGDwB,IACFY,EAAY,KAAOZ,GAEjBC,IACFW,EAAY,KAAOX,GAEjBC,IACFU,EAAY,QAAUV,IAEpBC,GAAgBpC,KAClB6C,EAAY,MAAQT,GAAgBpC,IAElCqC,GAAsBb,KACxBqB,EAAY,YAAcR,GAAsBb,IAE9Cc,GAAgBZ,KAClBmB,EAAY,MAAQP,GAAgBZ,GAElCa,IACFM,EAAY,SAAWN,GAGrB,OAAO,KAAKM,CAAW,EAAE,OAAS,GACpCrC,EAAmBqC,CAAW,EAI5BlC,GACFD,EAAkBC,CAAU,EAI1B6B,GACF1B,EAAa0B,EAAQlB,CAAU,CAEnC,EAAG,CAAA,CAAE,EAEC6B,EAAWF,EAAAA,YAAY,IAAM,CAGjC7C,EAAA,EACAe,EAAaG,CAAU,CACzB,EAAG,CAAA,CAAE,EAEL,MAAO,CACL,UAAA0B,EACA,SAAAG,CAAA,CAEJ"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EACV,QAAQ,EACR,aAAa,EACb,eAAe,EACf,UAAU,EACV,MAAM,EACN,YAAY,GACb,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { useEffect as M, useCallback as _ } from "react";
|
|
2
|
+
function W(e, t) {
|
|
3
|
+
const i = `meta[${e}="${t}"]`;
|
|
4
|
+
let n = document.querySelector(i);
|
|
5
|
+
return n || (n = document.createElement("meta"), n.setAttribute(e, t), document.head.appendChild(n)), n;
|
|
6
|
+
}
|
|
7
|
+
function c(e, t) {
|
|
8
|
+
if (!t) return;
|
|
9
|
+
W("name", e).setAttribute("content", t);
|
|
10
|
+
}
|
|
11
|
+
function a(e, t) {
|
|
12
|
+
if (t == null || t === "") return;
|
|
13
|
+
W("property", e).setAttribute("content", String(t));
|
|
14
|
+
}
|
|
15
|
+
function $(e) {
|
|
16
|
+
e && (document.title = e);
|
|
17
|
+
}
|
|
18
|
+
function x(e) {
|
|
19
|
+
if (!e) return;
|
|
20
|
+
let t = document.querySelector('link[rel="canonical"]');
|
|
21
|
+
t || (t = document.createElement("link"), t.setAttribute("rel", "canonical"), document.head.appendChild(t)), t.setAttribute("href", e);
|
|
22
|
+
}
|
|
23
|
+
function P() {
|
|
24
|
+
const e = document.querySelector('link[rel="canonical"]');
|
|
25
|
+
e && e.remove();
|
|
26
|
+
}
|
|
27
|
+
function B(e) {
|
|
28
|
+
e && (e.title && a("og:title", e.title), e.description && a("og:description", e.description), e.type && a("og:type", e.type), e.url && a("og:url", e.url), e.image && a("og:image", e.image), e.imageWidth && a("og:image:width", e.imageWidth), e.imageHeight && a("og:image:height", e.imageHeight), e.imageAlt && a("og:image:alt", e.imageAlt), e.siteName && a("og:site_name", e.siteName), e.locale && a("og:locale", e.locale), Object.keys(e).forEach((t) => {
|
|
29
|
+
if (t.startsWith("og:") && t !== "og:title" && t !== "og:description" && t !== "og:type" && t !== "og:url" && t !== "og:image" && t !== "og:imageWidth" && t !== "og:imageHeight" && t !== "og:imageAlt" && t !== "og:siteName" && t !== "og:locale") {
|
|
30
|
+
const i = e[t];
|
|
31
|
+
i != null && i !== "" && a(t, i);
|
|
32
|
+
}
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
function H(e) {
|
|
36
|
+
e && (e.card && c("twitter:card", e.card), e.site && c("twitter:site", e.site), e.creator && c("twitter:creator", e.creator), e.title && c("twitter:title", e.title), e.description && c("twitter:description", e.description), e.image && c("twitter:image", e.image), e.imageAlt && c("twitter:image:alt", e.imageAlt), Object.keys(e).forEach((t) => {
|
|
37
|
+
if (t.startsWith("twitter:") && t !== "twitter:card" && t !== "twitter:site" && t !== "twitter:creator" && t !== "twitter:title" && t !== "twitter:description" && t !== "twitter:image" && t !== "twitter:imageAlt") {
|
|
38
|
+
const i = e[t];
|
|
39
|
+
i != null && i !== "" && c(t, String(i));
|
|
40
|
+
}
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
function L(e) {
|
|
44
|
+
!e || !Array.isArray(e) || e.forEach((t) => {
|
|
45
|
+
if (!t.content) return;
|
|
46
|
+
let i = null;
|
|
47
|
+
if (t.name)
|
|
48
|
+
i = W("name", t.name);
|
|
49
|
+
else if (t.property)
|
|
50
|
+
i = W("property", t.property);
|
|
51
|
+
else if (t.httpEquiv) {
|
|
52
|
+
const n = `meta[http-equiv="${t.httpEquiv}"]`;
|
|
53
|
+
i = document.querySelector(n), i || (i = document.createElement("meta"), i.setAttribute("http-equiv", t.httpEquiv), document.head.appendChild(i));
|
|
54
|
+
} else if (t.charset) {
|
|
55
|
+
let n = document.querySelector("meta[charset]");
|
|
56
|
+
n || (n = document.createElement("meta"), n.setAttribute("charset", t.charset), document.head.insertBefore(n, document.head.firstChild));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
i && i.setAttribute("content", t.content);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function G(e, t) {
|
|
63
|
+
const i = document.createElement("script");
|
|
64
|
+
i.type = "application/ld+json";
|
|
65
|
+
{
|
|
66
|
+
i.id = t;
|
|
67
|
+
const n = document.getElementById(t);
|
|
68
|
+
n && n.remove();
|
|
69
|
+
}
|
|
70
|
+
return i.textContent = JSON.stringify(e), document.head.appendChild(i), () => {
|
|
71
|
+
i.remove();
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function z(e) {
|
|
75
|
+
const t = document.getElementById(e);
|
|
76
|
+
t && t.getAttribute("type") === "application/ld+json" && t.remove();
|
|
77
|
+
}
|
|
78
|
+
function K(e) {
|
|
79
|
+
return e ? Array.isArray(e) ? e.join(", ") : e : "";
|
|
80
|
+
}
|
|
81
|
+
const F = "react-client-seo-jsonld";
|
|
82
|
+
function R({
|
|
83
|
+
title: e,
|
|
84
|
+
description: t,
|
|
85
|
+
keywords: i,
|
|
86
|
+
canonical: n,
|
|
87
|
+
ogImage: r,
|
|
88
|
+
ogType: C,
|
|
89
|
+
ogUrl: u,
|
|
90
|
+
ogTitle: m,
|
|
91
|
+
ogDescription: d,
|
|
92
|
+
ogSiteName: p,
|
|
93
|
+
ogLocale: h,
|
|
94
|
+
twitterCard: g,
|
|
95
|
+
twitterSite: A,
|
|
96
|
+
twitterCreator: b,
|
|
97
|
+
twitterTitle: v,
|
|
98
|
+
twitterDescription: E,
|
|
99
|
+
twitterImage: S,
|
|
100
|
+
twitterImageAlt: j,
|
|
101
|
+
jsonLd: y,
|
|
102
|
+
customMeta: O,
|
|
103
|
+
openGraph: N,
|
|
104
|
+
twitter: D
|
|
105
|
+
}) {
|
|
106
|
+
return M(() => {
|
|
107
|
+
e && $(e), t && c("description", t);
|
|
108
|
+
const J = K(i);
|
|
109
|
+
J && c("keywords", J), n && x(n);
|
|
110
|
+
const f = {
|
|
111
|
+
...N
|
|
112
|
+
};
|
|
113
|
+
(m || e) && (f.title = m || e), (d || t) && (f.description = d || t), C && (f.type = C), (u || n) && (f.url = u || n), r && (f.image = r), p && (f.siteName = p), h && (f.locale = h), Object.keys(f).length > 0 && B(f);
|
|
114
|
+
const o = {
|
|
115
|
+
...D
|
|
116
|
+
};
|
|
117
|
+
g && (o.card = g), A && (o.site = A), b && (o.creator = b), (v || e) && (o.title = v || e), (E || t) && (o.description = E || t), (S || r) && (o.image = S || r), j && (o.imageAlt = j), Object.keys(o).length > 0 && H(o), O && L(O);
|
|
118
|
+
let q = null;
|
|
119
|
+
return y && (q = G(y, F)), () => {
|
|
120
|
+
q && q();
|
|
121
|
+
};
|
|
122
|
+
}, [
|
|
123
|
+
e,
|
|
124
|
+
t,
|
|
125
|
+
i,
|
|
126
|
+
n,
|
|
127
|
+
r,
|
|
128
|
+
C,
|
|
129
|
+
u,
|
|
130
|
+
m,
|
|
131
|
+
d,
|
|
132
|
+
p,
|
|
133
|
+
h,
|
|
134
|
+
g,
|
|
135
|
+
A,
|
|
136
|
+
b,
|
|
137
|
+
v,
|
|
138
|
+
E,
|
|
139
|
+
S,
|
|
140
|
+
j,
|
|
141
|
+
y,
|
|
142
|
+
O,
|
|
143
|
+
N,
|
|
144
|
+
D
|
|
145
|
+
]), null;
|
|
146
|
+
}
|
|
147
|
+
const T = "react-client-seo-jsonld";
|
|
148
|
+
function V() {
|
|
149
|
+
const e = _((i) => {
|
|
150
|
+
const {
|
|
151
|
+
title: n,
|
|
152
|
+
description: r,
|
|
153
|
+
keywords: C,
|
|
154
|
+
canonical: u,
|
|
155
|
+
ogImage: m,
|
|
156
|
+
ogType: d,
|
|
157
|
+
ogUrl: p,
|
|
158
|
+
ogTitle: h,
|
|
159
|
+
ogDescription: g,
|
|
160
|
+
ogSiteName: A,
|
|
161
|
+
ogLocale: b,
|
|
162
|
+
twitterCard: v,
|
|
163
|
+
twitterSite: E,
|
|
164
|
+
twitterCreator: S,
|
|
165
|
+
twitterTitle: j,
|
|
166
|
+
twitterDescription: y,
|
|
167
|
+
twitterImage: O,
|
|
168
|
+
twitterImageAlt: N,
|
|
169
|
+
jsonLd: D,
|
|
170
|
+
customMeta: J,
|
|
171
|
+
openGraph: f,
|
|
172
|
+
twitter: o
|
|
173
|
+
} = i;
|
|
174
|
+
n && $(n), r && c("description", r);
|
|
175
|
+
const q = K(C);
|
|
176
|
+
q && c("keywords", q), u && x(u);
|
|
177
|
+
const l = {
|
|
178
|
+
...f
|
|
179
|
+
};
|
|
180
|
+
(h || n) && (l.title = h || n), (g || r) && (l.description = g || r), d && (l.type = d), (p || u) && (l.url = p || u), m && (l.image = m), A && (l.siteName = A), b && (l.locale = b), Object.keys(l).length > 0 && B(l);
|
|
181
|
+
const s = {
|
|
182
|
+
...o
|
|
183
|
+
};
|
|
184
|
+
v && (s.card = v), E && (s.site = E), S && (s.creator = S), (j || n) && (s.title = j || n), (y || r) && (s.description = y || r), (O || m) && (s.image = O || m), N && (s.imageAlt = N), Object.keys(s).length > 0 && H(s), J && L(J), D && G(D, T);
|
|
185
|
+
}, []), t = _(() => {
|
|
186
|
+
P(), z(T);
|
|
187
|
+
}, []);
|
|
188
|
+
return {
|
|
189
|
+
updateSeo: e,
|
|
190
|
+
clearSeo: t
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
export {
|
|
194
|
+
R as Seo,
|
|
195
|
+
V as useSeo
|
|
196
|
+
};
|
|
197
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/utils.ts","../src/Seo.tsx","../src/useSeo.ts"],"sourcesContent":["import type { CustomMeta, JsonLd, OpenGraphMeta, TwitterCardMeta } from './types';\r\n\r\n/**\r\n * Get or create a meta tag element\r\n */\r\nfunction getOrCreateMetaTag(attribute: 'name' | 'property', value: string): HTMLMetaElement {\r\n const selector = `meta[${attribute}=\"${value}\"]`;\r\n let element = document.querySelector<HTMLMetaElement>(selector);\r\n\r\n if (!element) {\r\n element = document.createElement('meta');\r\n element.setAttribute(attribute, value);\r\n document.head.appendChild(element);\r\n }\r\n\r\n return element;\r\n}\r\n\r\n/**\r\n * Set a meta tag by name\r\n */\r\nexport function setMetaTag(name: string, content: string): void {\r\n if (!content) return;\r\n\r\n const element = getOrCreateMetaTag('name', name);\r\n element.setAttribute('content', content);\r\n}\r\n\r\n/**\r\n * Set a meta tag by property (for Open Graph)\r\n */\r\nexport function setMetaProperty(property: string, content: string | number): void {\r\n if (content === undefined || content === null || content === '') return;\r\n\r\n const element = getOrCreateMetaTag('property', property);\r\n element.setAttribute('content', String(content));\r\n}\r\n\r\n/**\r\n * Set the page title\r\n */\r\nexport function setTitle(title: string): void {\r\n if (!title) return;\r\n document.title = title;\r\n}\r\n\r\n/**\r\n * Set canonical URL\r\n */\r\nexport function setCanonical(url: string): void {\r\n if (!url) return;\r\n\r\n let link = document.querySelector<HTMLLinkElement>('link[rel=\"canonical\"]');\r\n if (!link) {\r\n link = document.createElement('link');\r\n link.setAttribute('rel', 'canonical');\r\n document.head.appendChild(link);\r\n }\r\n link.setAttribute('href', url);\r\n}\r\n\r\n/**\r\n * Remove canonical URL\r\n */\r\nexport function removeCanonical(): void {\r\n const link = document.querySelector<HTMLLinkElement>('link[rel=\"canonical\"]');\r\n if (link) {\r\n link.remove();\r\n }\r\n}\r\n\r\n/**\r\n * Set Open Graph meta tags\r\n */\r\nexport function setOpenGraphTags(og: OpenGraphMeta): void {\r\n if (!og) return;\r\n\r\n // Standard Open Graph properties\r\n if (og.title) setMetaProperty('og:title', og.title);\r\n if (og.description) setMetaProperty('og:description', og.description);\r\n if (og.type) setMetaProperty('og:type', og.type);\r\n if (og.url) setMetaProperty('og:url', og.url);\r\n if (og.image) setMetaProperty('og:image', og.image);\r\n if (og.imageWidth) setMetaProperty('og:image:width', og.imageWidth);\r\n if (og.imageHeight) setMetaProperty('og:image:height', og.imageHeight);\r\n if (og.imageAlt) setMetaProperty('og:image:alt', og.imageAlt);\r\n if (og.siteName) setMetaProperty('og:site_name', og.siteName);\r\n if (og.locale) setMetaProperty('og:locale', og.locale);\r\n\r\n // Handle additional custom og:* properties\r\n Object.keys(og).forEach((key) => {\r\n if (key.startsWith('og:') && key !== 'og:title' && key !== 'og:description' && \r\n key !== 'og:type' && key !== 'og:url' && key !== 'og:image' && \r\n key !== 'og:imageWidth' && key !== 'og:imageHeight' && \r\n key !== 'og:imageAlt' && key !== 'og:siteName' && key !== 'og:locale') {\r\n const value = og[key as keyof OpenGraphMeta];\r\n if (value !== undefined && value !== null && value !== '') {\r\n setMetaProperty(key, value as string | number);\r\n }\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Set Twitter Card meta tags\r\n */\r\nexport function setTwitterCardTags(twitter: TwitterCardMeta): void {\r\n if (!twitter) return;\r\n\r\n if (twitter.card) setMetaTag('twitter:card', twitter.card);\r\n if (twitter.site) setMetaTag('twitter:site', twitter.site);\r\n if (twitter.creator) setMetaTag('twitter:creator', twitter.creator);\r\n if (twitter.title) setMetaTag('twitter:title', twitter.title);\r\n if (twitter.description) setMetaTag('twitter:description', twitter.description);\r\n if (twitter.image) setMetaTag('twitter:image', twitter.image);\r\n if (twitter.imageAlt) setMetaTag('twitter:image:alt', twitter.imageAlt);\r\n\r\n // Handle additional custom twitter:* properties\r\n Object.keys(twitter).forEach((key) => {\r\n if (key.startsWith('twitter:') && key !== 'twitter:card' && key !== 'twitter:site' && \r\n key !== 'twitter:creator' && key !== 'twitter:title' && \r\n key !== 'twitter:description' && key !== 'twitter:image' && \r\n key !== 'twitter:imageAlt') {\r\n const value = twitter[key as keyof TwitterCardMeta];\r\n if (value !== undefined && value !== null && value !== '') {\r\n setMetaTag(key, String(value));\r\n }\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Set custom meta tags\r\n */\r\nexport function setCustomMetaTags(customMeta: CustomMeta[]): void {\r\n if (!customMeta || !Array.isArray(customMeta)) return;\r\n\r\n customMeta.forEach((meta) => {\r\n if (!meta.content) return;\r\n\r\n let element: HTMLMetaElement | null = null;\r\n\r\n if (meta.name) {\r\n element = getOrCreateMetaTag('name', meta.name);\r\n } else if (meta.property) {\r\n element = getOrCreateMetaTag('property', meta.property);\r\n } else if (meta.httpEquiv) {\r\n const selector = `meta[http-equiv=\"${meta.httpEquiv}\"]`;\r\n element = document.querySelector<HTMLMetaElement>(selector);\r\n if (!element) {\r\n element = document.createElement('meta');\r\n element.setAttribute('http-equiv', meta.httpEquiv);\r\n document.head.appendChild(element);\r\n }\r\n } else if (meta.charset) {\r\n let charsetElement = document.querySelector<HTMLMetaElement>('meta[charset]');\r\n if (!charsetElement) {\r\n charsetElement = document.createElement('meta');\r\n charsetElement.setAttribute('charset', meta.charset);\r\n document.head.insertBefore(charsetElement, document.head.firstChild);\r\n }\r\n return;\r\n }\r\n\r\n if (element) {\r\n element.setAttribute('content', meta.content);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Inject JSON-LD structured data\r\n */\r\nexport function injectJsonLd(data: JsonLd, id?: string): () => void {\r\n const script = document.createElement('script');\r\n script.type = 'application/ld+json';\r\n if (id) {\r\n script.id = id;\r\n // Remove existing script with same id\r\n const existing = document.getElementById(id);\r\n if (existing) {\r\n existing.remove();\r\n }\r\n }\r\n script.textContent = JSON.stringify(data);\r\n document.head.appendChild(script);\r\n\r\n // Return cleanup function\r\n return () => {\r\n script.remove();\r\n };\r\n}\r\n\r\n/**\r\n * Remove JSON-LD script by id\r\n */\r\nexport function removeJsonLd(id: string): void {\r\n const script = document.getElementById(id);\r\n if (script && script.getAttribute('type') === 'application/ld+json') {\r\n script.remove();\r\n }\r\n}\r\n\r\n/**\r\n * Format keywords as string\r\n */\r\nexport function formatKeywords(keywords: string | string[] | undefined): string {\r\n if (!keywords) return '';\r\n if (Array.isArray(keywords)) {\r\n return keywords.join(', ');\r\n }\r\n return keywords;\r\n}\r\n\r\n","import { useEffect } from 'react';\r\nimport type { SeoProps } from './types';\r\nimport {\r\n setTitle,\r\n setMetaTag,\r\n setCanonical,\r\n setOpenGraphTags,\r\n setTwitterCardTags,\r\n setCustomMetaTags,\r\n injectJsonLd,\r\n formatKeywords,\r\n} from './utils';\r\n\r\nconst JSON_LD_ID = 'react-client-seo-jsonld';\r\n\r\n/**\r\n * SEO Component\r\n * \r\n * Renders and updates SEO meta tags in the document head.\r\n * Returns null (no UI rendering).\r\n * \r\n * @example\r\n * ```tsx\r\n * <Seo\r\n * title=\"My Page Title\"\r\n * description=\"Page description\"\r\n * keywords={['react', 'seo']}\r\n * canonical=\"https://example.com/page\"\r\n * ogImage=\"https://example.com/image.jpg\"\r\n * jsonLd={{ \"@context\": \"https://schema.org\", \"@type\": \"WebPage\" }}\r\n * />\r\n * ```\r\n */\r\nexport function Seo({\r\n title,\r\n description,\r\n keywords,\r\n canonical,\r\n ogImage,\r\n ogType,\r\n ogUrl,\r\n ogTitle,\r\n ogDescription,\r\n ogSiteName,\r\n ogLocale,\r\n twitterCard,\r\n twitterSite,\r\n twitterCreator,\r\n twitterTitle,\r\n twitterDescription,\r\n twitterImage,\r\n twitterImageAlt,\r\n jsonLd,\r\n customMeta,\r\n openGraph,\r\n twitter,\r\n}: SeoProps) {\r\n useEffect(() => {\r\n // Set title\r\n if (title) {\r\n setTitle(title);\r\n }\r\n\r\n // Set description\r\n if (description) {\r\n setMetaTag('description', description);\r\n }\r\n\r\n // Set keywords\r\n const keywordsStr = formatKeywords(keywords);\r\n if (keywordsStr) {\r\n setMetaTag('keywords', keywordsStr);\r\n }\r\n\r\n // Set canonical URL\r\n if (canonical) {\r\n setCanonical(canonical);\r\n }\r\n\r\n // Build Open Graph object\r\n const ogData: any = {\r\n ...openGraph,\r\n };\r\n\r\n if (ogTitle || title) {\r\n ogData.title = ogTitle || title;\r\n }\r\n if (ogDescription || description) {\r\n ogData.description = ogDescription || description;\r\n }\r\n if (ogType) {\r\n ogData.type = ogType;\r\n }\r\n if (ogUrl || canonical) {\r\n ogData.url = ogUrl || canonical;\r\n }\r\n if (ogImage) {\r\n ogData.image = ogImage;\r\n }\r\n if (ogSiteName) {\r\n ogData.siteName = ogSiteName;\r\n }\r\n if (ogLocale) {\r\n ogData.locale = ogLocale;\r\n }\r\n\r\n if (Object.keys(ogData).length > 0) {\r\n setOpenGraphTags(ogData);\r\n }\r\n\r\n // Build Twitter Card object\r\n const twitterData: any = {\r\n ...twitter,\r\n };\r\n\r\n if (twitterCard) {\r\n twitterData.card = twitterCard;\r\n }\r\n if (twitterSite) {\r\n twitterData.site = twitterSite;\r\n }\r\n if (twitterCreator) {\r\n twitterData.creator = twitterCreator;\r\n }\r\n if (twitterTitle || title) {\r\n twitterData.title = twitterTitle || title;\r\n }\r\n if (twitterDescription || description) {\r\n twitterData.description = twitterDescription || description;\r\n }\r\n if (twitterImage || ogImage) {\r\n twitterData.image = twitterImage || ogImage;\r\n }\r\n if (twitterImageAlt) {\r\n twitterData.imageAlt = twitterImageAlt;\r\n }\r\n\r\n if (Object.keys(twitterData).length > 0) {\r\n setTwitterCardTags(twitterData);\r\n }\r\n\r\n // Set custom meta tags\r\n if (customMeta) {\r\n setCustomMetaTags(customMeta);\r\n }\r\n\r\n // Inject JSON-LD\r\n let cleanupJsonLd: (() => void) | null = null;\r\n if (jsonLd) {\r\n cleanupJsonLd = injectJsonLd(jsonLd, JSON_LD_ID);\r\n }\r\n\r\n // Cleanup function\r\n return () => {\r\n if (cleanupJsonLd) {\r\n cleanupJsonLd();\r\n }\r\n };\r\n }, [\r\n title,\r\n description,\r\n keywords,\r\n canonical,\r\n ogImage,\r\n ogType,\r\n ogUrl,\r\n ogTitle,\r\n ogDescription,\r\n ogSiteName,\r\n ogLocale,\r\n twitterCard,\r\n twitterSite,\r\n twitterCreator,\r\n twitterTitle,\r\n twitterDescription,\r\n twitterImage,\r\n twitterImageAlt,\r\n jsonLd,\r\n customMeta,\r\n openGraph,\r\n twitter,\r\n ]);\r\n\r\n return null;\r\n}\r\n\r\n","import { useCallback } from 'react';\r\nimport type { SeoProps, UseSeoReturn } from './types';\r\nimport {\r\n setTitle,\r\n setMetaTag,\r\n setCanonical,\r\n setOpenGraphTags,\r\n setTwitterCardTags,\r\n setCustomMetaTags,\r\n injectJsonLd,\r\n formatKeywords,\r\n removeCanonical,\r\n removeJsonLd,\r\n} from './utils';\r\n\r\nconst JSON_LD_ID = 'react-client-seo-jsonld';\r\n\r\n/**\r\n * useSeo Hook\r\n * \r\n * Hook-based API for managing SEO meta tags.\r\n * \r\n * @example\r\n * ```tsx\r\n * function MyComponent() {\r\n * const { updateSeo } = useSeo();\r\n * \r\n * useEffect(() => {\r\n * updateSeo({\r\n * title: 'My Page',\r\n * description: 'Page description',\r\n * });\r\n * }, []);\r\n * \r\n * return <div>Content</div>;\r\n * }\r\n * ```\r\n */\r\nexport function useSeo(): UseSeoReturn {\r\n const updateSeo = useCallback((props: SeoProps) => {\r\n const {\r\n title,\r\n description,\r\n keywords,\r\n canonical,\r\n ogImage,\r\n ogType,\r\n ogUrl,\r\n ogTitle,\r\n ogDescription,\r\n ogSiteName,\r\n ogLocale,\r\n twitterCard,\r\n twitterSite,\r\n twitterCreator,\r\n twitterTitle,\r\n twitterDescription,\r\n twitterImage,\r\n twitterImageAlt,\r\n jsonLd,\r\n customMeta,\r\n openGraph,\r\n twitter,\r\n } = props;\r\n\r\n // Set title\r\n if (title) {\r\n setTitle(title);\r\n }\r\n\r\n // Set description\r\n if (description) {\r\n setMetaTag('description', description);\r\n }\r\n\r\n // Set keywords\r\n const keywordsStr = formatKeywords(keywords);\r\n if (keywordsStr) {\r\n setMetaTag('keywords', keywordsStr);\r\n }\r\n\r\n // Set canonical URL\r\n if (canonical) {\r\n setCanonical(canonical);\r\n }\r\n\r\n // Build Open Graph object\r\n const ogData: any = {\r\n ...openGraph,\r\n };\r\n\r\n if (ogTitle || title) {\r\n ogData.title = ogTitle || title;\r\n }\r\n if (ogDescription || description) {\r\n ogData.description = ogDescription || description;\r\n }\r\n if (ogType) {\r\n ogData.type = ogType;\r\n }\r\n if (ogUrl || canonical) {\r\n ogData.url = ogUrl || canonical;\r\n }\r\n if (ogImage) {\r\n ogData.image = ogImage;\r\n }\r\n if (ogSiteName) {\r\n ogData.siteName = ogSiteName;\r\n }\r\n if (ogLocale) {\r\n ogData.locale = ogLocale;\r\n }\r\n\r\n if (Object.keys(ogData).length > 0) {\r\n setOpenGraphTags(ogData);\r\n }\r\n\r\n // Build Twitter Card object\r\n const twitterData: any = {\r\n ...twitter,\r\n };\r\n\r\n if (twitterCard) {\r\n twitterData.card = twitterCard;\r\n }\r\n if (twitterSite) {\r\n twitterData.site = twitterSite;\r\n }\r\n if (twitterCreator) {\r\n twitterData.creator = twitterCreator;\r\n }\r\n if (twitterTitle || title) {\r\n twitterData.title = twitterTitle || title;\r\n }\r\n if (twitterDescription || description) {\r\n twitterData.description = twitterDescription || description;\r\n }\r\n if (twitterImage || ogImage) {\r\n twitterData.image = twitterImage || ogImage;\r\n }\r\n if (twitterImageAlt) {\r\n twitterData.imageAlt = twitterImageAlt;\r\n }\r\n\r\n if (Object.keys(twitterData).length > 0) {\r\n setTwitterCardTags(twitterData);\r\n }\r\n\r\n // Set custom meta tags\r\n if (customMeta) {\r\n setCustomMetaTags(customMeta);\r\n }\r\n\r\n // Inject JSON-LD\r\n if (jsonLd) {\r\n injectJsonLd(jsonLd, JSON_LD_ID);\r\n }\r\n }, []);\r\n\r\n const clearSeo = useCallback(() => {\r\n // Note: We don't clear title, description, keywords as they might be set by other means\r\n // Only clear what we manage\r\n removeCanonical();\r\n removeJsonLd(JSON_LD_ID);\r\n }, []);\r\n\r\n return {\r\n updateSeo,\r\n clearSeo,\r\n };\r\n}\r\n\r\n"],"names":["getOrCreateMetaTag","attribute","value","selector","element","setMetaTag","name","content","setMetaProperty","property","setTitle","title","setCanonical","url","link","removeCanonical","setOpenGraphTags","og","key","setTwitterCardTags","twitter","setCustomMetaTags","customMeta","meta","charsetElement","injectJsonLd","data","id","script","existing","removeJsonLd","formatKeywords","keywords","JSON_LD_ID","Seo","description","canonical","ogImage","ogType","ogUrl","ogTitle","ogDescription","ogSiteName","ogLocale","twitterCard","twitterSite","twitterCreator","twitterTitle","twitterDescription","twitterImage","twitterImageAlt","jsonLd","openGraph","useEffect","keywordsStr","ogData","twitterData","cleanupJsonLd","useSeo","updateSeo","useCallback","props","clearSeo"],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Graph metadata
|
|
3
|
+
*/
|
|
4
|
+
export interface OpenGraphMeta {
|
|
5
|
+
title?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
type?: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
image?: string;
|
|
10
|
+
imageWidth?: string | number;
|
|
11
|
+
imageHeight?: string | number;
|
|
12
|
+
imageAlt?: string;
|
|
13
|
+
siteName?: string;
|
|
14
|
+
locale?: string;
|
|
15
|
+
[key: `og:${string}`]: string | number | undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Twitter Card metadata
|
|
19
|
+
*/
|
|
20
|
+
export interface TwitterCardMeta {
|
|
21
|
+
card?: 'summary' | 'summary_large_image' | 'app' | 'player';
|
|
22
|
+
site?: string;
|
|
23
|
+
creator?: string;
|
|
24
|
+
title?: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
image?: string;
|
|
27
|
+
imageAlt?: string;
|
|
28
|
+
[key: `twitter:${string}`]: string | number | undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Custom meta tag
|
|
32
|
+
*/
|
|
33
|
+
export interface CustomMeta {
|
|
34
|
+
name?: string;
|
|
35
|
+
property?: string;
|
|
36
|
+
content: string;
|
|
37
|
+
httpEquiv?: string;
|
|
38
|
+
charset?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* JSON-LD structured data
|
|
42
|
+
*/
|
|
43
|
+
export type JsonLd = Record<string, any> | Array<Record<string, any>>;
|
|
44
|
+
/**
|
|
45
|
+
* SEO component props
|
|
46
|
+
*/
|
|
47
|
+
export interface SeoProps {
|
|
48
|
+
/** Page title */
|
|
49
|
+
title?: string;
|
|
50
|
+
/** Meta description */
|
|
51
|
+
description?: string;
|
|
52
|
+
/** Meta keywords (comma-separated string or array) */
|
|
53
|
+
keywords?: string | string[];
|
|
54
|
+
/** Canonical URL */
|
|
55
|
+
canonical?: string;
|
|
56
|
+
/** Open Graph metadata */
|
|
57
|
+
ogImage?: string;
|
|
58
|
+
ogType?: string;
|
|
59
|
+
ogUrl?: string;
|
|
60
|
+
ogTitle?: string;
|
|
61
|
+
ogDescription?: string;
|
|
62
|
+
ogSiteName?: string;
|
|
63
|
+
ogLocale?: string;
|
|
64
|
+
/** Twitter Card metadata */
|
|
65
|
+
twitterCard?: TwitterCardMeta['card'];
|
|
66
|
+
twitterSite?: string;
|
|
67
|
+
twitterCreator?: string;
|
|
68
|
+
twitterTitle?: string;
|
|
69
|
+
twitterDescription?: string;
|
|
70
|
+
twitterImage?: string;
|
|
71
|
+
twitterImageAlt?: string;
|
|
72
|
+
/** JSON-LD structured data */
|
|
73
|
+
jsonLd?: JsonLd;
|
|
74
|
+
/** Custom meta tags */
|
|
75
|
+
customMeta?: CustomMeta[];
|
|
76
|
+
/** Additional Open Graph properties */
|
|
77
|
+
openGraph?: OpenGraphMeta;
|
|
78
|
+
/** Additional Twitter Card properties */
|
|
79
|
+
twitter?: TwitterCardMeta;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Hook return type
|
|
83
|
+
*/
|
|
84
|
+
export interface UseSeoReturn {
|
|
85
|
+
updateSeo: (props: SeoProps) => void;
|
|
86
|
+
clearSeo: () => void;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/useSeo.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { UseSeoReturn } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useSeo Hook
|
|
5
|
+
*
|
|
6
|
+
* Hook-based API for managing SEO meta tags.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* function MyComponent() {
|
|
11
|
+
* const { updateSeo } = useSeo();
|
|
12
|
+
*
|
|
13
|
+
* useEffect(() => {
|
|
14
|
+
* updateSeo({
|
|
15
|
+
* title: 'My Page',
|
|
16
|
+
* description: 'Page description',
|
|
17
|
+
* });
|
|
18
|
+
* }, []);
|
|
19
|
+
*
|
|
20
|
+
* return <div>Content</div>;
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function useSeo(): UseSeoReturn;
|
|
25
|
+
//# sourceMappingURL=useSeo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSeo.d.ts","sourceRoot":"","sources":["../src/useSeo.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,SAAS,CAAC;AAgBtD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,MAAM,IAAI,YAAY,CAoIrC"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { CustomMeta, JsonLd, OpenGraphMeta, TwitterCardMeta } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Set a meta tag by name
|
|
5
|
+
*/
|
|
6
|
+
export declare function setMetaTag(name: string, content: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* Set a meta tag by property (for Open Graph)
|
|
9
|
+
*/
|
|
10
|
+
export declare function setMetaProperty(property: string, content: string | number): void;
|
|
11
|
+
/**
|
|
12
|
+
* Set the page title
|
|
13
|
+
*/
|
|
14
|
+
export declare function setTitle(title: string): void;
|
|
15
|
+
/**
|
|
16
|
+
* Set canonical URL
|
|
17
|
+
*/
|
|
18
|
+
export declare function setCanonical(url: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Remove canonical URL
|
|
21
|
+
*/
|
|
22
|
+
export declare function removeCanonical(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Set Open Graph meta tags
|
|
25
|
+
*/
|
|
26
|
+
export declare function setOpenGraphTags(og: OpenGraphMeta): void;
|
|
27
|
+
/**
|
|
28
|
+
* Set Twitter Card meta tags
|
|
29
|
+
*/
|
|
30
|
+
export declare function setTwitterCardTags(twitter: TwitterCardMeta): void;
|
|
31
|
+
/**
|
|
32
|
+
* Set custom meta tags
|
|
33
|
+
*/
|
|
34
|
+
export declare function setCustomMetaTags(customMeta: CustomMeta[]): void;
|
|
35
|
+
/**
|
|
36
|
+
* Inject JSON-LD structured data
|
|
37
|
+
*/
|
|
38
|
+
export declare function injectJsonLd(data: JsonLd, id?: string): () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Remove JSON-LD script by id
|
|
41
|
+
*/
|
|
42
|
+
export declare function removeJsonLd(id: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* Format keywords as string
|
|
45
|
+
*/
|
|
46
|
+
export declare function formatKeywords(keywords: string | string[] | undefined): string;
|
|
47
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAkBlF;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAK9D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKhF;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAU9C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,aAAa,GAAG,IAAI,CA2BxD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAuBjE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,IAAI,CAkChE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,IAAI,CAkBlE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAK7C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,CAM9E"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-client-seo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight client-side SEO renderer for React and Vite apps",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "vite build --watch --mode development",
|
|
14
|
+
"build": "tsc && vite build",
|
|
15
|
+
"preview": "vite preview",
|
|
16
|
+
"prepare": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"react",
|
|
20
|
+
"seo",
|
|
21
|
+
"meta-tags",
|
|
22
|
+
"open-graph",
|
|
23
|
+
"twitter-cards",
|
|
24
|
+
"json-ld",
|
|
25
|
+
"vite",
|
|
26
|
+
"client-side",
|
|
27
|
+
"typescript"
|
|
28
|
+
],
|
|
29
|
+
"author": "Rainard Joseph",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": ">=17.0.0",
|
|
33
|
+
"react-dom": ">=17.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/react": "^18.2.0",
|
|
37
|
+
"@types/react-dom": "^18.2.0",
|
|
38
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
39
|
+
"react": "^18.2.0",
|
|
40
|
+
"react-dom": "^18.2.0",
|
|
41
|
+
"typescript": "^5.2.0",
|
|
42
|
+
"vite": "^5.0.0",
|
|
43
|
+
"vite-plugin-dts": "^3.6.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|