feat(apk): edition de l'entree entiere en un seul enregistrement, et icone choisie dans la galerie

Un crayon ouvre un formulaire pre-rempli : tous les champs modifiables a la
fois, un seul Save, donc UN commit au lieu d'un par champ ('setfields',
plusieurs PatchEntryField dans le meme CommitChange).

L'icone quitte le menu de l'entree pour ce formulaire : From site (recherche),
Image... (galerie, reduite a 128 px, provenance custom) et Remove.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-09-25 14:57:58 +01:00
parent 5f1a98df7d
commit 0c12997ad4
3 changed files with 250 additions and 3 deletions
+77 -2
View File
@@ -585,6 +585,7 @@
<div class="head">
<button class="btn ico-btn back-btn" id="back"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-label="Back" role="img"><path d="M20 12H5M11 5l-7 7 7 7"/></svg></button>
<h1 id="dtitle"></h1>
<button class="btn ico-btn" id="pencil" aria-label="Edit entry"></button>
<button class="btn ico-btn" id="kebab" aria-label="Entry menu"></button>
</div>
<div class="scroll"><div id="fields"></div></div>
@@ -1067,6 +1068,7 @@ function renderDetail(d) {
detailRows = d.rows || [];
document.getElementById('dtitle').textContent = d.title || '';
document.getElementById('kebab').hidden = false;
document.getElementById('pencil').hidden = false;
var html = '', i;
if (d.totp)
html += '<div class="field totp" id="totp"><div class="cap">2FA code</div>'
@@ -1126,6 +1128,8 @@ document.getElementById('fields').addEventListener('click', function (ev) {
send('touch');
return;
}
var ia = t.getAttribute('data-iconact');
if (ia !== null) { send('icon?a=' + ia); return; }
var save = t.getAttribute('data-save');
if (save !== null) { send('savefile?k=' + save); return; }
var copy = t.getAttribute('data-copy');
@@ -1458,6 +1462,7 @@ function openNewEntry() {
// qui, cote Delphi, visent la derniere entree ouverte.
closeMenu();
document.getElementById('kebab').hidden = true;
document.getElementById('pencil').hidden = true;
document.getElementById('fields').innerHTML =
'<div class="form">'
+ '<div class="field"><div class="cap">Kind</div><div class="line">'
@@ -1565,13 +1570,83 @@ function closeMenu() {
function openMenu() {
var m = document.getElementById('menu');
m.innerHTML = '<button data-hist>Previous passwords</button>'
+ '<button data-icon="fetch">Fetch icon</button>'
+ '<button data-icon="clear">Remove icon</button>'
+ '<button data-dup>Duplicate entry</button>'
+ '<button class="danger" data-del>Delete entry</button>';
m.hidden = false;
}
// MODIFIER L'ENTREE ENTIERE, PAS UN CHAMP A LA FOIS.
//
// Chaque ligne avait son crayon : ouvrir, taper, valider, recommencer - et
// CHAQUE validation etait un enregistrement complet, donc un commit de plus
// dans le depot. Ici un seul formulaire, un seul Save, un seul commit.
//
// La page ne connait toujours pas le schema : elle rend une ligne par champ
// que Delphi a marque modifiable ('e' porte son nom), dans l'ordre qu'il a
// choisi. Ajouter un champ modifiable cote Delphi suffit a le voir ici.
function openEditEntry() {
if (!lastDetail) return;
closeMenu();
var html = '<div class="form">', i, r;
for (i = 0; i < detailRows.length; i++) {
r = detailRows[i];
if (!r.e) continue;
html += '<div class="field"><div class="cap">' + esc(r.c) + '</div><div class="line'
+ (r.m ? ' wrap' : '') + '">'
+ (r.m
? '<textarea class="field-in" data-ef="' + esc(r.e) + '" rows="5"'
+ ' autocorrect="off" spellcheck="false" style="' + FIELD_STYLE
+ ';height:auto;resize:vertical">' + esc(r.v) + '</textarea>'
: '<input class="field-in"' + (r.e === 'password' ? ' id="editpwd"' : '')
+ ' data-ef="' + esc(r.e) + '" type="text"'
+ ' autocomplete="off" autocorrect="off" autocapitalize="none"'
+ ' spellcheck="false" style="' + FIELD_STYLE + '" value="'
+ esc(r.v) + '">')
+ (r.e === 'password' ? genButton('editpwd') : '')
+ '</div></div>';
}
// L'ICONE SE CHANGE OU SE CHANGENT LES CHAMPS. Elle vivait dans le menu de
// l'entree, a cote de Supprimer : deux actions de nature opposee au meme
// endroit, et rien qui montre l'icone du moment.
html += '<div class="field"><div class="cap">Icon</div><div class="line">'
+ '<button class="btn" data-iconact="fetch">From site</button>'
+ '<button class="btn" data-iconact="pick">Image...</button>'
+ '<button class="btn" data-iconact="clear">Remove</button>'
+ '</div></div>'
+ '</div>'
+ '<div class="line" style="display:flex;gap:8px;margin-top:12px">'
+ '<button class="btn wide" id="eCancel" style="flex:1">Cancel</button>'
+ '<button class="btn main wide" id="eSave" style="flex:1">Save</button>'
+ '</div>';
document.getElementById('fields').innerHTML = html;
document.getElementById('eCancel').addEventListener('click', function () {
renderDetail(lastDetail);
});
document.getElementById('eSave').addEventListener('click', saveEditEntry);
send('touch');
}
/// UN SEUL ENVOI, ET SEULEMENT CE QUI A CHANGE : le reste du coffre n'a pas a
/// etre reecrit parce qu'on a corrige une faute de frappe dans un nom.
function saveEditEntry() {
var ins = document.querySelectorAll('#fields [data-ef]'), q = [], n = 0, i, el, f, v, k;
for (i = 0; i < ins.length; i++) {
el = ins[i];
f = el.getAttribute('data-ef');
v = el.value;
for (k = 0; k < detailRows.length; k++)
if (detailRows[k].e === f && detailRows[k].v === v) { f = ''; break; }
if (!f) continue;
q.push('f' + n + '=' + encodeURIComponent(f) + '&v' + n + '=' + encodeURIComponent(v));
n++;
}
if (!n) { renderDetail(lastDetail); return; }
document.getElementById('eSave').disabled = true;
send('setfields?' + q.join('&'));
}
document.getElementById('pencil').innerHTML = ico('edit', 'Edit entry');
document.getElementById('pencil').addEventListener('click', openEditEntry);
document.getElementById('kebab').innerHTML = ico('dots', 'Entry menu');
document.getElementById('kebab').addEventListener('click', function (ev) {
ev.stopPropagation();