fix: 5 bugs — couleur tag, tags existants, modal settings, rename, filtrage
1. ctxAddNewTag lit window._ctxMenuData.ctxNewTagColor (pas this.ctxNewTagColor) → la couleur sélectionnée dans le menu est maintenant sauvegardée 2. onContextMenu: charge workspaceTags si vide avant de calculer ctxAvailTags → les tags existants apparaissent dans le dropdown 3. Settings: CSS modal-overlay/modal-box/.btn-danger ajouté → le modal delete n'est plus transparent 4. Rename tag: modal avec input au lieu de prompt() → double-clic → modal avec couleur + input + autofocus 5. doFilter: _filterTreeByTag filtre displayTree quand tagFilter est actif → le filtrage par tags fonctionne dans toutes les vues
This commit is contained in:
@@ -751,6 +751,10 @@ window._wsInitData = (function() {
|
||||
if (!id) { menu.style.display = 'none'; return; }
|
||||
var node = this._findNodeById(this.displayTree, id);
|
||||
if (!node) { menu.style.display = 'none'; return; }
|
||||
// Ensure workspace tags are loaded
|
||||
if (!this.workspaceTags || this.workspaceTags.length === 0) {
|
||||
this.loadWorkspaceTags();
|
||||
}
|
||||
// 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;
|
||||
@@ -868,7 +872,7 @@ window._wsInitData = (function() {
|
||||
if (!tagName) return;
|
||||
var node = this.ctxMenu.node;
|
||||
if (!node) return;
|
||||
color = color || this.ctxNewTagColor;
|
||||
color = color || window._ctxMenuData.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(), color: color})
|
||||
@@ -1393,9 +1397,28 @@ window._wsInitData = (function() {
|
||||
var q = (this.searchQuery || '').toLowerCase().trim();
|
||||
var ft = this.filterType || '';
|
||||
if (!q && !ft && !this.tagFilter) { this.displayTree = [...this.tree]; this._computeFilteredTableItems(); return; }
|
||||
this.displayTree = [...this.tree];
|
||||
if (this.tagFilter) {
|
||||
// Filter tree by tag
|
||||
this.displayTree = this._filterTreeByTag([...this.tree], this.tagFilter);
|
||||
} else {
|
||||
this.displayTree = [...this.tree];
|
||||
}
|
||||
this._computeFilteredTableItems();
|
||||
},
|
||||
_filterTreeByTag(nodes, tagName) {
|
||||
var result = [];
|
||||
for (var i=0; i<nodes.length; i++) {
|
||||
var n = nodes[i];
|
||||
var hasTag = (n.tags || []).some(function(t) { return t.name === tagName; });
|
||||
var filteredChildren = n.children ? this._filterTreeByTag(n.children, tagName) : [];
|
||||
if (hasTag || filteredChildren.length > 0) {
|
||||
var clone = Object.assign({}, n);
|
||||
clone.children = filteredChildren;
|
||||
result.push(clone);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
filterByType(type) {
|
||||
this.filterType = (this.filterType === type) ? '' : type;
|
||||
this.doFilter();
|
||||
|
||||
@@ -57,6 +57,13 @@
|
||||
.color-swatch:hover{transform:scale(1.15);}
|
||||
.color-swatch.selected{border-color:var(--text);box-shadow:0 0 0 2px var(--accent);}
|
||||
|
||||
/* Modal (for tag delete/rename) */
|
||||
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:999;backdrop-filter:blur(4px);}
|
||||
.modal-box{background:var(--bg);border:1px solid var(--border-strong);border-radius:12px;padding:24px;min-width:320px;box-shadow:0 12px 40px rgba(0,0,0,.5);}
|
||||
.modal-box h3{font-size:16px;font-weight:600;color:var(--text);}
|
||||
.btn-danger{background:#E03E3E;color:#fff;border:none;padding:6px 16px;border-radius:6px;cursor:pointer;font-size:13px;}
|
||||
.btn-danger:hover{background:#C93535;}
|
||||
|
||||
/* Close button */
|
||||
.settings-close{position:absolute;top:12px;right:12px;width:32px;height:32px;background:none;border:none;color:var(--text-dim);font-size:20px;cursor:pointer;border-radius:6px;display:flex;align-items:center;justify-content:center;}
|
||||
.settings-close:hover{background:var(--bg-hover);color:var(--text);}
|
||||
@@ -218,7 +225,7 @@
|
||||
<p style="color:var(--text-dim);margin:0 0 16px;">
|
||||
Are you sure you want to delete the tag "<strong x-text="deletingTag?.name"></strong>"?
|
||||
<span x-show="deletingTag?.count > 0" style="color:#E03E3E;display:block;margin-top:4px;">
|
||||
⚠️ This tag is used on <span x-text="deletingTag?.count"></span> item(s). Removing it will also remove it from those items.
|
||||
⚠️ This tag is used on <span x-text="deletingTag?.count"></span> item(s).
|
||||
</span>
|
||||
</p>
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;">
|
||||
@@ -227,6 +234,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Rename modal -->
|
||||
<div class="modal-overlay" x-show="renamingTag" @click.self="renamingTag=null" @keydown.escape="renamingTag=null" style="z-index:1000;">
|
||||
<div class="modal-box" style="max-width:360px;">
|
||||
<h3 style="margin:0 0 12px;">Rename tag</h3>
|
||||
<div style="display:flex;gap:8px;align-items:center;">
|
||||
<div class="tag-color-dot" :style="{background: renamingTag?.color}" style="width:16px;height:16px;"></div>
|
||||
<input type="text" class="settings-input" x-model="renameValue"
|
||||
@keydown.enter="confirmRenameTag()" @keydown.escape="renamingTag=null"
|
||||
style="flex:1;" autofocus>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px;">
|
||||
<button class="btn" @click="renamingTag=null">Cancel</button>
|
||||
<button class="btn btn-primary" @click="confirmRenameTag()" :disabled="!renameValue.trim()">Rename</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Features -->
|
||||
@@ -288,6 +311,8 @@ document.addEventListener('alpine:init', function() {
|
||||
newTagName: '',
|
||||
newTagColor: '#787774',
|
||||
deletingTag: null,
|
||||
renamingTag: null,
|
||||
renameValue: '',
|
||||
presetColors: ['#787774','#E03E3E','#D9730D','#DFAB01','#0F7B6C','#0B6E99','#6940A5','#AD1A72','#E16259','#D4A72C','#448361','#337EA9','#9065B0','#C94D8B'],
|
||||
avatarUrl: '{{ avatar_url or "" }}',
|
||||
avatarColor: '{{ avatar_color or "#3A3A3A" }}',
|
||||
@@ -332,12 +357,27 @@ document.addEventListener('alpine:init', function() {
|
||||
},
|
||||
|
||||
async renameTag(tag) {
|
||||
var newName = prompt('Rename tag "' + tag.name + '":', tag.name);
|
||||
if (!newName || !newName.trim() || newName.trim().toLowerCase() === tag.name.toLowerCase()) return;
|
||||
this.renamingTag = tag;
|
||||
this.renameValue = tag.name;
|
||||
// Focus the input after Alpine renders the modal
|
||||
var self = this;
|
||||
this.$nextTick(function() {
|
||||
var input = document.querySelector('.modal-box input[type=text]');
|
||||
if (input) { input.focus(); input.select(); }
|
||||
});
|
||||
},
|
||||
|
||||
async confirmRenameTag() {
|
||||
var tag = this.renamingTag;
|
||||
var newName = this.renameValue.trim();
|
||||
if (!tag || !newName || newName.toLowerCase() === tag.name.toLowerCase()) {
|
||||
this.renamingTag = null; return;
|
||||
}
|
||||
await fetch('/api/settings/tags/' + tag.id, {
|
||||
method: 'PUT', headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({name: newName.trim()})
|
||||
body: JSON.stringify({name: newName})
|
||||
});
|
||||
this.renamingTag = null;
|
||||
await this.loadTags();
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user