feat(android): entries can be created on the phone
A vault you cannot add to is not a vault, so an account created here was useless without this. A plus button next to the padlock, a form, and a kind - login or note, which the vault distinguishes and whose body is the password field either way. One save path now, two transports, and the rule that decides between them is: if a local container exists it is the authority. A vault born here has no repository; a vault pulled from one has no local container. The case where both exist - a local vault that later gains a repository - needs a merge, and that is section 7 step 4. The patch arrives as a function rather than an already-patched snapshot: the remote path must modify WHAT IT HAS JUST PULLED, or it would overwrite whatever another device published in between. That constraint is why the two transports could not simply share a finished payload. Two traps closed while passing: the form left the typed password sitting in the document after the save, and a refusal left its Save button dead with the text behind it. Verified on the device that the button and the form appear and read correctly. Saving was not exercised - the vault on this phone syncs to a real repository, and a test entry would have been published to it with no way to delete it from here yet.
This commit is contained in:
@@ -323,6 +323,12 @@
|
||||
autocapitalize="none" spellcheck="false">
|
||||
<div class="clear" id="qx">×</div>
|
||||
</div>
|
||||
<button class="ico" id="new" aria-label="New entry">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round">
|
||||
<path d="M12 5v14M5 12h14" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="ico" id="lock" aria-label="Lock vault">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round">
|
||||
@@ -689,6 +695,80 @@ addEventListener('keydown', function (ev) {
|
||||
if (save && !save.disabled) save.click();
|
||||
});
|
||||
|
||||
// NOUVELLE ENTREE - dans l'ecran de DETAIL, pas dans une quatrieme vue.
|
||||
//
|
||||
// Le rail en porte trois, et chacune coute ses regles de transformation. Le
|
||||
// detail a deja tout ce qu'il faut : un titre, un retour, une colonne qui
|
||||
// defile. Une entree qu'on cree et une entree qu'on consulte occupent la meme
|
||||
// place a l'ecran, et c'est exactement ce que cette vue veut dire.
|
||||
var newKind = 'login';
|
||||
|
||||
function newField(id, label, type) {
|
||||
return '<div class="field"><div class="cap">' + label + '</div>'
|
||||
+ '<div class="line"><input class="field-in" id="' + id + '" type="'
|
||||
+ type + '" autocomplete="off" autocorrect="off" autocapitalize="none"'
|
||||
+ ' spellcheck="false" style="margin:0;flex:1;min-width:0;width:auto">'
|
||||
+ '</div></div>';
|
||||
}
|
||||
|
||||
function openNewEntry() {
|
||||
newKind = 'login';
|
||||
detailRows = [];
|
||||
document.getElementById('dtitle').textContent = 'New entry';
|
||||
document.getElementById('fields').innerHTML =
|
||||
'<div class="field"><div class="cap">Kind</div><div class="line">'
|
||||
+ '<button class="btn main" data-kind="login">Login</button>'
|
||||
+ '<button class="btn" data-kind="note">Note</button></div></div>'
|
||||
+ newField('nTitle', 'Name', 'text')
|
||||
+ newField('nSite', 'Site', 'text')
|
||||
+ newField('nUser', 'Username', 'text')
|
||||
// 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.
|
||||
+ newField('nPwd', 'Password', 'text')
|
||||
+ '<div class="field"><div class="line">'
|
||||
// flex:1 et non .wide : .wide ne regle que le rembourrage, et la largeur
|
||||
// pleine vient d'etre etire dans son conteneur.
|
||||
+ '<button class="btn main wide" id="nSave" style="flex:1">Save</button>'
|
||||
+ '</div></div>';
|
||||
document.body.classList.remove('on-list');
|
||||
document.body.classList.add('on-detail');
|
||||
view = 'detail';
|
||||
send('state?view=detail');
|
||||
}
|
||||
|
||||
// Un seul ecouteur pour le formulaire : ses boutons naissent et meurent avec
|
||||
// lui, et en attacher a chaque ouverture les empilerait.
|
||||
document.getElementById('fields').addEventListener('click', function (ev) {
|
||||
var kind = ev.target.getAttribute('data-kind');
|
||||
if (kind) {
|
||||
newKind = kind;
|
||||
var all = document.querySelectorAll('[data-kind]');
|
||||
for (var i = 0; i < all.length; i++)
|
||||
all[i].classList.toggle('main', all[i].getAttribute('data-kind') === kind);
|
||||
// Le corps d'une note EST son mot de passe cote stockage : meme champ,
|
||||
// autre intitule, et rien a masquer.
|
||||
var caps = document.querySelectorAll('#fields .cap');
|
||||
for (i = 0; i < caps.length; i++) {
|
||||
if (caps[i].textContent === 'Password' || caps[i].textContent === 'Content')
|
||||
caps[i].textContent = (kind === 'note') ? 'Content' : 'Password';
|
||||
// Une note n'a ni site ni identifiant. Les laisser proposes revient a
|
||||
// demander de remplir ce qui ne sera jamais affiche.
|
||||
if (caps[i].textContent === 'Site' || caps[i].textContent === 'Username')
|
||||
caps[i].parentNode.hidden = (kind === 'note');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ev.target.id !== 'nSave') return;
|
||||
var v = function (id) { return document.getElementById(id).value.trim(); };
|
||||
ev.target.disabled = true;
|
||||
send('newentry?t=' + encodeURIComponent(v('nTitle'))
|
||||
+ '&s=' + encodeURIComponent(v('nSite'))
|
||||
+ '&u=' + encodeURIComponent(v('nUser'))
|
||||
+ '&p=' + encodeURIComponent(document.getElementById('nPwd').value)
|
||||
+ '&k=' + newKind);
|
||||
});
|
||||
document.getElementById('new').addEventListener('click', openNewEntry);
|
||||
|
||||
function goList() {
|
||||
document.body.classList.remove('on-detail');
|
||||
document.body.classList.add('on-list');
|
||||
@@ -710,6 +790,11 @@ var PM = {
|
||||
q.value = ''; query = '';
|
||||
document.body.classList.remove('searching', 'on-detail');
|
||||
document.body.classList.add('on-list');
|
||||
// LE FORMULAIRE PART AVEC LA VUE, pas seulement de l'ecran. Il garde le
|
||||
// mot de passe qu'on vient de taper ; le laisser dans le document apres
|
||||
// l'enregistrement le laisserait la jusqu'au verrouillage.
|
||||
detailRows = [];
|
||||
document.getElementById('fields').innerHTML = '';
|
||||
view = 'list';
|
||||
renderFolders();
|
||||
renderRows();
|
||||
@@ -719,6 +804,11 @@ var PM = {
|
||||
/// immediats : l'editeur y restait ouvert, boutons desactives, et Cancel
|
||||
/// lui-meme ne repondait plus.
|
||||
editDone: function () {
|
||||
// Le formulaire de creation passe par ici aussi : son bouton se desactive
|
||||
// a l'envoi, et sans cela un refus le laissait mort avec le texte saisi
|
||||
// derriere - a retaper sans savoir pourquoi.
|
||||
var save = document.getElementById('nSave');
|
||||
if (save) { save.disabled = false; return; }
|
||||
if (lastDetail) renderDetail(lastDetail);
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user