fix(android): editing a note was about to strip its line breaks
Making every field editable put a note's body behind an <input>, and HTML's value sanitization removes CR and LF without a word. The formatting would have been lost on the phone and published to the repository at the next save - silent, and irreversible once the desktop pulled it. Multi-line is marked on the row in Delphi, like the field name, rather than guessed from the caption or from whether the value happens to contain a newline today: a single-line note one wants to make multi-line deserves the same control. The row wraps through a class set when the editor opens, not through :has() - that selector does not exist before Chrome 105, and an older engine would ignore the rule without saying so.
This commit is contained in:
@@ -166,6 +166,11 @@ type
|
|||||||
/// Rang de la piece jointe dans l'entree, ou -1. Il ne se deduit pas du
|
/// Rang de la piece jointe dans l'entree, ou -1. Il ne se deduit pas du
|
||||||
/// rang de la ligne : les champs personnalises s'intercalent avant.
|
/// rang de la ligne : les champs personnalises s'intercalent avant.
|
||||||
AttachIndex: Integer;
|
AttachIndex: Integer;
|
||||||
|
/// La valeur tient sur plusieurs lignes. Marque ICI, pas devine du libelle
|
||||||
|
/// ni du contenu : un <input> APPLIQUE la sanitisation de HTML et retire
|
||||||
|
/// les retours a la ligne en silence. Une note modifiee depuis le telephone
|
||||||
|
/// y perdait sa mise en forme, et la perte partait au depot.
|
||||||
|
Multiline: Boolean;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function BuildRows(const E: TVaultEntry): TArray<TDetailRow>;
|
function BuildRows(const E: TVaultEntry): TArray<TDetailRow>;
|
||||||
@@ -174,7 +179,8 @@ var
|
|||||||
I: Integer;
|
I: Integer;
|
||||||
|
|
||||||
procedure Add(const ACaption, AValue: string; ASecret: Boolean;
|
procedure Add(const ACaption, AValue: string; ASecret: Boolean;
|
||||||
const AField: string = ''; AAttach: Integer = -1);
|
const AField: string = ''; AAttach: Integer = -1;
|
||||||
|
AMultiline: Boolean = False);
|
||||||
begin
|
begin
|
||||||
// Une ligne vide n'apprend rien - SAUF si elle se remplit. Un identifiant
|
// Une ligne vide n'apprend rien - SAUF si elle se remplit. Un identifiant
|
||||||
// absent qu'on ne peut pas ajouter serait une impasse.
|
// absent qu'on ne peut pas ajouter serait une impasse.
|
||||||
@@ -185,6 +191,7 @@ var
|
|||||||
LRows[High(LRows)].Secret := ASecret;
|
LRows[High(LRows)].Secret := ASecret;
|
||||||
LRows[High(LRows)].Field := AField;
|
LRows[High(LRows)].Field := AField;
|
||||||
LRows[High(LRows)].AttachIndex := AAttach;
|
LRows[High(LRows)].AttachIndex := AAttach;
|
||||||
|
LRows[High(LRows)].Multiline := AMultiline;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@@ -195,7 +202,7 @@ begin
|
|||||||
// Un site et un identifiant n'ont aucun sens pour une note. Les proposer
|
// Un site et un identifiant n'ont aucun sens pour une note. Les proposer
|
||||||
// vides reviendrait a inviter a remplir ce que rien n'affichera jamais.
|
// vides reviendrait a inviter a remplir ce que rien n'affichera jamais.
|
||||||
if E.IsNote then
|
if E.IsNote then
|
||||||
Add('Content', E.Password, False, 'password')
|
Add('Content', E.Password, False, 'password', -1, True)
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
Add('Site', E.Site, False, 'site');
|
Add('Site', E.Site, False, 'site');
|
||||||
@@ -263,6 +270,7 @@ begin
|
|||||||
// "enregistrable", et la page y met le bouton de telechargement.
|
// "enregistrable", et la page y met le bouton de telechargement.
|
||||||
if LRows[I].AttachIndex >= 0 then
|
if LRows[I].AttachIndex >= 0 then
|
||||||
LObj.AddPair('a', TJSONNumber.Create(LRows[I].AttachIndex));
|
LObj.AddPair('a', TJSONNumber.Create(LRows[I].AttachIndex));
|
||||||
|
if LRows[I].Multiline then LObj.AddPair('m', TJSONBool.Create(True));
|
||||||
LArr.AddElement(LObj);
|
LArr.AddElement(LObj);
|
||||||
end;
|
end;
|
||||||
Result := LRoot.ToJSON;
|
Result := LRoot.ToJSON;
|
||||||
|
|||||||
@@ -85,6 +85,13 @@
|
|||||||
dependre d'une mesure qui n'arrive jamais. Elle disparait au premier
|
dependre d'une mesure qui n'arrive jamais. Elle disparait au premier
|
||||||
champ quitte, donc rien ne flotte quand on ne tape pas. */
|
champ quitte, donc rien ne flotte quand on ne tape pas. */
|
||||||
body.typing .scroll { padding-bottom: 60vh; }
|
body.typing .scroll { padding-bottom: 60vh; }
|
||||||
|
/* Une zone de texte occupe plusieurs lignes : la rangee cesse d'etre une
|
||||||
|
rangee. Sans cela les boutons la compriment a la hauteur d'un champ.
|
||||||
|
UNE CLASSE POSEE A L'OUVERTURE, pas un selecteur :has() - celui-ci
|
||||||
|
n'existe pas avant Chrome 105, et un moteur plus ancien ignorerait la
|
||||||
|
regle sans rien dire. */
|
||||||
|
.line.wrap { flex-wrap: wrap; }
|
||||||
|
.line.wrap textarea { flex-basis: 100%; }
|
||||||
|
|
||||||
/* --- barre de recherche ------------------------------------------------ */
|
/* --- barre de recherche ------------------------------------------------ */
|
||||||
.top { padding: 10px 12px 6px; display: flex; gap: 8px; }
|
.top { padding: 10px 12px 6px; display: flex; gap: 8px; }
|
||||||
@@ -830,6 +837,15 @@ document.getElementById('fields').addEventListener('click', function (ev) {
|
|||||||
function editorControl(k) {
|
function editorControl(k) {
|
||||||
var val = detailRows[k].v;
|
var val = detailRows[k].v;
|
||||||
var style = ' style="margin:0;flex:1;min-width:0;width:auto"';
|
var style = ' style="margin:0;flex:1;min-width:0;width:auto"';
|
||||||
|
// UNE ZONE DE TEXTE POUR CE QUI TIENT SUR PLUSIEURS LIGNES. Un <input>
|
||||||
|
// applique la sanitisation de HTML et retire les retours a la ligne en
|
||||||
|
// silence : le corps d'une note y perdait sa mise en forme, et la perte
|
||||||
|
// partait au depot a l'enregistrement suivant.
|
||||||
|
if (detailRows[k].m)
|
||||||
|
return '<textarea class="field-in" id="editval" rows="6"'
|
||||||
|
+ ' autocorrect="off" spellcheck="false"'
|
||||||
|
+ ' style="margin:0;flex:1;min-width:0;width:auto;height:auto;'
|
||||||
|
+ 'padding:10px 14px;resize:vertical"></textarea>';
|
||||||
if (editField !== 'folder')
|
if (editField !== 'folder')
|
||||||
return '<input class="field-in" id="editval" autocomplete="off"'
|
return '<input class="field-in" id="editval" autocomplete="off"'
|
||||||
+ ' type="' + (detailRows[k].s ? 'password' : 'text') + '"'
|
+ ' type="' + (detailRows[k].s ? 'password' : 'text') + '"'
|
||||||
@@ -891,6 +907,7 @@ function openEditor(k) {
|
|||||||
editField = detailRows[k].e || '';
|
editField = detailRows[k].e || '';
|
||||||
if (!editField) return;
|
if (!editField) return;
|
||||||
var line = row.parentNode;
|
var line = row.parentNode;
|
||||||
|
line.classList.toggle('wrap', !!detailRows[k].m);
|
||||||
line.innerHTML = editorControl(k)
|
line.innerHTML = editorControl(k)
|
||||||
// Show n'a de sens que sur un champ masque. Sur un site en clair il
|
// Show n'a de sens que sur un champ masque. Sur un site en clair il
|
||||||
// n'aurait rien a reveler - et l'inverse : il le CACHERAIT.
|
// n'aurait rien a reveler - et l'inverse : il le CACHERAIT.
|
||||||
|
|||||||
Reference in New Issue
Block a user