@@ -596,9 +597,12 @@ document.getElementById('rows').addEventListener('click', function (ev) {
// de copier. Le doigt tombant tantot sur le glyphe tantot sur la marge, cela
// ne ratait qu'une fois sur deux - la pire des pannes a diagnostiquer.
var btn = ev.target.closest('[data-copy]');
- if (btn) send('copy?i=' + row.getAttribute('data-i')
- + '&f=' + btn.getAttribute('data-copy'));
- else send('open?i=' + row.getAttribute('data-i'));
+ if (btn) { send('copy?i=' + row.getAttribute('data-i')
+ + '&f=' + btn.getAttribute('data-copy')); return; }
+ // Le menu vient de s'ouvrir sur cette ligne : le clic qui suit l'appui long
+ // ouvrirait l'entree DERRIERE le menu.
+ if (!document.getElementById('menuList').hidden) return;
+ send('open?i=' + row.getAttribute('data-i'));
});
document.getElementById('folders').addEventListener('click', function (ev) {
@@ -1045,14 +1049,23 @@ document.getElementById('new').addEventListener('click', openNewEntry);
// pierre tombale fait disparaitre l'entree du poste aussi, sans passer par la
// sienne. Le seul filet est la sauvegarde du poste.
function closeMenu() {
- var m = document.getElementById('menu');
- m.hidden = true;
- m.innerHTML = '';
+ ['menu', 'menuList'].forEach(function (id) {
+ var m = document.getElementById(id);
+ if (!m) return;
+ m.hidden = true;
+ m.innerHTML = '';
+ });
}
-function openMenu() {
- var m = document.getElementById('menu');
- m.innerHTML = '';
+/// AMenu vit dans l'ecran courant : le detail a le sien, la liste le sien.
+/// ATop, quand il est donne, ancre le menu a la hauteur de la ligne touchee -
+/// un menu qui apparait loin du doigt ne se rattache a rien.
+function openMenu(AIndex, ATop) {
+ var m = document.getElementById(view === 'list' ? 'menuList' : 'menu');
+ m.innerHTML = '';
+ if (ATop !== undefined) m.style.top = ATop + 'px';
m.hidden = false;
}
@@ -1062,7 +1075,7 @@ document.getElementById('kebab').addEventListener('click', function (ev) {
if (document.getElementById('menu').hidden) openMenu(); else closeMenu();
});
-document.getElementById('menu').addEventListener('click', function (ev) {
+function onMenuClick(ev) {
var b = ev.target.closest('button');
if (!b) return;
if (b.getAttribute('data-del') !== null) {
@@ -1076,14 +1089,47 @@ document.getElementById('menu').addEventListener('click', function (ev) {
if (b.getAttribute('data-delconfirm') === null) return;
b.disabled = true;
b.textContent = 'Deleting...';
- send('delentry');
-});
+ // L'index designe l'entree quand on supprime depuis la liste. Absent, c'est
+ // l'entree ouverte - Delphi tient deja laquelle.
+ var i = b.getAttribute('data-i');
+ send('delentry' + (i === null ? '' : '?i=' + i));
+}
+document.getElementById('menu').addEventListener('click', onMenuClick);
+document.getElementById('menuList').addEventListener('click', onMenuClick);
+
+// APPUI LONG POUR SUPPRIMER DEPUIS LA LISTE.
+//
+// Ni une croix par ligne - une action sans retour a un pouce d'ecart sur une
+// liste qu'on fait defiler - ni un troisieme bouton par ligne, alors qu'on
+// vient justement de les degager. L'appui long ne se voit pas tant qu'on ne
+// s'en sert pas, c'est la convention d'Android pour agir sur un element de
+// liste, et c'est la que se rangera la selection multiple si elle vient.
+(function () {
+ var timer = null, moved = false;
+ var rows = document.getElementById('rows');
+ rows.addEventListener('touchstart', function (ev) {
+ var row = ev.target.closest('.row');
+ // Pas sur les boutons de copie : ils ont deja leur geste.
+ if (!row || ev.target.closest('[data-copy]')) return;
+ moved = false;
+ timer = setTimeout(function () {
+ if (moved) return;
+ var r = row.getBoundingClientRect();
+ openMenu(row.getAttribute('data-i'), Math.round(r.bottom) - 40);
+ send('touch');
+ }, 600);
+ });
+ rows.addEventListener('touchmove', function () { moved = true; clearTimeout(timer); });
+ ['touchend', 'touchcancel'].forEach(function (e) {
+ rows.addEventListener(e, function () { clearTimeout(timer); });
+ });
+})();
// Un appui n'importe ou ailleurs referme le menu - y compris sur le contenu,
// sinon il resterait ouvert pendant qu'on lit l'entree.
document.addEventListener('click', function (ev) {
if (document.getElementById('menu').hidden) return;
- if (ev.target.closest('#menu, #kebab')) return;
+ if (ev.target.closest('#menu, #menuList, #kebab')) return;
closeMenu();
});
@@ -1128,6 +1174,10 @@ var PM = {
// derriere - a retaper sans savoir pourquoi.
var save = document.getElementById('nSave');
if (save) { save.disabled = false; return; }
+ // RENDRE LA MAIN A CE QUI EST A L'ECRAN, pas a ce qui y etait. Apres une
+ // suppression la vue est deja revenue a la liste, et redessiner le dernier
+ // detail rouvrait l'entree qu'on venait d'effacer - elle semblait revenue.
+ if (view !== 'detail') return;
if (lastDetail) renderDetail(lastDetail);
},
diff --git a/mainApk.pas b/mainApk.pas
index f513ae2..8acd7ab 100644
--- a/mainApk.pas
+++ b/mainApk.pas
@@ -276,8 +276,9 @@ type
/// comme 'setfield' - meme regle, meme journalisation muette.
procedure CreateEntry(const ATitle, ASite, AUser, APwd, AKind,
AFolder: string);
- /// Supprime l'entree ouverte, pierre tombale comprise. Sans retour.
- procedure DeleteCurrentEntry;
+ /// Supprime une entree, pierre tombale comprise. Sans retour.
+ /// AVaultIndex negatif = l'entree ouverte.
+ procedure DeleteEntryAt(AVaultIndex: Integer);
/// Fond le coffre ne ici dans le depot, puis passe l'autorite au depot.
procedure ReconcileWithRemote;
procedure ReopenEntryByUuid(const AUuid: string);
@@ -1173,17 +1174,37 @@ begin
end).Start;
end;
-procedure TForm8.DeleteCurrentEntry;
+procedure TForm8.DeleteEntryAt(AVaultIndex: Integer);
var
LUuid, LTitle: string;
+ E: TVaultEntry;
begin
- if FCurrent.Uuid = '' then
+ // UN RANG, OU L'ENTREE OUVERTE. Depuis la liste il n'y a pas d'entree
+ // ouverte : le rang designe laquelle, exactement comme pour 'open' et
+ // 'copy'. La page n'envoie jamais d'uuid - elle en a un, mais le faire
+ // voyager reviendrait a lui laisser designer une ligne que Delphi n'a pas
+ // affichee.
+ if AVaultIndex >= 0 then
+ begin
+ if AVaultIndex >= FVault.Count then
+ begin
+ RefuseChange('That entry is no longer there');
+ Exit;
+ end;
+ E := FVault.Entry(AVaultIndex);
+ LUuid := E.Uuid;
+ LTitle := E.Display;
+ end
+ else
+ begin
+ LUuid := FCurrent.Uuid;
+ LTitle := FCurrent.Display;
+ end;
+ if LUuid = '' then
begin
RefuseChange('This entry has no uuid - cannot be deleted');
Exit;
end;
- LUuid := FCurrent.Uuid;
- LTitle := FCurrent.Display;
// Pas de rang a rouvrir : l'entree n'existera plus. AdoptVault ramene la
// liste, ce qui est exactement ou l'on veut se retrouver.
CommitChange('Deleted: ' + LTitle, 'entry deleted from phone', '',
@@ -1357,7 +1378,7 @@ begin
TNetEncoding.URL.Decode(Param('d')))
else if LAction = 'delentry' then
- DeleteCurrentEntry
+ DeleteEntryAt(StrToIntDef(Param('i'), -1))
else if LAction = 'setfield' then
SaveEntryField(TNetEncoding.URL.Decode(Param('f')),