Some checks failed
Deploy XIP / deploy (push) Failing after 37s
Fonctionnel
- Backend messages : GET /api/messages/:id (detail) + recherche (q),
pagination par curseur (before/limit) avec enveloppe { items, nextCursor,
hasMore } ; le flux temps reel garde l'ancien format quand aucun parametre.
- Explorer (/explorer) : catalogue distant, recherche debouncee + annulable
(AbortController), filtre, defilement infini, etat garde (keep-alive).
- Details par id : /message/:id et /shop/p/:id (consomment route.params).
- Favoris (/favoris) : liste perso persistee en localStorage, notation
(note/rating/statut) via modale, refletee partout (bouton favori).
- Mes stats (/mes-stats) : agregats derives des favoris (note moyenne, top
pays/auteurs, statuts), auto-mis a jour, route gardee si liste vide.
- Routeur : pages secondaires en lazy-load + repli, garde beforeEnter.
Technique
- Slots : PrefSection (slot defaut + slot nomme) enveloppe les 5 sections
"Mes Persos" ; Modal (Teleport + slots).
- v-model custom : SearchBox (defineModel + debounce).
- Directive custom : v-click-outside.
- Tests Vitest : 25 tests (etat, fonctions, composants), ~86% du code metier.
- Retrait d'Ionic (inutilise). Script typecheck backend ; tsconfig @types/bun.
- Correctif type : garde stockLimit nullable dans l'achat (catalog.ts).
- README complet (URL, stack, run, tests, secrets, deploiement, mention IA).
61 lines
1.4 KiB
TypeScript
61 lines
1.4 KiB
TypeScript
/**
|
|
* Global singleton for the right-click style context menu.
|
|
* Any component calls openContextMenu() to display the floating picker,
|
|
* and StyleContextMenu.vue (mounted once in App.vue) renders it.
|
|
*/
|
|
import { reactive } from 'vue';
|
|
|
|
export interface ContextMenuItem {
|
|
value: string;
|
|
label: string;
|
|
swatch?: string; // optional color swatch dot
|
|
emoji?: string; // optional emoji shown instead of swatch
|
|
isHeader?: boolean; // non-interactive section heading
|
|
checked?: boolean; // explicit checkmark (for multi-group menus)
|
|
}
|
|
|
|
interface MenuState {
|
|
visible: boolean;
|
|
x: number;
|
|
y: number;
|
|
title: string;
|
|
items: ContextMenuItem[];
|
|
current: string;
|
|
onSelect: (value: string) => void;
|
|
}
|
|
|
|
const state = reactive<MenuState>({
|
|
visible: false,
|
|
x: 0,
|
|
y: 0,
|
|
title: '',
|
|
items: [],
|
|
current: '',
|
|
onSelect: () => {},
|
|
});
|
|
|
|
export function openContextMenu(opts: {
|
|
x: number;
|
|
y: number;
|
|
title: string;
|
|
items: ContextMenuItem[];
|
|
current: string;
|
|
onSelect: (value: string) => void;
|
|
}): void {
|
|
state.visible = true;
|
|
state.x = opts.x;
|
|
state.y = opts.y;
|
|
state.title = opts.title;
|
|
state.items = opts.items;
|
|
state.current = opts.current;
|
|
state.onSelect = opts.onSelect;
|
|
}
|
|
|
|
export function closeContextMenu(): void {
|
|
state.visible = false;
|
|
}
|
|
|
|
export function useContextMenu() {
|
|
return { state };
|
|
}
|