-drag menu continua
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
.q-select {
|
||||
.q-item {
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.q-item__section--main {
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,19 @@
|
||||
import { defineComponent, ref, computed, watch, reactive, toRaw, nextTick } from 'vue';
|
||||
import { defineComponent, reactive, computed, watch } from 'vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
import IconPicker from '../IconPicker/IconPicker.vue';
|
||||
import { IMyPage } from 'app/src/model';
|
||||
import { IconPicker } from '@src/components/IconPicker';
|
||||
import { useGlobalStore } from 'app/src/store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { costanti } from '@costanti';
|
||||
import { CMyFieldRec } from '@src/components/CMyFieldRec';
|
||||
|
||||
const norm = (s?: string) => (s || '').trim().replace(/^\//, '').toLowerCase();
|
||||
const withSlash = (s?: string) => {
|
||||
const p = (s || '').trim();
|
||||
if (!p) return '/';
|
||||
return p.startsWith('/') ? p : `/${p}`;
|
||||
return p ? (p.startsWith('/') ? p : `/${p}`) : '/';
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PageEditor',
|
||||
components: { IconPicker, CMyFieldRec },
|
||||
components: { IconPicker },
|
||||
props: {
|
||||
modelValue: { type: Object as () => IMyPage, required: true },
|
||||
nuovaPagina: { type: Boolean, required: true },
|
||||
@@ -27,480 +23,153 @@ export default defineComponent({
|
||||
const $q = useQuasar();
|
||||
const { t } = useI18n();
|
||||
const globalStore = useGlobalStore();
|
||||
const { mypage } = storeToRefs(globalStore);
|
||||
|
||||
const draft = reactive<IMyPage>({ ...props.modelValue });
|
||||
|
||||
const ui = reactive({
|
||||
pathText: toUiPath(draft.path),
|
||||
pathText: withSlash(draft.path),
|
||||
isSubmenu: !!draft.submenu,
|
||||
parentId: null as string | null,
|
||||
childrenPaths: [] as string[],
|
||||
});
|
||||
|
||||
watch(
|
||||
() => ui.isSubmenu,
|
||||
(isSub) => {
|
||||
draft.submenu = !!isSub;
|
||||
if (isSub) {
|
||||
// una pagina figlia non gestisce figli propri
|
||||
ui.childrenPaths = [];
|
||||
// se non c'è un parent pre-selezionato, azzera
|
||||
if (!ui.parentId) ui.parentId = findParentIdForChild(draft.path);
|
||||
} else {
|
||||
// tornando top-level, nessun parent selezionato
|
||||
ui.parentId = null;
|
||||
}
|
||||
}
|
||||
);
|
||||
const iconModel = computed({
|
||||
get() {
|
||||
return {
|
||||
icon: draft.icon,
|
||||
size: draft.iconsize || '20px',
|
||||
};
|
||||
},
|
||||
set(value) {
|
||||
draft.icon = value.icon || '';
|
||||
draft.iconsize = value.size || '20px';
|
||||
},
|
||||
});
|
||||
|
||||
// Draft indipendente
|
||||
const saving = ref(false);
|
||||
const syncingFromProps = ref(false);
|
||||
const previousPath = ref<string>(draft.path || '');
|
||||
|
||||
// ===== INIT =====
|
||||
// parent corrente (se questa pagina è sottomenu)
|
||||
ui.parentId = findParentIdForChild(draft.path);
|
||||
// inizializza lista figli (per TOP-LEVEL) con i path presenti nel draft
|
||||
// Inizializza i figli
|
||||
ui.childrenPaths = Array.isArray(draft.sottoMenu)
|
||||
? draft.sottoMenu.map((p) => withSlash(p))
|
||||
: [];
|
||||
|
||||
// --- Sync IN: quando cambia il modelValue (esterno)
|
||||
// Watch per sincronizzare con le modifiche esterne
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (v) => {
|
||||
syncingFromProps.value = true;
|
||||
Object.assign(draft, v || {});
|
||||
ui.pathText = toUiPath(draft.path);
|
||||
(v) => {
|
||||
Object.assign(draft, v);
|
||||
ui.pathText = withSlash(draft.path);
|
||||
ui.isSubmenu = !!draft.submenu;
|
||||
ui.parentId = findParentIdForChild(draft.path);
|
||||
ui.childrenPaths = Array.isArray(draft.sottoMenu)
|
||||
? draft.sottoMenu.map((p) => withSlash(p))
|
||||
: [];
|
||||
previousPath.value = draft.path || '';
|
||||
await nextTick();
|
||||
syncingFromProps.value = false;
|
||||
},
|
||||
{ deep: false }
|
||||
);
|
||||
|
||||
// --- Propagazione live (solo se NON nuovaPagina)
|
||||
watch(
|
||||
draft,
|
||||
(val) => {
|
||||
if (syncingFromProps.value) return;
|
||||
if (props.nuovaPagina) return;
|
||||
upsertIntoStore(val, mypage.value);
|
||||
emit('update:modelValue', { ...val });
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
function onToggleSubmenu(val: boolean) {
|
||||
draft.submenu = !!val;
|
||||
if (val) {
|
||||
draft.inmenu = true;
|
||||
ui.childrenPaths = []; // sicurezza
|
||||
}
|
||||
}
|
||||
|
||||
// ======= OPTIONS =======
|
||||
const parentOptions = computed(() =>
|
||||
(mypage.value || [])
|
||||
.filter(
|
||||
(p) =>
|
||||
p &&
|
||||
p.inmenu &&
|
||||
!p.submenu &&
|
||||
norm(p.path) !== norm(draft.path) &&
|
||||
p._id !== draft._id // <-- escludi se stesso
|
||||
)
|
||||
.map((p) => ({
|
||||
value: (p._id || p.path || '') as string,
|
||||
label: `${p.title || withSlash(p.path)}`,
|
||||
}))
|
||||
);
|
||||
|
||||
// Mappa path (display) -> page
|
||||
const pageByDisplayPath = computed(() => {
|
||||
const m = new Map<string, IMyPage>();
|
||||
(mypage.value || []).forEach((p) => {
|
||||
m.set(withSlash(p.path).toLowerCase(), p);
|
||||
});
|
||||
return m;
|
||||
});
|
||||
|
||||
// Mappa childPathDisplay -> parentId
|
||||
const parentByChildPath = computed(() => {
|
||||
const map = new Map<string, string>();
|
||||
(mypage.value || []).forEach((p) => {
|
||||
if (p && p.inmenu && !p.submenu && Array.isArray(p.sottoMenu)) {
|
||||
p.sottoMenu.forEach((sp) => {
|
||||
map.set(withSlash(sp).toLowerCase(), (p._id || p.path) as string);
|
||||
});
|
||||
}
|
||||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
// Candidati figli per il selettore (indent: 0 per "disponibili", 1 per "già figli di altri", 1 anche per già figli miei)
|
||||
// Opzioni per i figli
|
||||
const childCandidateOptions = computed(() => {
|
||||
const selfPathDisp = withSlash(draft.path).toLowerCase();
|
||||
const selfPath = withSlash(draft.path).toLowerCase();
|
||||
const mine = new Set(ui.childrenPaths.map((x) => x.toLowerCase()));
|
||||
const opts: Array<{
|
||||
value: string;
|
||||
label: string;
|
||||
level: number;
|
||||
disabled?: boolean;
|
||||
hint?: string;
|
||||
}> = [];
|
||||
|
||||
(mypage.value || [])
|
||||
.filter((p) => p._id !== draft._id && norm(p.path) !== norm(draft.path)) // escludi se stesso
|
||||
.forEach((p) => {
|
||||
return globalStore.mypage
|
||||
.filter((p) => p._id !== draft._id && norm(p.path) !== norm(draft.path))
|
||||
.map((p) => {
|
||||
const disp = withSlash(p.path);
|
||||
const dispKey = disp.toLowerCase();
|
||||
const parentId = parentByChildPath.value.get(dispKey);
|
||||
|
||||
if (mine.has(dispKey)) {
|
||||
// già selezionato come mio figlio
|
||||
opts.push({
|
||||
value: disp,
|
||||
label: labelForPage(p),
|
||||
level: 1,
|
||||
hint: 'figlio di questa pagina',
|
||||
});
|
||||
} else if (!parentId || parentId === (draft._id || draft.path)) {
|
||||
// orfano (o già mio → già gestito sopra)
|
||||
opts.push({
|
||||
value: disp,
|
||||
label: labelForPage(p),
|
||||
level: 0,
|
||||
});
|
||||
} else {
|
||||
// figlio di un altro parent → lo mostro ma lo disabilito
|
||||
const parent = (mypage.value || []).find(
|
||||
(pp) => (pp._id || pp.path) === parentId
|
||||
);
|
||||
opts.push({
|
||||
value: disp,
|
||||
label: labelForPage(p),
|
||||
level: 1,
|
||||
disabled: true,
|
||||
hint: `già sotto " ${parent?.title || withSlash(parent?.path)}"`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Ordina per livello e label
|
||||
return opts.sort((a, b) => a.level - b.level || a.label.localeCompare(b.label));
|
||||
return {
|
||||
value: disp,
|
||||
label: p.title || disp,
|
||||
level: mine.has(dispKey) ? 1 : 0,
|
||||
disabled: mine.has(dispKey)
|
||||
? false
|
||||
: globalStore.mypage.some(
|
||||
(parent) =>
|
||||
Array.isArray(parent.sottoMenu) &&
|
||||
parent.sottoMenu.some((sp) => norm(sp) === norm(p.path))
|
||||
),
|
||||
hint: mine.has(dispKey) ? 'figlio di questa pagina' : undefined,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.level - b.level || a.label.localeCompare(b.label));
|
||||
});
|
||||
|
||||
function toUiPath(storePath?: string) {
|
||||
const p = (storePath || '').trim();
|
||||
if (!p) return '/';
|
||||
return p.startsWith('/') ? p : `/${p}`;
|
||||
}
|
||||
|
||||
function toStorePath(uiPath?: string) {
|
||||
const p = (uiPath || '').trim();
|
||||
if (!p) return '';
|
||||
return p.startsWith('/') ? p.slice(1) : p;
|
||||
}
|
||||
|
||||
function normalizeAndApplyPath() {
|
||||
let p = (ui.pathText || '/').trim();
|
||||
p = p.replace(/\s+/g, '-');
|
||||
// Normalizza il percorso
|
||||
function normalizePath() {
|
||||
let p = ui.pathText.trim().replace(/\s+/g, '-');
|
||||
if (!p.startsWith('/')) p = '/' + p;
|
||||
ui.pathText = p;
|
||||
draft.path = toStorePath(p);
|
||||
draft.path = p.startsWith('/') ? p.slice(1) : p;
|
||||
}
|
||||
|
||||
function pathRule(v: string) {
|
||||
// Validazione percorso
|
||||
function validatePath(v: string) {
|
||||
if (!v) return 'Percorso richiesto';
|
||||
if (!v.startsWith('/')) return 'Deve iniziare con /';
|
||||
if (/\s/.test(v)) return 'Nessuno spazio nel path';
|
||||
return true;
|
||||
}
|
||||
|
||||
// ======= STORE UTILS =======
|
||||
function upsertIntoStore(page: IMyPage, arr: IMyPage[]) {
|
||||
if (!page) return;
|
||||
const keyId = page._id;
|
||||
const keyPath = page.path || '';
|
||||
let idx = -1;
|
||||
if (keyId) idx = arr.findIndex((p) => p._id === keyId);
|
||||
if (idx < 0 && keyPath) idx = arr.findIndex((p) => (p.path || '') === keyPath);
|
||||
if (idx >= 0) arr[idx] = { ...arr[idx], ...toRaw(page) };
|
||||
else arr.push({ ...toRaw(page) });
|
||||
}
|
||||
// Salva la pagina
|
||||
async function save() {
|
||||
normalizePath();
|
||||
|
||||
function findParentIdForChild(childPath?: string | null): string | null {
|
||||
const target = withSlash(childPath || '');
|
||||
for (const p of mypage.value) {
|
||||
if (p && p.inmenu && !p.submenu && Array.isArray(p.sottoMenu)) {
|
||||
if (
|
||||
p.sottoMenu.some((sp) => withSlash(sp).toLowerCase() === target.toLowerCase())
|
||||
) {
|
||||
return (p._id || p.path) as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function findParentsReferencing(childDisplayPath: string): IMyPage[] {
|
||||
const target = withSlash(childDisplayPath).toLowerCase();
|
||||
return (mypage.value || []).filter(
|
||||
(p) =>
|
||||
p &&
|
||||
p.inmenu &&
|
||||
!p.submenu &&
|
||||
Array.isArray(p.sottoMenu) &&
|
||||
p.sottoMenu.some((sp) => withSlash(sp).toLowerCase() === target)
|
||||
);
|
||||
}
|
||||
|
||||
function addChildToParent(parent: IMyPage, childDisplayPath: string) {
|
||||
if (!Array.isArray(parent.sottoMenu)) parent.sottoMenu = [];
|
||||
const target = withSlash(childDisplayPath);
|
||||
if (
|
||||
!parent.sottoMenu.some(
|
||||
(sp) => withSlash(sp).toLowerCase() === target.toLowerCase()
|
||||
)
|
||||
) {
|
||||
parent.sottoMenu.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
function removeChildFromParent(parent: IMyPage, childDisplayPath: string) {
|
||||
if (!Array.isArray(parent.sottoMenu)) return;
|
||||
const target = withSlash(childDisplayPath).toLowerCase();
|
||||
parent.sottoMenu = parent.sottoMenu.filter(
|
||||
(sp) => withSlash(sp).toLowerCase() !== target
|
||||
);
|
||||
}
|
||||
|
||||
// ======= SAVE =======
|
||||
async function checkAndSave(payloadDraft?: IMyPage) {
|
||||
const cur = payloadDraft || draft;
|
||||
|
||||
// validazioni base
|
||||
if (!cur.title?.trim()) {
|
||||
// Validazioni
|
||||
if (!draft.title?.trim()) {
|
||||
$q.notify({ message: 'Inserisci il titolo della pagina', type: 'warning' });
|
||||
return;
|
||||
}
|
||||
const pathText = (ui.pathText || '').trim();
|
||||
if (!pathText) {
|
||||
$q.notify({ message: 'Inserisci il percorso della pagina', type: 'warning' });
|
||||
|
||||
if (!validatePath(ui.pathText)) {
|
||||
$q.notify({ message: 'Percorso non valido', type: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
const candidatePath = toStorePath(pathText).toLowerCase();
|
||||
const existPath = globalStore.mypage.find(
|
||||
(r) => (r.path || '').toLowerCase() === candidatePath && r._id !== cur._id
|
||||
);
|
||||
if (existPath) {
|
||||
$q.notify({
|
||||
message: "Esiste già un'altra pagina con questo percorso",
|
||||
type: 'warning',
|
||||
});
|
||||
return;
|
||||
// Aggiorna la struttura
|
||||
draft.submenu = ui.isSubmenu;
|
||||
if (!ui.isSubmenu) {
|
||||
draft.sottoMenu = ui.childrenPaths.map((p) =>
|
||||
p.startsWith('/') ? p.slice(1) : p
|
||||
);
|
||||
}
|
||||
|
||||
const candidateTitle = (cur.title || '').toLowerCase();
|
||||
const existName = globalStore.mypage.find(
|
||||
(r) => (r.title || '').toLowerCase() === candidateTitle && r._id !== cur._id
|
||||
);
|
||||
if (existName) {
|
||||
$q.notify({ message: 'Il nome della pagina esiste già', type: 'warning' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (ui.isSubmenu && !ui.parentId) {
|
||||
$q.notify({
|
||||
message: 'Seleziona la pagina padre per questo sottomenu',
|
||||
type: 'warning',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await save();
|
||||
emit('hide');
|
||||
}
|
||||
|
||||
async function save() {
|
||||
try {
|
||||
saving.value = true;
|
||||
normalizeAndApplyPath();
|
||||
|
||||
// sync flag submenu
|
||||
draft.submenu = !!ui.isSubmenu;
|
||||
|
||||
// se top-level, sincronizza anche i figli dal selettore
|
||||
if (!ui.isSubmenu) {
|
||||
draft.sottoMenu = ui.childrenPaths.slice();
|
||||
const saved = await globalStore.savePage({ ...draft });
|
||||
if (saved) {
|
||||
emit('update:modelValue', { ...saved });
|
||||
emit('apply', { ...saved });
|
||||
$q.notify({ type: 'positive', message: 'Pagina salvata' });
|
||||
}
|
||||
|
||||
// --- salva/aggiorna pagina corrente
|
||||
const payload: IMyPage = { ...toRaw(draft), path: draft.path || '' };
|
||||
const saved = await globalStore.savePage(payload);
|
||||
if (saved && typeof saved === 'object') {
|
||||
syncingFromProps.value = true;
|
||||
Object.assign(draft, saved);
|
||||
upsertIntoStore(draft, mypage.value);
|
||||
await nextTick();
|
||||
syncingFromProps.value = false;
|
||||
}
|
||||
|
||||
// --- aggiorna legami parentali
|
||||
const prevDisplay = withSlash(previousPath.value);
|
||||
const newDisplay = withSlash(draft.path);
|
||||
|
||||
// 1) questa pagina è figlia? collega/sgancia dal parent
|
||||
const parentsPrev = findParentsReferencing(prevDisplay);
|
||||
for (const p of parentsPrev) {
|
||||
// se la pagina è ancora sottomenu con lo stesso parent e path invariato, mantieni
|
||||
const keep =
|
||||
ui.isSubmenu &&
|
||||
ui.parentId === (p._id || p.path) &&
|
||||
prevDisplay.toLowerCase() === newDisplay.toLowerCase();
|
||||
if (!keep) {
|
||||
removeChildFromParent(p, prevDisplay);
|
||||
await globalStore.savePage(p);
|
||||
}
|
||||
}
|
||||
if (ui.isSubmenu && ui.parentId) {
|
||||
const parent =
|
||||
mypage.value.find((pp) => (pp._id || pp.path) === ui.parentId) || null;
|
||||
if (parent) {
|
||||
parent.inmenu = true;
|
||||
parent.submenu = false;
|
||||
addChildToParent(parent, newDisplay);
|
||||
await globalStore.savePage(parent);
|
||||
}
|
||||
}
|
||||
|
||||
// 2) se questa pagina è TOP-LEVEL, salva i riferimenti dei figli
|
||||
if (!ui.isSubmenu) {
|
||||
// rimuovi da tutti i parent eventuali vecchi riferimenti ai miei figli (che non sono più nella lista)
|
||||
const still = new Set(ui.childrenPaths.map((x) => x.toLowerCase()));
|
||||
const parentsTouch = (mypage.value || []).filter(
|
||||
(p) => p.inmenu && !p.submenu && Array.isArray(p.sottoMenu)
|
||||
);
|
||||
for (const pr of parentsTouch) {
|
||||
const before = (pr.sottoMenu || []).slice();
|
||||
pr.sottoMenu = (pr.sottoMenu || []).filter((sp) => {
|
||||
const spKey = withSlash(sp).toLowerCase();
|
||||
// tieni solo i figli che non appartengono a me oppure appartengono a me e sono ancora in lista
|
||||
const belongsToMe = (pr._id || pr.path) === (draft._id || draft.path);
|
||||
return !belongsToMe || still.has(spKey);
|
||||
});
|
||||
if (JSON.stringify(before) !== JSON.stringify(pr.sottoMenu)) {
|
||||
await globalStore.savePage(pr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit('update:modelValue', { ...draft });
|
||||
emit('apply', { ...draft });
|
||||
$q.notify({ type: 'positive', message: 'Pagina salvata' });
|
||||
|
||||
previousPath.value = draft.path || '';
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$q.notify({ type: 'negative', message: 'Errore nel salvataggio' });
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadFromStore() {
|
||||
try {
|
||||
const absolute = ui.pathText || '/';
|
||||
const page = await globalStore.loadPage(absolute, '', true);
|
||||
if (page) {
|
||||
syncingFromProps.value = true;
|
||||
Object.assign(draft, page);
|
||||
ui.pathText = toUiPath(draft.path);
|
||||
ui.isSubmenu = !!draft.submenu;
|
||||
ui.parentId = findParentIdForChild(draft.path);
|
||||
ui.childrenPaths = Array.isArray(draft.sottoMenu)
|
||||
? draft.sottoMenu.map((p) => withSlash(p))
|
||||
: [];
|
||||
upsertIntoStore(draft, mypage.value);
|
||||
if (!props.nuovaPagina) emit('update:modelValue', { ...draft });
|
||||
await nextTick();
|
||||
syncingFromProps.value = false;
|
||||
previousPath.value = draft.path || '';
|
||||
$q.notify({ type: 'info', message: 'Pagina ricaricata' });
|
||||
} else {
|
||||
$q.notify({ type: 'warning', message: 'Pagina non trovata' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
$q.notify({ type: 'negative', message: 'Errore nel ricaricare la pagina' });
|
||||
}
|
||||
}
|
||||
|
||||
function resetDraft() {
|
||||
syncingFromProps.value = true;
|
||||
Object.assign(draft, props.modelValue || {});
|
||||
ui.pathText = toUiPath(draft.path);
|
||||
ui.isSubmenu = !!draft.submenu;
|
||||
ui.parentId = findParentIdForChild(draft.path);
|
||||
ui.childrenPaths = Array.isArray(draft.sottoMenu)
|
||||
? draft.sottoMenu.map((p) => withSlash(p))
|
||||
: [];
|
||||
if (!props.nuovaPagina) emit('update:modelValue', { ...draft });
|
||||
nextTick(() => {
|
||||
syncingFromProps.value = false;
|
||||
});
|
||||
previousPath.value = draft.path || '';
|
||||
}
|
||||
|
||||
// === LABEL UTILS ===
|
||||
// Funzioni per label
|
||||
function labelForPage(p: IMyPage) {
|
||||
return p.title || withSlash(p.path);
|
||||
}
|
||||
|
||||
function labelForPath(dispPath: string) {
|
||||
const p = pageByDisplayPath.value.get(dispPath.toLowerCase());
|
||||
const p = globalStore.mypage.find(
|
||||
(page) => withSlash(page.path).toLowerCase() === dispPath.toLowerCase()
|
||||
);
|
||||
return p ? labelForPage(p) : dispPath;
|
||||
}
|
||||
|
||||
function modifElem() {
|
||||
/* per compat compat con CMyFieldRec */
|
||||
}
|
||||
|
||||
const absolutePath = computed(() => toUiPath(draft.path));
|
||||
|
||||
return {
|
||||
draft,
|
||||
ui,
|
||||
saving,
|
||||
t,
|
||||
costanti,
|
||||
// helpers UI
|
||||
pathRule,
|
||||
normalizeAndApplyPath,
|
||||
validatePath,
|
||||
normalizePath,
|
||||
labelForPath,
|
||||
labelForPage,
|
||||
withSlash,
|
||||
// actions
|
||||
checkAndSave,
|
||||
save,
|
||||
reloadFromStore,
|
||||
resetDraft,
|
||||
modifElem,
|
||||
onToggleSubmenu,
|
||||
// options
|
||||
parentOptions,
|
||||
childCandidateOptions,
|
||||
// expose util
|
||||
absolutePath,
|
||||
iconModel,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
v-model="ui.pathText"
|
||||
label="Percorso (relativo, es: /about)"
|
||||
dense
|
||||
:rules="[pathRule]"
|
||||
@blur="normalizeAndApplyPath"
|
||||
:rules="[validatePath]"
|
||||
@blur="normalizePath"
|
||||
>
|
||||
<template #prepend><q-icon name="fas fa-link" /></template>
|
||||
</q-input>
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<!-- ICONA -->
|
||||
<div class="col-12 col-md-6">
|
||||
<icon-picker v-model="draft.icon" />
|
||||
<icon-picker v-model="iconModel"/>
|
||||
</div>
|
||||
|
||||
<!-- STATO & VISIBILITÀ -->
|
||||
@@ -81,12 +81,12 @@
|
||||
<!-- GESTIONE FIGLI (solo se questa pagina è TOP-LEVEL) -->
|
||||
<div
|
||||
class="col-12"
|
||||
v-if="!ui.isSubmenu"
|
||||
v-if="draft.inmenu && !ui.isSubmenu"
|
||||
>
|
||||
<q-separator spaced />
|
||||
<div class="text-subtitle2 q-mb-sm">SottoMenu</div>
|
||||
|
||||
<!-- Selettore multivalore dei figli (con label indentata nell'option slot) -->
|
||||
<!-- Selettore multivalore dei figli -->
|
||||
<q-select
|
||||
v-model="ui.childrenPaths"
|
||||
:options="childCandidateOptions"
|
||||
@@ -124,7 +124,7 @@
|
||||
</template>
|
||||
</q-select>
|
||||
|
||||
<!-- Preview gerarchica reale (indentata) -->
|
||||
<!-- Preview gerarchica -->
|
||||
<div class="q-mt-md">
|
||||
<div class="text-caption text-grey-7 q-mb-xs">Anteprima struttura</div>
|
||||
<q-list
|
||||
@@ -169,13 +169,7 @@
|
||||
<q-btn
|
||||
color="primary"
|
||||
label="Salva"
|
||||
:loading="saving"
|
||||
@click="checkAndSave(draft)"
|
||||
/>
|
||||
<q-btn
|
||||
outline
|
||||
label="Ricarica"
|
||||
@click="reloadFromStore"
|
||||
@click="save"
|
||||
/>
|
||||
<q-btn
|
||||
color="grey-7"
|
||||
@@ -187,7 +181,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" src="./PageEditor.ts"></script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './PageEditor.scss';
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user