fix: menu contextuel en DOM natif (bypass Alpine)
FlowDeck CI / test (push) Failing after 3s
FlowDeck CI / docker (push) Has been skipped

Alpine ne détecte pas les changements sur ctxMenu (objet imbriqué
hors du scope _wsInitData dans le DOM). Le :style et x-show restent
bloqués sur les valeurs initiales.

Solution radicale: manipulation DOM directe
- onContextMenu: menu.style.display='block' + position left/top
- ctxMenuOpen*/Rename/Duplicate/Delete: menu.style.display='none'
- @click.outside/@keydown.escape: DOM direct aussi
- ctx-menu HTML: style="display:none" initial
This commit is contained in:
2026-07-13 10:33:52 -04:00
parent 3e291ddc67
commit 0ad1a69994
+30 -38
View File
@@ -744,43 +744,36 @@ window._wsInitData = (function() {
// ── Context menu ──
onContextMenu(ev) {
console.log('onContextMenu fired', ev.clientX, ev.clientY);
var el = ev.target.closest('[data-ws-id]');
if (!el) { this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; return; }
var menu = document.querySelector('.ctx-menu');
if (!el || !menu) { if (menu) menu.style.display = 'none'; return; }
var id = parseInt(el.getAttribute('data-ws-id'));
if (!id) { this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; return; }
if (!id) { menu.style.display = 'none'; return; }
var node = this._findNodeById(this.displayTree, id);
if (!node) { this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; return; }
// Reassign whole object for Alpine reactivity
this.ctxMenu = {
visible: true,
x: ev.clientX,
y: ev.clientY,
node: node,
tags: [...(node.tags||[])]
};
if (!node) { menu.style.display = 'none'; return; }
// Store in Alpine data for menu actions (ctxMenuOpenPage, etc.)
this.ctxMenu = {visible:true, x:ev.clientX, y:ev.clientY, node:node, tags:[...(node.tags||[])]};
this.ctxTagAdding = false;
this.ctxTagExistingOpen = false;
this.ctxNewTagColor = '#787774';
// Load available tags
this.ctxAvailTags = (this.workspaceTags || []).filter(function(t) {
return !(node.tags||[]).some(function(nt) { return nt.id === t.id; });
});
// Reposition after DOM update
var self = this;
self.$nextTick(function() {
var menu = self.$el.querySelector('.ctx-menu');
if (!menu) return;
var rect = menu.getBoundingClientRect();
var x = self.ctxMenu.x;
var y = self.ctxMenu.y;
if (x + rect.width > window.innerWidth - 4) x = window.innerWidth - rect.width - 4;
if (y + rect.height > window.innerHeight - 4) y = window.innerHeight - rect.height - 4;
if (x < 4) x = 4;
if (y < 4) y = 4;
// Reassign for reactivity
self.ctxMenu = {visible:true, x:x, y:y, node:self.ctxMenu.node, tags:self.ctxMenu.tags};
});
// DOM manipulation (bypasses Alpine reactivity issues)
menu.style.display = 'block';
menu.style.left = ev.clientX + 'px';
menu.style.top = ev.clientY + 'px';
// Reposition if offscreen
var rect = menu.getBoundingClientRect();
var x = ev.clientX, y = ev.clientY;
if (x + rect.width > window.innerWidth - 4) x = window.innerWidth - rect.width - 4;
if (y + rect.height > window.innerHeight - 4) y = window.innerHeight - rect.height - 4;
if (x < 4) x = 4;
if (y < 4) y = 4;
menu.style.left = x + 'px';
menu.style.top = y + 'px';
this.ctxMenu.x = x;
this.ctxMenu.y = y;
},
// ── Long-press (mobile context menu) ──
@@ -829,24 +822,24 @@ window._wsInitData = (function() {
ctxMenuOpenPage() {
if (this.ctxMenu.node) this.openPage(this.ctxMenu.node.id);
this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]};
document.querySelector('.ctx-menu').style.display = 'none';
},
ctxMenuOpenFolder() {
if (this.ctxMenu.node) this.navigateToFolder(this.ctxMenu.node.id);
this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]};
document.querySelector('.ctx-menu').style.display = 'none';
},
ctxMenuRename() {
if (this.ctxMenu.node) { this.startRename(this.ctxMenu.node); this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; }
if (this.ctxMenu.node) { this.startRename(this.ctxMenu.node); document.querySelector('.ctx-menu').style.display = 'none'; }
},
ctxMenuDuplicate() {
if (this.ctxMenu.node) { this.duplicateItem(this.ctxMenu.node); this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; }
if (this.ctxMenu.node) { this.duplicateItem(this.ctxMenu.node); document.querySelector('.ctx-menu').style.display = 'none'; }
},
ctxMenuDelete() {
if (this.ctxMenu.node) { this.deleteWithUndo(this.ctxMenu.node); this.ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}; }
if (this.ctxMenu.node) { this.deleteWithUndo(this.ctxMenu.node); document.querySelector('.ctx-menu').style.display = 'none'; }
},
ctxRemoveTag(tagId) {
@@ -1928,7 +1921,7 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
<script>console.log('FLOWDECK v2.4.6 — ws-split about to render, _wsInitData exists:', typeof window._wsInitData !== 'undefined');</script>
<div class="ws-split"
@contextmenu.prevent="_wsInitData.onContextMenu($event); ctxMenu = _wsInitData.ctxMenu"
@contextmenu.prevent="_wsInitData.onContextMenu($event)"
@click.self="ctxMenu.visible = false"
@touchstart="onTouchStart($event)"
@touchend="onTouchEnd($event)"
@@ -2357,10 +2350,9 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
</div><!-- /ws-main -->
<!-- Context menu -->
<div class="ctx-menu"
:style="{ left: ctxMenu.x + 'px', top: ctxMenu.y + 'px', display: ctxMenu.visible ? 'block' : 'none' }"
@click.outside="ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}"
@keydown.escape="ctxMenu = {visible:false,x:0,y:0,node:null,tags:[]}"
<div class="ctx-menu" style="display:none"
@click.outside="document.querySelector('.ctx-menu').style.display='none'"
@keydown.escape="document.querySelector('.ctx-menu').style.display='none'"
x-transition>
<div class="ctx-item" @click="ctxMenuOpenPage()">📄 Open</div>
<div class="ctx-item" x-show="ctxMenu.node && ctxMenu.node.is_folder" @click="ctxMenuOpenFolder()">📂 Open folder</div>