Files
crank/apps/ui/js/settings.js
T
github-ops d072d142ca
Deploy / deploy (push) Successful in 1m36s
CI / Rust Checks (push) Successful in 4m50s
CI / UI Checks (push) Successful in 5s
CI / Deployment Manifests (push) Successful in 2s
CI / Frontend E2E (push) Failing after 5m5s
Polish community UI copy and cleanup
2026-06-19 21:15:02 +00:00

345 lines
11 KiB
JavaScript

var settingsSession = null;
var settingsCapabilities = null;
function tKey(key) {
return typeof t === 'function' ? t(key) : key;
}
function tfKey(key, vars) {
return typeof tf === 'function' ? tf(key, vars) : key;
}
function settingsWorkspaceId() {
var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null;
return workspace ? workspace.id : null;
}
function initials(displayName, email) {
var source = safeIdentityText(displayName, 80, '') || safeIdentityText(email, 254, '') || 'Crank';
return source
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map(function(part) { return part.charAt(0).toUpperCase(); })
.join('') || 'CR';
}
function safeIdentityText(value, maxLength, fallback) {
var text = String(value || '')
.replace(/[\r\n\t]+/g, ' ')
.replace(/\s{2,}/g, ' ')
.trim();
if (!text || /<\s*(?:!doctype|html|head|body|nav|script)\b/i.test(text)) {
return fallback || '';
}
if (text.length > maxLength) {
return fallback || '';
}
return text;
}
function setStatus(id, text, isError) {
var node = document.getElementById(id);
if (!node) return;
node.textContent = text || '';
node.style.color = text
? (isError ? 'var(--red)' : 'var(--green)')
: 'var(--text-muted)';
}
function titleCaseRole(role) {
var normalized = String(role || 'viewer').toLowerCase();
if (normalized === 'operator') return tKey('workspace_setup.role.operator');
if (normalized === 'owner') return tKey('workspace_setup.role.owner');
if (normalized === 'admin') return tKey('workspace_setup.role.admin');
if (normalized === 'viewer') return tKey('workspace_setup.role.viewer');
return normalized.replace(/^\w/, function(character) {
return character.toUpperCase();
});
}
function populateCurrentSession(session) {
var summary = document.getElementById('settings-session-summary');
if (!summary) {
return;
}
if (!(session && session.user)) {
summary.textContent = tKey('settings.session.unavailable');
return;
}
var membership = null;
if (Array.isArray(session.memberships)) {
membership = session.memberships.find(function(item) {
return item.workspace && item.workspace.id === session.current_workspace_id;
}) || session.memberships[0] || null;
}
var workspaceLabel = membership && membership.workspace
? (membership.workspace.display_name || membership.workspace.slug || membership.workspace.id)
: tKey('settings.session.current_workspace');
var roleLabel = membership ? titleCaseRole(membership.role) : tKey('workspace_setup.role.viewer');
summary.textContent = tfKey('settings.session.summary', {
email: session.user.email,
role: roleLabel,
workspace: workspaceLabel,
});
}
function populateProfile(session) {
if (!session || !session.user) {
return;
}
var user = session.user;
var displayName = safeIdentityText(user.display_name, 80, 'Crank');
var userEmail = safeIdentityText(user.email, 254, '');
document.getElementById('profile-avatar').textContent = initials(displayName, userEmail);
document.getElementById('profile-display-name').textContent = displayName || 'Crank';
document.getElementById('profile-email').textContent = userEmail;
var firstName = document.getElementById('field-firstname');
var email = document.getElementById('field-email');
if (firstName) {
firstName.value = displayName === 'Crank' ? '' : displayName;
}
if (email) {
email.value = userEmail;
}
populateCurrentSession(session);
}
async function loadProfile() {
if (!window.CrankAuth || !window.CrankApi) {
return;
}
settingsSession = await window.CrankAuth.fetchSession(true);
populateProfile(settingsSession);
}
async function loadCapabilities() {
if (!window.CrankApi || typeof window.CrankApi.getCapabilities !== 'function') {
return;
}
try {
settingsCapabilities = await window.CrankApi.getCapabilities();
} catch (_error) {
}
}
async function renderOverlaySlots() {
if (!window.CrankOverlay || typeof window.CrankOverlay.render !== 'function') {
return;
}
await (window.whenWorkspacesReady ? window.whenWorkspacesReady() : Promise.resolve());
await window.CrankOverlay.render(document, {
workspace: window.getCurrentWorkspace ? window.getCurrentWorkspace() : null,
capabilities: settingsCapabilities,
locale: localStorage.getItem('crank_lang') || 'en',
});
}
function bindProfileSave() {
var button = document.getElementById('settings-profile-save-btn');
if (!button || button.dataset.bound === 'true') {
return;
}
button.dataset.bound = 'true';
button.addEventListener('click', async function() {
var original = button.textContent;
button.disabled = true;
button.textContent = tKey('settings.profile.saving');
setStatus('settings-profile-status', '', false);
try {
var session = await window.CrankApi.updateProfile({
display_name: document.getElementById('field-firstname').value.trim(),
email: document.getElementById('field-email').value.trim(),
});
settingsSession = session;
if (window.CrankAuth && typeof window.CrankAuth.replaceSession === 'function') {
window.CrankAuth.replaceSession(session);
}
populateProfile(session);
setStatus('settings-profile-status', tKey('settings.profile.saved_status'), false);
button.textContent = tKey('settings.profile.saved');
} catch (error) {
setStatus('settings-profile-status', error.message || tKey('settings.profile.save_error'), true);
button.textContent = original;
} finally {
setTimeout(function() {
button.disabled = false;
button.textContent = original;
}, 1200);
}
});
}
function bindPasswordSave() {
var button = document.getElementById('settings-password-save-btn');
if (!button || button.dataset.bound === 'true') {
return;
}
button.dataset.bound = 'true';
button.addEventListener('click', async function() {
var currentPassword = document.getElementById('security-current-password').value;
var newPassword = document.getElementById('security-new-password').value;
var confirmPassword = document.getElementById('security-confirm-password').value;
if (newPassword !== confirmPassword) {
setStatus('settings-password-status', tKey('settings.security.mismatch'), true);
return;
}
var original = button.textContent;
button.disabled = true;
button.textContent = tKey('settings.profile.saving');
setStatus('settings-password-status', '', false);
try {
await window.CrankApi.changePassword({
current_password: currentPassword,
new_password: newPassword,
});
document.getElementById('security-current-password').value = '';
document.getElementById('security-new-password').value = '';
document.getElementById('security-confirm-password').value = '';
setStatus('settings-password-status', tKey('settings.security.saved'), false);
button.textContent = tKey('settings.profile.saved');
} catch (error) {
setStatus('settings-password-status', error.message || tKey('settings.security.save_error'), true);
button.textContent = original;
} finally {
setTimeout(function() {
button.disabled = false;
button.textContent = original;
}, 1200);
}
});
}
function bindSectionNavigation() {
var navItems = document.querySelectorAll('.settings-nav-item[data-section]');
navItems.forEach(function (button) {
button.addEventListener('click', function () {
navItems.forEach(function (item) { item.classList.remove('active'); });
this.classList.add('active');
var target = document.getElementById('section-' + this.dataset.section);
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
});
var initialSection = window.location.hash ? window.location.hash.slice(1) : 'profile';
var initialButton = document.querySelector('.settings-nav-item[data-section="' + initialSection + '"]')
|| document.querySelector('.settings-nav-item[data-section="profile"]');
if (initialButton) {
setTimeout(function () { initialButton.click(); }, 50);
}
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) {
return;
}
var section = entry.target.id.replace('section-', '');
document.querySelectorAll('.settings-nav-item[data-section]').forEach(function (button) {
button.classList.toggle('active', button.dataset.section === section);
});
});
}, { threshold: 0.3 });
document.querySelectorAll('[id^="section-"]').forEach(function (section) {
if (!section.hidden) {
observer.observe(section);
}
});
}
async function loadWorkspaceSettings() {
if (!window.CrankApi || !window.whenWorkspacesReady) {
return;
}
await window.whenWorkspacesReady();
var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null;
if (!workspace) {
return;
}
try {
var record = await window.CrankApi.getWorkspace(workspace.id);
var item = record.workspace;
var settings = item.settings || {};
document.getElementById('settings-ws-slug').value = item.slug || '';
document.getElementById('settings-ws-display-name').value = item.display_name || '';
document.getElementById('settings-ws-description').value = settings.description || '';
var saveButton = document.getElementById('settings-ws-save-btn');
if (saveButton.dataset.bound === 'true') {
return;
}
saveButton.dataset.bound = 'true';
saveButton.addEventListener('click', async function () {
var saveButton = this;
var original = saveButton.textContent;
saveButton.disabled = true;
saveButton.textContent = tKey('settings.workspace.saving');
try {
await window.CrankApi.updateWorkspace(item.id, {
slug: document.getElementById('settings-ws-slug').value.trim(),
display_name: document.getElementById('settings-ws-display-name').value.trim(),
settings: {
description: document.getElementById('settings-ws-description').value.trim(),
color: (workspace.settings && workspace.settings.color) || '#0d9488',
},
});
if (window.refreshWorkspaces) {
var workspaces = await window.refreshWorkspaces();
var updated = workspaces.find(function (entry) { return entry.id === item.id; });
if (updated && window.setCurrentWorkspace) {
await window.setCurrentWorkspace(updated);
}
}
saveButton.textContent = tKey('settings.workspace.saved');
} catch (error) {
if (window.CrankUi) {
window.CrankUi.error(error.message || tKey('settings.workspace.save_error'), tKey('settings.workspace.save_error_title'));
}
saveButton.textContent = original;
} finally {
setTimeout(function () {
saveButton.disabled = false;
saveButton.textContent = original;
}, 1200);
}
});
} catch (_error) {
}
}
async function initSettingsPage() {
bindSectionNavigation();
await loadProfile();
await loadCapabilities();
bindProfileSave();
bindPasswordSave();
await loadWorkspaceSettings();
await renderOverlaySlots();
}
document.addEventListener('DOMContentLoaded', function () {
void initSettingsPage();
});