feat(v1.4.0): exports + smart year level + progress + encrypted passwords
v1.4.0: CSV/Excel/JSON/NextUp exports, units-first smart year standing, live export progress bar, AES-GCM at-rest password encryption.
This commit was merged in pull request #4.
This commit is contained in:
@@ -25,10 +25,11 @@ $('#masterToggle').addEventListener('change', async (e) => {
|
||||
|
||||
// ---- PDF settings: truncate long course descriptions (default ON) ----
|
||||
async function loadPdfSettings() {
|
||||
const s = await browser.storage.local.get(['truncateDescription', 'showYearLevel', 'customYearLevel']);
|
||||
const s = await browser.storage.local.get(['truncateDescription', 'showYearLevel', 'customYearLevel', 'yearMode']);
|
||||
$('#truncateToggle').checked = s.truncateDescription !== false;
|
||||
$('#showYearToggle').checked = s.showYearLevel !== false;
|
||||
$('#customYear').value = s.customYearLevel || '';
|
||||
$('#yearMode').value = s.yearMode || 'auto';
|
||||
}
|
||||
$('#truncateToggle').addEventListener('change', async (e) => {
|
||||
await browser.storage.local.set({ truncateDescription: e.target.checked });
|
||||
@@ -36,6 +37,9 @@ $('#truncateToggle').addEventListener('change', async (e) => {
|
||||
$('#showYearToggle').addEventListener('change', async (e) => {
|
||||
await browser.storage.local.set({ showYearLevel: e.target.checked });
|
||||
});
|
||||
$('#yearMode').addEventListener('change', async (e) => {
|
||||
await browser.storage.local.set({ yearMode: e.target.value });
|
||||
});
|
||||
$('#customYear').addEventListener('change', async (e) => {
|
||||
await browser.storage.local.set({ customYearLevel: e.target.value.trim() });
|
||||
});
|
||||
@@ -96,27 +100,52 @@ function openProfileForm(p) {
|
||||
editingProfileId = p ? p.id : null;
|
||||
$('#pName').value = p ? p.name : '';
|
||||
$('#pUsername').value = p ? p.username : '';
|
||||
$('#pPassword').value = p ? p.password : '';
|
||||
// The stored password is encrypted and not revealed; leave the field blank.
|
||||
// On save, a blank password preserves the existing one.
|
||||
$('#pPassword').value = '';
|
||||
$('#pEnabled').checked = p ? !!p.enabled : true;
|
||||
$('#profileForm').hidden = false;
|
||||
if (p) $('#pPassword').placeholder = 'Leave blank to keep existing password';
|
||||
else $('#pPassword').placeholder = 'Password';
|
||||
}
|
||||
$('#addProfile').addEventListener('click', () => openProfileForm(null));
|
||||
$('#cancelProfile').addEventListener('click', () => { $('#profileForm').hidden = true; });
|
||||
$('#saveProfile').addEventListener('click', async () => {
|
||||
const profiles = await getProfiles();
|
||||
const data = {
|
||||
id: editingProfileId || Date.now().toString(),
|
||||
name: $('#pName').value.trim(),
|
||||
username: $('#pUsername').value.trim(),
|
||||
password: $('#pPassword').value,
|
||||
enabled: $('#pEnabled').checked
|
||||
};
|
||||
if (!data.name || !data.username) return;
|
||||
const name = $('#pName').value.trim();
|
||||
const username = $('#pUsername').value.trim();
|
||||
const typedPassword = $('#pPassword').value;
|
||||
const enabled = $('#pEnabled').checked;
|
||||
if (!name || !username) return;
|
||||
|
||||
// Encrypt a newly-typed password via the background.
|
||||
let passwordEnc = null;
|
||||
if (typedPassword) {
|
||||
const res = await browser.runtime.sendMessage({ type: 'encrypt-password', password: typedPassword });
|
||||
if (!res || !res.ok) { alert('Could not encrypt password: ' + ((res && res.error) || 'unknown error')); return; }
|
||||
passwordEnc = res.cipher;
|
||||
}
|
||||
|
||||
if (editingProfileId) {
|
||||
const i = profiles.findIndex((x) => x.id === editingProfileId);
|
||||
if (i >= 0) profiles[i] = data;
|
||||
if (i >= 0) {
|
||||
profiles[i].name = name;
|
||||
profiles[i].username = username;
|
||||
profiles[i].enabled = enabled;
|
||||
// Only replace the password if a new one was typed; otherwise keep the
|
||||
// existing (already encrypted) one.
|
||||
if (passwordEnc) profiles[i].passwordEnc = passwordEnc;
|
||||
delete profiles[i].password; // legacy plaintext no longer needed
|
||||
}
|
||||
} else {
|
||||
profiles.push(data);
|
||||
const profile = {
|
||||
id: Date.now().toString(),
|
||||
name,
|
||||
username,
|
||||
enabled,
|
||||
passwordEnc
|
||||
};
|
||||
profiles.push(profile);
|
||||
}
|
||||
await saveProfiles(profiles);
|
||||
$('#profileForm').hidden = true;
|
||||
|
||||
Reference in New Issue
Block a user