feat(apk): notes de commandes, une commande copiee a la fois

Bloc Commands en tete d'une note "commands", groupe par groupe, avec un
bouton copier. ParseCommandNote est le portage du decoupage du poste
(groupes, lignes continuees). La copie passe par un rang, comme les autres
lignes du detail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-10-10 19:39:38 +01:00
parent f112de508b
commit b1c0c1e11b
4 changed files with 118 additions and 0 deletions
+71
View File
@@ -37,6 +37,19 @@ function VaultToJson(AVault: TVault): string;
/// Les lignes d'une entree, dans l'ordre d'affichage.
function DetailToJson(const AEntry: TVaultEntry): string;
type
TCommandItem = record
Group, Cmd: string;
end;
/// NOTE DE COMMANDES (modele "commands"), meme regle que le poste
/// (js/app.js parseCommandNote) : "# titre" ouvre un groupe, chaque autre ligne
/// non vide est une commande ; une ligne finie par \, ` ou ^ se recolle a la
/// suivante, sur UNE ligne. Une ligne vide ou la fin du texte termine la
/// commande en cours.
function ParseCommandNote(const AText: string): TArray<TCommandItem>;
function IsCommandNote(const AEntry: TVaultEntry): Boolean;
/// Une ligne du detail par son rang, dans le MEME ordre que DetailToJson : la
/// page renvoie un rang, jamais une valeur, et Delphi relit le clair chez lui.
/// Faux si le rang ne designe rien.
@@ -245,6 +258,49 @@ begin
Result := LRows;
end;
function IsCommandNote(const AEntry: TVaultEntry): Boolean;
begin
Result := AEntry.IsNote and SameText(AEntry.Template, 'commands');
end;
function ParseCommandNote(const AText: string): TArray<TCommandItem>;
var
LGroup, LPending, LLine, LPart: string;
LCont: Boolean;
procedure Flush;
begin
if LPending <> '' then
begin
SetLength(Result, Length(Result) + 1);
Result[High(Result)].Group := LGroup;
Result[High(Result)].Cmd := LPending;
end;
LPending := '';
end;
begin
Result := nil;
LGroup := '';
LPending := '';
for LLine in AText.Replace(#13, '').Split([#10]) do
begin
var LTrim := LLine.Trim;
if LTrim = '' then begin Flush; Continue; end;
if (LPending = '') and LTrim.StartsWith('#') then
begin
LGroup := LTrim.TrimLeft(['#']).Trim;
Continue;
end;
LCont := CharInSet(LTrim[High(LTrim)], ['\', '`', '^']);
if LCont then LPart := Copy(LTrim, 1, Length(LTrim) - 1).TrimRight
else LPart := LTrim;
if LPending = '' then LPending := LPart else LPending := LPending + ' ' + LPart;
if not LCont then Flush;
end;
Flush;
end;
function DetailToJson(const AEntry: TVaultEntry): string;
var
LRoot: TJSONObject;
@@ -278,6 +334,21 @@ begin
if LRows[I].Multiline then LObj.AddPair('m', TJSONBool.Create(True));
LArr.AddElement(LObj);
end;
// La liste des commandes, pour l'affichage. La copie passe par un rang
// (copy?f=cmd&k=), comme les lignes : Delphi relit le clair chez lui.
if IsCommandNote(AEntry) then
begin
var LCmds := ParseCommandNote(AEntry.Password);
var LCArr := TJSONArray.Create;
LRoot.AddPair('cmds', LCArr);
for I := 0 to High(LCmds) do
begin
LObj := TJSONObject.Create;
LObj.AddPair('g', LCmds[I].Group);
LObj.AddPair('c', LCmds[I].Cmd);
LCArr.AddElement(LObj);
end;
end;
Result := LRoot.ToJSON;
finally
LRoot.Free;