fix(android): a note can be written on several lines, and Enter means Enter

The creation form still put a note's body behind a single-line input, so one
could read and edit multi-line text but never create it - the note had to be
created, then reopened to be laid out.

And the newline could not be typed at all: the handler that turns the
validate key into "fold the keyboard" applied to every field. That rule
exists because in a one-line field the key can only jump to the next one, and
the next one sat on an off-screen view - which is how the unclosable keyboard
happened. In a text area, Enter means Enter. It is the third time that
handler has misfired by treating "any field" as one thing; it now carries its
three exceptions explicitly.

Two things fixed in the same code, since they lived there:

Rows were recognised BY THEIR LABEL - prose. Renaming a caption would have
been enough for this code to stop acting on it, silently. They go by id now.

And what is already typed survives a change of kind. Replacing the control
without carrying the value over would have wiped the entry for a mere change
of mind between Login and Note.
This commit is contained in:
r-zakarya
2026-08-22 14:33:58 +01:00
parent 4672ba8fd8
commit 248cdafa49
+58 -23
View File
@@ -852,7 +852,7 @@ function editorControl(k) {
// partait au depot a l'enregistrement suivant. // partait au depot a l'enregistrement suivant.
if (detailRows[k].m) if (detailRows[k].m)
return '<textarea class="field-in" id="editval" rows="6"' return '<textarea class="field-in" id="editval" rows="6"'
+ ' autocorrect="off" spellcheck="false"' + ' enterkeyhint="enter" autocorrect="off" spellcheck="false"'
+ ' style="margin:0;flex:1;min-width:0;width:auto;height:auto;' + ' style="margin:0;flex:1;min-width:0;width:auto;height:auto;'
+ 'padding:10px 14px;resize:vertical"></textarea>'; + 'padding:10px 14px;resize:vertical"></textarea>';
if (editField !== 'folder') if (editField !== 'folder')
@@ -1013,6 +1013,11 @@ addEventListener('keydown', function (ev) {
var el = ev.target; var el = ev.target;
if (ev.key !== 'Enter' || !el.classList || !el.classList.contains('field-in')) if (ev.key !== 'Enter' || !el.classList || !el.classList.contains('field-in'))
return; return;
// DANS UNE ZONE DE TEXTE, ENTREE VEUT DIRE ENTREE. Cette regle vaut pour les
// champs d'une ligne, ou la touche ne peut que sauter au suivant ; appliquee
// au corps d'une note, elle rendait impossible d'y sauter une ligne - le
// clavier semblait ne pas en avoir la touche.
if (el.tagName === 'TEXTAREA') return;
ev.preventDefault(); ev.preventDefault();
el.blur(); el.blur();
if (el.id !== 'editval') return; if (el.id !== 'editval') return;
@@ -1042,12 +1047,28 @@ function newFolderField() {
return html + '</select></div></div>'; return html + '</select></div></div>';
} }
function newField(id, label, type) { var FIELD_STYLE = 'margin:0;flex:1;min-width:0;width:auto';
return '<div class="field"><div class="cap">' + label + '</div>'
+ '<div class="line"><input class="field-in" id="' + id + '" type="' function newField(id, label, rowId) {
+ type + '" autocomplete="off" autocorrect="off" autocapitalize="none"' return '<div class="field" id="' + rowId + '"><div class="cap">' + label
+ ' spellcheck="false" style="margin:0;flex:1;min-width:0;width:auto">' + '</div><div class="line"><input class="field-in" id="' + id + '"'
+ '</div></div>'; + ' type="text" autocomplete="off" autocorrect="off"'
+ ' autocapitalize="none" spellcheck="false" style="' + FIELD_STYLE
+ '"></div></div>';
}
/// LE CORPS D'UNE NOTE SE SAISIT SUR PLUSIEURS LIGNES. Un <input> applique la
/// sanitisation de HTML et retire les retours a la ligne : on pouvait lire et
/// modifier du multiligne, mais pas en creer - il fallait creer la note puis
/// l'ouvrir pour la mettre en forme.
function newBodyControl(kind) {
if (kind === 'note')
return '<textarea class="field-in" id="nPwd" rows="6" enterkeyhint="enter"'
+ ' autocorrect="off" spellcheck="false" style="' + FIELD_STYLE
+ ';height:auto;padding:10px 14px;resize:vertical"></textarea>';
return '<input class="field-in" id="nPwd" type="text" autocomplete="off"'
+ ' autocorrect="off" autocapitalize="none" spellcheck="false"'
+ ' style="' + FIELD_STYLE + '">';
} }
function openNewEntry() { function openNewEntry() {
@@ -1059,12 +1080,13 @@ function openNewEntry() {
+ '<button class="btn main" data-kind="login">Login</button>' + '<button class="btn main" data-kind="login">Login</button>'
+ '<button class="btn" data-kind="note">Note</button></div></div>' + '<button class="btn" data-kind="note">Note</button></div></div>'
+ newFolderField() + newFolderField()
+ newField('nTitle', 'Name', 'text') + newField('nTitle', 'Name', 'nTitleRow')
+ newField('nSite', 'Site', 'text') + newField('nSite', 'Site', 'nSiteRow')
+ newField('nUser', 'Username', 'text') + newField('nUser', 'Username', 'nUserRow')
// EN CLAIR pendant la saisie, comme l'editeur : on tape un mot de passe // EN CLAIR pendant la saisie, comme l'editeur : on tape un mot de passe
// qu'aucune confirmation ne rattrape et qu'on ne peut relire nulle part. // qu'aucune confirmation ne rattrape et qu'on ne peut relire nulle part.
+ newField('nPwd', 'Password', 'text') + '<div class="field" id="nBodyRow"><div class="cap">Password</div>'
+ '<div class="line">' + newBodyControl('login') + '</div></div>'
+ '<div class="field"><div class="line">' + '<div class="field"><div class="line">'
// flex:1 et non .wide : .wide ne regle que le rembourrage, et la largeur // flex:1 et non .wide : .wide ne regle que le rembourrage, et la largeur
// pleine vient d'etre etire dans son conteneur. // pleine vient d'etre etire dans son conteneur.
@@ -1082,20 +1104,33 @@ document.getElementById('fields').addEventListener('click', function (ev) {
var kind = ev.target.getAttribute('data-kind'); var kind = ev.target.getAttribute('data-kind');
if (kind) { if (kind) {
newKind = kind; newKind = kind;
var all = document.querySelectorAll('[data-kind]'); var all = document.querySelectorAll('[data-kind]'), i;
for (var i = 0; i < all.length; i++) for (i = 0; i < all.length; i++)
all[i].classList.toggle('main', all[i].getAttribute('data-kind') === kind); all[i].classList.toggle('main', all[i].getAttribute('data-kind') === kind);
// PAR IDENTIFIANT, PAS PAR LIBELLE. Les rangees se reconnaissaient a leur
// intitule - de la prose - donc renommer un champ aurait suffi a ce que
// ce code cesse silencieusement d'agir sur lui.
//
// Une note n'a ni site ni identifiant : les laisser proposes revient a
// demander de remplir ce que rien n'affichera.
document.getElementById('nSiteRow').hidden = (kind === 'note');
document.getElementById('nUserRow').hidden = (kind === 'note');
// Le corps d'une note EST son mot de passe cote stockage : meme champ, // Le corps d'une note EST son mot de passe cote stockage : meme champ,
// autre intitule, et rien a masquer. // autre intitule, et rien a masquer - mais plusieurs lignes.
var caps = document.querySelectorAll('#fields .cap'); var row = document.getElementById('nBodyRow');
for (i = 0; i < caps.length; i++) { row.querySelector('.cap').textContent =
if (caps[i].textContent === 'Password' || caps[i].textContent === 'Content') (kind === 'note') ? 'Content' : 'Password';
caps[i].textContent = (kind === 'note') ? 'Content' : 'Password'; var line = row.querySelector('.line');
// Une note n'a ni site ni identifiant. Les laisser proposes revient a // CE QUI EST DEJA TAPE SURVIT AU CHANGEMENT. Remplacer le controle sans le
// demander de remplir ce qui ne sera jamais affiche. // reporter effacerait la saisie sans un mot, pour un simple changement
if (caps[i].textContent === 'Site' || caps[i].textContent === 'Username') // d'avis sur le genre.
caps[i].parentNode.hidden = (kind === 'note'); var old = document.getElementById('nPwd');
} var kept = old ? old.value : '';
line.classList.toggle('wrap', kind === 'note');
line.innerHTML = newBodyControl(kind);
document.getElementById('nPwd').value = kept;
return; return;
} }
if (ev.target.id !== 'nSave') return; if (ev.target.id !== 'nSave') return;