@mel000000/weweb-dynamic-metadata 1.0.7 → 1.0.9
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
CHANGED
|
@@ -173,12 +173,23 @@ FOR SELECT
|
|
|
173
173
|
TO anon
|
|
174
174
|
USING (true);
|
|
175
175
|
```
|
|
176
|
-
#### 2.
|
|
176
|
+
#### 2. Get Your Supabase API Key
|
|
177
|
+
In your Supabase dashboard, go to **Project Settings > API Keys**. You'll see two types of keys:
|
|
178
|
+
|
|
179
|
+
| Key Type | Format | When to Use |
|
|
180
|
+
|----------|--------|-------------|
|
|
181
|
+
| **Project URL** | `https://your-project.supabase.co` | Always needed |
|
|
182
|
+
| **anon / publishable key** | `sb_publishable_...` or JWT | Legacy option (being phased out) |
|
|
183
|
+
| **secret key** (recommended) | `sb_secret_...` | ✅ **Recommended for new projects** |
|
|
184
|
+
|
|
185
|
+
> **Note:** Supabase is transitioning away from the legacy anon key. For new projects, use the **secret key** (starts with `sb_secret_...`). If you're using an older project, the anon key will continue working for now, but consider migrating to the new key format.
|
|
186
|
+
|
|
187
|
+
#### 3. Set Up Environment Variables
|
|
177
188
|
Create a ``.env`` file in your project root to store your Supabase credentials securely:
|
|
178
189
|
```.env
|
|
179
190
|
# .env file
|
|
180
191
|
SUPABASE_URL=https://your-project.supabase.co
|
|
181
|
-
|
|
192
|
+
SUPABASE_KEY=your-secret-or-anon-key-here
|
|
182
193
|
```
|
|
183
194
|
⚠️ Important: Never commit your .env file to version control. Add it to your .gitignore:
|
|
184
195
|
```text
|
|
@@ -187,14 +198,14 @@ SUPABASE_ANON_KEY=your-anon-key-here
|
|
|
187
198
|
```
|
|
188
199
|
The package uses dotenv to automatically load these environment variables when you run the generator.
|
|
189
200
|
|
|
190
|
-
####
|
|
201
|
+
#### 4. Create Config File
|
|
191
202
|
Create ``weweb.config.js`` in your project root:
|
|
192
203
|
```javascript
|
|
193
204
|
export default {
|
|
194
205
|
// Your Supabase configuration
|
|
195
206
|
supabase: {
|
|
196
207
|
url: process.env.SUPABASE_URL,
|
|
197
|
-
|
|
208
|
+
apikey: process.env.SUPABASE_KEY // Works with both anon and secret keys
|
|
198
209
|
},
|
|
199
210
|
|
|
200
211
|
// Optional: Specify your build folder (defaults to ./dist)
|
|
@@ -203,8 +214,8 @@ export default {
|
|
|
203
214
|
// Define your dynamic routes
|
|
204
215
|
pages: [
|
|
205
216
|
{
|
|
206
|
-
route: "/
|
|
207
|
-
table: "
|
|
217
|
+
route: "/your-page-name/:id",
|
|
218
|
+
table: "table-view-name", // Your Supabase table name
|
|
208
219
|
metadata: {
|
|
209
220
|
title: "title", // Database field for title
|
|
210
221
|
content: "excerpt", // Database field for description
|
|
@@ -214,13 +225,12 @@ export default {
|
|
|
214
225
|
]
|
|
215
226
|
};
|
|
216
227
|
```
|
|
217
|
-
####
|
|
228
|
+
#### 5. Run the Generator
|
|
218
229
|
```bash
|
|
219
230
|
# One-time generation
|
|
220
231
|
npx @mel000000/weweb-dynamic-metadata
|
|
221
232
|
|
|
222
233
|
```
|
|
223
|
-
|
|
224
234
|
## How It Works
|
|
225
235
|
|
|
226
236
|
### 1. Reads Your Config
|
package/package.json
CHANGED
|
@@ -172,7 +172,7 @@ export async function processFiles() {
|
|
|
172
172
|
|
|
173
173
|
// Ensure template exists and inject script
|
|
174
174
|
await ensureTemplateExists(templatePath);
|
|
175
|
-
await injectScriptInTemplate(templatePath);
|
|
175
|
+
await injectScriptInTemplate(templatePath, page);
|
|
176
176
|
|
|
177
177
|
// Fetch all metadata
|
|
178
178
|
const metadataMap = new Map();
|
|
@@ -1,53 +1,33 @@
|
|
|
1
1
|
// src/templates/metadata-injector.js
|
|
2
|
-
export
|
|
2
|
+
export async function metadata_injector_script(page) {
|
|
3
|
+
return `
|
|
3
4
|
<!-- METADATA INJECTOR -->
|
|
4
|
-
<script src="/
|
|
5
|
+
<script src="/${page.route}/metadata.js"></script>
|
|
5
6
|
|
|
6
7
|
<script>
|
|
7
8
|
(function() {
|
|
8
9
|
'use strict';
|
|
9
10
|
|
|
10
11
|
// Prevent duplicate execution
|
|
11
|
-
if (window.__METADATA_INJECTOR_LOADED)
|
|
12
|
-
console.log('🔄 Metadata injector already loaded, skipping...');
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
12
|
+
if (window.__METADATA_INJECTOR_LOADED) return;
|
|
15
13
|
window.__METADATA_INJECTOR_LOADED = true;
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// Configuration
|
|
20
|
-
const CONFIG = {
|
|
21
|
-
DEBUG: true,
|
|
22
|
-
META_TIMEOUT: 2000
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// State
|
|
26
|
-
let currentMetadata = null;
|
|
27
|
-
let appliedId = null;
|
|
28
|
-
|
|
29
|
-
// Debug logging
|
|
30
|
-
function debugLog(...args) {
|
|
31
|
-
if (CONFIG.DEBUG) console.log('[Metadata]', ...args);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Get ID from URL
|
|
35
|
-
function getArticleId() {
|
|
15
|
+
function getId() {
|
|
36
16
|
const path = window.location.pathname;
|
|
37
17
|
const parts = path.split('/').filter(p => p.length);
|
|
38
18
|
|
|
39
|
-
// Case 1: /
|
|
40
|
-
if (parts[0] ===
|
|
19
|
+
// Case 1: /content/2
|
|
20
|
+
if (parts[0] === ${JSON.stringify(page.route)} && parts[1] && parts[1] !== '_param') {
|
|
41
21
|
return parts[1];
|
|
42
22
|
}
|
|
43
23
|
|
|
44
|
-
// Case 2: /
|
|
45
|
-
if (parts[0] ===
|
|
24
|
+
// Case 2: /content/_param/?id=2
|
|
25
|
+
if (parts[0] === ${JSON.stringify(page.route)} && parts[1] === '_param') {
|
|
46
26
|
return new URLSearchParams(window.location.search).get('id');
|
|
47
27
|
}
|
|
48
28
|
|
|
49
29
|
// Case 3: Reference mode (ID stored in global)
|
|
50
|
-
return window.
|
|
30
|
+
return window.__REFERENCE_CONTENT_ID;
|
|
51
31
|
}
|
|
52
32
|
|
|
53
33
|
// Update meta tags
|
|
@@ -62,32 +42,21 @@ export const METADATA_INJECTOR_SCRIPT = `
|
|
|
62
42
|
el.setAttribute('content', String(value).replace(/[<>]/g, ''));
|
|
63
43
|
}
|
|
64
44
|
|
|
65
|
-
// Apply metadata
|
|
66
45
|
function applyMetadata() {
|
|
67
|
-
const id =
|
|
46
|
+
const id = getId();
|
|
68
47
|
if (!id || !window.METADATA) return false;
|
|
69
48
|
|
|
70
49
|
const meta = window.METADATA[id];
|
|
71
|
-
if (!meta)
|
|
72
|
-
console.warn('⚠️ No metadata for ID:', id);
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
50
|
+
if (!meta) return false;
|
|
75
51
|
|
|
76
|
-
//
|
|
77
|
-
if (id === appliedId) return true;
|
|
78
|
-
|
|
79
|
-
debugLog('Applying metadata for ID:', id);
|
|
80
|
-
|
|
81
|
-
// Set title - FIXED: Don't remove the title tag, just update it
|
|
52
|
+
// Set title
|
|
82
53
|
if (meta.title) {
|
|
83
|
-
// Find existing title tag or create new one
|
|
84
54
|
let titleTag = document.querySelector('title');
|
|
85
55
|
if (!titleTag) {
|
|
86
56
|
titleTag = document.createElement('title');
|
|
87
57
|
document.head.appendChild(titleTag);
|
|
88
58
|
}
|
|
89
59
|
titleTag.textContent = meta.title;
|
|
90
|
-
// Also set document.title for good measure
|
|
91
60
|
document.title = meta.title;
|
|
92
61
|
}
|
|
93
62
|
|
|
@@ -123,9 +92,8 @@ export const METADATA_INJECTOR_SCRIPT = `
|
|
|
123
92
|
}
|
|
124
93
|
canonical.setAttribute('href', window.location.href.split('?')[0]);
|
|
125
94
|
|
|
126
|
-
// Structured data
|
|
95
|
+
// Structured data
|
|
127
96
|
if (meta.title || desc || img) {
|
|
128
|
-
// Remove existing JSON-LD
|
|
129
97
|
document.querySelectorAll('script[type="application/ld+json"]').forEach(el => el.remove());
|
|
130
98
|
|
|
131
99
|
const ldJson = {
|
|
@@ -142,10 +110,6 @@ export const METADATA_INJECTOR_SCRIPT = `
|
|
|
142
110
|
document.head.appendChild(script);
|
|
143
111
|
}
|
|
144
112
|
|
|
145
|
-
currentMetadata = meta;
|
|
146
|
-
appliedId = id;
|
|
147
|
-
|
|
148
|
-
console.log('✅ Metadata applied for', id);
|
|
149
113
|
return true;
|
|
150
114
|
}
|
|
151
115
|
|
|
@@ -155,15 +119,9 @@ export const METADATA_INJECTOR_SCRIPT = `
|
|
|
155
119
|
|
|
156
120
|
// Wait for metadata if not loaded
|
|
157
121
|
if (!window.METADATA) {
|
|
158
|
-
const timeout = setTimeout(() => {
|
|
159
|
-
console.log('⏰ Metadata timeout');
|
|
160
|
-
clearInterval(checkInterval);
|
|
161
|
-
}, CONFIG.META_TIMEOUT);
|
|
162
|
-
|
|
163
122
|
const checkInterval = setInterval(() => {
|
|
164
123
|
if (window.METADATA && applyMetadata()) {
|
|
165
124
|
clearInterval(checkInterval);
|
|
166
|
-
clearTimeout(timeout);
|
|
167
125
|
}
|
|
168
126
|
}, 50);
|
|
169
127
|
}
|
|
@@ -176,16 +134,7 @@ export const METADATA_INJECTOR_SCRIPT = `
|
|
|
176
134
|
init();
|
|
177
135
|
}
|
|
178
136
|
|
|
179
|
-
// Handle SPA navigation
|
|
180
|
-
let lastUrl = location.href;
|
|
181
|
-
setInterval(() => {
|
|
182
|
-
if (location.href !== lastUrl) {
|
|
183
|
-
lastUrl = location.href;
|
|
184
|
-
appliedId = null;
|
|
185
|
-
init();
|
|
186
|
-
}
|
|
187
|
-
}, 500);
|
|
188
|
-
|
|
189
137
|
})();
|
|
190
138
|
</script>
|
|
191
|
-
`;
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// src/utils/template-injector.js
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import {
|
|
4
|
+
import { metadata_injector_script } from '../templates/metadata-injector.js';
|
|
5
5
|
|
|
6
|
-
export async function injectScriptInTemplate(templatePath) {
|
|
6
|
+
export async function injectScriptInTemplate(templatePath, page) {
|
|
7
7
|
try {
|
|
8
8
|
// Check if file exists
|
|
9
9
|
if (!await fs.pathExists(templatePath)) {
|
|
@@ -34,7 +34,7 @@ export async function injectScriptInTemplate(templatePath) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
// Insert script before </head>
|
|
37
|
-
template = template.replace('</head>',
|
|
37
|
+
template = template.replace('</head>', metadata_injector_script(page) + '\n</head>');
|
|
38
38
|
|
|
39
39
|
// Write back
|
|
40
40
|
await fs.writeFile(templatePath, template, 'utf-8');
|