fix: tags contextuels + rename/delete confirm dans Settings
Context menu (_ctxMenuData): - Remplacé getters par propriétés simples (Alpine ne réagit pas aux getters) - Sync _wsInitData → _ctxMenuData dans onContextMenu, ctxAddNewTag, ctxRemoveTag - Les 4 tags existants apparaissent maintenant dans le dropdown Settings > Tags: - Double-clic sur un tag → prompt rename (PUT /api/settings/tags/<id>) - Suppression: modal de confirmation au lieu de confirm() natif - Affiche le nombre d'items impactés si tag utilisé
This commit is contained in:
@@ -774,6 +774,13 @@ window._wsInitData = (function() {
|
|||||||
menu.style.top = y + 'px';
|
menu.style.top = y + 'px';
|
||||||
this.ctxMenu.x = x;
|
this.ctxMenu.x = x;
|
||||||
this.ctxMenu.y = y;
|
this.ctxMenu.y = y;
|
||||||
|
// Sync to _ctxMenuData for Alpine reactivity
|
||||||
|
var c = window._ctxMenuData;
|
||||||
|
c.ctxMenu = this.ctxMenu;
|
||||||
|
c.ctxTagAdding = this.ctxTagAdding;
|
||||||
|
c.ctxTagExistingOpen = this.ctxTagExistingOpen;
|
||||||
|
c.ctxAvailTags = this.ctxAvailTags;
|
||||||
|
c.ctxNewTagColor = this.ctxNewTagColor;
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Long-press (mobile context menu) ──
|
// ── Long-press (mobile context menu) ──
|
||||||
@@ -851,6 +858,9 @@ window._wsInitData = (function() {
|
|||||||
this.ctxAvailTags = (this.workspaceTags || []).filter(function(t) {
|
this.ctxAvailTags = (this.workspaceTags || []).filter(function(t) {
|
||||||
return !(this.ctxMenu.tags||[]).some(function(nt) { return nt.id === t.id; });
|
return !(this.ctxMenu.tags||[]).some(function(nt) { return nt.id === t.id; });
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
// Sync to _ctxMenuData
|
||||||
|
window._ctxMenuData.ctxAvailTags = this.ctxAvailTags;
|
||||||
|
window._ctxMenuData.ctxMenu = this.ctxMenu;
|
||||||
},
|
},
|
||||||
|
|
||||||
async ctxAddNewTag(tagName, color) {
|
async ctxAddNewTag(tagName, color) {
|
||||||
@@ -875,6 +885,9 @@ window._wsInitData = (function() {
|
|||||||
self.ctxAvailTags = (self.workspaceTags || []).filter(function(t) {
|
self.ctxAvailTags = (self.workspaceTags || []).filter(function(t) {
|
||||||
return !(self.ctxMenu.tags||[]).some(function(nt) { return nt.id === t.id; });
|
return !(self.ctxMenu.tags||[]).some(function(nt) { return nt.id === t.id; });
|
||||||
});
|
});
|
||||||
|
// Sync to _ctxMenuData
|
||||||
|
window._ctxMenuData.ctxAvailTags = self.ctxAvailTags;
|
||||||
|
window._ctxMenuData.ctxMenu = self.ctxMenu;
|
||||||
}); }, 200);
|
}); }, 200);
|
||||||
}
|
}
|
||||||
this.ctxTagAdding = false;
|
this.ctxTagAdding = false;
|
||||||
@@ -1918,17 +1931,12 @@ for (var _i = 0; _i < _wsKeys.length; _i++) {
|
|||||||
_wsInitData = window._wsInitData;
|
_wsInitData = window._wsInitData;
|
||||||
// Objet léger pour le ctx-menu (évite le conflit $nextTick avec Alpine)
|
// Objet léger pour le ctx-menu (évite le conflit $nextTick avec Alpine)
|
||||||
window._ctxMenuData = {
|
window._ctxMenuData = {
|
||||||
get ctxMenu() { return window._wsInitData.ctxMenu; },
|
ctxMenu: {visible:false,x:0,y:0,node:null,tags:[]},
|
||||||
set ctxMenu(v) { window._wsInitData.ctxMenu = v; },
|
ctxTagAdding: false,
|
||||||
get ctxTagAdding() { return window._wsInitData.ctxTagAdding; },
|
ctxTagExistingOpen: false,
|
||||||
set ctxTagAdding(v) { window._wsInitData.ctxTagAdding = v; },
|
ctxAvailTags: [],
|
||||||
get ctxTagExistingOpen() { return window._wsInitData.ctxTagExistingOpen; },
|
ctxNewTagColor: '#787774',
|
||||||
set ctxTagExistingOpen(v) { window._wsInitData.ctxTagExistingOpen = v; },
|
ctxTagColors: ['#787774','#E03E3E','#D9730D','#DFAB01','#0F7B6C','#0B6E99','#6940A5','#AD1A72','#E16259','#D4A72C','#448361','#337EA9','#9065B0','#C94D8B'],
|
||||||
get ctxAvailTags() { return window._wsInitData.ctxAvailTags; },
|
|
||||||
set ctxAvailTags(v) { window._wsInitData.ctxAvailTags = v; },
|
|
||||||
get ctxNewTagColor() { return window._wsInitData.ctxNewTagColor; },
|
|
||||||
set ctxNewTagColor(v) { window._wsInitData.ctxNewTagColor = v; },
|
|
||||||
get ctxTagColors() { return window._wsInitData.ctxTagColors; },
|
|
||||||
ctxMenuOpenPage() { window._wsInitData.ctxMenuOpenPage(); },
|
ctxMenuOpenPage() { window._wsInitData.ctxMenuOpenPage(); },
|
||||||
ctxMenuOpenFolder() { window._wsInitData.ctxMenuOpenFolder(); },
|
ctxMenuOpenFolder() { window._wsInitData.ctxMenuOpenFolder(); },
|
||||||
ctxMenuRename() { window._wsInitData.ctxMenuRename(); },
|
ctxMenuRename() { window._wsInitData.ctxMenuRename(); },
|
||||||
|
|||||||
@@ -193,10 +193,14 @@
|
|||||||
<template x-for="tag in allTags" :key="tag.id">
|
<template x-for="tag in allTags" :key="tag.id">
|
||||||
<div class="tag-mgmt-item">
|
<div class="tag-mgmt-item">
|
||||||
<div class="tag-color-dot" :style="{background: tag.color}"></div>
|
<div class="tag-color-dot" :style="{background: tag.color}"></div>
|
||||||
<span class="tag-name" x-text="tag.name"></span>
|
<span class="tag-name"
|
||||||
|
x-text="tag.name"
|
||||||
|
@dblclick="renameTag(tag)"
|
||||||
|
title="Double-click to rename"
|
||||||
|
style="cursor:pointer;"></span>
|
||||||
<span class="tag-count" x-text="tag.count + ' items'"></span>
|
<span class="tag-count" x-text="tag.count + ' items'"></span>
|
||||||
<input type="color" :value="tag.color" @input="updateTagColor(tag.id, $event.target.value)" style="width:24px;height:24px;border:none;cursor:pointer;background:none;padding:0;" title="Change color">
|
<input type="color" :value="tag.color" @input="updateTagColor(tag.id, $event.target.value)" style="width:24px;height:24px;border:none;cursor:pointer;background:none;padding:0;" title="Change color">
|
||||||
<button class="tag-chip rm-btn" @click="deleteTag(tag.id)" title="Delete">×</button>
|
<button class="tag-chip rm-btn" @click="deletingTag=tag" title="Delete">×</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="new-tag-form">
|
<div class="new-tag-form">
|
||||||
@@ -207,6 +211,22 @@
|
|||||||
<button class="btn btn-primary" @click="createTag()" :disabled="!newTagName.trim()" style="padding:4px 12px;font-size:12px;">Add</button>
|
<button class="btn btn-primary" @click="createTag()" :disabled="!newTagName.trim()" style="padding:4px 12px;font-size:12px;">Add</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Delete confirmation modal -->
|
||||||
|
<div class="modal-overlay" x-show="deletingTag" @click.self="deletingTag=null" @keydown.escape="deletingTag=null" style="z-index:1000;">
|
||||||
|
<div class="modal-box" style="max-width:360px;">
|
||||||
|
<h3 style="margin:0 0 8px;">Delete tag</h3>
|
||||||
|
<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.
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<div style="display:flex;gap:8px;justify-content:flex-end;">
|
||||||
|
<button class="btn" @click="deletingTag=null">Cancel</button>
|
||||||
|
<button class="btn btn-danger" @click="confirmDeleteTag()">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Features -->
|
<!-- Features -->
|
||||||
@@ -267,6 +287,7 @@ document.addEventListener('alpine:init', function() {
|
|||||||
allTags: [],
|
allTags: [],
|
||||||
newTagName: '',
|
newTagName: '',
|
||||||
newTagColor: '#787774',
|
newTagColor: '#787774',
|
||||||
|
deletingTag: null,
|
||||||
presetColors: ['#787774','#E03E3E','#D9730D','#DFAB01','#0F7B6C','#0B6E99','#6940A5','#AD1A72','#E16259','#D4A72C','#448361','#337EA9','#9065B0','#C94D8B'],
|
presetColors: ['#787774','#E03E3E','#D9730D','#DFAB01','#0F7B6C','#0B6E99','#6940A5','#AD1A72','#E16259','#D4A72C','#448361','#337EA9','#9065B0','#C94D8B'],
|
||||||
avatarUrl: '{{ avatar_url or "" }}',
|
avatarUrl: '{{ avatar_url or "" }}',
|
||||||
avatarColor: '{{ avatar_color or "#3A3A3A" }}',
|
avatarColor: '{{ avatar_color or "#3A3A3A" }}',
|
||||||
@@ -310,6 +331,24 @@ document.addEventListener('alpine:init', function() {
|
|||||||
await this.loadTags();
|
await this.loadTags();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async renameTag(tag) {
|
||||||
|
var newName = prompt('Rename tag "' + tag.name + '":', tag.name);
|
||||||
|
if (!newName || !newName.trim() || newName.trim().toLowerCase() === tag.name.toLowerCase()) return;
|
||||||
|
await fetch('/api/settings/tags/' + tag.id, {
|
||||||
|
method: 'PUT', headers: {'Content-Type':'application/json'},
|
||||||
|
body: JSON.stringify({name: newName.trim()})
|
||||||
|
});
|
||||||
|
await this.loadTags();
|
||||||
|
},
|
||||||
|
|
||||||
|
async confirmDeleteTag() {
|
||||||
|
var tag = this.deletingTag;
|
||||||
|
if (!tag) return;
|
||||||
|
await fetch('/api/settings/tags/' + tag.id, { method: 'DELETE' });
|
||||||
|
this.deletingTag = null;
|
||||||
|
await this.loadTags();
|
||||||
|
},
|
||||||
|
|
||||||
async uploadAvatar(ev) {
|
async uploadAvatar(ev) {
|
||||||
var file = ev.target.files[0];
|
var file = ev.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user