@geenius/seo 0.1.0 → 0.3.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/package.json +16 -3
- package/packages/convex/dist/index.d.ts +56 -0
- package/packages/convex/dist/index.js +133 -0
- package/packages/convex/dist/index.js.map +1 -0
- package/packages/react/README.md +1 -1
- package/packages/react/dist/index.d.ts +156 -0
- package/packages/react/dist/index.js +567 -0
- package/packages/react/dist/index.js.map +1 -0
- package/packages/react-css/README.md +1 -1
- package/packages/react-css/dist/index.cjs +571 -0
- package/packages/react-css/dist/index.cjs.map +1 -0
- package/packages/react-css/{src/seo.css → dist/index.css} +7 -153
- package/packages/react-css/dist/index.css.map +1 -0
- package/packages/react-css/dist/index.d.cts +53 -0
- package/packages/react-css/dist/index.d.ts +53 -0
- package/packages/react-css/dist/index.js +539 -0
- package/packages/react-css/dist/index.js.map +1 -0
- package/packages/shared/README.md +1 -1
- package/packages/shared/dist/index.d.ts +262 -0
- package/packages/shared/dist/index.js +381 -0
- package/packages/shared/dist/index.js.map +1 -0
- package/packages/solidjs/README.md +1 -1
- package/packages/solidjs/dist/index.d.ts +133 -0
- package/packages/solidjs/dist/index.js +416 -0
- package/packages/solidjs/dist/index.js.map +1 -0
- package/packages/solidjs-css/README.md +1 -1
- package/packages/solidjs-css/dist/index.cjs +399 -0
- package/packages/solidjs-css/dist/index.cjs.map +1 -0
- package/packages/solidjs-css/{src/seo.css → dist/index.css} +7 -153
- package/packages/solidjs-css/dist/index.css.map +1 -0
- package/packages/solidjs-css/dist/index.d.cts +53 -0
- package/packages/solidjs-css/dist/index.d.ts +53 -0
- package/packages/solidjs-css/dist/index.js +367 -0
- package/packages/solidjs-css/dist/index.js.map +1 -0
- package/.changeset/config.json +0 -11
- package/.github/CODEOWNERS +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/ci.yml +0 -23
- package/.github/workflows/release.yml +0 -29
- package/.nvmrc +0 -1
- package/.project/ACCOUNT.yaml +0 -4
- package/.project/IDEAS.yaml +0 -7
- package/.project/PROJECT.yaml +0 -11
- package/.project/ROADMAP.yaml +0 -15
- package/CODE_OF_CONDUCT.md +0 -16
- package/CONTRIBUTING.md +0 -26
- package/SECURITY.md +0 -15
- package/SUPPORT.md +0 -8
- package/packages/convex/package.json +0 -42
- package/packages/convex/src/functions.ts +0 -5
- package/packages/convex/src/mutations.ts +0 -83
- package/packages/convex/src/queries.ts +0 -57
- package/packages/convex/src/schema.ts +0 -23
- package/packages/convex/tsconfig.json +0 -19
- package/packages/convex/tsup.config.ts +0 -18
- package/packages/react/package.json +0 -49
- package/packages/react/src/components/ArticleJsonLd.tsx +0 -42
- package/packages/react/src/components/BreadcrumbsJsonLd.tsx +0 -24
- package/packages/react/src/components/MetaEditor.tsx +0 -147
- package/packages/react/src/components/SEOHead.tsx +0 -107
- package/packages/react/src/components/SEOPreview.tsx +0 -42
- package/packages/react/src/components/SEOScoreCard.tsx +0 -51
- package/packages/react/src/components/SitemapViewer.tsx +0 -36
- package/packages/react/src/components/index.ts +0 -7
- package/packages/react/src/hooks/index.ts +0 -4
- package/packages/react/src/hooks/useSEO.ts +0 -27
- package/packages/react/src/hooks/useSEOAdmin.ts +0 -42
- package/packages/react/src/hooks/useSEOScore.ts +0 -7
- package/packages/react/src/hooks/useSitemap.ts +0 -8
- package/packages/react/src/index.ts +0 -51
- package/packages/react/src/index.tsx +0 -11
- package/packages/react/src/pages/SEOAdminPage.tsx +0 -101
- package/packages/react/src/pages/SEOAnalyticsPage.tsx +0 -96
- package/packages/react/src/pages/index.ts +0 -2
- package/packages/react/tsconfig.json +0 -19
- package/packages/react/tsup.config.ts +0 -12
- package/packages/react-css/package.json +0 -36
- package/packages/react-css/src/components/ArticleJsonLd.tsx +0 -42
- package/packages/react-css/src/components/BreadcrumbsJsonLd.tsx +0 -24
- package/packages/react-css/src/components/MetaEditor.tsx +0 -147
- package/packages/react-css/src/components/SEOHead.tsx +0 -95
- package/packages/react-css/src/components/SEOPreview.tsx +0 -42
- package/packages/react-css/src/components/SEOScoreCard.tsx +0 -42
- package/packages/react-css/src/components/SitemapViewer.tsx +0 -36
- package/packages/react-css/src/components/index.ts +0 -7
- package/packages/react-css/src/index.ts +0 -9
- package/packages/react-css/src/pages/SEOAdminPage.tsx +0 -88
- package/packages/react-css/src/pages/SEOAnalyticsPage.tsx +0 -82
- package/packages/react-css/src/pages/index.ts +0 -2
- package/packages/react-css/tsup.config.ts +0 -2
- package/packages/shared/package.json +0 -42
- package/packages/shared/src/__tests__/seo.test.ts +0 -70
- package/packages/shared/src/config.ts +0 -297
- package/packages/shared/src/index.ts +0 -207
- package/packages/shared/tsconfig.json +0 -18
- package/packages/shared/tsup.config.ts +0 -11
- package/packages/shared/vitest.config.ts +0 -4
- package/packages/solidjs/package.json +0 -45
- package/packages/solidjs/src/components/ArticleJsonLd.tsx +0 -35
- package/packages/solidjs/src/components/BreadcrumbsJsonLd.tsx +0 -24
- package/packages/solidjs/src/components/MetaEditor.tsx +0 -155
- package/packages/solidjs/src/components/SEOHead.tsx +0 -109
- package/packages/solidjs/src/components/SEOPreview.tsx +0 -42
- package/packages/solidjs/src/components/SEOScoreCard.tsx +0 -57
- package/packages/solidjs/src/components/SitemapViewer.tsx +0 -44
- package/packages/solidjs/src/components/index.ts +0 -7
- package/packages/solidjs/src/index.ts +0 -11
- package/packages/solidjs/src/pages/SEOAdminPage.tsx +0 -104
- package/packages/solidjs/src/pages/SEOAnalyticsPage.tsx +0 -102
- package/packages/solidjs/src/pages/index.ts +0 -2
- package/packages/solidjs/src/primitives/index.ts +0 -4
- package/packages/solidjs/src/primitives/useSEO.ts +0 -27
- package/packages/solidjs/src/primitives/useSEOAdmin.ts +0 -42
- package/packages/solidjs/src/primitives/useSEOScore.ts +0 -7
- package/packages/solidjs/src/primitives/useSitemap.ts +0 -8
- package/packages/solidjs/tsconfig.json +0 -20
- package/packages/solidjs/tsup.config.ts +0 -12
- package/packages/solidjs-css/package.json +0 -35
- package/packages/solidjs-css/src/index.ts +0 -5
- package/packages/solidjs-css/src/primitives/index.ts +0 -1
- package/packages/solidjs-css/tsup.config.ts +0 -2
- package/pnpm-workspace.yaml +0 -2
- package/tsconfig.json +0 -23
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/SEOHead.tsx","../src/components/SEOPreview.tsx","../src/components/SEOScoreCard.tsx","../src/components/MetaEditor.tsx","../src/components/SitemapViewer.tsx","../src/components/BreadcrumbsJsonLd.tsx","../src/components/ArticleJsonLd.tsx","../src/pages/SEOAdminPage.tsx","../src/pages/SEOAnalyticsPage.tsx"],"sourcesContent":["// Re-export shared types, utilities, hooks, and provider from react package\n// (components are overridden by CSS-styled versions below)\nexport {\n // Types\n type OGMeta,\n type TwitterMeta,\n type SEOMeta,\n type SitemapEntry,\n type RobotsTxt,\n type SEOConfig,\n // Utilities\n buildTitle,\n applyTitleTemplate,\n truncateDescription,\n buildCanonical,\n buildAlternates,\n estimateReadingTime,\n calcSEOScore,\n generateRobotsTxt,\n generateSitemapXml,\n articleSchema,\n productSchema,\n orgSchema,\n breadcrumbSchema,\n faqSchema,\n webApplicationSchema,\n // Config\n SEOConfigBuilder,\n createSEOConfig,\n configureSEO,\n PageMetaBuilder,\n createPageMeta,\n validateSEOMeta,\n seoPresets,\n // Provider\n SeoProvider,\n useSEOContext,\n type SeoProviderProps,\n // Hooks\n useSEO,\n useSEOAdmin,\n useSEOScore,\n useSitemap,\n useCanonical,\n useStructuredData,\n} from '@geenius/seo-react'\n\n// CSS-styled components (override headless versions)\nexport * from './components'\nexport * from './pages'\n\n// CSS Styles\nimport './seo.css'\n","import { useEffect } from 'react'\nimport type { SEOMeta } from '@geenius/seo-shared'\n\ninterface SEOHeadProps {\n meta: SEOMeta\n}\n\nexport function SEOHead({ meta }: SEOHeadProps) {\n useEffect(() => {\n document.title = meta.title\n\n const setMetaTag = (name: string, content: string, isProperty = false) => {\n let tag = document.querySelector(\n `meta[${isProperty ? 'property' : 'name'}=\"${name}\"]`,\n ) as HTMLMetaElement\n if (!tag) {\n tag = document.createElement('meta')\n isProperty ? tag.setAttribute('property', name) : tag.setAttribute('name', name)\n document.head.appendChild(tag)\n }\n tag.content = content\n }\n\n setMetaTag('description', meta.description)\n if (meta.keywords.length > 0) {\n setMetaTag('keywords', meta.keywords.join(', '))\n }\n if (meta.robots) {\n setMetaTag('robots', meta.robots)\n }\n\n setMetaTag('og:title', meta.og.title, true)\n setMetaTag('og:description', meta.og.description, true)\n if (meta.og.image) {\n setMetaTag('og:image', meta.og.image, true)\n if (meta.og.imageAlt) {\n setMetaTag('og:image:alt', meta.og.imageAlt, true)\n }\n }\n setMetaTag('og:type', meta.og.type, true)\n setMetaTag('og:url', meta.og.url, true)\n if (meta.og.siteName) {\n setMetaTag('og:site_name', meta.og.siteName, true)\n }\n\n setMetaTag('twitter:card', meta.twitter.card)\n setMetaTag('twitter:title', meta.twitter.title)\n setMetaTag('twitter:description', meta.twitter.description)\n if (meta.twitter.image) {\n setMetaTag('twitter:image', meta.twitter.image)\n }\n if (meta.twitter.creator) {\n setMetaTag('twitter:creator', meta.twitter.creator)\n }\n\n if (meta.canonical) {\n let canonicalLink = document.querySelector('link[rel=\"canonical\"]') as HTMLLinkElement\n if (!canonicalLink) {\n canonicalLink = document.createElement('link')\n canonicalLink.rel = 'canonical'\n document.head.appendChild(canonicalLink)\n }\n canonicalLink.href = meta.canonical\n }\n\n if (meta.alternates) {\n Object.entries(meta.alternates).forEach(([lang, url]) => {\n let altLink = document.querySelector(\n `link[rel=\"alternate\"][hreflang=\"${lang}\"]`,\n ) as HTMLLinkElement\n if (!altLink) {\n altLink = document.createElement('link')\n altLink.rel = 'alternate'\n altLink.setAttribute('hreflang', lang)\n document.head.appendChild(altLink)\n }\n altLink.href = url\n })\n }\n\n if (meta.jsonLd && meta.jsonLd.length > 0) {\n meta.jsonLd.forEach((schema) => {\n let script = document.querySelector('script[type=\"application/ld+json\"]') as HTMLScriptElement\n if (!script) {\n script = document.createElement('script')\n script.type = 'application/ld+json'\n document.head.appendChild(script)\n }\n script.textContent = JSON.stringify(schema)\n })\n }\n }, [meta])\n\n return <div className=\"seo__head\" />\n}\n","import type { SEOMeta } from '@geenius/seo-shared'\n\ninterface SEOPreviewProps {\n meta: SEOMeta\n}\n\nexport function SEOPreview({ meta }: SEOPreviewProps) {\n return (\n <div className=\"seo__preview\">\n <div>\n <h3 style={{ fontSize: '0.875rem', fontWeight: 600, marginBottom: '0.5rem' }}>\n Google Search Preview\n </h3>\n <div className=\"seo__preview-google\">\n <div className=\"seo__preview-google-url\">{new URL(meta.og.url).hostname}</div>\n <div className=\"seo__preview-google-title\">{meta.title}</div>\n <div className=\"seo__preview-google-desc\">{meta.description}</div>\n </div>\n </div>\n\n <div>\n <h3 style={{ fontSize: '0.875rem', fontWeight: 600, marginBottom: '0.5rem' }}>\n Social Media Preview\n </h3>\n <div className=\"seo__preview-social\">\n {meta.og.image && (\n <img\n src={meta.og.image}\n alt={meta.og.imageAlt || meta.og.title}\n className=\"seo__preview-social-image\"\n />\n )}\n <div className=\"seo__preview-social-info\">\n <div className=\"seo__preview-social-title\">{meta.og.title}</div>\n <div className=\"seo__preview-social-desc\">{meta.og.description}</div>\n <div className=\"seo__preview-social-url\">{meta.og.url}</div>\n </div>\n </div>\n </div>\n </div>\n )\n}\n","import { useSEOScore } from '@geenius/seo-react'\nimport type { SEOMeta } from '@geenius/seo-shared'\n\ninterface SEOScoreCardProps {\n meta: SEOMeta\n}\n\nexport function SEOScoreCard({ meta }: SEOScoreCardProps) {\n const { score, issues } = useSEOScore(meta)\n\n const scoreClass =\n score >= 80 ? 'seo__score-number--good' : score >= 50 ? 'seo__score-number--fair' : 'seo__score-number--poor'\n const barClass =\n score >= 80 ? 'seo__score-bar-fill--good' : score >= 50 ? 'seo__score-bar-fill--fair' : 'seo__score-bar-fill--poor'\n\n return (\n <div className=\"seo__score-card\">\n <div className=\"seo__score-card-header\">\n <div className=\"seo__score-card-title\">SEO Score</div>\n <span className={`seo__score-number ${scoreClass}`}>{score}/100</span>\n </div>\n\n <div className=\"seo__score-bar\">\n <div className={`seo__score-bar-fill ${barClass}`} style={{ width: `${score}%` }} />\n </div>\n\n {issues.length > 0 && (\n <div>\n <div className=\"seo__issue-list-title\">Issues Found</div>\n <ul className=\"seo__issue-list\">\n {issues.map((issue, i) => (\n <li key={i} className=\"seo__issue-item\">\n <span className=\"seo__issue-item-bullet\">•</span>\n {issue}\n </li>\n ))}\n </ul>\n </div>\n )}\n </div>\n )\n}\n","import { useState } from 'react'\nimport type { SEOMeta } from '@geenius/seo-shared'\n\ninterface MetaEditorProps {\n meta: SEOMeta\n onChange: (meta: SEOMeta) => void\n}\n\nexport function MetaEditor({ meta, onChange }: MetaEditorProps) {\n const [localMeta, setLocalMeta] = useState(meta)\n\n const handleChange = (updates: Partial<SEOMeta>) => {\n const updated = { ...localMeta, ...updates }\n setLocalMeta(updated)\n onChange(updated)\n }\n\n return (\n <div className=\"seo__meta-editor\">\n <div className=\"seo__meta-field\">\n <label className=\"seo__meta-field-label\">\n Title ({localMeta.title.length}/60)\n </label>\n <input\n type=\"text\"\n value={localMeta.title}\n onChange={(e) =>\n handleChange({\n title: e.target.value.slice(0, 60),\n })\n }\n className=\"seo__meta-field-input\"\n placeholder=\"Page title\"\n />\n <div className=\"seo__meta-char-count\">\n {localMeta.title.length < 30\n ? 'Too short'\n : localMeta.title.length > 60\n ? 'Too long'\n : 'Good length'}\n </div>\n </div>\n\n <div className=\"seo__meta-field\">\n <label className=\"seo__meta-field-label\">\n Description ({localMeta.description.length}/160)\n </label>\n <textarea\n value={localMeta.description}\n onChange={(e) =>\n handleChange({\n description: e.target.value.slice(0, 160),\n })\n }\n className=\"seo__meta-field-textarea\"\n placeholder=\"Page description\"\n />\n <div className=\"seo__meta-char-count\">\n {localMeta.description.length < 50\n ? 'Too short'\n : localMeta.description.length > 160\n ? 'Too long'\n : 'Good length'}\n </div>\n </div>\n\n <div className=\"seo__meta-field\">\n <label className=\"seo__meta-field-label\">Keywords</label>\n <input\n type=\"text\"\n value={localMeta.keywords.join(', ')}\n onChange={(e) =>\n handleChange({\n keywords: e.target.value.split(',').map((k) => k.trim()),\n })\n }\n className=\"seo__meta-field-input\"\n placeholder=\"keyword1, keyword2, keyword3\"\n />\n </div>\n\n <div className=\"seo__meta-field\">\n <label className=\"seo__meta-field-label\">Canonical URL</label>\n <input\n type=\"url\"\n value={localMeta.canonical || ''}\n onChange={(e) =>\n handleChange({\n canonical: e.target.value,\n })\n }\n className=\"seo__meta-field-input\"\n placeholder=\"https://example.com/page\"\n />\n </div>\n\n <div className=\"seo__meta-field\">\n <label className=\"seo__meta-field-label\">OG Image URL</label>\n <input\n type=\"url\"\n value={localMeta.og.image || ''}\n onChange={(e) =>\n handleChange({\n og: { ...localMeta.og, image: e.target.value },\n })\n }\n className=\"seo__meta-field-input\"\n placeholder=\"https://example.com/image.jpg\"\n />\n </div>\n\n <div className=\"seo__meta-field\">\n <label className=\"seo__meta-field-label\">Twitter Card Type</label>\n <select\n value={localMeta.twitter.card}\n onChange={(e) =>\n handleChange({\n twitter: {\n ...localMeta.twitter,\n card: e.target.value as 'summary' | 'summary_large_image',\n },\n })\n }\n className=\"seo__meta-field-select\"\n >\n <option value=\"summary\">Summary</option>\n <option value=\"summary_large_image\">Summary Large Image</option>\n </select>\n </div>\n\n <div className=\"seo__meta-field\">\n <label className=\"seo__meta-field-label\">Robots Directive</label>\n <input\n type=\"text\"\n value={localMeta.robots || ''}\n onChange={(e) =>\n handleChange({\n robots: e.target.value,\n })\n }\n className=\"seo__meta-field-input\"\n placeholder=\"index, follow\"\n />\n </div>\n </div>\n )\n}\n","import type { SitemapEntry } from '@geenius/seo-shared'\n\ninterface SitemapViewerProps {\n entries: SitemapEntry[]\n}\n\nexport function SitemapViewer({ entries }: SitemapViewerProps) {\n return (\n <div>\n <table className=\"seo__sitemap-table\">\n <thead className=\"seo__sitemap-table-head\">\n <tr>\n <th className=\"seo__sitemap-table-header\">URL</th>\n <th className=\"seo__sitemap-table-header\">Last Modified</th>\n <th className=\"seo__sitemap-table-header\">Change Frequency</th>\n <th className=\"seo__sitemap-table-header\">Priority</th>\n </tr>\n </thead>\n <tbody>\n {entries.map((entry, i) => (\n <tr key={i} className=\"seo__sitemap-row\">\n <td className=\"seo__sitemap-cell\">\n <a href={entry.url} target=\"_blank\" rel=\"noopener noreferrer\" className=\"seo__sitemap-url\">\n {entry.url}\n </a>\n </td>\n <td className=\"seo__sitemap-cell seo__sitemap-cell-muted\">{entry.lastmod || '-'}</td>\n <td className=\"seo__sitemap-cell seo__sitemap-cell-muted\">{entry.changefreq || '-'}</td>\n <td className=\"seo__sitemap-cell seo__sitemap-cell-muted\">{entry.priority || '-'}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )\n}\n","import { useEffect } from 'react'\nimport { breadcrumbSchema } from '@geenius/seo-shared'\n\ninterface BreadcrumbsJsonLdProps {\n items: {\n name: string\n url: string\n }[]\n}\n\nexport function BreadcrumbsJsonLd({ items }: BreadcrumbsJsonLdProps) {\n useEffect(() => {\n const script = document.createElement('script')\n script.type = 'application/ld+json'\n script.textContent = JSON.stringify(breadcrumbSchema(items))\n document.head.appendChild(script)\n\n return () => {\n document.head.removeChild(script)\n }\n }, [items])\n\n return <div className=\"seo__breadcrumbs\" />\n}\n","import { useEffect } from 'react'\nimport { articleSchema } from '@geenius/seo-shared'\n\ninterface ArticleJsonLdProps {\n title: string\n description: string\n author: string\n datePublished: string\n url: string\n image?: string\n}\n\nexport function ArticleJsonLd({\n title,\n description,\n author,\n datePublished,\n url,\n image,\n}: ArticleJsonLdProps) {\n useEffect(() => {\n const script = document.createElement('script')\n script.type = 'application/ld+json'\n script.textContent = JSON.stringify(\n articleSchema({\n title,\n description,\n author,\n datePublished,\n url,\n image,\n }),\n )\n document.head.appendChild(script)\n\n return () => {\n document.head.removeChild(script)\n }\n }, [title, description, author, datePublished, url, image])\n\n return <div className=\"seo__article-jsonld\" />\n}\n","import { useState } from 'react'\nimport { useSEOAdmin } from '@geenius/seo-react'\nimport { MetaEditor } from '../components/MetaEditor'\nimport { SEOPreview } from '../components/SEOPreview'\nimport { SEOScoreCard } from '../components/SEOScoreCard'\nimport type { SEOMeta } from '@geenius/seo-shared'\n\nexport function SEOAdminPage() {\n const { pages, upsertMeta, deleteMeta, isLoading } = useSEOAdmin()\n const [selectedPath, setSelectedPath] = useState<string | null>(null)\n const [editingMeta, setEditingMeta] = useState<SEOMeta | null>(null)\n\n const currentPage = pages.find((p) => p.path === selectedPath)\n\n const handleSave = async () => {\n if (selectedPath && editingMeta) {\n await upsertMeta(selectedPath, editingMeta)\n setEditingMeta(null)\n }\n }\n\n const handleDelete = async () => {\n if (selectedPath) {\n await deleteMeta(selectedPath)\n setSelectedPath(null)\n setEditingMeta(null)\n }\n }\n\n if (isLoading) return <div style={{ padding: '1rem' }}>Loading...</div>\n\n return (\n <div className=\"seo__admin-container\">\n <h1 className=\"seo__admin-title\">SEO Admin</h1>\n\n <div className=\"seo__admin-grid\">\n <div className=\"seo__admin-sidebar\">\n <div className=\"seo__admin-sidebar-title\">Pages</div>\n <div className=\"seo__admin-page-list\">\n {pages.map((page) => (\n <button\n key={page.path}\n onClick={() => {\n setSelectedPath(page.path)\n setEditingMeta(page.meta)\n }}\n className={`seo__admin-page-button ${\n selectedPath === page.path ? 'seo__admin-page-button--active' : ''\n }`}\n >\n {page.path || '/'}\n </button>\n ))}\n </div>\n </div>\n\n {currentPage && editingMeta && (\n <div className=\"seo__admin-editor\">\n <div>\n <h2 style={{ fontWeight: 600, marginBottom: '1rem' }}>Edit Metadata</h2>\n <MetaEditor meta={editingMeta} onChange={setEditingMeta} />\n </div>\n\n <div className=\"seo__space-y\">\n <div>\n <h2 style={{ fontWeight: 600, marginBottom: '1rem' }}>Preview</h2>\n <SEOPreview meta={editingMeta} />\n </div>\n\n <SEOScoreCard meta={editingMeta} />\n </div>\n </div>\n )}\n </div>\n\n {currentPage && editingMeta && (\n <div className=\"seo__admin-actions\">\n <button onClick={handleSave} className=\"seo__button seo__button--success\">\n Save\n </button>\n <button onClick={handleDelete} className=\"seo__button seo__button--danger\">\n Delete\n </button>\n </div>\n )}\n </div>\n )\n}\n","import { useState, useEffect } from 'react'\n\ninterface AnalyticsSummary {\n path: string\n views: number\n avgTimeOnPage: number\n bounceRate: number\n}\n\nexport function SEOAnalyticsPage() {\n const [topPages, setTopPages] = useState<AnalyticsSummary[]>([])\n const [isLoading, setIsLoading] = useState(false)\n\n useEffect(() => {\n setIsLoading(true)\n setIsLoading(false)\n }, [])\n\n const totalViews = topPages.reduce((sum, p) => sum + p.views, 0)\n const avgTimeOnPage =\n topPages.length > 0\n ? topPages.reduce((sum, p) => sum + p.avgTimeOnPage, 0) / topPages.length\n : 0\n const avgBounceRate =\n topPages.length > 0\n ? (topPages.reduce((sum, p) => sum + p.bounceRate, 0) / topPages.length) * 100\n : 0\n\n if (isLoading) return <div style={{ padding: '1rem' }}>Loading...</div>\n\n return (\n <div className=\"seo__analytics-container\">\n <h1 className=\"seo__analytics-title\">SEO Analytics</h1>\n\n <div className=\"seo__analytics-table-wrapper\">\n <table className=\"seo__sitemap-table\">\n <thead className=\"seo__sitemap-table-head\">\n <tr>\n <th className=\"seo__sitemap-table-header\">Page</th>\n <th className=\"seo__sitemap-table-header\">Views</th>\n <th className=\"seo__sitemap-table-header\">Avg Time (s)</th>\n <th className=\"seo__sitemap-table-header\">Bounce Rate</th>\n </tr>\n </thead>\n <tbody>\n {topPages.map((page, i) => (\n <tr key={i} className=\"seo__sitemap-row\">\n <td className=\"seo__sitemap-cell\">{page.path}</td>\n <td className=\"seo__sitemap-cell\">{page.views.toLocaleString()}</td>\n <td className=\"seo__sitemap-cell\">{page.avgTimeOnPage.toFixed(1)}</td>\n <td className=\"seo__sitemap-cell\">{(page.bounceRate * 100).toFixed(1)}%</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n {topPages.length === 0 && (\n <p style={{ textAlign: 'center', color: 'var(--seo-text-muted)', padding: '2rem' }}>\n No analytics data available\n </p>\n )}\n\n <div className=\"seo__analytics-summary\">\n <div className=\"seo__analytics-card\">\n <div className=\"seo__analytics-card-label\">Total Views</div>\n <div className=\"seo__analytics-card-value\">{totalViews.toLocaleString()}</div>\n </div>\n\n <div className=\"seo__analytics-card\">\n <div className=\"seo__analytics-card-label\">Average Time on Page</div>\n <div className=\"seo__analytics-card-value\">{avgTimeOnPage.toFixed(1)}s</div>\n </div>\n\n <div className=\"seo__analytics-card\">\n <div className=\"seo__analytics-card-label\">Average Bounce Rate</div>\n <div className=\"seo__analytics-card-value\">{avgBounceRate.toFixed(1)}%</div>\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":["articleSchema","breadcrumbSchema","useSEOAdmin","useSEOScore","jsx","jsx","jsxs","jsx","jsxs","jsx","jsxs","useEffect","jsx","useEffect","jsx","useState","jsx","jsxs","useState","useState","useEffect","jsx","jsxs"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
# ✦ @geenius-
|
|
1
|
+
# ✦ @geenius/seo-shared\n\n> Geenius Seo — Shared types & Convex schema\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius/seo-shared\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius/seo-shared';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO configuration and utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SEO configuration builder for type-safe setup
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const seoConfig = createSEOConfig()
|
|
11
|
+
* .withSiteName('My Site')
|
|
12
|
+
* .withSiteUrl('https://example.com')
|
|
13
|
+
* .withDefaultImage('https://example.com/og-image.jpg')
|
|
14
|
+
* .withTwitterHandle('@mysite')
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
declare class SEOConfigBuilder {
|
|
18
|
+
private config;
|
|
19
|
+
/**
|
|
20
|
+
* Sets the site name
|
|
21
|
+
*/
|
|
22
|
+
withSiteName(name: string): this;
|
|
23
|
+
/**
|
|
24
|
+
* Sets the site URL
|
|
25
|
+
*/
|
|
26
|
+
withSiteUrl(url: string): this;
|
|
27
|
+
/**
|
|
28
|
+
* Sets default OG image
|
|
29
|
+
*/
|
|
30
|
+
withDefaultImage(url: string): this;
|
|
31
|
+
/**
|
|
32
|
+
* Sets Twitter handle
|
|
33
|
+
*/
|
|
34
|
+
withTwitterHandle(handle: string): this;
|
|
35
|
+
/**
|
|
36
|
+
* Sets title template (e.g., '%s — My Site')
|
|
37
|
+
*/
|
|
38
|
+
withTitleTemplate(template: string): this;
|
|
39
|
+
/**
|
|
40
|
+
* Sets default locale
|
|
41
|
+
*/
|
|
42
|
+
withLocale(locale: string): this;
|
|
43
|
+
/**
|
|
44
|
+
* Sets Google Analytics ID
|
|
45
|
+
*/
|
|
46
|
+
withGoogleAnalyticsId(id: string): this;
|
|
47
|
+
/**
|
|
48
|
+
* Sets Google Tag Manager ID
|
|
49
|
+
*/
|
|
50
|
+
withGoogleTagManagerId(id: string): this;
|
|
51
|
+
/**
|
|
52
|
+
* Builds the configuration
|
|
53
|
+
*/
|
|
54
|
+
build(): SEOConfig;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates a new SEO configuration builder
|
|
58
|
+
*/
|
|
59
|
+
declare function createSEOConfig(): SEOConfigBuilder;
|
|
60
|
+
/**
|
|
61
|
+
* Creates a validated SEO configuration from a partial config object.
|
|
62
|
+
* Requires siteName and siteUrl at minimum.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const config = configureSEO({
|
|
67
|
+
* siteName: 'My Site',
|
|
68
|
+
* siteUrl: 'https://example.com',
|
|
69
|
+
* titleTemplate: '%s — My Site',
|
|
70
|
+
* })
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
declare function configureSEO(config: Partial<SEOConfig> & {
|
|
74
|
+
siteName: string;
|
|
75
|
+
siteUrl: string;
|
|
76
|
+
}): SEOConfig;
|
|
77
|
+
/**
|
|
78
|
+
* Meta tags builder for individual pages
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* const meta = createPageMeta({
|
|
83
|
+
* title: 'Home',
|
|
84
|
+
* description: 'Welcome to my site'
|
|
85
|
+
* })
|
|
86
|
+
* .withKeywords(['web', 'development'])
|
|
87
|
+
* .withImage('https://example.com/image.jpg')
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
declare class PageMetaBuilder {
|
|
91
|
+
private config;
|
|
92
|
+
private meta;
|
|
93
|
+
constructor(config: SEOConfig, title: string, description: string);
|
|
94
|
+
/**
|
|
95
|
+
* Adds keywords
|
|
96
|
+
*/
|
|
97
|
+
withKeywords(keywords: string[]): this;
|
|
98
|
+
/**
|
|
99
|
+
* Sets canonical URL
|
|
100
|
+
*/
|
|
101
|
+
withCanonical(url: string): this;
|
|
102
|
+
/**
|
|
103
|
+
* Sets OG image
|
|
104
|
+
*/
|
|
105
|
+
withImage(url: string, alt?: string): this;
|
|
106
|
+
/**
|
|
107
|
+
* Sets OG type
|
|
108
|
+
*/
|
|
109
|
+
withType(type: 'website' | 'article' | 'product'): this;
|
|
110
|
+
/**
|
|
111
|
+
* Adds JSON-LD schema
|
|
112
|
+
*/
|
|
113
|
+
withJsonLd(schema: Record<string, unknown>): this;
|
|
114
|
+
/**
|
|
115
|
+
* Sets robots meta
|
|
116
|
+
*/
|
|
117
|
+
withRobots(robots: string): this;
|
|
118
|
+
/**
|
|
119
|
+
* Adds alternate language links
|
|
120
|
+
*/
|
|
121
|
+
withAlternates(alternates: Record<string, string>): this;
|
|
122
|
+
/**
|
|
123
|
+
* Builds the meta tags
|
|
124
|
+
*/
|
|
125
|
+
build(): SEOMeta;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Creates page-specific meta tags
|
|
129
|
+
*/
|
|
130
|
+
declare function createPageMeta(config: SEOConfig, title: string, description: string): PageMetaBuilder;
|
|
131
|
+
/**
|
|
132
|
+
* Validates SEO meta configuration
|
|
133
|
+
*/
|
|
134
|
+
declare function validateSEOMeta(meta: SEOMeta): {
|
|
135
|
+
valid: boolean;
|
|
136
|
+
errors: string[];
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Presets for common SEO configurations
|
|
140
|
+
*/
|
|
141
|
+
declare const seoPresets: {
|
|
142
|
+
/**
|
|
143
|
+
* Blog article preset
|
|
144
|
+
*/
|
|
145
|
+
article: (_config: SEOConfig) => Partial<SEOMeta>;
|
|
146
|
+
/**
|
|
147
|
+
* E-commerce product preset
|
|
148
|
+
*/
|
|
149
|
+
product: (_config: SEOConfig) => Partial<SEOMeta>;
|
|
150
|
+
/**
|
|
151
|
+
* Service/company page preset
|
|
152
|
+
*/
|
|
153
|
+
business: (_config: SEOConfig) => Partial<SEOMeta>;
|
|
154
|
+
/**
|
|
155
|
+
* Private/unlisted page preset
|
|
156
|
+
*/
|
|
157
|
+
private: () => Partial<SEOMeta>;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
interface OGMeta {
|
|
161
|
+
title: string;
|
|
162
|
+
description: string;
|
|
163
|
+
image?: string;
|
|
164
|
+
imageAlt?: string;
|
|
165
|
+
type: 'website' | 'article' | 'product';
|
|
166
|
+
url: string;
|
|
167
|
+
siteName?: string;
|
|
168
|
+
}
|
|
169
|
+
interface TwitterMeta {
|
|
170
|
+
card: 'summary' | 'summary_large_image';
|
|
171
|
+
title: string;
|
|
172
|
+
description: string;
|
|
173
|
+
image?: string;
|
|
174
|
+
creator?: string;
|
|
175
|
+
}
|
|
176
|
+
interface SEOMeta {
|
|
177
|
+
title: string;
|
|
178
|
+
description: string;
|
|
179
|
+
keywords: string[];
|
|
180
|
+
canonical?: string;
|
|
181
|
+
og: OGMeta;
|
|
182
|
+
twitter: TwitterMeta;
|
|
183
|
+
robots?: string;
|
|
184
|
+
alternates?: Record<string, string>;
|
|
185
|
+
jsonLd?: Record<string, unknown>[];
|
|
186
|
+
}
|
|
187
|
+
interface SitemapEntry {
|
|
188
|
+
url: string;
|
|
189
|
+
lastmod?: string;
|
|
190
|
+
changefreq?: string;
|
|
191
|
+
priority?: number;
|
|
192
|
+
}
|
|
193
|
+
interface RobotsTxt {
|
|
194
|
+
allow: string[];
|
|
195
|
+
disallow: string[];
|
|
196
|
+
sitemap?: string;
|
|
197
|
+
}
|
|
198
|
+
interface SEOConfig {
|
|
199
|
+
siteName: string;
|
|
200
|
+
siteUrl: string;
|
|
201
|
+
titleTemplate?: string;
|
|
202
|
+
defaultImage?: string;
|
|
203
|
+
twitterHandle?: string;
|
|
204
|
+
locale?: string;
|
|
205
|
+
googleAnalyticsId?: string;
|
|
206
|
+
googleTagManagerId?: string;
|
|
207
|
+
}
|
|
208
|
+
declare function buildTitle(pageTitle: string, siteName: string, sep?: string): string;
|
|
209
|
+
declare function applyTitleTemplate(pageTitle: string, template?: string): string;
|
|
210
|
+
declare function truncateDescription(desc: string, max?: number): string;
|
|
211
|
+
declare function articleSchema(data: {
|
|
212
|
+
title: string;
|
|
213
|
+
description: string;
|
|
214
|
+
author: string;
|
|
215
|
+
datePublished: string;
|
|
216
|
+
url: string;
|
|
217
|
+
image?: string;
|
|
218
|
+
}): Record<string, unknown>;
|
|
219
|
+
declare function productSchema(data: {
|
|
220
|
+
name: string;
|
|
221
|
+
description: string;
|
|
222
|
+
price: number;
|
|
223
|
+
currency: string;
|
|
224
|
+
url: string;
|
|
225
|
+
image?: string;
|
|
226
|
+
}): Record<string, unknown>;
|
|
227
|
+
declare function orgSchema(data: {
|
|
228
|
+
name: string;
|
|
229
|
+
url: string;
|
|
230
|
+
logo?: string;
|
|
231
|
+
sameAs?: string[];
|
|
232
|
+
}): Record<string, unknown>;
|
|
233
|
+
declare function breadcrumbSchema(items: {
|
|
234
|
+
name: string;
|
|
235
|
+
url: string;
|
|
236
|
+
}[]): Record<string, unknown>;
|
|
237
|
+
declare function faqSchema(items: {
|
|
238
|
+
question: string;
|
|
239
|
+
answer: string;
|
|
240
|
+
}[]): Record<string, unknown>;
|
|
241
|
+
declare function webApplicationSchema(data: {
|
|
242
|
+
name: string;
|
|
243
|
+
url: string;
|
|
244
|
+
description: string;
|
|
245
|
+
applicationCategory?: string;
|
|
246
|
+
operatingSystem?: string;
|
|
247
|
+
offers?: {
|
|
248
|
+
price: number;
|
|
249
|
+
currency: string;
|
|
250
|
+
};
|
|
251
|
+
}): Record<string, unknown>;
|
|
252
|
+
declare function buildCanonical(path: string, baseUrl: string): string;
|
|
253
|
+
declare function buildAlternates(locales: string[], baseUrl: string, path: string): Record<string, string>;
|
|
254
|
+
declare function generateRobotsTxt(config: RobotsTxt): string;
|
|
255
|
+
declare function generateSitemapXml(entries: SitemapEntry[]): string;
|
|
256
|
+
declare function estimateReadingTime(content: string): number;
|
|
257
|
+
declare function calcSEOScore(meta: SEOMeta): {
|
|
258
|
+
score: number;
|
|
259
|
+
issues: string[];
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export { type OGMeta, PageMetaBuilder, type RobotsTxt, type SEOConfig, SEOConfigBuilder, type SEOMeta, type SitemapEntry, type TwitterMeta, applyTitleTemplate, articleSchema, breadcrumbSchema, buildAlternates, buildCanonical, buildTitle, calcSEOScore, configureSEO, createPageMeta, createSEOConfig, estimateReadingTime, faqSchema, generateRobotsTxt, generateSitemapXml, orgSchema, productSchema, seoPresets, truncateDescription, validateSEOMeta, webApplicationSchema };
|