Fix icons, export working
|
|
@ -1,3 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:#ccc;">
|
||||
<path d="M22.5 38V25.5H10v-3h12.5V10h3v12.5H38v3H25.5V38Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 183 B |
|
|
@ -1,3 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:#ccc;">
|
||||
<path d="m12.45 37.65-2.1-2.1L21.9 24 10.35 12.45l2.1-2.1L24 21.9l11.55-11.55 2.1 2.1L26.1 24l11.55 11.55-2.1 2.1L24 26.1Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 248 B After Width: | Height: | Size: 248 B |
|
|
@ -1,3 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:#ccc;">
|
||||
<path d="M13.05 42q-1.2 0-2.1-.9-.9-.9-.9-2.1V10.5H8v-3h9.4V6h13.2v1.5H40v3h-2.05V39q0 1.2-.9 2.1-.9.9-2.1.9Zm21.9-31.5h-21.9V39h21.9Zm-16.6 24.2h3V14.75h-3Zm8.3 0h3V14.75h-3Zm-13.6-24.2V39Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 316 B |
|
|
@ -1,3 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:#ccc;">
|
||||
<path d="M18.9 35.7 7.7 24.5l2.15-2.15 9.05 9.05 19.2-19.2 2.15 2.15Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 195 B After Width: | Height: | Size: 195 B |
|
|
@ -1,3 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:#ccc;">
|
||||
<path d="M9 39h2.2l22.15-22.15-2.2-2.2L9 36.8Zm30.7-24.3-6.4-6.4 2.1-2.1q.85-.85 2.1-.85t2.1.85l2.2 2.2q.85.85.85 2.1t-.85 2.1Zm-2.1 2.1L12.4 42H6v-6.4l25.2-25.2Zm-5.35-1.05-1.1-1.1 2.2 2.2Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 316 B |
3
icons/export.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:#ccc;">
|
||||
<path d="M11 40q-1.2 0-2.1-.9Q8 38.2 8 37v-7.15h3V37h26v-7.15h3V37q0 1.2-.9 2.1-.9.9-2.1.9Zm13-7.65-9.65-9.65 2.15-2.15 6 6V8h3v18.55l6-6 2.15 2.15Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 274 B |
3
icons/import.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:#ccc;">
|
||||
<path d="M11 40q-1.2 0-2.1-.9Q8 38.2 8 37v-7.15h3V37h26v-7.15h3V37q0 1.2-.9 2.1-.9.9-2.1.9Zm11.5-7.65V13.8l-6 6-2.15-2.15L24 8l9.65 9.65-2.15 2.15-6-6v18.55Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 283 B |
|
|
@ -1,3 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:#ccc;">
|
||||
<path d="M9 42q-1.2 0-2.1-.9Q6 40.2 6 39V9q0-1.2.9-2.1Q7.8 6 9 6h14.55v3H9v30h14.55v3Zm24.3-9.25-2.15-2.15 5.1-5.1h-17.5v-3h17.4l-5.1-5.1 2.15-2.15 8.8 8.8Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 282 B |
|
|
@ -1,3 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0 0 48 48" style="stroke:#ccc;fill:#ccc;">
|
||||
<path d="M24 31.5q3.55 0 6.025-2.475Q32.5 26.55 32.5 23q0-3.55-2.475-6.025Q27.55 14.5 24 14.5q-3.55 0-6.025 2.475Q15.5 19.45 15.5 23q0 3.55 2.475 6.025Q20.45 31.5 24 31.5Zm0-2.9q-2.35 0-3.975-1.625T18.4 23q0-2.35 1.625-3.975T24 17.4q2.35 0 3.975 1.625T29.6 23q0 2.35-1.625 3.975T24 28.6Zm0 9.4q-7.3 0-13.2-4.15Q4.9 29.7 2 23q2.9-6.7 8.8-10.85Q16.7 8 24 8q7.3 0 13.2 4.15Q43.1 16.3 46 23q-2.9 6.7-8.8 10.85Q31.3 38 24 38Zm0-15Zm0 12q6.05 0 11.125-3.275T42.85 23q-2.65-5.45-7.725-8.725Q30.05 11 24 11t-11.125 3.275Q7.8 17.55 5.1 23q2.7 5.45 7.775 8.725Q17.95 35 24 35Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 692 B After Width: | Height: | Size: 692 B |
21
popup.css
|
|
@ -60,7 +60,8 @@ h2 {
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.entry .header h2, .entry .header input {
|
||||
.entry .header h2,
|
||||
.entry .header input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
|
|
@ -89,8 +90,13 @@ h2 {
|
|||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
#entry-list .entry input {
|
||||
font-size: 1.2em;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
|
@ -115,6 +121,7 @@ h2 {
|
|||
|
||||
#entry-add-container {
|
||||
position: relative;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
#entry-form {
|
||||
|
|
@ -151,3 +158,13 @@ h2 {
|
|||
#password-form label {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#store-action {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#store-action img {
|
||||
height: 2em;
|
||||
cursor: pointer;
|
||||
}
|
||||
10
popup.html
|
|
@ -10,14 +10,14 @@
|
|||
<form id="password-form">
|
||||
<label for="master-password">Password</label>
|
||||
<div class="password-container">
|
||||
<input type="password" id="master-password" />
|
||||
<input type="password" id="master-password" autofocus />
|
||||
<img id="master-visibility" class="icon visibility" src="/icons/visibility.svg" />
|
||||
</div>
|
||||
<button>Open</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="entry add hidden" id="entry-add-container">
|
||||
<img id="logout" class="icon" src="/icons/logout.svg">
|
||||
<img id="logout" class="icon" src="/icons/logout.svg" title="Logout">
|
||||
<form id="entry-form">
|
||||
<div>
|
||||
<label for="entry-name">Name</label>
|
||||
|
|
@ -33,7 +33,11 @@
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="entry-list"></div>
|
||||
<div id="entry-list">
|
||||
<div id="store-action" class="hidden">
|
||||
<img id="export" src="/icons/export.svg" title="Export" />
|
||||
</div>
|
||||
</div>
|
||||
<script src="/popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
23
popup.js
|
|
@ -1,10 +1,11 @@
|
|||
const body_elt = document.getElementsByTagName('body')[0]
|
||||
const password_container_elt = document.querySelector('#password-container')
|
||||
const password_form_elt = document.querySelector('#password-form')
|
||||
const master_password_elt = document.querySelector('#master-password')
|
||||
const master_visibility_elt = document.querySelector('#master-visibility')
|
||||
const entry_add_container_elt = document.querySelector('#entry-add-container')
|
||||
const logout_elt = document.querySelector('#logout')
|
||||
const store_action_elt = document.querySelector('#store-action')
|
||||
const export_elt = document.querySelector('#export')
|
||||
const entry_form_elt = document.querySelector('#entry-form')
|
||||
const entry_name_elt = document.querySelector('#entry-name')
|
||||
const entry_secret_elt = document.querySelector('#entry-secret')
|
||||
|
|
@ -135,11 +136,11 @@ class Store
|
|||
if(this.key === null)
|
||||
throw `Cannot save store without key`
|
||||
let encoder = new TextEncoder();
|
||||
const entries = this.entries.map(entry => {return {name: entry.name, secret: entry.secret}})
|
||||
const encrypted = await window.crypto.subtle.encrypt(
|
||||
{name: 'AES-GCM', iv: this.iv},
|
||||
this.key,
|
||||
encoder.encode(JSON.stringify(entries)))
|
||||
encoder.encode(JSON.stringify(
|
||||
this.entries.map(entry => {return {name: entry.name, secret: entry.secret}}))))
|
||||
await browser.storage.local.set({
|
||||
iv: b32encode(this.iv),
|
||||
salt: b32encode(this.salt),
|
||||
|
|
@ -169,6 +170,18 @@ class Store
|
|||
this.entries = []
|
||||
this.key = null
|
||||
}
|
||||
|
||||
export()
|
||||
{
|
||||
let export_elt = document.createElement('a')
|
||||
export_elt.setAttribute('href', 'data:application/json,' + JSON.stringify(
|
||||
this.entries.map(entry => {return {name: entry.name, secret: entry.secret}})))
|
||||
export_elt.setAttribute('download', 'uotp_export.json')
|
||||
export_elt.style.display = 'none'
|
||||
document.body.appendChild(export_elt)
|
||||
export_elt.click()
|
||||
document.body.removeChild(export_elt)
|
||||
}
|
||||
}
|
||||
|
||||
let store = new Store()
|
||||
|
|
@ -304,6 +317,7 @@ class Entry
|
|||
|
||||
done_elt.addEventListener('click', () => {
|
||||
this.name = name_input_elt.value
|
||||
this.elements.title.textContent = this.name
|
||||
this.secret = secret_input_elt.value
|
||||
this.regenerateKey().then(() => { this.refresh(Math.floor(Date.now() / 30000)) })
|
||||
store.save()
|
||||
|
|
@ -340,6 +354,7 @@ async function addEntry(event)
|
|||
function start()
|
||||
{
|
||||
password_container_elt.classList.add('hidden')
|
||||
store_action_elt.classList.remove('hidden')
|
||||
entry_add_container_elt.classList.remove('hidden')
|
||||
const now = Date.now()
|
||||
if(refresh_timeout !== null)
|
||||
|
|
@ -353,6 +368,7 @@ function start()
|
|||
|
||||
function stop()
|
||||
{
|
||||
store_action_elt.classList.add('hidden')
|
||||
entry_add_container_elt.classList.add('hidden')
|
||||
password_container_elt.classList.remove('hidden')
|
||||
if(refresh_timeout !== null)
|
||||
|
|
@ -376,6 +392,7 @@ async function init()
|
|||
background_port.postMessage({command: 'logout', password: master_password_elt.value})
|
||||
stop()
|
||||
})
|
||||
export_elt.addEventListener('click', () => { store.export() })
|
||||
|
||||
background_port.postMessage({command: 'init'})
|
||||
background_port.onMessage.addListener((state) => {
|
||||
|
|
|
|||