Sign in with Google, Apple…: remember prompt, item row, new-login picker
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
+21
-3
@@ -10,10 +10,11 @@ import {
|
||||
|
||||
interface AppState { has_account: boolean; locked: boolean; remembers_secret_key: boolean; ipc_error: string | null; idle_minutes: number }
|
||||
interface ItemSummary { id: string; title: string; username: string | null; urls: string[]; favorite: boolean; has_totp: boolean; tags: string[] }
|
||||
interface ItemDto { id: string; title: string; username: string | null; password: string | null; urls: string[]; totp: string | null; notes: string | null; tags: string[] }
|
||||
interface SignInView { provider: string; provider_name: string; account: string | null }
|
||||
interface ItemDto { id: string; title: string; username: string | null; password: string | null; urls: string[]; totp: string | null; notes: string | null; tags: string[]; sign_in_with: SignInView | null }
|
||||
interface PairingView { id: string; name: string; caller: string; words: string[] }
|
||||
interface Pairing { id: string; name: string; caller: string; words: string[]; created_at: number }
|
||||
interface SaveView { id: string; origin: string; username: string; update: boolean }
|
||||
interface SaveView { id: string; origin: string; username: string; update: boolean; provider: string | null }
|
||||
interface PasskeyChoice { credential_id: string; user_name: string; title: string }
|
||||
interface PasskeyView { id: string; kind: "get" | "create"; origin: string; rp_id: string; rp_name: string; user_name: string; choices: PasskeyChoice[]; locked: boolean }
|
||||
interface SyncStatus {
|
||||
@@ -630,6 +631,10 @@ async function renderDetail(target: HTMLElement, id: string) {
|
||||
if (item.username) {
|
||||
rows.push(fieldRow("Username", h("span", "", item.username), copyButton("Copy username", () => invoke("copy_secret", { id, field: "username" }))));
|
||||
}
|
||||
if (item.sign_in_with) {
|
||||
const w = item.sign_in_with;
|
||||
rows.push(fieldRow("Sign in with", h("span", "", w.account ? `${w.provider_name} · ${w.account}` : w.provider_name)));
|
||||
}
|
||||
if (item.password) {
|
||||
const value = h("span", "", dots("Password"));
|
||||
let shown = false;
|
||||
@@ -713,13 +718,17 @@ function renderEditor(target: HTMLElement) {
|
||||
const url = input({ type: "url", placeholder: "https://example.com", spellcheck: false });
|
||||
const totp = input({ placeholder: "otpauth://totp/… or base32 secret" }, true);
|
||||
const tags = input({ placeholder: "work, finance", spellcheck: false });
|
||||
const provider = h("select", "kv-field", Object.assign(h("option", "", "Password only"), { value: "" }));
|
||||
void invoke<{ id: string; name: string }[]>("sign_in_providers").then((ps) => provider.append(...ps.map((x) => Object.assign(h("option", "", x.name), { value: x.id }))));
|
||||
const account = input({ placeholder: "Account used with the provider (optional)", spellcheck: false });
|
||||
const gen = button("Generate", "quiet", async () => { pass.value = await invoke<string>("generate_password", { length: 24 }); });
|
||||
const err = errBox();
|
||||
const save = button("Save", "primary");
|
||||
save.onclick = async () => {
|
||||
try {
|
||||
await invoke("save_item", {
|
||||
item: { title: title.value, username: user.value || null, password: pass.value || null, urls: url.value ? [url.value] : [], totp: totp.value || null, tags: parseTags(tags.value) },
|
||||
item: { title: title.value, username: user.value || null, password: pass.value || null, urls: url.value ? [url.value] : [], totp: totp.value || null, tags: parseTags(tags.value),
|
||||
sign_in_with: provider.value ? { provider: provider.value, account: account.value.trim() || null } : null },
|
||||
});
|
||||
await render();
|
||||
} catch (e) {
|
||||
@@ -731,6 +740,7 @@ function renderEditor(target: HTMLElement) {
|
||||
field("Title", title), field("Username", user),
|
||||
h("label", "kv-fieldset", h("span", "kv-label", "Password"), h("div", "row", pass, gen)),
|
||||
field("Website", url), field("One-time code", totp, "Optional."), field("Tags", tags, "Optional. Separate tags with commas."),
|
||||
field("Sign in with", provider, "For sites where you use Google, Apple… instead of a password."), field("Provider account", account),
|
||||
err, h("div", "actions", save, button("Cancel", "secondary", () => target.replaceChildren(emptyDetail())))));
|
||||
title.focus();
|
||||
}
|
||||
@@ -1115,6 +1125,14 @@ function saveCard(s: SaveView): HTMLElement {
|
||||
};
|
||||
yes.onclick = () => resolve(true);
|
||||
no.onclick = () => resolve(false);
|
||||
if (s.provider) {
|
||||
yes.textContent = "Remember";
|
||||
return h("div", "kv-sheet prompt",
|
||||
h("h2", "kv-title-2", `Signed in with ${s.provider}?`),
|
||||
p("", `Remember that you sign in to ${s.origin.replace(/^https?:\/\/(www\.)?/, "")} with ${s.provider}${s.username ? ` (${s.username})` : ""}. Kestrove will offer it next time.`),
|
||||
err,
|
||||
h("div", "actions end", no, yes));
|
||||
}
|
||||
return h("div", "kv-sheet prompt",
|
||||
h("h2", "kv-title-2", s.update ? "Update saved password?" : "Save this login?"),
|
||||
h("div", "kv-card rows", h("div", "kv-frow", tile(s.origin.replace(/^https?:\/\/(www\.)?/, "")),
|
||||
|
||||
Reference in New Issue
Block a user