diff --git a/src/App.css b/src/App.css index 1cc2757..3fb8941 100644 --- a/src/App.css +++ b/src/App.css @@ -2910,6 +2910,16 @@ font-size: 0.9rem; } +.import-row--actionable { + cursor: pointer; +} + +.import-row--actionable:hover, +.import-row--actionable:focus-visible { + border-color: rgba(86, 132, 243, 0.32); + box-shadow: 0 0 0 2px rgba(86, 132, 243, 0.08); +} + .import-row--invalid { background: rgba(229, 107, 85, 0.06); border-color: rgba(229, 107, 85, 0.22); @@ -2931,6 +2941,7 @@ display: flex; flex-direction: column; gap: 2px; + flex: 1; min-width: 0; word-break: break-all; } @@ -2946,6 +2957,33 @@ font-size: 0.82rem; } +.import-row__remove { + width: 24px; + height: 24px; + border: 1px solid var(--panel-outline, rgba(0, 0, 0, 0.08)); + border-radius: 999px; + background: var(--panel); + color: var(--muted); + cursor: pointer; + flex-shrink: 0; + font: inherit; + font-size: 0.8rem; + line-height: 1; + opacity: 0; + transition: opacity 0.15s ease, color 0.15s ease, border-color 0.15s ease; +} + +.import-row:hover .import-row__remove, +.import-row:focus-within .import-row__remove { + opacity: 1; +} + +.import-row__remove:hover, +.import-row__remove:focus-visible { + border-color: rgba(229, 107, 85, 0.5); + color: #e56b55; +} + @keyframes vault-modal-in { from { opacity: 0; diff --git a/src/components/VaultImportModal.js b/src/components/VaultImportModal.js index af5be8f..06bcfb5 100644 --- a/src/components/VaultImportModal.js +++ b/src/components/VaultImportModal.js @@ -76,6 +76,16 @@ function rowStatusClass(row) { return 'is-negative'; } +function lineBounds(text, lineNo) { + const lines = String(text || '').split('\n'); + if (lineNo < 1 || lineNo > lines.length) return null; + let start = 0; + for (let i = 0; i < lineNo - 1; i += 1) { + start += lines[i].length + 1; + } + return { start, end: start + lines[lineNo - 1].length }; +} + export default function VaultImportModal({ open, onClose }) { const vault = useVault(); const { user } = useAuth(); @@ -117,6 +127,39 @@ export default function VaultImportModal({ open, onClose }) { [vault.data] ); + const selectPasteLine = useCallback( + (lineNo) => { + const bounds = lineBounds(paste, lineNo); + if (!bounds || !pasteRef.current) return; + const textarea = pasteRef.current; + textarea.focus(); + textarea.setSelectionRange(bounds.start, bounds.end); + + const lineHeight = + parseFloat(window.getComputedStyle(textarea).lineHeight) || 20; + textarea.scrollTop = Math.max( + 0, + (lineNo - 1) * lineHeight - textarea.clientHeight / 2 + ); + }, + [paste] + ); + + const removePasteLine = useCallback( + (lineNo) => { + const lines = paste.split('\n'); + if (lineNo < 1 || lineNo > lines.length) return; + lines.splice(lineNo - 1, 1); + const nextPaste = lines.join('\n'); + applyPastePreview(nextPaste); + setPaste(nextPaste); + setTimeout(() => { + if (pasteRef.current) pasteRef.current.focus(); + }, 0); + }, + [applyPastePreview, paste] + ); + const canSave = !saving && !validating && @@ -343,36 +386,72 @@ export default function VaultImportModal({ open, onClose }) { ) : null}
{r.address}
- {r.label ? (
-
- {r.label}
-
- ) : null}
- >
- ) : (
-
- Line {r.lineNo}
- {r.label ? ` — ${r.label}` : ''}
-
- )}
-
- {r.address}
+ {r.label ? (
+
+ {r.label}
+
+ ) : null}
+ >
+ ) : (
+
+ Line {r.lineNo}
+ {r.label ? ` — ${r.label}` : ''}
+
+ )}
+
+ {canEditSource ? (
+
+ ) : null}
+
+ );
+ })}