Files
rspade_system/app/RSpade/Core/Js/browser.js
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 02:08:33 +00:00

198 lines
6.7 KiB
JavaScript
Executable File

/*
* Browser and DOM utility functions for the RSpade framework.
* These functions handle browser detection, viewport utilities, and DOM manipulation.
*/
// ============================================================================
// BROWSER DETECTION
// ============================================================================
/**
* Detects if user is on a mobile device or using mobile viewport
* @returns {boolean} True if mobile device or viewport < 992px
* @todo Improve user agent detection for all mobile devices
*/
function is_mobile() {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
return true;
} else if ($(window).width() < 992) {
// 992px = bootstrap 4 col-md-
return true;
} else {
return false;
}
}
/**
* Detects if user is on desktop (not mobile)
* @returns {boolean} True if not mobile device/viewport
*/
function is_desktop() {
return !is_mobile();
}
/**
* Detects the user's operating system
* @returns {string} OS name: 'Mac OS', 'iPhone', 'iPad', 'Windows', 'Android-Phone', 'Android-Tablet', 'Linux', or 'Unknown'
*/
function get_os() {
let user_agent = window.navigator.userAgent,
platform = window.navigator.platform,
macos_platforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'],
windows_platforms = ['Win32', 'Win64', 'Windows', 'WinCE'],
ios_platforms = ['iPhone', 'iPad', 'iPod'],
os = null;
let is_mobile_device = is_mobile();
if (macos_platforms.indexOf(platform) !== -1) {
os = 'Mac OS';
} else if (ios_platforms.indexOf(platform) !== -1 && is_mobile_device) {
os = 'iPhone';
} else if (ios_platforms.indexOf(platform) !== -1 && !is_mobile_device) {
os = 'iPad';
} else if (windows_platforms.indexOf(platform) !== -1) {
os = 'Windows';
} else if (/Android/.test(user_agent) && is_mobile_device) {
os = 'Android-Phone';
} else if (/Android/.test(user_agent) && !is_mobile_device) {
os = 'Android-Tablet';
} else if (!os && /Linux/.test(platform)) {
os = 'Linux';
} else {
os = 'Unknown';
}
return os;
}
/**
* Detects if the user agent is a web crawler/bot
* @returns {boolean} True if user agent appears to be a bot/crawler
*/
function is_crawler() {
let user_agent = navigator.userAgent;
let bot_pattern = /bot|spider|crawl|slurp|archiver|ping|search|dig|tracker|monitor|snoopy|yahoo|baidu|msn|ask|teoma|axios/i;
return bot_pattern.test(user_agent);
}
// ============================================================================
// DOM SCROLLING UTILITIES
// ============================================================================
/**
* Scrolls parent container to make target element visible if needed
* @param {string|HTMLElement|jQuery} target - Target element to scroll into view
*/
function scroll_into_view_if_needed(target) {
const $target = $(target);
// Find the closest parent with overflow-y: auto
const $parent = $target.parent();
// Calculate the absolute top position of the target
const target_top = $target.position().top + $parent.scrollTop();
const target_height = $target.outerHeight();
const parent_height = $parent.height();
const scroll_position = $parent.scrollTop();
// Check if the target is out of view
if (target_top < scroll_position || target_top + target_height > scroll_position + parent_height) {
Debugger.console_debug('UI', 'Scrolling!', target_top);
// Calculate the new scroll position to center the target
let new_scroll_position = target_top + target_height / 2 - parent_height / 2;
// Limit the scroll position between 0 and the maximum scrollable height
new_scroll_position = Math.max(0, Math.min(new_scroll_position, $parent[0].scrollHeight - parent_height));
// Scroll the parent to the new scroll position
$parent.scrollTop(new_scroll_position);
}
}
/**
* Scrolls page to make target element visible if needed (with animation)
* @param {string|HTMLElement|jQuery} target - Target element to scroll into view
*/
function scroll_page_into_view_if_needed(target) {
const $target = $(target);
// Calculate the absolute top position of the target relative to the document
const target_top = $target.offset().top;
const target_height = $target.outerHeight();
const window_height = $(window).height();
const window_scroll_position = $(window).scrollTop();
// Check if the target is out of view
if (target_top < window_scroll_position || target_top + target_height > window_scroll_position + window_height) {
Debugger.console_debug('UI', 'Scrolling!', target_top);
// Calculate the new scroll position to center the target
const new_scroll_position = target_top + target_height / 2 - window_height / 2;
// Animate the scroll to the new position
$('html, body').animate(
{
scrollTop: new_scroll_position,
},
1000
); // duration of the scroll animation in milliseconds
}
}
// ============================================================================
// DOM UTILITIES
// ============================================================================
/**
* Waits for all images on the page to load
* @param {Function} callback - Function to call when all images are loaded
*/
function wait_for_images(callback) {
const $images = $('img'); // Get all img tags
const total_images = $images.length;
let images_loaded = 0;
if (total_images === 0) {
callback(); // if there are no images, immediately call the callback
}
$images.each(function () {
const img = new Image();
img.onload = function () {
images_loaded++;
if (images_loaded === total_images) {
callback(); // call the callback when all images are loaded
}
};
img.onerror = function () {
images_loaded++;
if (images_loaded === total_images) {
callback(); // also call the callback if an image fails to load
}
};
img.src = this.src; // this triggers the loading
});
}
/**
* Creates a jQuery element containing a non-breaking space
* @returns {jQuery} jQuery span element with &nbsp;
*/
function $nbsp() {
return $('<span>&nbsp;</span>');
}
/**
* Escapes special characters in a jQuery selector
* @param {string} id - Element ID to escape
* @returns {string} jQuery selector string with escaped special characters
* @warning Not safe for security-critical operations
*/
function escape_jq_selector(id) {
return '#' + id.replace(/(:|\.|\[|\]|,|=|@)/g, '\\$1');
}