Skip to content

Commit 573eb65

Browse files
committed
Fix clipped header padding; add browser copy-to-clipboard button
1 parent a8e0402 commit 573eb65

1 file changed

Lines changed: 43 additions & 7 deletions

File tree

app.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""
1616

1717
import os
18+
import json
1819
import time
1920
from html import escape
2021

@@ -38,6 +39,7 @@
3839
os.environ.setdefault(_k, _v)
3940

4041
import streamlit as st
42+
import streamlit.components.v1 as components
4143

4244
# Import the REAL logic. Nothing below reimplements it.
4345
from PasswordGenerator import (
@@ -65,7 +67,7 @@
6567
"""
6668
<style>
6769
.stApp { background: #0d1117; }
68-
.block-container { max-width: 760px; padding-top: 2.2rem; }
70+
.block-container { max-width: 760px; padding-top: 3.5rem; }
6971
h1, h2, h3 { color: #e6edf3; letter-spacing: -0.01em; }
7072
p, label, .stMarkdown { color: #adbac7; }
7173
.tl-tag {
@@ -172,12 +174,46 @@
172174
res = st.session_state.last_pw
173175
if res:
174176
st.markdown("### Result")
175-
display = res["password"] if show_pw else "•" * len(res["password"])
176-
# Escape before injecting: generated passwords contain HTML-special chars
177-
# like < > & " which would otherwise break the markup (InvalidCharacterError).
178-
st.markdown(f'<div class="tl-pw">{escape(display)}</div>', unsafe_allow_html=True)
179-
st.markdown('<div class="tl-note">Triple-click to select · nothing is stored server-side</div>',
180-
unsafe_allow_html=True)
177+
# Shown value is masked when "Show password" is off, but the Copy button
178+
# always copies the real password. Escape the shown text (passwords contain
179+
# < > & ") and pass the real value to JS safely via json.dumps.
180+
shown = escape(res["password"]) if show_pw else "•" * len(res["password"])
181+
pw_js = json.dumps(res["password"])
182+
# Two lines max for a 64-char password at this font size.
183+
box_height = 150 if len(res["password"]) > 40 else 120
184+
components.html(
185+
f"""
186+
<div style="font-family:ui-monospace,SFMono-Regular,Menlo,monospace;">
187+
<div style="display:flex; gap:10px; align-items:stretch;">
188+
<div style="flex:1; color:#e6edf3; background:#161b22; border:1px solid #30363d;
189+
border-radius:10px; padding:16px 18px; font-size:22px; line-height:1.5;
190+
word-break:break-all; user-select:all;">{shown}</div>
191+
<button id="cp" style="flex:0 0 auto; cursor:pointer; color:#fff; background:#238636;
192+
border:0; border-radius:10px; padding:0 18px; font:600 14px ui-monospace,monospace;">
193+
Copy
194+
</button>
195+
</div>
196+
<div style="font:12px ui-monospace,monospace; color:#768390; margin-top:8px;">
197+
Copy button works whether or not the password is shown · nothing is stored server-side
198+
</div>
199+
</div>
200+
<script>
201+
const pw = {pw_js};
202+
const btn = document.getElementById('cp');
203+
btn.addEventListener('click', async () => {{
204+
try {{ await navigator.clipboard.writeText(pw); }}
205+
catch (e) {{
206+
const ta = document.createElement('textarea');
207+
ta.value = pw; document.body.appendChild(ta); ta.select();
208+
document.execCommand('copy'); ta.remove();
209+
}}
210+
btn.textContent = 'Copied ✓'; btn.style.background = '#2ea043';
211+
setTimeout(() => {{ btn.textContent = 'Copy'; btn.style.background = '#238636'; }}, 1500);
212+
}});
213+
</script>
214+
""",
215+
height=box_height,
216+
)
181217

182218
m1, m2, m3 = st.columns(3)
183219
m1.markdown(

0 commit comments

Comments
 (0)