cinqcinqdev-seo 0.1.25 → 0.1.27

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/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "0.1.25",
7
+ "version": "0.1.27",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "unknown"
@@ -225,6 +225,8 @@ export const DEFAULT_SECTIONS = {
225
225
  question: { type: "text", label: "Question", i18n: true },
226
226
  answer: { type: "textarea", label: "Answer", i18n: true }
227
227
  } },
228
+ bgColor: { type: "color", label: "Background" },
229
+ textColor: { type: "color", label: "Text Color" },
228
230
  spacing: { type: "cards", label: "Espacement", options: spacingOptions },
229
231
  radius: { type: "cards", label: "Arrondis", options: radiusOptions }
230
232
  }
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { ref, computed, watch, onMounted, definePageMeta, useRoute, useRuntimeConfig, useAsyncData, useHead, useSupabaseClient, useNuxtApp } from "#imports";
2
+ import { ref, computed, watch, onMounted, triggerRef, definePageMeta, useRoute, useRuntimeConfig, useAsyncData, useHead, useSupabaseClient, useNuxtApp } from "#imports";
3
3
  import { useAdminSections } from "../../../composables/useAdminSections";
4
4
  import { useAdminSettings, useAdminBasePath } from "#imports";
5
5
  definePageMeta({ layout: false });
@@ -15,10 +15,11 @@ const adminSettings = useAdminSettings();
15
15
  const basePath = useAdminBasePath();
16
16
  onMounted(() => adminSettings.load());
17
17
  const resolveSection = (type) => vueApp.component(`Sections${type}`) || vueApp.component(type) || null;
18
- const { data: page } = await useAsyncData(`admin-edit-${pageId}`, async () => {
18
+ const { data: _rawPage } = await useAsyncData(`admin-edit-${pageId}`, async () => {
19
19
  const { data } = await supabase.from(table).select("*").eq("id", pageId).maybeSingle();
20
20
  return data;
21
21
  });
22
+ const page = ref(_rawPage.value);
22
23
  useHead({ title: computed(() => page.value?.title ? `${page.value.title} \u2014 \xC9diteur` : "\xC9diteur \u2014 Admin") });
23
24
  const isLeftSidebarOpen = ref(true);
24
25
  const isRightSidebarOpen = ref(true);
@@ -111,12 +112,14 @@ const setI18nProp = (fieldName, lang, newValue) => {
111
112
  }
112
113
  obj[lang] = newValue;
113
114
  props[fieldName] = obj;
115
+ triggerRef(page);
114
116
  };
115
117
  const getI18nVal = (val, lang) => {
116
118
  if (!val) return "";
117
119
  if (typeof val === "string") return lang === "fr" ? val : "";
118
120
  return val[lang] || "";
119
121
  };
122
+ const trigger = () => triggerRef(page);
120
123
  const setI18nVal = (obj, key, lang, newValue) => {
121
124
  const current = obj[key];
122
125
  const i18nObj = { fr: "", ar: "", en: "" };
@@ -127,6 +130,7 @@ const setI18nVal = (obj, key, lang, newValue) => {
127
130
  }
128
131
  i18nObj[lang] = newValue;
129
132
  obj[key] = i18nObj;
133
+ triggerRef(page);
130
134
  };
131
135
  const tagDepth = (tag) => {
132
136
  const n = parseInt(tag.replace("h", ""));
@@ -638,7 +642,8 @@ const savePage = async () => {
638
642
  <span class="absolute -top-1 right-0 text-[6px] font-black uppercase text-[#3d35ff]/40">{{ langLabel(editingLang) }}</span>
639
643
  <input :value="getI18nProp(name, editingLang)" @input="setI18nProp(name, editingLang, $event.target.value)" :dir="editingLang === 'ar' ? 'rtl' : 'ltr'" class="w-full bg-gray-50 rounded-xl p-3 text-[10px] font-bold outline-none ring-1 ring-gray-100 focus:ring-[#3d35ff]" />
640
644
  </div>
641
- <input v-else v-model="page.content[selectedBlockIndex].props[name]" class="w-full bg-gray-50 rounded-xl p-3 text-[10px] font-bold outline-none ring-1 ring-gray-100 focus:ring-[#3d35ff]" />
645
+ <input v-else :value="page.content[selectedBlockIndex].props[name]" @input="page.content[selectedBlockIndex].props[name] = $event.target.value;
646
+ trigger()" class="w-full bg-gray-50 rounded-xl p-3 text-[10px] font-bold outline-none ring-1 ring-gray-100 focus:ring-[#3d35ff]" />
642
647
  </template>
643
648
 
644
649
  <!-- Textarea i18n -->
@@ -647,18 +652,21 @@ const savePage = async () => {
647
652
  <span class="absolute -top-1 right-0 text-[6px] font-black uppercase text-[#3d35ff]/40">{{ langLabel(editingLang) }}</span>
648
653
  <textarea :value="getI18nProp(name, editingLang)" @input="setI18nProp(name, editingLang, $event.target.value)" :dir="editingLang === 'ar' ? 'rtl' : 'ltr'" rows="3" class="w-full bg-gray-50 rounded-xl p-3 text-[10px] outline-none ring-1 ring-gray-100 focus:ring-[#3d35ff]"></textarea>
649
654
  </div>
650
- <textarea v-else v-model="page.content[selectedBlockIndex].props[name]" rows="3" class="w-full bg-gray-50 rounded-xl p-3 text-[10px] outline-none ring-1 ring-gray-100"></textarea>
655
+ <textarea v-else :value="page.content[selectedBlockIndex].props[name]" @input="page.content[selectedBlockIndex].props[name] = $event.target.value;
656
+ trigger()" rows="3" class="w-full bg-gray-50 rounded-xl p-3 text-[10px] outline-none ring-1 ring-gray-100"></textarea>
651
657
  </template>
652
658
 
653
659
  <!-- Select -->
654
- <select v-else-if="field.type === 'select'" v-model="page.content[selectedBlockIndex].props[name]" class="w-full bg-gray-50 rounded-xl p-3 text-[10px] font-bold ring-1 ring-gray-100 outline-none">
660
+ <select v-else-if="field.type === 'select'" :value="page.content[selectedBlockIndex].props[name]" @change="page.content[selectedBlockIndex].props[name] = $event.target.value;
661
+ trigger()" class="w-full bg-gray-50 rounded-xl p-3 text-[10px] font-bold ring-1 ring-gray-100 outline-none">
655
662
  <option v-for="opt in field.options" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
656
663
  </select>
657
664
 
658
665
  <!-- Cards -->
659
666
  <div v-else-if="field.type === 'cards'" class="grid grid-cols-3 gap-2">
660
667
  <button v-for="opt in field.options" :key="opt.value"
661
- @click="page.content[selectedBlockIndex].props[name] = opt.value"
668
+ @click="page.content[selectedBlockIndex].props[name] = opt.value;
669
+ trigger()"
662
670
  :class="[
663
671
  'p-2.5 rounded-xl border-2 text-center transition-all',
664
672
  page.content[selectedBlockIndex].props[name] === opt.value ? 'border-[#3d35ff] bg-blue-50/50 text-[#3d35ff]' : 'border-gray-100 text-gray-400 hover:border-gray-200'
@@ -670,14 +678,17 @@ const savePage = async () => {
670
678
 
671
679
  <!-- Color -->
672
680
  <div v-else-if="field.type === 'color'" class="flex gap-2 bg-gray-50 p-2 rounded-xl ring-1 ring-gray-100">
673
- <button v-for="c in ['#3d35ff', '#ffffff', '#000000', '#F9F9FB']" :key="c" @click="page.content[selectedBlockIndex].props[name] = c" class="w-5 h-5 rounded-full border border-black/5" :style="{ background: c }"></button>
674
- <input v-model="page.content[selectedBlockIndex].props[name]" class="flex-1 bg-transparent text-[9px] font-mono outline-none" />
681
+ <button v-for="c in ['#3d35ff', '#ffffff', '#000000', '#F9F9FB']" :key="c" @click="page.content[selectedBlockIndex].props[name] = c;
682
+ trigger()" class="w-5 h-5 rounded-full border border-black/5" :style="{ background: c }"></button>
683
+ <input :value="page.content[selectedBlockIndex].props[name]" @input="page.content[selectedBlockIndex].props[name] = $event.target.value;
684
+ trigger()" class="flex-1 bg-transparent text-[9px] font-mono outline-none" />
675
685
  </div>
676
686
 
677
687
  <!-- List -->
678
688
  <div v-else-if="field.type === 'list'" class="space-y-4">
679
689
  <div v-for="(item, idx) in page.content[selectedBlockIndex].props[name] || []" :key="idx" class="p-4 bg-gray-50 rounded-2xl relative border">
680
- <button @click="page.content[selectedBlockIndex].props[name].splice(idx, 1)" class="absolute top-2 right-2 text-red-400 text-[8px]">✕</button>
690
+ <button @click="page.content[selectedBlockIndex].props[name].splice(idx, 1);
691
+ trigger()" class="absolute top-2 right-2 text-red-400 text-[8px]">✕</button>
681
692
  <div v-for="(subField, subName) in field.itemFields" :key="subName" class="mb-2">
682
693
  <label class="text-[7px] font-black uppercase opacity-30">{{ subField.label }}</label>
683
694
  <div v-if="subField.type === 'image'" class="mt-1 space-y-1.5">
@@ -687,7 +698,8 @@ const savePage = async () => {
687
698
  <button @click="openGallery(item, subName)" class="flex-1 text-[8px] font-black uppercase bg-white px-2 py-1.5 rounded-lg shadow-sm border">⊞ Galerie</button>
688
699
  </div>
689
700
  </div>
690
- <select v-else-if="subField.type === 'select'" v-model="item[subName]" class="w-full bg-white rounded-lg p-2 text-[10px] font-bold outline-none shadow-sm border">
701
+ <select v-else-if="subField.type === 'select'" :value="item[subName]" @change="item[subName] = $event.target.value;
702
+ trigger()" class="w-full bg-white rounded-lg p-2 text-[10px] font-bold outline-none shadow-sm border">
691
703
  <option v-for="opt in subField.options" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
692
704
  </select>
693
705
  <template v-else-if="subField.i18n">
@@ -701,16 +713,20 @@ const savePage = async () => {
701
713
  :dir="editingLang === 'ar' ? 'rtl' : 'ltr'"
702
714
  :placeholder="langLabel(editingLang)"
703
715
  class="flex-1 bg-white rounded-lg p-2 text-[10px] font-bold outline-none shadow-sm" />
704
- <button @click="item[subName].splice(fi, 1)" class="text-red-400 text-[8px] px-1 shrink-0">✕</button>
716
+ <button @click="item[subName].splice(fi, 1);
717
+ trigger()" class="text-red-400 text-[8px] px-1 shrink-0">✕</button>
705
718
  </div>
706
719
  <button @click="if (!item[subName]) item[subName] = [];
707
- item[subName].push({ fr: '', ar: '', en: '' })" class="w-full py-1.5 border border-dashed rounded-lg text-[8px] font-black text-gray-400 hover:border-gray-400 transition">+ Fonctionnalité</button>
720
+ item[subName].push({ fr: '', ar: '', en: '' });
721
+ trigger()" class="w-full py-1.5 border border-dashed rounded-lg text-[8px] font-black text-gray-400 hover:border-gray-400 transition">+ Fonctionnalité</button>
708
722
  </div>
709
- <input v-else v-model="item[subName]" class="w-full bg-white rounded-lg p-2 text-[10px] font-bold outline-none shadow-sm" />
723
+ <input v-else :value="item[subName]" @input="item[subName] = $event.target.value;
724
+ trigger()" class="w-full bg-white rounded-lg p-2 text-[10px] font-bold outline-none shadow-sm" />
710
725
  </div>
711
726
  </div>
712
727
  <button @click="if (!page.content[selectedBlockIndex].props[name]) page.content[selectedBlockIndex].props[name] = [];
713
- page.content[selectedBlockIndex].props[name].push({})" class="w-full py-2 border-2 border-dashed rounded-xl text-[8px] font-black text-gray-400">+ AJOUTER</button>
728
+ page.content[selectedBlockIndex].props[name].push({});
729
+ trigger()" class="w-full py-2 border-2 border-dashed rounded-xl text-[8px] font-black text-gray-400">+ AJOUTER</button>
714
730
  </div>
715
731
  </div>
716
732
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cinqcinqdev-seo",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "A reusable Nuxt 3 admin CMS module with visual page editor powered by Supabase",
5
5
  "license": "MIT",
6
6
  "module": "./dist/module.mjs",