feat(ctxmenu): unify row context menu via shared partial (_ctx_menu.html) for library+workspace; keep data-cfasync=false on page_editor_scripts
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
{###############################################################################
|
||||
_ctx_menu.html — LE menu contextuel UNIQUE de FlowDeck (v5.4.x)
|
||||
═══════════════════════════════════════════════════════════════════════════
|
||||
Inclus par library.html ET local_workspace.html (les 2 seules pages du
|
||||
contexte fichier). Ce partial est LE SEUL menu : son markup décrit l'UNION
|
||||
des options des deux pages d'origine. Les exécutions sont déléguées par
|
||||
chaque page au travers d'un store Alpine enregistré PLUS BAS (IIFE + défense
|
||||
d'exécution unique → include 2 fois = store 1 fois).
|
||||
|
||||
Union des items (identiques sur les 2 pages) :
|
||||
1. Open .................... library: openItem / ws: ctxMenuOpenPage
|
||||
2. Open in side peek ....... library: openPeek (ws: item caché)
|
||||
3. Open folder ............. ws: ctxMenuOpenFolder (dossier uniquement)
|
||||
4. Rename .................. library: startRename / ws: ctxMenuRename
|
||||
5. Add/Remove Favorites .... library: toggleFavoriteItem (ws: caché)
|
||||
6. Duplicate ............... library: duplicateItem / ws: ctxMenuDuplicate
|
||||
7. Copy link ............... library: copyLink (ws: caché)
|
||||
8. Move to ................. library: openMovePicker (ws: caché)
|
||||
9. Tags (section dynamique) ws: ctxTagExisting/… (s'affiche si node.tags)
|
||||
10. Delete .................. library: deleteItem / ws: ctxMenuDelete (danger)
|
||||
|
||||
Chaque item masque ses entrées quand `handlers` de l'autre page est vide
|
||||
(x-show="fdCtx.has('key')") → le menu est STRUCTURELLEMENT identique des
|
||||
deux côtés, avec la capacité respective au clic.
|
||||
############################################################################}
|
||||
<script data-cfasync="false">
|
||||
/* ═════════════════════════════════════════════════════════════════════
|
||||
fdCtx — store Alpine UNIQUE du menu contextuel partagé.
|
||||
Enregistré dans 'alpine:init' avec garde d'exécution : que la page
|
||||
inclue ce partial dans base.html ou directement, le js ne s'exécute
|
||||
qu'une seule fois. Chaque page, à l'ouverture du menu, positionne :
|
||||
fdCtx.open(evtX, evtY, node, pageHash, handlers)
|
||||
où handlers = { open, peek, folder, rename, fav, duplicate, link,
|
||||
move, del, tagAddExisting, tagAddNew, tagRemove, avail }
|
||||
─────────────────────────────────────────────────────────────────── */
|
||||
(function () {
|
||||
if (window.__fdCtxMenuRegistered) return;
|
||||
window.__fdCtxMenuRegistered = true;
|
||||
|
||||
document.addEventListener('alpine:init', function () {
|
||||
if (window.Alpine && window.Alpine.__fdCtxStore) return;
|
||||
if (window.Alpine) window.Alpine.__fdCtxStore = true;
|
||||
|
||||
Alpine.store('fdCtx', {
|
||||
open: false, x: 0, y: 0, node: null, page: null,
|
||||
handlers: {}, _cache: {},
|
||||
|
||||
/* Positionne le menu à l'écran et expose les handlers de la page.
|
||||
⚠️ Nom : openMenu (et PAS open) — « open » est réservé au booléen
|
||||
d'état x-show. Une collision ici rendrait le menu PERMANENT. */
|
||||
openMenu(ev, node, pageHash, handlers) {
|
||||
handlers = handlers || {};
|
||||
this.x = Math.min((ev && ev.clientX) || 0, window.innerWidth - 230);
|
||||
this.y = Math.min((ev && ev.clientY) || 0, window.innerHeight - 60);
|
||||
this.node = node;
|
||||
this.page = pageHash;
|
||||
this.handlers = handlers;
|
||||
this.open = true;
|
||||
},
|
||||
close() { this.open = false; this.node = null; },
|
||||
|
||||
/* Un item est-il disponible pour CETTE page ? (union vs capacités) */
|
||||
has(key) { return typeof this.handlers[key] === 'function'; },
|
||||
|
||||
/* Exécute l'action → handler fourni par la page courante. */
|
||||
run(key) {
|
||||
if (!this.node) { this.close(); return; }
|
||||
var fn = this.handlers[key];
|
||||
if (fn) try { fn(this.node); } catch (e) { console.error('fdCtx.run', e); }
|
||||
this.close();
|
||||
},
|
||||
hop(key) { this.run(key); },
|
||||
|
||||
/* Routage Tags (workspace) — cache + ajout/suppression. */
|
||||
avail() { return this._cache.avail || []; },
|
||||
setAvail(a) { this._cache.avail = a || []; },
|
||||
removeTag(id) { this.run('tagRemove'); },
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="more-menu" x-show="$store.fdCtx.open"
|
||||
@click.outside="$store.fdCtx.close()" x-transition
|
||||
:style="{ top: $store.fdCtx.y + 'px', left: $store.fdCtx.x + 'px' }">
|
||||
|
||||
{# ── 1. Open ── #}
|
||||
<div class="menu-item" @click="$store.fdCtx.run('open')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
||||
Open
|
||||
</div>
|
||||
|
||||
{# ── 2. Open in side peek (library) ── #}
|
||||
<div class="menu-item" x-show="$store.fdCtx.has('peek')" @click="$store.fdCtx.run('peek')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="13" y1="3" x2="13" y2="21"/></svg>
|
||||
Open in side peek
|
||||
</div>
|
||||
|
||||
{# ── 3. Open folder (workspace, dossiers) ── #}
|
||||
<div class="menu-item" x-show="$store.fdCtx.has('folder')" @click="$store.fdCtx.run('folder')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
|
||||
Open folder
|
||||
</div>
|
||||
|
||||
{# ── 4. Rename ── #}
|
||||
<div class="menu-item" @click="$store.fdCtx.run('rename')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.12 2.12 0 013 3L12 15l-4 1 1-4z"/></svg>
|
||||
Rename
|
||||
</div>
|
||||
|
||||
{# ── 5. Add/Remove Favorites (library) ── #}
|
||||
<div class="menu-item" x-show="$store.fdCtx.has('fav')" @click="$store.fdCtx.run('fav')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||||
<span x-text="$store.fdCtx.node && $store.fdCtx.node.favorited ? 'Remove from Favorites' : 'Add to Favorites'"></span>
|
||||
</div>
|
||||
|
||||
{# ── 6. Duplicate ── #}
|
||||
<div class="menu-item" @click="$store.fdCtx.run('duplicate')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
||||
Duplicate
|
||||
</div>
|
||||
|
||||
{# ── 7. Copy link (library) ── #}
|
||||
<div class="menu-item" x-show="$store.fdCtx.has('link')" @click="$store.fdCtx.run('link')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg>
|
||||
Copy link
|
||||
</div>
|
||||
|
||||
{# ── 8. Move to (library) ── #}
|
||||
<div class="menu-item" x-show="$store.fdCtx.has('move')" @click="$store.fdCtx.run('move')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
|
||||
Move to
|
||||
</div>
|
||||
|
||||
{# ── 9. Tags (workspace) ── #}
|
||||
<div class="more-sep" x-show="$store.fdCtx.has('tagExisting') || $store.fdCtx.has('tagNew')"></div>
|
||||
<template x-if="$store.fdCtx.has('tagExisting') || $store.fdCtx.has('tagNew')">
|
||||
<div style="padding:2px 0;">
|
||||
<div class="menu-item" @click="$store.fdCtx.handlers.tagExisting" style="cursor:default;">
|
||||
<span style="flex:1;">Tags</span>
|
||||
</div>
|
||||
<template x-for="t in ($store.fdCtx.node && $store.fdCtx.node.tags) || []" :key="'ctx-tag' + t.id">
|
||||
<div class="menu-item" @click.stop="$store.fdCtx.has('tagRemove') && $store.fdCtx.handlers.tagRemove(t.id)">
|
||||
<span style="display:flex;align-items:center;gap:8px;"><span class="ctx-tag-color" :style="{ background: t.color }"></span><span x-text="t.name"></span></span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="menu-item" x-show="!($store.fdCtx.node && ($store.fdCtx.node.tags||[]).length)" style="color:var(--text-dim);font-size:11px;cursor:default;"> No tags </div>
|
||||
<div class="menu-item" @click="$store.fdCtx.handlers.tagExisting">
|
||||
<span style="flex:1;">Add existing tag</span><span style="font-size:10px;">▸</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="$store.fdCtx.handlers.tagNew">
|
||||
<span style="flex:1;">New tag</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
{# ── 10. Delete (danger) ── #}
|
||||
<div class="more-sep"></div>
|
||||
<div class="menu-item menu-danger" @click="$store.fdCtx.run('delete')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
|
||||
Delete
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script type="application/json" id="page-data">
|
||||
{{ page_data | tojson }}
|
||||
</script>
|
||||
<script>
|
||||
<script data-cfasync="false">
|
||||
/* eslint-disable */
|
||||
const CMDS=[{name:'BASIC',items:[
|
||||
{id:'paragraph',name:'Text',icon:'¶'},{id:'heading_1',name:'Heading 1',icon:'H1'},{id:'heading_2',name:'Heading 2',icon:'H2'},
|
||||
|
||||
+20
-40
@@ -363,45 +363,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Row context menu ── -->
|
||||
<div class="more-menu" x-show="ctxMenuItem" @click.outside="ctxMenuItem=null" x-transition
|
||||
:style="'top:' + ctxMenuY + 'px;left:' + ctxMenuX + 'px;'">
|
||||
<template x-if="ctxMenuItem">
|
||||
<div>
|
||||
<div class="menu-item" @click="openItem(ctxMenuItem); ctxMenuItem=null">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
||||
Open
|
||||
</div>
|
||||
<div class="menu-item" @click="openPeek(ctxMenuItem); ctxMenuItem=null">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="15" y1="3" x2="15" y2="21"/></svg>
|
||||
Open in side peek
|
||||
</div>
|
||||
<div class="menu-item" @click="startRename(ctxMenuItem); ctxMenuItem=null">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.12 2.12 0 013 3L12 15l-4 1 1-4z"/></svg>
|
||||
Rename
|
||||
</div>
|
||||
<div class="menu-item" @click="toggleFavoriteItem(ctxMenuItem); ctxMenuItem=null">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||||
<span x-text="ctxMenuItem.favorited ? 'Remove from Favorites' : 'Add to Favorites'"></span>
|
||||
</div>
|
||||
<div class="menu-item" @click="duplicateItem(ctxMenuItem); ctxMenuItem=null">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
|
||||
Duplicate
|
||||
</div>
|
||||
<div class="menu-item" @click="copyLink(ctxMenuItem); ctxMenuItem=null">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg>
|
||||
Copy link
|
||||
</div>
|
||||
<div class="menu-item" @click="openMovePicker([ctxMenuItem.id]); ctxMenuItem=null">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
|
||||
Move to
|
||||
</div>
|
||||
<div class="more-sep"></div>
|
||||
<div class="menu-item menu-danger" @click="deleteItem(ctxMenuItem); ctxMenuItem=null">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6M14 11v6"/></svg>
|
||||
Move to Trash
|
||||
</div>
|
||||
</div>
|
||||
{% include "_ctx_menu.html" %}
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -711,7 +673,25 @@ function libraryPage() {
|
||||
evt.preventDefault();
|
||||
var item = this._findItem(id);
|
||||
if (!item) return;
|
||||
this.ctxMenuItem = item;
|
||||
/* MENU UNIFIÉ (partial _ctx_menu.html + store fdCtx) — handlers library
|
||||
complète l'union (8 items). Open folder/tags non prévus côté library :
|
||||
fdCtx les cache automatiquement via handlers absents. */
|
||||
var handlers = {
|
||||
open: function (n) { libraryPage().openItem(n); },
|
||||
peek: function (n) { libraryPage().openPeek(n); },
|
||||
rename: function (n) { libraryPage().startRename(n); },
|
||||
fav: function (n) { libraryPage().toggleFavoriteItem(n); },
|
||||
duplicate: function (n) { libraryPage().duplicateItem(n); },
|
||||
link: function (n) { libraryPage().copyLink(n); },
|
||||
move: function (n) { libraryPage().openMovePicker([n.id]); },
|
||||
del: function (n) { libraryPage().deleteItem(n); }
|
||||
};
|
||||
var self = this;
|
||||
if (window.Alpine && Alpine.store('fdCtx')) {
|
||||
Alpine.store('fdCtx').open(evt, item, 'library', handlers);
|
||||
Alpine.store('fdCtx').closeMenu = function () { ctxMenuItem = null; };
|
||||
}
|
||||
this.ctxMenuItem = item parallels ?? null;
|
||||
this.ctxMenuX = Math.min(evt.clientX, window.innerWidth - 220);
|
||||
this.ctxMenuY = Math.min(evt.clientY, window.innerHeight - 320);
|
||||
},
|
||||
|
||||
@@ -3029,63 +3029,10 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
|
||||
|
||||
</div><!-- /ws-main -->
|
||||
|
||||
<!-- Context menu (Library-style) -->
|
||||
<div class="more-menu" x-data="_ctxMenuData" style="display:none"
|
||||
@click.outside="document.querySelector('.more-menu').style.display='none'"
|
||||
@keydown.escape="document.querySelector('.more-menu').style.display='none'"
|
||||
x-transition>
|
||||
<div class="menu-item" @click="ctxMenuOpenPage()">{{ fd_icon("file",14) }} Open</div>
|
||||
<div class="menu-item" x-show="ctxMenu.node && ctxMenu.node.is_folder" @click="ctxMenuOpenFolder()">{{ fd_icon("folder",14) }} Open folder</div>
|
||||
<div class="menu-item" @click="ctxMenuRename()">{{ fd_icon("edit",14) }} Rename</div>
|
||||
<div class="menu-item" @click="ctxMenuDuplicate()">{{ fd_icon("copy",14) }} Duplicate</div>
|
||||
<div class="more-sep"></div>
|
||||
<!-- Current tags -->
|
||||
<template x-for="t in (ctxMenu.tags||[])" :key="'ctx-tag'+t.id">
|
||||
<div class="menu-item" style="justify-content:space-between;">
|
||||
<span style="display:flex;align-items:center;gap:8px;"><span class="ctx-tag-color" :style="{background: t.color}"></span><span style="font-size:13px;" x-text="t.name"></span></span>
|
||||
<button class="tag-chip rm-btn" @click.stop="ctxRemoveTag(t.id)" title="Remove">×</button>
|
||||
</div>
|
||||
</template>
|
||||
<div x-show="(ctxMenu.tags||[]).length === 0" style="padding:4px 10px;font-size:11px;color:var(--text-dim);">No tags</div>
|
||||
<div class="more-sep"></div>
|
||||
<!-- Add existing tag -->
|
||||
<div class="menu-item" @click.stop="ctxTagExistingOpen=!ctxTagExistingOpen" x-show="ctxAvailTags.length > 0">
|
||||
<span style="flex:1;">Add existing tag</span>
|
||||
<span x-text="ctxTagExistingOpen ? '▾' : '▸'" style="font-size:11px;"></span>
|
||||
</div>
|
||||
<div x-show="ctxTagExistingOpen && ctxAvailTags.length > 0" style="padding:0 0 4px;background:var(--bg-tertiary);border-radius:6px;margin:2px 6px;">
|
||||
<template x-for="t in ctxAvailTags" :key="'ctx-avail'+t.id">
|
||||
<div class="menu-item" @click.stop="ctxAddExistingTag(t), ctxTagExistingOpen=false" style="justify-content:space-between;">
|
||||
<span style="display:flex;align-items:center;gap:8px;"><span class="ctx-tag-color" :style="{background: t.color}"></span><span style="font-size:13px;" x-text="t.name"></span></span>
|
||||
<span style="font-size:10px;color:var(--text-dim);" x-text="t.count||''"></span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Create new tag -->
|
||||
<div class="menu-item" @click.stop="ctxTagAdding=!ctxTagAdding">
|
||||
<span style="flex:1;">{{ fd_icon("plus",14) }} New tag</span>
|
||||
<span x-text="ctxTagAdding ? '▾' : '▸'" style="font-size:11px;"></span>
|
||||
</div>
|
||||
<div x-show="ctxTagAdding" style="padding:6px 10px;background:var(--bg-tertiary);border-radius:6px;margin:2px 6px;">
|
||||
<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:4px;margin-bottom:6px;">
|
||||
<template x-for="c in ctxTagColors" :key="c">
|
||||
<div class="color-swatch-ctx" :class="{ selected: ctxNewTagColor === c }"
|
||||
:style="{background: c}" @click.stop="ctxNewTagColor=c"></div>
|
||||
</template>
|
||||
</div>
|
||||
<div style="display:flex;gap:4px;">
|
||||
<input class="tag-input" x-ref="ctxTagInput"
|
||||
@keydown.enter="ctxAddNewTag($event.target.value), $event.target.value=''"
|
||||
@keydown.escape="ctxTagAdding=false"
|
||||
@click.stop placeholder="Tag name..." style="flex:1;">
|
||||
<button class="btn btn-primary" @click.stop="ctxAddNewTag($refs.ctxTagInput.value), $refs.ctxTagInput.value=''" style="padding:2px 10px;font-size:12px;">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="more-sep"></div>
|
||||
<div class="menu-item menu-danger" @click="ctxMenuDelete()">{{ fd_icon("trash",14) }} Delete</div>
|
||||
</div>
|
||||
<!-- Context menu (Library-style) — MENU UNIQUE partagé (workspace ⇄ library) -->
|
||||
{% include "_ctx_menu.html" %}
|
||||
|
||||
<!-- Preview tooltip -->
|
||||
<!-- Preview tooltip -->
|
||||
<div class="file-preview" x-show="previewVisible"
|
||||
:style="{ left: previewX + 'px', top: previewY + 'px' }"
|
||||
@mouseenter="previewVisible = true" @mouseleave="previewVisible = false">
|
||||
|
||||
Reference in New Issue
Block a user