diff --git a/app/RSpade/Core/Js/Mutex.js b/app/RSpade/Core/Js/Mutex.js index 915db43a8..3504a88a8 100755 --- a/app/RSpade/Core/Js/Mutex.js +++ b/app/RSpade/Core/Js/Mutex.js @@ -9,11 +9,16 @@ * @mutex('operation_name') * async my_method() { ... } * + * Uses the 2023-11 decorator proposal (Stage 3). + * Decorator receives (value, context) where: + * - value: the original method function + * - context: { kind, name, static, private, access, addInitializer } + * * @decorator * @param {string} [global_id] - Optional global mutex ID for cross-instance locking */ -function mutex(global_id) { - // Storage (using IIFEs to keep WeakMap/Map in closure scope) +function mutex(arg) { + // Storage for mutex locks const instance_mutexes = (function() { if (!mutex._instance_storage) { mutex._instance_storage = new WeakMap(); @@ -89,39 +94,48 @@ function mutex(global_id) { }); } - // If called with an ID argument: @mutex('id') - if (typeof global_id === 'string') { - return function(target, key, descriptor) { - const original_method = descriptor.value; - - if (typeof original_method !== 'function') { - throw new Error(`@mutex can only be applied to methods (tried to apply to ${key})`); + /** + * Create the wrapper method for a given original method + * @param {Function} original_method - The original method + * @param {string} method_name - The method name + * @param {string|null} global_id - Global mutex ID (null for instance-level) + */ + function create_mutex_wrapper(original_method, method_name, global_id) { + return function(...args) { + let lock_state; + if (global_id) { + lock_state = get_global_mutex(global_id); + } else { + lock_state = get_instance_mutex(this, method_name); } - - descriptor.value = function(...args) { - const lock_state = get_global_mutex(global_id); - return acquire_lock(lock_state, () => original_method.apply(this, args)); - }; - - return descriptor; + return acquire_lock(lock_state, () => original_method.apply(this, args)); }; } - // If called without arguments: @mutex (target is the first argument) - const target = global_id; // In this case, first arg is target - const key = arguments[1]; - const descriptor = arguments[2]; + // 2023-11 decorator spec: decorators receive (value, context) + // If called with a string argument: @mutex('id') - returns decorator function + // If called directly on method: @mutex - arg is the method function, second arg is context - const original_method = descriptor.value; - - if (typeof original_method !== 'function') { - throw new Error(`@mutex can only be applied to methods (tried to apply to ${key})`); + if (typeof arg === 'string') { + // Called with ID: @mutex('operation_name') + // Returns a decorator function + const global_id = arg; + return function(value, context) { + if (context.kind !== 'method') { + throw new Error(`@mutex can only be applied to methods, not ${context.kind}`); + } + return create_mutex_wrapper(value, context.name, global_id); + }; } - descriptor.value = function(...args) { - const lock_state = get_instance_mutex(this, key); - return acquire_lock(lock_state, () => original_method.apply(this, args)); - }; + // Called without arguments: @mutex + // In 2023-11 spec, arg is the method function, second argument is context + const value = arg; + const context = arguments[1]; - return descriptor; + if (!context || context.kind !== 'method') { + throw new Error(`@mutex can only be applied to methods`); + } + + return create_mutex_wrapper(value, context.name, null); } diff --git a/app/RSpade/Lib/Flash/Flash_Alert.js b/app/RSpade/Lib/Flash/Flash_Alert.js index b65058fa0..08c4a5bca 100755 --- a/app/RSpade/Lib/Flash/Flash_Alert.js +++ b/app/RSpade/Lib/Flash/Flash_Alert.js @@ -14,7 +14,6 @@ * Features: * - Queue system prevents alert spam (2.5s minimum between alerts) * - Auto-dismiss after timeout (success: 4s, others: 6s) - * - Click to dismiss immediately * - Smooth fade in/out animations * - Bootstrap alert styling with icons * - Persistent queue across page navigations (sessionStorage, tab-specific) @@ -99,7 +98,7 @@ class Flash_Alert { // Process each visible alert this._container.find('.alert-wrapper').each((index, wrapper) => { const $wrapper = $(wrapper); - const message_text = $wrapper.find('.alert').text().trim().replace(/×$/, '').trim(); + const message_text = $wrapper.find('.alert').text().trim(); // Find this message in persistence queue to check fade_in_complete const msg = this._persistence_queue.find(m => m.message === message_text); @@ -476,9 +475,7 @@ class Flash_Alert { // Check if an alert with the same message is already displayed let duplicate_found = false; this._container.find('.alert-wrapper').each(function () { - const existing_text = $(this).find('.alert').text().trim(); - // Remove the close button text (×) for comparison - const existing_message = existing_text.replace(/×$/, '').trim(); + const existing_message = $(this).find('.alert').text().trim(); if (existing_message === message) { duplicate_found = true; return false; // Break loop @@ -491,8 +488,8 @@ class Flash_Alert { return; } - // Create alert element - const $alert = $(`