Make username optional. Sometimes we just want to store passwords

This commit is contained in:
Timothy Farrell 2026-05-15 21:19:55 +00:00
parent 84f861a06a
commit 7a930228fc
5 changed files with 59 additions and 50 deletions

67
dist/index.html vendored
View File

@ -4840,7 +4840,7 @@ function generateId() {
* @typedef {Object} CredentialEntry
* @property {string} id - Unique identifier
* @property {string} title - Display name (e.g. "GitHub", "Gmail")
* @property {string} username - Login username or email
* @property {string} [username] - Login username or email (optional)
* @property {string} encryptedPassword - AES-GCM encrypted password blob (JSON string)
* @property {string} [url] - Website URL
* @property {string} [notes] - Free-form notes
@ -4854,7 +4854,7 @@ function generateId() {
*
* @param {Object} data
* @param {string} data.title
* @param {string} data.username
* @param {string} [data.username]
* @param {string} data.encryptedPassword - Must already be encrypted
* @param {string} [data.url]
* @param {string} [data.notes]
@ -4867,7 +4867,7 @@ function createEntry(data) {
return {
id: generateId(),
title: data.title.trim(),
username: data.username.trim(),
username: data.username?.trim() || "",
encryptedPassword: data.encryptedPassword,
url: data.url?.trim() || "",
notes: data.notes?.trim() || "",
@ -4888,7 +4888,7 @@ function updateEntry$1(existing, data) {
return {
...existing,
title: data.title !== void 0 ? data.title.trim() : existing.title,
username: data.username !== void 0 ? data.username.trim() : existing.username,
username: data.username !== void 0 ? data.username?.trim() || "" : existing.username,
encryptedPassword: data.encryptedPassword !== void 0 ? data.encryptedPassword : existing.encryptedPassword,
url: data.url !== void 0 ? data.url.trim() : existing.url,
notes: data.notes !== void 0 ? data.notes.trim() : existing.notes,
@ -4940,7 +4940,6 @@ function createGroup(name, color) {
function validateEntry(data) {
const errors = [];
if (!data.title || !data.title.trim()) errors.push("Title is required");
if (!data.username || !data.username.trim()) errors.push("Username is required");
if (!data.encryptedPassword) errors.push("Password is required");
return {
valid: errors.length === 0,
@ -6110,7 +6109,7 @@ function EntryList($$anchor, $$props) {
reset(tr);
template_effect(() => {
set_text(text_6, get(entry).title);
set_text(text_7, get(entry).username);
set_text(text_7, get(entry).username || "—");
set_text(text_8, get(entry).url || "—");
});
delegated("click", tr, () => $$props.onSelect(get(entry).id));
@ -6138,10 +6137,11 @@ var root_1$5 = /* @__PURE__ */ from_html(`<div class="toast svelte-dssgjx"> </di
var root_2$3 = /* @__PURE__ */ from_html(`<div class="loading svelte-dssgjx">Loading...</div>`);
var root_3$3 = /* @__PURE__ */ from_html(`<div class="error-banner svelte-dssgjx"> </div>`);
var root_4$3 = /* @__PURE__ */ from_html(`<div class="empty-state svelte-dssgjx">Entry not found</div>`);
var root_6$3 = /* @__PURE__ */ from_html(`<div class="detail-field"><span class="field-label svelte-dssgjx">URL</span> <div class="field-value svelte-dssgjx"><a target="_blank" rel="noopener noreferrer" class="svelte-dssgjx"> </a> <button class="btn btn-ghost btn-sm copy-btn svelte-dssgjx" title="Copy URL">📋</button></div></div>`);
var root_7$1 = /* @__PURE__ */ from_html(`<div class="detail-field"><span class="field-label svelte-dssgjx">Notes</span> <div class="field-value notes svelte-dssgjx"> </div></div>`);
var root_8 = /* @__PURE__ */ from_html(`<div class="modal-overlay svelte-dssgjx" role="presentation"><div class="modal svelte-dssgjx" role="dialog" aria-modal="true" aria-label="Delete confirmation" tabindex="-1"><h3 class="svelte-dssgjx">Delete Entry</h3> <p class="svelte-dssgjx">Are you sure you want to delete "<strong> </strong>"? This cannot be undone.</p> <div class="modal-actions svelte-dssgjx"><button class="btn btn-danger"> </button> <button class="btn btn-ghost">Cancel</button></div></div></div>`);
var root_5$2 = /* @__PURE__ */ from_html(`<div class="detail-card svelte-dssgjx"><div class="detail-header svelte-dssgjx"><h2 class="svelte-dssgjx"> </h2> <div class="header-actions svelte-dssgjx"><button class="btn btn-ghost btn-sm">✏️ Edit</button> <button class="btn btn-danger btn-sm">🗑 Delete</button></div></div> <div class="detail-fields svelte-dssgjx"><div class="detail-field"><span class="field-label svelte-dssgjx">Username</span> <div class="field-value svelte-dssgjx"><span> </span> <button class="btn btn-ghost btn-sm copy-btn svelte-dssgjx" title="Copy username">📋</button></div></div> <div class="detail-field"><span class="field-label svelte-dssgjx">Password</span> <div class="field-value svelte-dssgjx"><span> </span> <button class="btn btn-ghost btn-sm" title="Toggle visibility"> </button> <button class="btn btn-ghost btn-sm copy-btn svelte-dssgjx" title="Copy password">📋</button></div></div> <!> <!></div> <div class="detail-meta svelte-dssgjx"><span class="text-xs text-muted"> </span> <span class="text-xs text-muted"> </span></div></div> <!>`, 1);
var root_6$3 = /* @__PURE__ */ from_html(`<div class="detail-field"><span class="field-label svelte-dssgjx">Username</span> <div class="field-value svelte-dssgjx"><span> </span> <button class="btn btn-ghost btn-sm copy-btn svelte-dssgjx" title="Copy username">📋</button></div></div>`);
var root_7$1 = /* @__PURE__ */ from_html(`<div class="detail-field"><span class="field-label svelte-dssgjx">URL</span> <div class="field-value svelte-dssgjx"><a target="_blank" rel="noopener noreferrer" class="svelte-dssgjx"> </a> <button class="btn btn-ghost btn-sm copy-btn svelte-dssgjx" title="Copy URL">📋</button></div></div>`);
var root_8 = /* @__PURE__ */ from_html(`<div class="detail-field"><span class="field-label svelte-dssgjx">Notes</span> <div class="field-value notes svelte-dssgjx"> </div></div>`);
var root_9 = /* @__PURE__ */ from_html(`<div class="modal-overlay svelte-dssgjx" role="presentation"><div class="modal svelte-dssgjx" role="dialog" aria-modal="true" aria-label="Delete confirmation" tabindex="-1"><h3 class="svelte-dssgjx">Delete Entry</h3> <p class="svelte-dssgjx">Are you sure you want to delete "<strong> </strong>"? This cannot be undone.</p> <div class="modal-actions svelte-dssgjx"><button class="btn btn-danger"> </button> <button class="btn btn-ghost">Cancel</button></div></div></div>`);
var root_5$2 = /* @__PURE__ */ from_html(`<div class="detail-card svelte-dssgjx"><div class="detail-header svelte-dssgjx"><h2 class="svelte-dssgjx"> </h2> <div class="header-actions svelte-dssgjx"><button class="btn btn-ghost btn-sm">✏️ Edit</button> <button class="btn btn-danger btn-sm">🗑 Delete</button></div></div> <div class="detail-fields svelte-dssgjx"><!> <div class="detail-field"><span class="field-label svelte-dssgjx">Password</span> <div class="field-value svelte-dssgjx"><span> </span> <button class="btn btn-ghost btn-sm" title="Toggle visibility"> </button> <button class="btn btn-ghost btn-sm copy-btn svelte-dssgjx" title="Copy password">📋</button></div></div> <!> <!></div> <div class="detail-meta svelte-dssgjx"><span class="text-xs text-muted"> </span> <span class="text-xs text-muted"> </span></div></div> <!>`, 1);
var root$4 = /* @__PURE__ */ from_html(`<div class="entry-detail"><!> <!></div>`);
function EntryDetail($$anchor, $$props) {
push($$props, true);
@ -6244,7 +6244,9 @@ function EntryDetail($$anchor, $$props) {
reset(div_7);
reset(div_6);
var div_8 = sibling(div_6, 2);
var div_9 = child(div_8);
var node_2 = child(div_8);
var consequent_4 = ($$anchor) => {
var div_9 = root_6$3();
var div_10 = sibling(child(div_9), 2);
var span = child(div_10);
var text_4 = child(span, true);
@ -6252,7 +6254,14 @@ function EntryDetail($$anchor, $$props) {
var button_2 = sibling(span, 2);
reset(div_10);
reset(div_9);
var div_11 = sibling(div_9, 2);
template_effect(() => set_text(text_4, get(entry).username));
delegated("click", button_2, () => copyToClipboard(get(entry).username, "Username"));
append($$anchor, div_9);
};
if_block(node_2, ($$render) => {
if (get(entry).username) $$render(consequent_4);
});
var div_11 = sibling(node_2, 2);
var div_12 = sibling(child(div_11), 2);
var span_1 = child(div_12);
var text_5 = child(span_1, true);
@ -6263,9 +6272,9 @@ function EntryDetail($$anchor, $$props) {
var button_4 = sibling(button_3, 2);
reset(div_12);
reset(div_11);
var node_2 = sibling(div_11, 2);
var consequent_4 = ($$anchor) => {
var div_13 = root_6$3();
var node_3 = sibling(div_11, 2);
var consequent_5 = ($$anchor) => {
var div_13 = root_7$1();
var div_14 = sibling(child(div_13), 2);
var a = child(div_14);
var text_7 = child(a, true);
@ -6280,12 +6289,12 @@ function EntryDetail($$anchor, $$props) {
delegated("click", button_5, () => copyToClipboard(get(entry).url, "URL"));
append($$anchor, div_13);
};
if_block(node_2, ($$render) => {
if (get(entry).url) $$render(consequent_4);
if_block(node_3, ($$render) => {
if (get(entry).url) $$render(consequent_5);
});
var node_3 = sibling(node_2, 2);
var consequent_5 = ($$anchor) => {
var div_15 = root_7$1();
var node_4 = sibling(node_3, 2);
var consequent_6 = ($$anchor) => {
var div_15 = root_8();
var div_16 = sibling(child(div_15), 2);
var text_8 = child(div_16, true);
reset(div_16);
@ -6293,8 +6302,8 @@ function EntryDetail($$anchor, $$props) {
template_effect(() => set_text(text_8, get(entry).notes));
append($$anchor, div_15);
};
if_block(node_3, ($$render) => {
if (get(entry).notes) $$render(consequent_5);
if_block(node_4, ($$render) => {
if (get(entry).notes) $$render(consequent_6);
});
reset(div_8);
var div_17 = sibling(div_8, 2);
@ -6306,9 +6315,9 @@ function EntryDetail($$anchor, $$props) {
reset(span_3);
reset(div_17);
reset(div_5);
var node_4 = sibling(div_5, 2);
var consequent_6 = ($$anchor) => {
var div_18 = root_8();
var node_5 = sibling(div_5, 2);
var consequent_7 = ($$anchor) => {
var div_18 = root_9();
var div_19 = child(div_18);
var p = sibling(child(div_19), 2);
var strong = sibling(child(p));
@ -6335,12 +6344,11 @@ function EntryDetail($$anchor, $$props) {
delegated("click", button_7, () => set(showDeleteConfirm, false));
append($$anchor, div_18);
};
if_block(node_4, ($$render) => {
if (get(showDeleteConfirm)) $$render(consequent_6);
if_block(node_5, ($$render) => {
if (get(showDeleteConfirm)) $$render(consequent_7);
});
template_effect(($0, $1) => {
set_text(text_3, get(entry).title);
set_text(text_4, get(entry).username);
set_text(text_5, get(passwordVisible) ? get(decryptedPassword) : "••••••••••••");
set_text(text_6, get(passwordVisible) ? "🙈" : "👁");
set_text(text_9, `Created: ${$0 ?? ""}`);
@ -6348,7 +6356,6 @@ function EntryDetail($$anchor, $$props) {
}, [() => new Date(get(entry).createdAt).toLocaleString(), () => new Date(get(entry).updatedAt).toLocaleString()]);
delegated("click", button, () => $$props.onEdit(get(entry).id));
delegated("click", button_1, () => set(showDeleteConfirm, true));
delegated("click", button_2, () => copyToClipboard(get(entry).username, "Username"));
delegated("click", button_3, () => set(passwordVisible, !get(passwordVisible)));
delegated("click", button_4, () => copyToClipboard(get(decryptedPassword), "Password"));
append($$anchor, fragment);
@ -6372,7 +6379,7 @@ var root_3$2 = /* @__PURE__ */ from_html(`<div class="error-banner svelte-pafazm
var root_5$1 = /* @__PURE__ */ from_html(`<div class="validation-error svelte-pafazm"> </div>`);
var root_4$2 = /* @__PURE__ */ from_html(`<div class="validation-errors svelte-pafazm"></div>`);
var root_6$2 = /* @__PURE__ */ from_html(`<option> </option>`);
var root_2$2 = /* @__PURE__ */ from_html(`<!> <form class="form-card svelte-pafazm"><!> <div class="form-group"><label for="title">Title *</label> <input id="title" type="text" placeholder="e.g. GitHub, Gmail"/></div> <div class="form-group"><label for="username">Username / Email *</label> <input id="username" type="text" placeholder="username or email"/></div> <div class="form-group"><label for="password">Password *</label> <div class="password-input-group svelte-pafazm"><input id="password" placeholder="Password" class="svelte-pafazm"/> <button type="button" class="btn btn-ghost btn-sm" title="Toggle visibility"> </button> <button type="button" class="btn btn-ghost btn-sm" title="Generate password">🎲</button></div></div> <div class="form-group"><label for="url">URL</label> <input id="url" type="url" placeholder="https://example.com"/></div> <div class="form-group"><label for="group">Group</label> <select id="group"><option>No group</option><!></select></div> <div class="form-group"><label for="notes">Notes</label> <textarea id="notes" placeholder="Any additional notes..."></textarea></div> <div class="form-actions svelte-pafazm"><button type="submit" class="btn btn-primary"> </button> <button type="button" class="btn btn-ghost">Cancel</button></div></form>`, 1);
var root_2$2 = /* @__PURE__ */ from_html(`<!> <form class="form-card svelte-pafazm"><!> <div class="form-group"><label for="title">Title *</label> <input id="title" type="text" placeholder="e.g. GitHub, Gmail"/></div> <div class="form-group"><label for="username">Username / Email</label> <input id="username" type="text" placeholder="username or email"/></div> <div class="form-group"><label for="password">Password *</label> <div class="password-input-group svelte-pafazm"><input id="password" placeholder="Password" class="svelte-pafazm"/> <button type="button" class="btn btn-ghost btn-sm" title="Toggle visibility"> </button> <button type="button" class="btn btn-ghost btn-sm" title="Generate password">🎲</button></div></div> <div class="form-group"><label for="url">URL</label> <input id="url" type="url" placeholder="https://example.com"/></div> <div class="form-group"><label for="group">Group</label> <select id="group"><option>No group</option><!></select></div> <div class="form-group"><label for="notes">Notes</label> <textarea id="notes" placeholder="Any additional notes..."></textarea></div> <div class="form-actions svelte-pafazm"><button type="submit" class="btn btn-primary"> </button> <button type="button" class="btn btn-ghost">Cancel</button></div></form>`, 1);
var root$2 = /* @__PURE__ */ from_html(`<div class="entry-form"><!></div>`);
function EntryForm($$anchor, $$props) {
push($$props, true);

View File

@ -100,6 +100,7 @@
</div>
<div class="detail-fields">
{#if entry.username}
<div class="detail-field">
<span class="field-label">Username</span>
<div class="field-value">
@ -107,6 +108,7 @@
<button class="btn btn-ghost btn-sm copy-btn" onclick={() => copyToClipboard(entry.username, 'Username')} title="Copy username">📋</button>
</div>
</div>
{/if}
<div class="detail-field">
<span class="field-label">Password</span>

View File

@ -118,7 +118,7 @@
</div>
<div class="form-group">
<label for="username">Username / Email *</label>
<label for="username">Username / Email</label>
<input id="username" type="text" bind:value={username} placeholder="username or email" />
</div>

View File

@ -87,7 +87,7 @@
<span class="entry-title">{entry.title}</span>
</td>
<td>
<span class="entry-username">{entry.username}</span>
<span class="entry-username">{entry.username || '—'}</span>
</td>
<td>
<span class="entry-url truncate">{entry.url || '—'}</span>

View File

@ -21,7 +21,7 @@ export function generateId() {
* @typedef {Object} CredentialEntry
* @property {string} id - Unique identifier
* @property {string} title - Display name (e.g. "GitHub", "Gmail")
* @property {string} username - Login username or email
* @property {string} [username] - Login username or email (optional)
* @property {string} encryptedPassword - AES-GCM encrypted password blob (JSON string)
* @property {string} [url] - Website URL
* @property {string} [notes] - Free-form notes
@ -36,7 +36,7 @@ export function generateId() {
*
* @param {Object} data
* @param {string} data.title
* @param {string} data.username
* @param {string} [data.username]
* @param {string} data.encryptedPassword - Must already be encrypted
* @param {string} [data.url]
* @param {string} [data.notes]
@ -49,7 +49,7 @@ export function createEntry(data) {
return {
id: generateId(),
title: data.title.trim(),
username: data.username.trim(),
username: data.username?.trim() || '',
encryptedPassword: data.encryptedPassword,
url: data.url?.trim() || '',
notes: data.notes?.trim() || '',
@ -71,7 +71,7 @@ export function updateEntry(existing, data) {
return {
...existing,
title: data.title !== undefined ? data.title.trim() : existing.title,
username: data.username !== undefined ? data.username.trim() : existing.username,
username: data.username !== undefined ? (data.username?.trim() || '') : existing.username,
encryptedPassword: data.encryptedPassword !== undefined ? data.encryptedPassword : existing.encryptedPassword,
url: data.url !== undefined ? data.url.trim() : existing.url,
notes: data.notes !== undefined ? data.notes.trim() : existing.notes,
@ -119,7 +119,7 @@ export function createGroup(name, color) {
export function validateEntry(data) {
const errors = []
if (!data.title || !data.title.trim()) errors.push('Title is required')
if (!data.username || !data.username.trim()) errors.push('Username is required')
if (!data.encryptedPassword) errors.push('Password is required')
return { valid: errors.length === 0, errors }
}