Fix code quality violations and exclude Manifest from checks

Document application modes (development/debug/production)
Add global file drop handler, order column normalization, SPA hash fix
Serve CDN assets via /_vendor/ URLs instead of merging into bundles
Add production minification with license preservation
Improve JSON formatting for debugging and production optimization
Add CDN asset caching with CSS URL inlining for production builds
Add three-mode system (development, debug, production)
Update Manifest CLAUDE.md to reflect helper class architecture
Refactor Manifest.php into helper classes for better organization
Pre-manifest-refactor checkpoint: Add app_mode documentation

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-01-14 10:38:22 +00:00
parent bb9046af1b
commit d523f0f600
2355 changed files with 231384 additions and 32223 deletions

View File

@@ -23,6 +23,10 @@
*
* If conditions fail, falls back to flash alert.
* If layout display throws, falls back to flash alert.
*
* Error Hints:
* Common error patterns are detected and helpful hints are appended:
* - "this.$ is not a function" / "that.$ is not a function" → suggest this.$.find()
*/
class Exception_Handler {
@@ -51,6 +55,39 @@ class Exception_Handler {
Exception_Handler.display_unhandled_exception(exception, meta);
});
// Patch console.error to add helpful hints for common JQHTML errors
Exception_Handler._patch_console_error();
}
/**
* Patch console.error to detect common error patterns and add helpful hints
* This helps developers quickly understand common mistakes like:
* - Using this.$ or that.$ as a function (should use this.$.find())
*/
static _patch_console_error() {
const original_console_error = console.error.bind(console);
console.error = function(...args) {
// Call original console.error first
original_console_error(...args);
// Check if this looks like a JQHTML callback error
if (args.length >= 2 && typeof args[0] === 'string' && args[0].includes('[JQHTML]')) {
const error = args[1];
const error_message = error instanceof Error ? error.message : String(error);
// Check for common "this.$ is not a function" or "that.$ is not a function" error
if (error_message.includes('this.$ is not a function') ||
error_message.includes('that.$ is not a function')) {
original_console_error(
'%c[Hint] Did you mean this.$.find() or that.$.find()? ' +
'The component element (this.$) is a jQuery object, not a function.',
'color: #0dcaf0; font-style: italic;'
);
}
}
};
}
/**

View File

@@ -15,6 +15,7 @@ class Rsx_Behaviors {
static _on_framework_core_init() {
Rsx_Behaviors._init_ignore_invalid_anchor_links();
Rsx_Behaviors._trim_copied_text();
Rsx_Behaviors._init_file_drop_handler();
}
/**
@@ -105,4 +106,164 @@ class Rsx_Behaviors {
}
});
}
/**
* Global file drop handler
*
* Intercepts all file drag/drop operations and routes them to components
* marked with the `rsx-droppable` class. Components receive a `file-drop`
* event with the dropped files.
*
* CSS Classes:
* - `rsx-droppable` - Marks element as a valid drop target
* - `rsx-drop-active` - Added to all droppables during file drag
* - `rsx-drop-target` - Added to the specific element that will receive drop
*
* Behavior:
* - Single visible droppable: auto-becomes target
* - Multiple visible droppables: must hover over specific element
* - No-drop cursor when not over valid target
* - Widgets handle their own file type filtering
*/
static _init_file_drop_handler() {
let drag_counter = 0;
let current_target = null;
// Get all visible droppable elements
const get_visible_droppables = () => {
return $('.rsx-droppable').filter(function() {
const $el = $(this);
// Must be visible and not hidden by CSS
return $el.is(':visible') && $el.css('visibility') !== 'hidden';
});
};
// Check if event contains files
const has_files = (e) => {
if (e.originalEvent && e.originalEvent.dataTransfer) {
const types = e.originalEvent.dataTransfer.types;
return types && (types.includes('Files') || types.indexOf('Files') >= 0);
}
return false;
};
// Update which element is the current target
const update_target = ($new_target) => {
if (current_target) {
$(current_target).removeClass('rsx-drop-target');
}
current_target = $new_target ? $new_target[0] : null;
if (current_target) {
$(current_target).addClass('rsx-drop-target');
}
};
// Clear all drag state
const clear_drag_state = () => {
drag_counter = 0;
$('.rsx-drop-active').removeClass('rsx-drop-active');
update_target(null);
};
// dragenter - file enters the window
$(document).on('dragenter', function(e) {
if (!has_files(e)) return;
drag_counter++;
if (drag_counter === 1) {
// First entry - activate all droppables
const $droppables = get_visible_droppables();
$droppables.addClass('rsx-drop-active');
// If only one droppable, auto-target it
if ($droppables.length === 1) {
update_target($droppables.first());
}
}
});
// dragleave - file leaves an element
$(document).on('dragleave', function(e) {
if (!has_files(e)) return;
drag_counter--;
if (drag_counter <= 0) {
clear_drag_state();
}
});
// dragover - file is over an element (fires continuously)
$(document).on('dragover', function(e) {
if (!has_files(e)) return;
e.preventDefault(); // Required to allow drop
const $droppables = get_visible_droppables();
if ($droppables.length === 0) {
// No drop targets - show no-drop cursor
e.originalEvent.dataTransfer.dropEffect = 'none';
return;
}
if ($droppables.length === 1) {
// Single target - already set, allow copy
e.originalEvent.dataTransfer.dropEffect = 'copy';
return;
}
// Multiple targets - find if we're over one
const $hovered = $(e.target).closest('.rsx-droppable');
if ($hovered.length && $hovered.hasClass('rsx-drop-active')) {
// Over a valid droppable
update_target($hovered);
e.originalEvent.dataTransfer.dropEffect = 'copy';
} else {
// Not over any droppable - show no-drop cursor
update_target(null);
e.originalEvent.dataTransfer.dropEffect = 'none';
}
});
// drop - file is dropped
$(document).on('drop', function(e) {
if (!has_files(e)) return;
e.preventDefault();
e.stopPropagation();
const files = e.originalEvent.dataTransfer.files;
if (current_target && files.length > 0) {
// Trigger file-drop event on the component
const $target = $(current_target);
const component = $target.component();
if (component) {
component.trigger('file-drop', {
files: files,
dataTransfer: e.originalEvent.dataTransfer,
originalEvent: e.originalEvent
});
} else {
// No component - trigger jQuery event on element directly
$target.trigger('file-drop', {
files: files,
dataTransfer: e.originalEvent.dataTransfer,
originalEvent: e.originalEvent
});
}
}
clear_drag_state();
});
// Handle drag end (e.g., user presses Escape)
$(document).on('dragend', function(e) {
clear_drag_state();
});
}
}