$(document).ready(function () { var nextCtl = $('#vars').attr('nextCtl'); var c = $('button.accordion-button[data-bs-target="#ctl' + nextCtl + '"]'); $(c).click(); document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function(el) { new bootstrap.Tooltip(el); }); $('.cal-grid-cell').click(function() { var ctl = $(this).data('ctl'); var btn = $('button.accordion-button[data-bs-target="#ctl' + ctl + '"]'); if (btn.hasClass('collapsed')) { btn.click(); } btn.closest('.accordion-item')[0].scrollIntoView({ behavior: 'smooth', block: 'start' }); }); $('button[action]').click(function() { var action = $(this).attr('action'); var url = null; var target = action; if ( action == 'jitsi' ) { url = 'https://meet.homelabbrisbane.com.au/hlb'; } if ( action == 'map' ) { url = 'https://www.google.com/maps/place/' + $(this).attr('coordinates'); } if ( action == 'copy' ) { navigator.clipboard.writeText( $(this).attr('value') ); } if ( url != null ) { window.open( url, target ); } }); $('.unlock-pin').click(async function() { const btn = $(this); const pinDisplay = btn.siblings('.pin-display'); const ciphertext = btn.data('ciphertext'); const iv = btn.data('iv'); const salt = btn.data('salt'); const tag = btn.data('tag'); const password = prompt("Enter the members password to unlock the PIN:"); if (!password) return; try { const decrypted = await decryptPin(ciphertext, iv, salt, tag, password); pinDisplay.val(decrypted); btn.text('Copy').removeClass('unlock-pin').attr('action', 'copy').attr('value', decrypted); // Re-bind the click event for the new copy action (or use delegation) btn.off('click').click(function() { navigator.clipboard.writeText($(this).attr('value')); alert('PIN copied to clipboard!'); }); } catch (e) { alert(e.message); } }); async function decryptPin(ciphertextBase64, ivBase64, saltBase64, tagBase64, password) { const encoder = new TextEncoder(); const passwordKey = await crypto.subtle.importKey( "raw", encoder.encode(password), "PBKDF2", false, ["deriveKey"] ); const salt = Uint8Array.from(atob(saltBase64), c => c.charCodeAt(0)); const iv = Uint8Array.from(atob(ivBase64), c => c.charCodeAt(0)); const tag = Uint8Array.from(atob(tagBase64), c => c.charCodeAt(0)); const ciphertext = Uint8Array.from(atob(ciphertextBase64), c => c.charCodeAt(0)); const data = new Uint8Array(ciphertext.length + tag.length); data.set(ciphertext); data.set(tag, ciphertext.length); const key = await crypto.subtle.deriveKey( { name: "PBKDF2", salt, iterations: 100000, hash: "SHA-256" }, passwordKey, { name: "AES-GCM", length: 256 }, false, ["decrypt"] ); try { const decrypted = await crypto.subtle.decrypt( { name: "AES-GCM", iv, tagLength: 128 }, key, data ); return new TextDecoder().decode(decrypted); } catch (e) { throw new Error("Decryption failed. Probably wrong password."); } } var isPhone = /Mobi|Android|iPhone|iPod/i.test(navigator.userAgent) || window.innerWidth <= 768; if (isPhone) { $("body").addClass("phone"); } });