fix(ext/popup): auto-popout for attachment types, keep login/note in popup
- Login and secure_note types stay in popup without attachment UI - All other types (identity, card, key, totp, document) auto-redirect to full tab when selected - Attachments only shown for login/secure_note when opened in tab Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
5
.claude/settings.json
Normal file
5
.claude/settings.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"enabledPlugins": {
|
||||||
|
"superpowers@claude-plugins-official": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,7 +78,11 @@ function renderTypeSelection(app: HTMLElement): void {
|
|||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
const type = btn.dataset.type as ItemType;
|
const type = btn.dataset.type as ItemType;
|
||||||
setState({ newType: type });
|
setState({ newType: type });
|
||||||
renderItemForm(app, 'add');
|
if (type === 'login' || type === 'secure_note') {
|
||||||
|
renderItemForm(app, 'add');
|
||||||
|
} else {
|
||||||
|
popOutToTab();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/// Login type detail + form. Reference implementation for the shared
|
/// Login type detail + form. Reference implementation for the shared
|
||||||
/// field helpers introduced in Slice 2.
|
/// field helpers introduced in Slice 2.
|
||||||
|
|
||||||
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../popup';
|
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab, isInTab } from '../../popup';
|
||||||
import type { Item, ItemId, LoginCore, ManifestEntry, Section, TotpConfig, AttachmentRef } from '../../../shared/types';
|
import type { Item, ItemId, LoginCore, ManifestEntry, Section, TotpConfig, AttachmentRef } from '../../../shared/types';
|
||||||
import { DEFAULT_PASSWORD_REQUEST } from '../../../shared/types';
|
import { DEFAULT_PASSWORD_REQUEST } from '../../../shared/types';
|
||||||
import { base32Decode, base32Encode } from '../../../shared/base32';
|
import { base32Decode, base32Encode } from '../../../shared/base32';
|
||||||
@@ -267,7 +267,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
|
|||||||
<div class="form-group"><label class="label" for="f-notes">notes</label>
|
<div class="form-group"><label class="label" for="f-notes">notes</label>
|
||||||
<textarea id="f-notes" placeholder="recovery codes, security questions...">${escapeHtml(notes)}</textarea></div>
|
<textarea id="f-notes" placeholder="recovery codes, security questions...">${escapeHtml(notes)}</textarea></div>
|
||||||
${renderSectionsEditor(sectionsDraft, sectionsExpanded)}
|
${renderSectionsEditor(sectionsDraft, sectionsExpanded)}
|
||||||
${renderAttachmentsDisclosure({ itemId: existing?.id ?? '', attachments: attachmentsDraft, mode: 'edit' })}
|
${isInTab() ? renderAttachmentsDisclosure({ itemId: existing?.id ?? '', attachments: attachmentsDraft, mode: 'edit' }) : ''}
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button class="btn" id="cancel-btn">cancel</button>
|
<button class="btn" id="cancel-btn">cancel</button>
|
||||||
<button class="btn btn-primary" id="save-btn">${state.loading ? '<span class="spinner"></span>' : 'save'}</button>
|
<button class="btn btn-primary" id="save-btn">${state.loading ? '<span class="spinner"></span>' : 'save'}</button>
|
||||||
@@ -284,26 +284,28 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
|
|||||||
};
|
};
|
||||||
wireSectionsEditor(app, sectionsDraft, rerender);
|
wireSectionsEditor(app, sectionsDraft, rerender);
|
||||||
|
|
||||||
const wireDisclosure = (): void => {
|
if (isInTab()) {
|
||||||
wireAttachmentsDisclosure(app, {
|
const wireDisclosure = (): void => {
|
||||||
itemId: existing?.id ?? '',
|
wireAttachmentsDisclosure(app, {
|
||||||
attachments: attachmentsDraft,
|
itemId: existing?.id ?? '',
|
||||||
mode: 'edit',
|
attachments: attachmentsDraft,
|
||||||
onChange: (next) => {
|
mode: 'edit',
|
||||||
attachmentsDraft = next;
|
onChange: (next) => {
|
||||||
const disc = app.querySelector('.attachments-disclosure');
|
attachmentsDraft = next;
|
||||||
if (disc) {
|
const disc = app.querySelector('.attachments-disclosure');
|
||||||
disc.outerHTML = renderAttachmentsDisclosure({
|
if (disc) {
|
||||||
itemId: existing?.id ?? '',
|
disc.outerHTML = renderAttachmentsDisclosure({
|
||||||
attachments: attachmentsDraft,
|
itemId: existing?.id ?? '',
|
||||||
mode: 'edit',
|
attachments: attachmentsDraft,
|
||||||
});
|
mode: 'edit',
|
||||||
wireDisclosure();
|
});
|
||||||
}
|
wireDisclosure();
|
||||||
},
|
}
|
||||||
});
|
},
|
||||||
};
|
});
|
||||||
wireDisclosure();
|
};
|
||||||
|
wireDisclosure();
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('gen-btn')?.addEventListener('click', (e) => {
|
document.getElementById('gen-btn')?.addEventListener('click', (e) => {
|
||||||
const trigger = e.currentTarget as HTMLElement;
|
const trigger = e.currentTarget as HTMLElement;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/// SecureNote: a single multiline body field. Concealed by default in the
|
/// SecureNote: a single multiline body field. Concealed by default in the
|
||||||
/// detail view; the form is just a big <textarea>.
|
/// detail view; the form is just a big <textarea>.
|
||||||
|
|
||||||
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab } from '../../popup';
|
import { getState, setState, sendMessage, navigate, escapeHtml, popOutToTab, isInTab } from '../../popup';
|
||||||
import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types';
|
import type { Item, ItemId, ManifestEntry, Section, AttachmentRef } from '../../../shared/types';
|
||||||
import {
|
import {
|
||||||
renderConcealedRow, renderSignatureBlock, wireFieldHandlers, renderSections,
|
renderConcealedRow, renderSignatureBlock, wireFieldHandlers, renderSections,
|
||||||
@@ -122,7 +122,7 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
|
|||||||
<div class="form-group"><label class="label" for="f-body">body</label>
|
<div class="form-group"><label class="label" for="f-body">body</label>
|
||||||
<textarea id="f-body" rows="10" placeholder="paste secrets here">${escapeHtml(body)}</textarea></div>
|
<textarea id="f-body" rows="10" placeholder="paste secrets here">${escapeHtml(body)}</textarea></div>
|
||||||
${renderSectionsEditor(sectionsDraft, sectionsExpanded)}
|
${renderSectionsEditor(sectionsDraft, sectionsExpanded)}
|
||||||
${renderAttachmentsDisclosure({ itemId: existing?.id ?? '', attachments: attachmentsDraft, mode: 'edit' })}
|
${isInTab() ? renderAttachmentsDisclosure({ itemId: existing?.id ?? '', attachments: attachmentsDraft, mode: 'edit' }) : ''}
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button class="btn" id="cancel-btn">cancel</button>
|
<button class="btn" id="cancel-btn">cancel</button>
|
||||||
<button class="btn btn-primary" id="save-btn">save</button>
|
<button class="btn btn-primary" id="save-btn">save</button>
|
||||||
@@ -139,26 +139,28 @@ export function renderForm(app: HTMLElement, mode: 'add' | 'edit', existing: Ite
|
|||||||
};
|
};
|
||||||
wireSectionsEditor(app, sectionsDraft, rerender);
|
wireSectionsEditor(app, sectionsDraft, rerender);
|
||||||
|
|
||||||
const wireDisclosure = (): void => {
|
if (isInTab()) {
|
||||||
wireAttachmentsDisclosure(app, {
|
const wireDisclosure = (): void => {
|
||||||
itemId: existing?.id ?? '',
|
wireAttachmentsDisclosure(app, {
|
||||||
attachments: attachmentsDraft,
|
itemId: existing?.id ?? '',
|
||||||
mode: 'edit',
|
attachments: attachmentsDraft,
|
||||||
onChange: (next) => {
|
mode: 'edit',
|
||||||
attachmentsDraft = next;
|
onChange: (next) => {
|
||||||
const disc = app.querySelector('.attachments-disclosure');
|
attachmentsDraft = next;
|
||||||
if (disc) {
|
const disc = app.querySelector('.attachments-disclosure');
|
||||||
disc.outerHTML = renderAttachmentsDisclosure({
|
if (disc) {
|
||||||
itemId: existing?.id ?? '',
|
disc.outerHTML = renderAttachmentsDisclosure({
|
||||||
attachments: attachmentsDraft,
|
itemId: existing?.id ?? '',
|
||||||
mode: 'edit',
|
attachments: attachmentsDraft,
|
||||||
});
|
mode: 'edit',
|
||||||
wireDisclosure();
|
});
|
||||||
}
|
wireDisclosure();
|
||||||
},
|
}
|
||||||
});
|
},
|
||||||
};
|
});
|
||||||
wireDisclosure();
|
};
|
||||||
|
wireDisclosure();
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('cancel-btn')?.addEventListener('click', () => {
|
document.getElementById('cancel-btn')?.addEventListener('click', () => {
|
||||||
setState({ error: null });
|
setState({ error: null });
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ export function escapeHtml(str: string): string {
|
|||||||
|
|
||||||
// --- Pop out to tab ---
|
// --- Pop out to tab ---
|
||||||
|
|
||||||
|
export function isInTab(): boolean {
|
||||||
|
return window.location.search.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
export function popOutToTab(): void {
|
export function popOutToTab(): void {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|||||||
Reference in New Issue
Block a user