- Picker : recherche instantanée, aperçu, navigation clavier complète, raccourcis actifs seulement dans sa fenêtre (HotIfWinActive) - Gestionnaire : arbre des catégories, favoris/récents/erreurs, aperçu, création depuis le presse-papiers, suppression par la corbeille - Éditeur : métadonnées, insertion/déclaration de variables synchronisées, validation en direct, Tester (prévisualisation résolue, sans injection) - VarForm : un contrôle par type de variable, requis validés, Échap annule toute l'injection et rend le focus
200 lines
6.8 KiB
AutoHotkey
200 lines
6.8 KiB
AutoHotkey
; Manager.ahk — Le Gestionnaire : bibliothèque en trois volets (architecture §9.1).
|
|
|
|
class PromptManager {
|
|
GuiObj := ""
|
|
TV := ""
|
|
LV := ""
|
|
Preview := ""
|
|
Status := ""
|
|
NodeFilter := Map() ; id de nœud TreeView -> filtre (catégorie ou __all__/__fav__/__recent__/__errors__)
|
|
Items := []
|
|
Filter := "__all__"
|
|
OnUse := "" ; Func(prompt)
|
|
OnEdit := "" ; Func(prompt)
|
|
|
|
Build(onUse, onEdit) {
|
|
this.OnUse := onUse
|
|
this.OnEdit := onEdit
|
|
g := Gui("-MaximizeBox +Resize", "PromptKey — Gestionnaire")
|
|
g.SetFont("s10", "Segoe UI")
|
|
this.GuiObj := g
|
|
this.TV := g.Add("TreeView", "xm w180 r18")
|
|
this.TV.OnEvent("ItemSelect", (tv, id) => this.NodeFilter.Has(id) ? this.SetFilter(this.NodeFilter[id]) : "")
|
|
this.LV := g.Add("ListView", "x+10 w430 r18", ["Titre", "Modifié", "Utilisations"])
|
|
this.LV.ModifyCol(1, 240)
|
|
this.LV.ModifyCol(2, 90)
|
|
this.LV.ModifyCol(3, 95)
|
|
this.LV.OnEvent("ItemSelect", (lv, row, selected) => selected ? this.UpdatePreview(row) : "")
|
|
this.LV.OnEvent("DoubleClick", (lv, row) => row > 0 ? this.EditSelected() : "")
|
|
this.Preview := g.Add("Edit", "x+10 w330 r18 ReadOnly -Wrap", "")
|
|
|
|
btnUse := g.Add("Button", "xm w130", "Utiliser")
|
|
btnUse.OnEvent("Click", (*) => this.UseSelected())
|
|
btnEdit := g.Add("Button", "x+8 w130", "Modifier")
|
|
btnEdit.OnEvent("Click", (*) => this.EditSelected())
|
|
btnNew := g.Add("Button", "x+8 w130", "Nouveau")
|
|
btnNew.OnEvent("Click", (*) => App.NewPromptInteractive())
|
|
btnClip := g.Add("Button", "x+8 w180", "Nouveau depuis le presse-papiers")
|
|
btnClip.OnEvent("Click", (*) => this.NewFromClipboard())
|
|
btnDup := g.Add("Button", "xm w130", "Dupliquer")
|
|
btnDup.OnEvent("Click", (*) => this.DuplicateSelected())
|
|
btnDel := g.Add("Button", "x+8 w130", "Supprimer")
|
|
btnDel.OnEvent("Click", (*) => this.DeleteSelected())
|
|
btnFolder := g.Add("Button", "x+8 w160", "Ouvrir le dossier")
|
|
btnFolder.OnEvent("Click", (*) => Run(App.Settings.LibraryPath))
|
|
btnReload := g.Add("Button", "x+8 w130", "Recharger")
|
|
btnReload.OnEvent("Click", (*) => this.Refresh())
|
|
this.Status := g.Add("Text", "xm w950", "")
|
|
g.OnEvent("Close", (*) => g.Hide())
|
|
g.OnEvent("Escape", (*) => g.Hide())
|
|
}
|
|
|
|
Show() {
|
|
this.Refresh()
|
|
this.GuiObj.Show("w990")
|
|
}
|
|
|
|
Hide() {
|
|
this.GuiObj.Hide()
|
|
}
|
|
|
|
SetFilter(filter) {
|
|
this.Filter := filter
|
|
this.RefreshList()
|
|
}
|
|
|
|
Refresh() {
|
|
App.Store.Load()
|
|
this.NodeFilter := Map()
|
|
this.TV.Delete()
|
|
root := this.TV.Add("Bibliothèque")
|
|
this.NodeFilter[root] := "__all__"
|
|
for cat in App.Store.Categories() {
|
|
node := this.TV.Add(cat, root)
|
|
this.NodeFilter[node] := cat
|
|
}
|
|
fav := this.TV.Add("★ Favoris", root)
|
|
this.NodeFilter[fav] := "__fav__"
|
|
rec := this.TV.Add("◷ Récents", root)
|
|
this.NodeFilter[rec] := "__recent__"
|
|
if (App.Store.ErrorFiles.Length > 0) {
|
|
err := this.TV.Add("⚠ En erreur (" App.Store.ErrorFiles.Length ")", root)
|
|
this.NodeFilter[err] := "__errors__"
|
|
}
|
|
this.TV.Modify(root, "Expand")
|
|
this.RefreshList()
|
|
}
|
|
|
|
FilteredPrompts() {
|
|
out := []
|
|
switch this.Filter {
|
|
case "__all__":
|
|
return App.Store.Prompts
|
|
case "__fav__":
|
|
for p in App.Store.Prompts {
|
|
if p.Favorite
|
|
out.Push(p)
|
|
}
|
|
case "__recent__":
|
|
for p in App.Store.Prompts {
|
|
if (App.Usage && App.Usage.Data.Has(p.Id))
|
|
out.Push(p)
|
|
}
|
|
case "__errors__":
|
|
return App.Store.ErrorFiles
|
|
default:
|
|
for p in App.Store.Prompts {
|
|
if (p.Category = this.Filter)
|
|
out.Push(p)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
RefreshList() {
|
|
this.Items := this.FilteredPrompts()
|
|
this.LV.Delete()
|
|
for p in this.Items {
|
|
count := App.Usage ? App.Usage.Count(p.Id) : 0
|
|
marker := (p.Favorite ? "★ " : "") (p.Pinned ? "📌 " : "")
|
|
this.LV.Add("", marker p.Title, p.Updated, count)
|
|
}
|
|
if (this.Items.Length > 0) {
|
|
this.LV.Modify(1, "Select")
|
|
this.UpdatePreview(1)
|
|
} else {
|
|
this.Preview.Value := ""
|
|
}
|
|
this.Status.Text := App.Store.Prompts.Length " prompt(s) dans la bibliothèque · filtre : " this.Filter
|
|
}
|
|
|
|
UpdatePreview(row) {
|
|
if (row < 1 || row > this.Items.Length)
|
|
return
|
|
p := this.Items[row]
|
|
txt := p.Body
|
|
if (p.ParseError != "")
|
|
txt := "⚠ Erreur d'en-tête : " p.ParseError "`r`nFichier : " p.Path "`r`n`r`n" txt
|
|
else
|
|
txt := "Catégorie : " p.Category " · Étiquettes : " Join(p.Tags, ", ") "`r`n" (p.Hotkey != "" ? "Raccourci : " p.Hotkey "`r`n" : "") (p.Hotstring != "" ? "Hotstring : ::" p.Hotstring "`r`n" : "") "`r`n" txt
|
|
this.Preview.Value := txt
|
|
}
|
|
|
|
SelectedPrompt() {
|
|
row := this.LV.GetNext()
|
|
if (row >= 1 && row <= this.Items.Length)
|
|
return this.Items[row]
|
|
return ""
|
|
}
|
|
|
|
UseSelected() {
|
|
p := this.SelectedPrompt()
|
|
if (p = "")
|
|
return
|
|
this.Hide()
|
|
this.OnUse(p)
|
|
}
|
|
|
|
EditSelected() {
|
|
p := this.SelectedPrompt()
|
|
if (p = "")
|
|
return
|
|
this.OnEdit(p)
|
|
}
|
|
|
|
DuplicateSelected() {
|
|
p := this.SelectedPrompt()
|
|
if (p = "")
|
|
return
|
|
App.Store.Duplicate(p)
|
|
this.Refresh()
|
|
}
|
|
|
|
DeleteSelected() {
|
|
p := this.SelectedPrompt()
|
|
if (p = "")
|
|
return
|
|
r := MsgBox("Envoyer « " p.Title " » à la corbeille ?", "PromptKey", "YesNo Icon?")
|
|
if (r != "Yes")
|
|
return
|
|
App.Store.Delete(p)
|
|
App.RefreshAfterLibraryChange()
|
|
this.Refresh()
|
|
}
|
|
|
|
NewFromClipboard() {
|
|
txt := ClipboardGuard.ClipboardText()
|
|
if (Trim(txt) = "") {
|
|
TrayTip("Le presse-papiers ne contient pas de texte.", "PromptKey")
|
|
return
|
|
}
|
|
ib := InputBox("Titre du nouveau prompt :", "PromptKey — Depuis le presse-papiers")
|
|
if (ib.Result != "OK" || Trim(ib.Value) = "")
|
|
return
|
|
item := App.Store.CreateNew(Trim(ib.Value), "Sans catégorie", txt)
|
|
App.RefreshAfterLibraryChange()
|
|
this.Refresh()
|
|
TrayTip("Prompt « " item.Title " » créé depuis le presse-papiers.", "PromptKey")
|
|
}
|
|
}
|