Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions app/assets/javascripts/mobile_menu.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,42 @@
// Mobile Menu Toggle Functionality
var MobileMenu = {
init: function() {
console.log('MobileMenu: Initializing...');
this.bindEvents();
},

toggleMenu: function() {
console.log('MobileMenu: Toggle clicked!');
var mobileMenu = document.getElementById('mobile-menu');

if (mobileMenu) {
if (mobileMenu.classList.contains('show')) {
mobileMenu.classList.remove('show');
console.log('MobileMenu: Menu closed');
} else {
mobileMenu.classList.add('show');
console.log('MobileMenu: Menu opened');
}
} else {
console.log('MobileMenu: WARNING - Mobile menu element not found!');
}
},

closeMenu: function() {
var mobileMenu = document.getElementById('mobile-menu');
if (mobileMenu) {
mobileMenu.classList.remove('show');
console.log('MobileMenu: Menu closed');
}
},

bindEvents: function() {
var self = this;
var toggleBtn = document.getElementById('mobile-menu-toggle');

console.log('MobileMenu: Toggle button found?', !!toggleBtn);

if (toggleBtn) {
toggleBtn.onclick = function(e) {
e.preventDefault();
self.toggleMenu();
return false;
};
console.log('MobileMenu: Click handler attached');
} else {
console.log('MobileMenu: WARNING - Mobile menu toggle button not found!');
}

// Close menu when clicking on a link
var mobileMenuLinks = document.querySelectorAll('.mobile-menu-items a, .mobile-menu-user a, .mobile-menu-signin a');
console.log('MobileMenu: Found', mobileMenuLinks.length, 'menu links');

for (var i = 0; i < mobileMenuLinks.length; i++) {
mobileMenuLinks[i].onclick = function() {
Expand All @@ -69,18 +56,15 @@ var MobileMenu = {
// Initialize on DOM ready
if (typeof $ !== 'undefined') {
$(document).on('page:change', function() {
console.log('MobileMenu: page:change event fired');
MobileMenu.init();
});

$(document).ready(function() {
console.log('MobileMenu: document.ready event fired');
MobileMenu.init();
});
} else {
// Fallback if jQuery is not available
document.addEventListener('DOMContentLoaded', function() {
console.log('MobileMenu: DOMContentLoaded event fired');
MobileMenu.init();
});
}
148 changes: 128 additions & 20 deletions app/assets/javascripts/theme_toggle.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,134 @@
// Theme Toggle Functionality
var ThemeToggle = {
COOKIE_NAME: 'theme_preference',
COOKIE_DAYS: 365,

init: function() {
console.log('ThemeToggle: Initializing...');
var savedTheme = this.getSavedTheme();
console.log('ThemeToggle: Saved theme is', savedTheme);
this.applyTheme(savedTheme);
this.bindEvents();
var self = this;
this.isAuthenticated = this.checkAuthentication();

if (this.isAuthenticated) {
this.loadServerThemePreference(function(theme) {
self.applyTheme(theme);
self.bindEvents();
});
} else {
var savedTheme = this.getSavedTheme();
this.applyTheme(savedTheme);
this.bindEvents();
}
},

checkAuthentication: function() {
var metaTag = document.querySelector('meta[name="current-user"]');
return metaTag && metaTag.getAttribute('content');
},

getCurrentUserId: function() {
var metaTag = document.querySelector('meta[name="current-user"]');
return metaTag ? metaTag.getAttribute('content') : null;
},

loadServerThemePreference: function(callback) {
var userId = this.getCurrentUserId();
if (!userId) {
callback(this.getSystemTheme());
return;
}

var self = this;
fetch('/accounts/' + userId + '/theme_preference.json', {
method: 'GET',
credentials: 'same-origin',
headers: {
'Accept': 'application/json'
}
})
.then(function(response) {
if (!response.ok) {
return callback(self.getSystemTheme());
}
return response.json();
})
.then(function(data) {
if (data && data.theme_preference) {
callback(data.theme_preference);
} else {
callback(self.getSystemTheme());
}
})
.catch(function(error) {
callback(self.getSystemTheme());
});
},

saveServerThemePreference: function(theme) {
if (!this.isAuthenticated) {
return;
}

var userId = this.getCurrentUserId();
if (!userId) {
return;
}

fetch('/accounts/' + userId + '/set_theme_preference', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': this.getCsrfToken()
},
body: JSON.stringify({ theme: theme })
})
.then(function(response) {
return response.json();
})
.catch(function(error) {
// Silent fail - cookie already set
});
},

getCsrfToken: function() {
var token = document.querySelector('meta[name="csrf-token"]');
return token ? token.getAttribute('content') : '';
},

getCookie: function(name) {
var nameEQ = name + '=';
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.indexOf(nameEQ) === 0) {
return cookie.substring(nameEQ.length);
}
}
return null;
},

setCookie: function(name, value, days) {
var expires = '';
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = '; expires=' + date.toUTCString();
}
document.cookie = name + '=' + value + expires + '; path=/; SameSite=Lax';
},

getSystemTheme: function() {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
},

getSavedTheme: function() {
try {
return localStorage.getItem('theme') || 'light';
} catch (e) {
return 'light';
var cookieTheme = this.getCookie(this.COOKIE_NAME);
if (cookieTheme && (cookieTheme === 'light' || cookieTheme === 'dark')) {
return cookieTheme;
}
return this.getSystemTheme();
},

applyTheme: function(theme) {
Expand All @@ -31,11 +146,7 @@ var ThemeToggle = {
if (sunIcon) sunIcon.classList.add('hidden');
}

try {
localStorage.setItem('theme', theme);
} catch (e) {
console.log('Could not save theme preference');
}
this.setCookie(this.COOKIE_NAME, theme, this.COOKIE_DAYS);

if (typeof Charts !== 'undefined') {
Charts.updateWatermarks(theme === 'dark');
Expand All @@ -46,24 +157,21 @@ var ThemeToggle = {
var currentTheme = this.getSavedTheme();
var newTheme = currentTheme === 'light' ? 'dark' : 'light';
this.applyTheme(newTheme);
if (this.isAuthenticated) {
this.saveServerThemePreference(newTheme);
}
},

bindEvents: function() {
var self = this;
var themeToggleBtn = document.getElementById('theme-toggle');

console.log('ThemeToggle: Theme toggle button found?', !!themeToggleBtn);

if (themeToggleBtn) {
themeToggleBtn.onclick = function(e) {
e.preventDefault();
console.log('ThemeToggle: Toggle clicked!');
self.toggleTheme();
return false;
};
console.log('ThemeToggle: Click handler attached');
} else {
console.log('ThemeToggle: WARNING - Theme toggle button not found!');
}
}
};
Expand Down
22 changes: 19 additions & 3 deletions app/controllers/accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ class AccountsController < ApplicationController
helper MapHelper

skip_before_action :store_location, only: %i[new create]
before_action :session_required, only: %i[edit destroy confirm_delete me]
before_action :set_account, only: %i[destroy show update edit confirm_delete disabled settings]
before_action :session_required,
only: %i[edit destroy confirm_delete me theme_preference set_theme_preference]
before_action :set_account,
only: %i[destroy show update edit confirm_delete disabled settings theme_preference
set_theme_preference]
Comment thread
Niharika1117 marked this conversation as resolved.
before_action :redirect_if_disabled, only: %i[show update edit]
before_action :redirect_unverified_account, only: %i[edit update me]
before_action :disabled_during_read_only_mode, only: %i[edit update]
before_action :account_context, only: %i[edit update confirm_delete]
before_action :must_own_account, only: %i[edit update destroy confirm_delete]
before_action :must_own_account, only: %i[edit update destroy confirm_delete theme_preference set_theme_preference]
before_action :find_claimed_people, only: :index
before_action :redirect_if_logged_in, only: :new
before_action :check_honeypot, only: :create
Expand Down Expand Up @@ -91,6 +94,19 @@ def disabled; end

def settings; end

def theme_preference
respond_to do |format|
format.json { render json: { theme_preference: @account.theme_preference } }
end
end

def set_theme_preference
@account.theme_preference = params[:theme]
render json: { success: true, theme: @account.theme_preference }
rescue ArgumentError
render json: { error: 'Invalid theme preference' }, status: :unprocessable_entity
end

private

def find_claimed_people
Expand Down
13 changes: 13 additions & 0 deletions app/models/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,19 @@ def project_core
@project_core ||= Account::ProjectCore.new(self)
end

def theme_preference
setting = Setting.find_by(key: "account_#{id}_theme_preference")
setting&.value
end

def theme_preference=(theme)
if theme == 'dark'
Setting.find_or_create_by(key: "account_#{id}_theme_preference").update(value: 'dark')
else
Setting.find_by(key: "account_#{id}_theme_preference")&.destroy
end
end

class << self
def resolve_login(login)
Account.find_by('lower(login) = ?', login.to_s.downcase)
Expand Down
27 changes: 26 additions & 1 deletion app/views/layouts/application.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,30 @@
%html
%head
:javascript
try { if (localStorage.getItem('theme') === 'dark') document.documentElement.classList.add('dark'); } catch(e) {}
(function() {
var COOKIE_NAME = 'theme_preference';
var getCookie = function(name) {
var nameEQ = name + '=';
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.indexOf(nameEQ) === 0) {
return cookie.substring(nameEQ.length);
}
}
return null;
};
var cookieTheme = getCookie(COOKIE_NAME);
var theme = 'light';
if (cookieTheme && (cookieTheme === 'light' || cookieTheme === 'dark')) {
theme = cookieTheme;
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
theme = 'dark';
}
if (theme === 'dark') {
document.documentElement.classList.add('dark');
}
})();
- if Rails.env.production?
= render partial: 'layouts/tracking_scripts/google_analytics'
- page_title = content_for?(:html_title) ? yield(:html_title).to_s : t('.openhub')
Expand All @@ -17,6 +40,8 @@
= yield :custom_head
= stylesheet_link_tag 'application', media: 'all'
= csrf_meta_tags
- unless current_user.nil?
%meta{ name: 'current-user', content: current_user.id }

%body{ zoom: 1 }
= yield :session_projects_banner
Expand Down
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@
get :confirm_delete
get :disabled
get :settings
get :theme_preference, defaults: { format: :json }
post :set_theme_preference
get 'alter_password/edit', to: 'alter_passwords#edit'
patch 'alter_password/edit', to: 'alter_passwords#update'
get :edit_privacy, to: 'privacy#edit', as: :edit_account_privacy
Expand Down
Loading
Loading