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>
This commit is contained in:
root
2025-10-21 02:08:33 +00:00
commit f6fac6c4bc
79758 changed files with 10547827 additions and 0 deletions

View File

@@ -0,0 +1,365 @@
<?php
namespace App\RSpade\Integrations\Jqhtml;
/**
* Custom Blade precompiler for jqhtml components
*
* Transforms uppercase component tags into jqhtml component calls.
* Example: <User_Card name="John" /> becomes Jqhtml::component('User_Card', ['name' => 'John'])
*/
class JqhtmlBladeCompiler
{
/**
* Cached list of jqhtml component names from manifest
*/
private static ?array $jqhtml_components = null;
/**
* Get list of jqhtml components from manifest
*/
private static function __get_jqhtml_components(): array
{
if (self::$jqhtml_components === null) {
// Get the cached list of jqhtml components from the manifest support module
self::$jqhtml_components = \App\RSpade\Integrations\Jqhtml\Jqhtml_ManifestSupport::get_jqhtml_components();
}
return self::$jqhtml_components;
}
/**
* Precompile Blade template to transform jqhtml component tags
*
* @param string $value The Blade template content
* @return string Transformed content
*/
public static function precompile(string $value): string
{
// Pattern to match tags that start with uppercase letter
// Matches both self-closing and paired tags
$pattern = '/<([A-Z][a-zA-Z0-9_]*)((?:\s+\$?[a-zA-Z0-9_\-:]+(?:=(?:"[^"]*"|\'[^\']*\'|[^>\s]+))?)*)\s*(?:\/>|>(.*?)<\/\1>)/s';
$value = preg_replace_callback($pattern, function ($matches) {
$component_name = $matches[1];
$attributes = $matches[2] ?? '';
$slot_content = $matches[3] ?? null;
// Parse attributes into array
$parsed_attrs = self::__parse_attributes($attributes);
// Convert to PHP array syntax
$php_array = self::__to_php_array($parsed_attrs);
// If there's slot content, we need to output the div directly to allow blade processing of the content
if ($slot_content !== null && trim($slot_content) !== '') {
// Check for slot syntax - not allowed in Blade
if (preg_match('/<#[a-zA-Z0-9_]+/', $slot_content)) {
throw new \RuntimeException(
"JQHTML slot syntax (<#slotname>) is not allowed in Blade files.\n" .
"Component '{$component_name}' contains slot tags in its innerHTML.\n" .
"Use standard innerHTML with content() function instead.\n\n" .
"Blade usage:\n" .
" <{$component_name}>\n" .
" Your content here\n" .
" </{$component_name}>\n\n" .
"Template definition:\n" .
" <Define:{$component_name}>\n" .
" <%= content() %>\n" .
" </Define:{$component_name}>"
);
}
// Check for block syntax - not allowed in Blade
if (preg_match('/<\/?Block:([a-zA-Z0-9_]+)/i', $slot_content, $block_match)) {
$block_name = $block_match[1] ?? 'Unknown';
throw new \RuntimeException(
"JQHTML block/slot syntax (<Block:{$block_name}>) is not allowed in Blade files.\n" .
"Component '{$component_name}' contains block tags in its innerHTML.\n\n" .
"Block/slot content requires jqhtml template compilation and ONLY works in .jqhtml files.\n" .
"Blade renders components server-side as standard HTML - it cannot pass block content to jqhtml components.\n\n" .
"OPTION 1: Pass data via component arguments (recommended)\n" .
" Instead of using blocks, pass all data as component arguments:\n\n" .
" WRONG (Blade):\n" .
" <{$component_name}>\n" .
" <Block:{$block_name}>\n" .
" Custom content...\n" .
" </Block:{$block_name}>\n" .
" </{$component_name}>\n\n" .
" CORRECT (Blade):\n" .
" <{$component_name} \$custom_content=\"...\" />\n\n" .
" Then handle customization in the component's .jqhtml template file.\n\n" .
"OPTION 2: Move to .jqhtml template file\n" .
" If you need block/slot functionality:\n\n" .
" 1. Create a .jqhtml wrapper component that uses blocks\n" .
" 2. Use the wrapper in Blade as a self-closing tag\n\n" .
"BLADE RULE: Jqhtml components in Blade are ALWAYS self-closing or use standard innerHTML only.\n" .
"Blocks/slots only work in .jqhtml → .jqhtml relationships."
);
}
// Recursively process slot content for nested components
$slot_content = self::precompile($slot_content);
// Separate $-prefixed attributes (component args) from regular attributes
$component_args = [];
$html_attrs = [];
$wrapper_tag = self::__get_default_wrapper_tag($component_name);
foreach ($parsed_attrs as $key => $attr) {
if (str_starts_with($key, '$')) {
// Component arg - remove $ prefix
$arg_key = substr($key, 1);
if ($attr['type'] === 'expression') {
$component_args[$arg_key] = ['type' => 'expression', 'value' => $attr['value']];
} else {
$component_args[$arg_key] = ['type' => 'string', 'value' => $attr['value']];
}
} elseif (str_starts_with($key, 'data-')) {
// Component arg - remove data- prefix
$arg_key = substr($key, 5);
if ($attr['type'] === 'expression') {
$component_args[$arg_key] = ['type' => 'expression', 'value' => $attr['value']];
} else {
$component_args[$arg_key] = ['type' => 'string', 'value' => $attr['value']];
}
} elseif ($key === 'tag') {
// Special case: tag attribute becomes _tag component arg AND sets the wrapper element
if ($attr['type'] === 'expression') {
$component_args['_tag'] = ['type' => 'expression', 'value' => $attr['value']];
} else {
$component_args['_tag'] = ['type' => 'string', 'value' => $attr['value']];
// Use the tag value as the wrapper element (only for string literals, not expressions)
$tag_value = $attr['value'];
if (preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $tag_value)) {
$wrapper_tag = $tag_value;
}
}
} else {
// Regular HTML attribute
$html_attrs[$key] = $attr;
}
}
// Build component args JSON
$json_args = self::__to_php_array($component_args);
if (empty($json_args) || $json_args === '[]') {
$json_args = '[]';
} else {
$json_args = "json_encode({$json_args})";
}
// Build HTML attributes string
// Handle class attribute specially to merge with Jqhtml_Component_Init
$class_value = 'Jqhtml_Component_Init';
if (isset($html_attrs['class'])) {
if ($html_attrs['class']['type'] === 'expression') {
$class_value = "Jqhtml_Component_Init ' . {$html_attrs['class']['value']} . '";
} else {
$class_value = 'Jqhtml_Component_Init ' . $html_attrs['class']['value'];
}
}
$attrs_string = ' class="' . $class_value . '"';
foreach ($html_attrs as $key => $attr) {
if ($key === 'class') {
continue; // Already handled above
}
if ($attr['type'] === 'expression') {
$attrs_string .= ' :' . $key . '="' . htmlspecialchars($attr['value']) . '"';
} elseif ($attr['value'] === true) {
$attrs_string .= ' ' . $key;
} else {
$attrs_string .= ' ' . $key . '="' . htmlspecialchars($attr['value']) . '"';
}
}
// Use {!! !!} not {{ }} because htmlspecialchars is already encoding the value
$args_output = $json_args === '[]' ? '[]' : "{!! htmlspecialchars({$json_args}, ENT_QUOTES, 'UTF-8') !!}";
return sprintf(
'<%s data-component-init-name="%s" data-component-args="%s"%s>%s</%s>',
$wrapper_tag,
$component_name,
$args_output,
$attrs_string,
$slot_content,
$wrapper_tag
);
}
// Generate the wrapper element for self-closing tags
// Separate $-prefixed attributes (component args) from regular attributes
$component_args = [];
$html_attrs = [];
$wrapper_tag = self::__get_default_wrapper_tag($component_name);
foreach ($parsed_attrs as $key => $attr) {
if (str_starts_with($key, '$')) {
// Component arg - remove $ prefix
$arg_key = substr($key, 1);
$component_args[$arg_key] = $attr;
} elseif (str_starts_with($key, 'data-')) {
// Component arg - remove data- prefix
$arg_key = substr($key, 5);
$component_args[$arg_key] = $attr;
} elseif ($key === 'tag') {
// Special case: tag attribute becomes _tag component arg AND sets the wrapper element
$component_args['_tag'] = $attr;
// Use the tag value as the wrapper element (only for string literals, not expressions)
if ($attr['type'] === 'string') {
$tag_value = $attr['value'];
if (preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $tag_value)) {
$wrapper_tag = $tag_value;
}
}
} else {
// Regular HTML attribute
$html_attrs[$key] = $attr;
}
}
// Build component args JSON
$json_args = self::__to_php_array($component_args);
if (empty($json_args) || $json_args === '[]') {
$json_args = '[]';
} else {
$json_args = "json_encode({$json_args})";
}
// Build HTML attributes string
// Handle class attribute specially to merge with Jqhtml_Component_Init
$class_value = 'Jqhtml_Component_Init';
if (isset($html_attrs['class'])) {
if ($html_attrs['class']['type'] === 'expression') {
$class_value = "Jqhtml_Component_Init ' . {$html_attrs['class']['value']} . '";
} else {
$class_value = 'Jqhtml_Component_Init ' . $html_attrs['class']['value'];
}
}
$attrs_string = ' class="' . $class_value . '"';
foreach ($html_attrs as $key => $attr) {
if ($key === 'class') {
continue; // Already handled above
}
if ($attr['type'] === 'expression') {
$attrs_string .= ' :' . $key . '="' . htmlspecialchars($attr['value']) . '"';
} elseif ($attr['value'] === true) {
$attrs_string .= ' ' . $key;
} else {
$attrs_string .= ' ' . $key . '="' . htmlspecialchars($attr['value']) . '"';
}
}
// Use {!! !!} not {{ }} because htmlspecialchars is already encoding the value
$args_output = $json_args === '[]' ? '[]' : "{!! htmlspecialchars({$json_args}, ENT_QUOTES, 'UTF-8') !!}";
return sprintf(
'<%s data-component-init-name="%s" data-component-args="%s"%s></%s>',
$wrapper_tag,
$component_name,
$args_output,
$attrs_string,
$wrapper_tag
);
}, $value);
return $value;
}
/**
* Parse HTML attributes into key-value pairs
*
* @param string $attributes HTML attributes string
* @return array
*/
private static function __parse_attributes(string $attributes): array
{
$parsed = [];
// Match attribute patterns (including $ prefix for literal variable names)
preg_match_all('/(\$?[a-zA-Z0-9_\-:]+)(?:=(?:"([^"]*)"|\'([^\']*)\'|([^>\s]+)))?/', $attributes, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$key = $match[1];
// Handle different attribute value formats
if (isset($match[2]) && $match[2] !== '') {
// Double quoted value
$value = $match[2];
} elseif (isset($match[3]) && $match[3] !== '') {
// Single quoted value
$value = $match[3];
} elseif (isset($match[4]) && $match[4] !== '') {
// Unquoted value
$value = $match[4];
} else {
// Boolean attribute (no value)
$value = true;
}
// Handle Blade expressions (: prefix means it's a PHP expression)
if (str_starts_with($key, ':')) {
$key = substr($key, 1);
// Value is already a PHP expression, keep as is
$parsed[$key] = ['type' => 'expression', 'value' => $value];
} else {
// Regular string value
$parsed[$key] = ['type' => 'string', 'value' => $value];
}
}
return $parsed;
}
/**
* Convert parsed attributes to PHP array syntax
*
* @param array $attrs
* @return string
*/
private static function __to_php_array(array $attrs): string
{
if (empty($attrs)) {
return '[]';
}
$parts = [];
foreach ($attrs as $key => $attr) {
if ($attr['type'] === 'expression') {
// PHP expression, use as is
$parts[] = "'{$key}' => {$attr['value']}";
} elseif ($attr['value'] === true) {
// Boolean true
$parts[] = "'{$key}' => true";
} else {
// String value, escape it
$escaped = addslashes($attr['value']);
$parts[] = "'{$key}' => '{$escaped}'";
}
}
return '[' . implode(', ', $parts) . ']';
}
/**
* Get the default wrapper tag for a component from its template metadata
*
* @param string $component_name Component name
* @return string Default wrapper tag ('div' if not specified in template)
*/
private static function __get_default_wrapper_tag(string $component_name): string
{
$template_metadata = \App\RSpade\Integrations\Jqhtml\Jqhtml::get_jqhtml_template_by_id($component_name);
if ($template_metadata !== null && isset($template_metadata['tag_name'])) {
return $template_metadata['tag_name'];
}
return 'div';
}
}