feat: menu contextuel — tags avec couleurs + liste des tags disponibles
- Section TAGS toujours visible (plus besoin de cliquer pour ouvrir un sous-menu) - Tags existants affichés avec leur pastille de couleur (⦿ 10px) + bouton ✕ - Liste des tags DISPONIBLES (pas encore assignés) avec couleurs, clic → ajoute le tag à l'élément. Compteur d'utilisation affiché - Palette 14 couleurs pour créer un nouveau tag (carrés 18px arrondis) - Input pour nouveau tag avec palette de couleurs - CSS: .ctx-section, .ctx-tag-color, .color-swatch-ctx, .ctx-add-tag-item
This commit is contained in:
@@ -278,6 +278,12 @@
|
||||
.ctx-tag-item{justify-content:space-between;}
|
||||
.ctx-tag-add{color:var(--text-tertiary);font-size:12px;padding:4px 14px;cursor:pointer;}
|
||||
.ctx-tag-add:hover{color:var(--accent);}
|
||||
.ctx-section{padding:5px 14px;font-size:11px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;}
|
||||
.ctx-tag-color{width:10px;height:10px;border-radius:50%;flex-shrink:0;}
|
||||
.color-swatch-ctx{width:18px;height:18px;border-radius:4px;cursor:pointer;border:2px solid transparent;transition:all 100ms;}
|
||||
.color-swatch-ctx:hover{transform:scale(1.15);}
|
||||
.color-swatch-ctx.selected{border-color:var(--text);box-shadow:0 0 0 2px var(--accent);}
|
||||
.ctx-add-tag-item:hover{background:rgba(35,131,226,.08);}
|
||||
.ctx-has-sub{justify-content:space-between;}
|
||||
|
||||
/* Old view-selector kept for no conflict, but hidden */
|
||||
@@ -768,25 +774,43 @@
|
||||
<div class="ctx-item" @click="ctxMenuRename()">✏️ Rename</div>
|
||||
<div class="ctx-item" @click="ctxMenuDuplicate()">📋 Duplicate</div>
|
||||
<div class="ctx-sep"></div>
|
||||
<div class="ctx-item ctx-has-sub" @click.stop="ctxTagSub=!ctxTagSub">
|
||||
🏷️ Tags
|
||||
<span style="margin-left:auto">▸</span>
|
||||
</div>
|
||||
<div x-show="ctxTagSub" style="padding:4px 0;">
|
||||
<template x-for="t in (ctxMenu.tags||[])" :key="'ctx-tag'+t.id">
|
||||
<div class="ctx-item ctx-tag-item">
|
||||
<span class="tag-chip" style="margin-right:6px;"><span x-text="t.name"></span></span>
|
||||
<button class="tag-chip rm-btn" @click.stop="ctxRemoveTag(t.id)" title="Remove tag">×</button>
|
||||
<div class="ctx-section">🏷️ Tags</div>
|
||||
<!-- Current tags with remove button -->
|
||||
<template x-for="t in (ctxMenu.tags||[])" :key="'ctx-tag'+t.id">
|
||||
<div class="ctx-item ctx-tag-item">
|
||||
<span class="ctx-tag-color" :style="{background: t.color}"></span>
|
||||
<span style="flex:1;font-size:13px;" x-text="t.name"></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:2px 14px;font-size:11px;color:var(--text-dim);">No tags</div>
|
||||
<div class="ctx-sep" x-show="ctxAvailTags.length > 0"></div>
|
||||
<!-- Available tags to add -->
|
||||
<div x-show="ctxAvailTags.length > 0" style="padding:0 0 4px;">
|
||||
<div style="padding:2px 14px;font-size:11px;color:var(--text-dim);">Add tag:</div>
|
||||
<template x-for="t in ctxAvailTags" :key="'ctx-avail'+t.id">
|
||||
<div class="ctx-item ctx-add-tag-item" @click.stop="ctxAddExistingTag(t)">
|
||||
<span class="ctx-tag-color" :style="{background: t.color}"></span>
|
||||
<span style="flex:1;font-size:13px;" x-text="t.name"></span>
|
||||
<span style="font-size:10px;color:var(--text-dim);" x-text="t.count||''"></span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="ctx-tag-add" x-show="!ctxTagAdding" @click.stop="ctxTagAdding=true">+ Add tag</div>
|
||||
<div x-show="ctxTagAdding" style="display:flex;gap:4px;padding:4px 12px;">
|
||||
<input class="tag-input" x-ref="ctxTagInput"
|
||||
@keydown.enter="ctxAddTag($event.target.value); $event.target.value=''"
|
||||
@keydown.escape="ctxTagAdding=false; ctxTagSub=false"
|
||||
@click.stop
|
||||
placeholder="tag name...">
|
||||
</div>
|
||||
</div>
|
||||
<div class="ctx-tag-add" x-show="!ctxTagAdding" @click.stop="ctxTagAdding=true">
|
||||
<span class="ctx-tag-color" style="background:var(--bg-tertiary);border:1px dashed var(--border);"></span>
|
||||
+ New tag
|
||||
</div>
|
||||
<div x-show="ctxTagAdding" style="display:flex;align-items:center;gap:4px;padding:4px 14px;">
|
||||
<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>
|
||||
<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="margin-left:4px;">
|
||||
</div>
|
||||
<div class="ctx-sep"></div>
|
||||
<div class="ctx-item ctx-danger" @click="ctxMenuDelete()">🗑 Delete</div>
|
||||
@@ -874,8 +898,10 @@ document.addEventListener('alpine:init', function() {
|
||||
|
||||
// Context menu
|
||||
ctxMenu: {visible:false,x:0,y:0,node:null,tags:[]},
|
||||
ctxTagSub: false,
|
||||
ctxTagAdding: false,
|
||||
ctxAvailTags: [],
|
||||
ctxNewTagColor: '#787774',
|
||||
ctxTagColors: ['#787774','#E03E3E','#D9730D','#DFAB01','#0F7B6C','#0B6E99','#6940A5','#AD1A72','#E16259','#D4A72C','#448361','#337EA9','#9065B0','#C94D8B'],
|
||||
|
||||
// Tags
|
||||
workspaceTags: [],
|
||||
@@ -1236,8 +1262,16 @@ document.addEventListener('alpine:init', function() {
|
||||
node: node,
|
||||
tags: [...(node.tags||[])]
|
||||
};
|
||||
this.ctxTagSub = false;
|
||||
this.ctxTagAdding = false;
|
||||
this.ctxNewTagColor = '#787774';
|
||||
// Load available tags (not already on the item)
|
||||
this.ctxAvailTags = (this.workspaceTags || []).filter(function(t) {
|
||||
return !(node.tags||[]).some(function(nt) { return nt.id === t.id; });
|
||||
});
|
||||
},
|
||||
|
||||
ctxAddExistingTag(tag) {
|
||||
this.ctxAddNewTag(tag.name, tag.color);
|
||||
},
|
||||
|
||||
ctxMenuOpenPage() {
|
||||
@@ -1266,14 +1300,19 @@ document.addEventListener('alpine:init', function() {
|
||||
var node = this.ctxMenu.node;
|
||||
if (!node) return;
|
||||
this.removeTag(node.id, tagId);
|
||||
// Update ctxMenu tags
|
||||
this.ctxMenu.tags = this.ctxMenu.tags.filter(function(t) { return t.id !== tagId; });
|
||||
this.ctxTagSub = true; // keep sub open
|
||||
// Refresh available tags
|
||||
this.ctxAvailTags = (this.workspaceTags || []).filter(function(t) {
|
||||
return !(this.ctxMenu.tags||[]).some(function(nt) { return nt.id === t.id; });
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
async ctxAddTag(tagName) {
|
||||
async ctxAddNewTag(tagName, color) {
|
||||
tagName = (tagName || '').trim();
|
||||
if (!tagName) return;
|
||||
var node = this.ctxMenu.node;
|
||||
if (!node || !tagName) return;
|
||||
if (!node) return;
|
||||
color = color || this.ctxNewTagColor;
|
||||
var r = await fetch('/api/local-workspace/items/' + node.id + '/tags', {
|
||||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({name: tagName.trim()})
|
||||
@@ -1284,9 +1323,15 @@ document.addEventListener('alpine:init', function() {
|
||||
this._updateNodeTags(this.tree, node.id, d.tag, 'add');
|
||||
this.displayTree = [...this.tree];
|
||||
this.loadWorkspaceTags();
|
||||
// Refresh available tags
|
||||
var self = this;
|
||||
setTimeout(function() { self.loadWorkspaceTags().then(function() {
|
||||
self.ctxAvailTags = (self.workspaceTags || []).filter(function(t) {
|
||||
return !(self.ctxMenu.tags||[]).some(function(nt) { return nt.id === t.id; });
|
||||
});
|
||||
}); }, 200);
|
||||
}
|
||||
this.ctxTagAdding = false;
|
||||
this.ctxTagSub = true;
|
||||
},
|
||||
|
||||
_markTree(nodes, depth) {
|
||||
|
||||
Reference in New Issue
Block a user