Rend enfin tapable le champ où l'on écrit ce qu'un numéro annonce
Le champ « À la une » se reliait à chaque frappe sur une propriété qui découpait puis recomposait la ligne : le point-virgule tapé ouvrait une entrée vide aussitôt élaguée, et l'espace qui le suit était rogné. Le seul caractère qu'on ne pouvait pas y taper était son propre séparateur. Un titre s'ajoute maintenant seul, par Entrée, et se modifie ou se retire depuis la liste. Plus rien ne découpe, donc plus rien n'est intapable. ArticlesTexte disparaît, et avec lui le paramètre virguleAussi de ListeSaisie, qui n'existait que pour ce champ. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,16 +26,77 @@
|
||||
</div>
|
||||
|
||||
@*
|
||||
⚠️ Séparateur : le POINT-VIRGULE, et pas la virgule des thèmes de livres. Un titre d'article
|
||||
en contient souvent (« Ukraine, deux ans après »), et la virgule le couperait en deux.
|
||||
⚠️ Un titre à la fois, et surtout PAS une ligne de titres séparés par des points-virgules.
|
||||
Le champ unique se reliait à chaque frappe sur une propriété qui découpait puis recomposait
|
||||
la liste : la saisie était donc réécrite sous les doigts — un point-virgule tapé disparaissait
|
||||
(il ouvrait une valeur vide, aussitôt élaguée), et l'espace qui le suit était rogné par le
|
||||
même élagage. Autrement dit, le séparateur du champ était le seul caractère qu'on ne pouvait
|
||||
pas y taper.
|
||||
|
||||
Ici la liste est la donnée : on écrit un titre, Entrée l'y range, et chaque titre se modifie
|
||||
ou se retire ensuite. Une virgule (« Ukraine, deux ans après ») n'a plus rien à couper,
|
||||
puisque plus rien ne découpe.
|
||||
*@
|
||||
<label class="champ">
|
||||
<span class="champ-libelle">
|
||||
À la une <span class="champ-aide">(titres séparés par des points-virgules)</span>
|
||||
</span>
|
||||
<input class="champ-saisie" type="text" @bind="Saisie.ArticlesTexte" @bind:event="oninput"
|
||||
placeholder="Ukraine, deux ans après ; Le retour du train de nuit" autocomplete="off" />
|
||||
</label>
|
||||
<div class="champ">
|
||||
<label class="champ-libelle" for="@_idChamp">
|
||||
À la une <span class="champ-aide">(un titre à la fois : tapez-le, puis Entrée)</span>
|
||||
</label>
|
||||
|
||||
<div class="ligne-creation">
|
||||
<input id="@_idChamp" class="champ-saisie" type="text" @bind="_titreSaisi"
|
||||
@bind:event="oninput" @onkeydown="SurToucheArticle"
|
||||
placeholder="Ukraine, deux ans après" autocomplete="off" />
|
||||
|
||||
<button type="button" class="bouton bouton-compact"
|
||||
disabled="@(string.IsNullOrWhiteSpace(_titreSaisi))" @onclick="ValiderArticle">
|
||||
@(_indexModifie is null ? "Ajouter" : "Enregistrer")
|
||||
</button>
|
||||
|
||||
@if (_indexModifie is not null)
|
||||
{
|
||||
<button type="button" class="bouton bouton-compact bouton-discret"
|
||||
@onclick="AnnulerModification">
|
||||
Annuler
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (_messageArticle is not null)
|
||||
{
|
||||
<p class="message-discret" role="status">@_messageArticle</p>
|
||||
}
|
||||
|
||||
@if (Saisie.Articles.Count > 0)
|
||||
{
|
||||
<ul class="liste-unes">
|
||||
@for (var rang = 0; rang < Saisie.Articles.Count; rang++)
|
||||
{
|
||||
@* La copie locale est indispensable : sans elle, les fermetures des deux
|
||||
boutons captureraient la variable de boucle, donc son état final. *@
|
||||
var index = rang;
|
||||
var titre = Saisie.Articles[rang];
|
||||
|
||||
<li class="ligne-une" @key="index">
|
||||
<span class="ligne-une-titre">@titre</span>
|
||||
|
||||
<div class="actions-tome">
|
||||
<button type="button" class="bouton bouton-compact"
|
||||
aria-label="@($"Modifier « {titre} »")"
|
||||
@onclick="() => ModifierArticle(index)">
|
||||
Modifier
|
||||
</button>
|
||||
|
||||
<button type="button" class="bouton bouton-compact bouton-danger"
|
||||
aria-label="@($"Supprimer « {titre} »")"
|
||||
@onclick="() => SupprimerArticle(index)">
|
||||
Supprimer
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
|
||||
@*
|
||||
⚠️ Une URL, pas une photo. Aucune source ne donne la couverture d'un NUMÉRO : l'ISSN désigne
|
||||
@@ -53,6 +114,106 @@
|
||||
@code {
|
||||
[Parameter, EditorRequired] public AjoutNumeroRevue Saisie { get; set; } = default!;
|
||||
|
||||
/// <summary>Identifiant du champ « à la une », pour que son libellé le désigne vraiment.</summary>
|
||||
private readonly string _idChamp = $"une-{Guid.NewGuid():N}";
|
||||
|
||||
/// <summary>Titre en cours de frappe. <b>Il n'est relié à rien d'autre</b> : voir plus haut.</summary>
|
||||
private string? _titreSaisi;
|
||||
|
||||
/// <summary>Rang du titre qu'on modifie — <c>null</c> = on en ajoute un nouveau.</summary>
|
||||
private int? _indexModifie;
|
||||
|
||||
private string? _messageArticle;
|
||||
|
||||
/// <summary>Saisie servie au rendu précédent, pour repérer que le parent en a changé.</summary>
|
||||
private AjoutNumeroRevue? _saisiePrecedente;
|
||||
|
||||
/// <summary>
|
||||
/// Le parent remplace la saisie entière (numéro enregistré, autre numéro ouvert en
|
||||
/// modification) : ce qui restait dans le champ appartenait au numéro d'avant.
|
||||
/// </summary>
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (!ReferenceEquals(_saisiePrecedente, Saisie))
|
||||
{
|
||||
_saisiePrecedente = Saisie;
|
||||
RemettreAZero();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemettreAZero()
|
||||
{
|
||||
_titreSaisi = null;
|
||||
_indexModifie = null;
|
||||
_messageArticle = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entrée range le titre, comme le bouton.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ⚠️ Cette touche ne remonte <b>pas</b> à <c>OnEntree</c> : celui-ci enregistre le numéro
|
||||
/// entier, et l'on serait alors bien en peine de saisir deux titres à la une.
|
||||
/// </remarks>
|
||||
private void SurToucheArticle(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Key == "Enter")
|
||||
{
|
||||
ValiderArticle();
|
||||
}
|
||||
}
|
||||
|
||||
private void ValiderArticle()
|
||||
{
|
||||
var titre = _titreSaisi?.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(titre))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Même règle de doublon que partout ailleurs : au sens de la normalisation, et le
|
||||
// libellé déjà rangé est celui qui reste.
|
||||
var cle = NormalisationTexte.Normaliser(titre);
|
||||
var deja = Saisie.Articles.FindIndex(a => NormalisationTexte.Normaliser(a) == cle);
|
||||
|
||||
if (deja >= 0 && deja != _indexModifie)
|
||||
{
|
||||
_messageArticle = "Ce titre est déjà à la une de ce numéro.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (_indexModifie is { } index)
|
||||
{
|
||||
Saisie.Articles[index] = titre;
|
||||
}
|
||||
else
|
||||
{
|
||||
Saisie.Articles.Add(titre);
|
||||
}
|
||||
|
||||
RemettreAZero();
|
||||
}
|
||||
|
||||
/// <summary>Reprend un titre dans le champ : on le corrige là où on l'a écrit.</summary>
|
||||
private void ModifierArticle(int index)
|
||||
{
|
||||
_messageArticle = null;
|
||||
_indexModifie = index;
|
||||
_titreSaisi = Saisie.Articles[index];
|
||||
}
|
||||
|
||||
private void AnnulerModification() => RemettreAZero();
|
||||
|
||||
private void SupprimerArticle(int index)
|
||||
{
|
||||
Saisie.Articles.RemoveAt(index);
|
||||
|
||||
// Le titre qu'on modifiait vient peut-être de disparaître, et les rangs suivants ont
|
||||
// glissé : reprendre la modification écrirait sur le mauvais titre.
|
||||
RemettreAZero();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Date affichée par le champ, en <b>local</b>.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user