cinqcinqdev-seo 0.1.28 → 0.1.30
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
|
@@ -23,7 +23,7 @@ A Nuxt 3 admin CMS module with a visual page editor, AI content generation, and
|
|
|
23
23
|
- `@nuxtjs/supabase`
|
|
24
24
|
- `@pinia/nuxt`
|
|
25
25
|
- `@nuxt/icon`
|
|
26
|
-
- An [OpenRouter](https://openrouter.ai) API key (
|
|
26
|
+
- An [OpenRouter](https://openrouter.ai) API key (optional — only needed if `features.ai: true`)
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -51,12 +51,6 @@ export default defineNuxtConfig({
|
|
|
51
51
|
})
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
Add the OpenRouter key to `.env`:
|
|
55
|
-
|
|
56
|
-
```env
|
|
57
|
-
OPENROUTER_API_KEY=sk-or-...
|
|
58
|
-
```
|
|
59
|
-
|
|
60
54
|
---
|
|
61
55
|
|
|
62
56
|
## Database Setup
|
|
@@ -182,6 +176,7 @@ All options go under the `adminCms` key in `nuxt.config.js`.
|
|
|
182
176
|
| `storageBucket` | `string` | `'site'` | Supabase storage bucket for image uploads |
|
|
183
177
|
| `navSections` | `NavSectionItem[]` | `[]` | Extra sidebar links |
|
|
184
178
|
| `extraSections` | `Record<string, SectionConfig>` | `{}` | Extra section types for the visual editor |
|
|
179
|
+
| `features.ai` | `boolean` | `false` | Enable AI content generation and SEO audit (requires `OPENROUTER_API_KEY`) |
|
|
185
180
|
|
|
186
181
|
### Default page types
|
|
187
182
|
|
|
@@ -398,7 +393,19 @@ Plain fields (colors, images, URLs, booleans) remain scalar values.
|
|
|
398
393
|
|
|
399
394
|
## AI Features
|
|
400
395
|
|
|
401
|
-
|
|
396
|
+
AI features are **disabled by default**. To enable them, set `features.ai: true` in your config and add `OPENROUTER_API_KEY` to your `.env`:
|
|
397
|
+
|
|
398
|
+
```js
|
|
399
|
+
adminCms: {
|
|
400
|
+
features: { ai: true },
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
```env
|
|
405
|
+
OPENROUTER_API_KEY=sk-or-...
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Uses `google/gemini-2.0-flash-lite-001` via OpenRouter.
|
|
402
409
|
|
|
403
410
|
### Content generation
|
|
404
411
|
|
package/dist/module.d.mts
CHANGED
|
@@ -82,6 +82,14 @@ interface ModuleOptions {
|
|
|
82
82
|
* Defaults to ['fr', 'ar', 'en']. Set to ['fr'] for French-only projects.
|
|
83
83
|
*/
|
|
84
84
|
langs?: string[];
|
|
85
|
+
/**
|
|
86
|
+
* Feature flags to enable/disable optional functionality.
|
|
87
|
+
* All features are disabled by default.
|
|
88
|
+
*/
|
|
89
|
+
features?: {
|
|
90
|
+
/** Enable AI content generation and SEO audit in the editor. Requires OPENROUTER_API_KEY. */
|
|
91
|
+
ai?: boolean;
|
|
92
|
+
};
|
|
85
93
|
}
|
|
86
94
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
87
95
|
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -26,7 +26,8 @@ const module$1 = defineNuxtModule({
|
|
|
26
26
|
storageBucket: "site",
|
|
27
27
|
navSections: [],
|
|
28
28
|
extraSections: {},
|
|
29
|
-
basePath: "/admin"
|
|
29
|
+
basePath: "/admin",
|
|
30
|
+
features: { ai: false }
|
|
30
31
|
},
|
|
31
32
|
setup(options, nuxt) {
|
|
32
33
|
const { resolve } = createResolver(import.meta.url);
|
|
@@ -38,7 +39,8 @@ const module$1 = defineNuxtModule({
|
|
|
38
39
|
tables: { pages: "pages", users: "users" },
|
|
39
40
|
storageBucket: "site",
|
|
40
41
|
navSections: [],
|
|
41
|
-
extraSections: {}
|
|
42
|
+
extraSections: {},
|
|
43
|
+
features: { ai: false }
|
|
42
44
|
}),
|
|
43
45
|
pageTypes: options.pageTypes?.length ? options.pageTypes : defaultPageTypes,
|
|
44
46
|
plans: options.plans?.length ? options.plans : [],
|
|
@@ -6,6 +6,7 @@ definePageMeta({ layout: false });
|
|
|
6
6
|
const route = useRoute();
|
|
7
7
|
const supabase = useSupabaseClient();
|
|
8
8
|
const config = useRuntimeConfig().public.adminCms;
|
|
9
|
+
const aiEnabled = computed(() => config?.features?.ai === true);
|
|
9
10
|
const table = config?.tables?.pages || "pages";
|
|
10
11
|
const bucket = config?.storageBucket || "site";
|
|
11
12
|
const pageId = route.params.id;
|
|
@@ -575,7 +576,7 @@ const savePage = async () => {
|
|
|
575
576
|
</div>
|
|
576
577
|
|
|
577
578
|
<!-- AI Generate -->
|
|
578
|
-
<div class="mb-5">
|
|
579
|
+
<div v-if="aiEnabled" class="mb-5">
|
|
579
580
|
<div v-if="!showAiPrompt && !isGenerating">
|
|
580
581
|
<button @click="showAiPrompt = true" class="w-full py-2.5 rounded-xl border-2 border-dashed border-[#3d35ff]/30 text-[#3d35ff] text-[8px] font-black uppercase tracking-widest hover:border-[#3d35ff] hover:bg-[#3d35ff]/5 transition-all">
|
|
581
582
|
✦ Générer avec l'IA
|
|
@@ -736,7 +737,7 @@ const savePage = async () => {
|
|
|
736
737
|
<h3 class="text-[9px] font-black text-gray-300 uppercase mb-4 italic tracking-widest">Config SEO</h3>
|
|
737
738
|
|
|
738
739
|
<!-- AI Audit -->
|
|
739
|
-
<div class="mb-6">
|
|
740
|
+
<div v-if="aiEnabled" class="mb-6">
|
|
740
741
|
<button @click="runSeoAudit" :disabled="isAuditing"
|
|
741
742
|
class="w-full flex items-center justify-center gap-2 py-2.5 rounded-xl text-[10px] font-black uppercase tracking-widest bg-[#3d35ff]/10 text-[#3d35ff] hover:bg-[#3d35ff] hover:text-white transition-all disabled:opacity-50">
|
|
742
743
|
<svg v-if="isAuditing" class="w-3.5 h-3.5 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"/></svg>
|
|
@@ -746,7 +747,7 @@ const savePage = async () => {
|
|
|
746
747
|
</div>
|
|
747
748
|
|
|
748
749
|
<!-- Audit results -->
|
|
749
|
-
<div v-if="seoAudit" class="mb-6 space-y-3">
|
|
750
|
+
<div v-if="aiEnabled && seoAudit" class="mb-6 space-y-3">
|
|
750
751
|
<div class="flex items-center gap-3 p-3 bg-gray-50 rounded-xl">
|
|
751
752
|
<div :class="[
|
|
752
753
|
'w-11 h-11 rounded-xl flex items-center justify-center text-sm font-black shrink-0',
|
|
@@ -203,7 +203,7 @@ useHead({ title: computed(() => `${typeLabel.value} \u2014 Admin`) });
|
|
|
203
203
|
<div>
|
|
204
204
|
<label class="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-2 block">Titre</label>
|
|
205
205
|
<input v-model="newPageTitle" type="text" placeholder="Ex: Création site web"
|
|
206
|
-
class="w-full border border-gray-200 p-3 rounded-xl text-sm focus:ring-2 focus:ring-[#3d35ff] outline-none transition" autofocus />
|
|
206
|
+
class="w-full border border-gray-200 p-3 rounded-xl text-sm text-gray-900 focus:ring-2 focus:ring-[#3d35ff] outline-none transition" autofocus />
|
|
207
207
|
</div>
|
|
208
208
|
<div>
|
|
209
209
|
<label class="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-2 block">Départ</label>
|
|
@@ -226,7 +226,7 @@ useHead({ title: computed(() => `${typeLabel.value} \u2014 Admin`) });
|
|
|
226
226
|
</div>
|
|
227
227
|
<div v-if="createMode === 'template'">
|
|
228
228
|
<label class="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-2 block">Template</label>
|
|
229
|
-
<select v-model="selectedTemplateId" class="w-full border border-gray-200 p-3 rounded-xl text-sm outline-none focus:ring-2 focus:ring-[#3d35ff] transition bg-white">
|
|
229
|
+
<select v-model="selectedTemplateId" class="w-full border border-gray-200 p-3 rounded-xl text-sm text-gray-900 outline-none focus:ring-2 focus:ring-[#3d35ff] transition bg-white">
|
|
230
230
|
<option value="">— Choisir —</option>
|
|
231
231
|
<option v-for="tpl in templates" :key="tpl.id" :value="tpl.id">{{ tpl.name }}</option>
|
|
232
232
|
</select>
|
|
@@ -235,7 +235,7 @@ useHead({ title: computed(() => `${typeLabel.value} \u2014 Admin`) });
|
|
|
235
235
|
</div>
|
|
236
236
|
|
|
237
237
|
<div class="px-7 pb-7 flex gap-3">
|
|
238
|
-
<button @click="isCreating = false" class="flex-1 px-4 py-2.5 border border-gray-200 rounded-xl text-sm font-semibold hover:bg-gray-50 transition">
|
|
238
|
+
<button @click="isCreating = false" class="flex-1 px-4 py-2.5 border border-gray-200 rounded-xl text-sm font-semibold text-gray-900 hover:bg-gray-50 transition">
|
|
239
239
|
Annuler
|
|
240
240
|
</button>
|
|
241
241
|
<button @click="handleCreate"
|
|
@@ -262,7 +262,7 @@ useHead({ title: computed(() => `${typeLabel.value} \u2014 Admin`) });
|
|
|
262
262
|
</p>
|
|
263
263
|
</div>
|
|
264
264
|
<div class="px-7 pb-7 flex gap-3">
|
|
265
|
-
<button @click="deleteModal = null" class="flex-1 px-4 py-2.5 border border-gray-200 rounded-xl text-sm font-semibold hover:bg-gray-50 transition">
|
|
265
|
+
<button @click="deleteModal = null" class="flex-1 px-4 py-2.5 border border-gray-200 rounded-xl text-sm font-semibold text-gray-900 hover:bg-gray-50 transition">
|
|
266
266
|
Annuler
|
|
267
267
|
</button>
|
|
268
268
|
<button @click="handleDelete" :disabled="isDeleting"
|
|
@@ -290,10 +290,10 @@ useHead({ title: computed(() => `${typeLabel.value} \u2014 Admin`) });
|
|
|
290
290
|
<div class="px-7 py-6">
|
|
291
291
|
<label class="text-[10px] font-black uppercase tracking-widest text-gray-400 mb-2 block">Nom du template</label>
|
|
292
292
|
<input v-model="templateName" type="text" placeholder="Ex: Landing page service"
|
|
293
|
-
class="w-full border border-gray-200 p-3 rounded-xl text-sm focus:ring-2 focus:ring-[#3d35ff] outline-none transition" autofocus />
|
|
293
|
+
class="w-full border border-gray-200 p-3 rounded-xl text-sm text-gray-900 focus:ring-2 focus:ring-[#3d35ff] outline-none transition" autofocus />
|
|
294
294
|
</div>
|
|
295
295
|
<div class="px-7 pb-7 flex gap-3">
|
|
296
|
-
<button @click="templateModal = null" class="flex-1 px-4 py-2.5 border border-gray-200 rounded-xl text-sm font-semibold hover:bg-gray-50 transition">
|
|
296
|
+
<button @click="templateModal = null" class="flex-1 px-4 py-2.5 border border-gray-200 rounded-xl text-sm font-semibold text-gray-900 hover:bg-gray-50 transition">
|
|
297
297
|
Annuler
|
|
298
298
|
</button>
|
|
299
299
|
<button @click="handleTurnIntoTemplate" :disabled="!templateName.trim() || isSavingTemplate"
|