diff --git a/app/Models/Plugin.php b/app/Models/Plugin.php index bc46559..5eeeb6b 100644 --- a/app/Models/Plugin.php +++ b/app/Models/Plugin.php @@ -60,8 +60,14 @@ class Plugin extends Model }); static::updating(function ($model): void { - // Reset image cache when markup changes - if ($model->isDirty('render_markup')) { + // Reset image cache when any markup changes + if ($model->isDirty([ + 'render_markup', + 'render_markup_half_horizontal', + 'render_markup_half_vertical', + 'render_markup_quadrant', + 'render_markup_shared', + ])) { $model->current_image = null; } }); @@ -421,7 +427,9 @@ class Plugin extends Model throw new InvalidArgumentException('Render method is only applicable for recipe plugins.'); } - if ($this->render_markup) { + $markup = $this->getMarkupForSize($size); + + if ($markup) { $renderedContent = ''; if ($this->markup_language === 'liquid') { @@ -471,7 +479,7 @@ class Plugin extends Model // Check if external renderer should be used if ($this->preferred_renderer === 'trmnl-liquid' && config('services.trmnl.liquid_enabled')) { // Use external Ruby renderer - pass raw template without preprocessing - $renderedContent = $this->renderWithExternalLiquidRenderer($this->render_markup, $context); + $renderedContent = $this->renderWithExternalLiquidRenderer($markup, $context); } else { // Use PHP keepsuit/liquid renderer // Create a custom environment with inline templates support @@ -493,14 +501,14 @@ class Plugin extends Model $environment->tagRegistry->register(TemplateTag::class); // Apply Liquid replacements (including 'with' syntax conversion) - $processedMarkup = $this->applyLiquidReplacements($this->render_markup); + $processedMarkup = $this->applyLiquidReplacements($markup); $template = $environment->parseString($processedMarkup); $liquidContext = $environment->newRenderContext(data: $context); $renderedContent = $template->render($liquidContext); } } else { - $renderedContent = Blade::render($this->render_markup, [ + $renderedContent = Blade::render($markup, [ 'size' => $size, 'data' => $this->data_payload, 'config' => $this->configuration ?? [], @@ -581,6 +589,30 @@ class Plugin extends Model return $this->configuration[$key] ?? $default; } + /** + * Get the appropriate markup for a given size, including shared prepending logic + * + * @param string $size The layout size (full, half_horizontal, half_vertical, quadrant) + * @return string|null The markup code for the given size, with shared prepended if available + */ + public function getMarkupForSize(string $size): ?string + { + $markup = match ($size) { + 'full' => $this->render_markup, + 'half_horizontal' => $this->render_markup_half_horizontal ?? $this->render_markup, + 'half_vertical' => $this->render_markup_half_vertical ?? $this->render_markup, + 'quadrant' => $this->render_markup_quadrant ?? $this->render_markup, + default => $this->render_markup, + }; + + // Prepend shared markup if it exists + if ($markup && $this->render_markup_shared) { + $markup = $this->render_markup_shared."\n".$markup; + } + + return $markup; + } + public function getPreviewMashupLayoutForSize(string $size): string { return match ($size) { diff --git a/app/Services/PluginExportService.php b/app/Services/PluginExportService.php index 241764d..be98461 100644 --- a/app/Services/PluginExportService.php +++ b/app/Services/PluginExportService.php @@ -51,17 +51,35 @@ class PluginExportService $settings = $this->generateSettingsYaml($plugin); $settingsYaml = Yaml::dump($settings, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); File::put($tempDir.'/settings.yml', $settingsYaml); - // Generate full template content - $fullTemplate = $this->generateFullTemplate($plugin); + $extension = $plugin->markup_language === 'liquid' ? 'liquid' : 'blade.php'; - File::put($tempDir.'/full.'.$extension, $fullTemplate); - // Generate shared.liquid if needed (for liquid templates) - if ($plugin->markup_language === 'liquid') { - $sharedTemplate = $this->generateSharedTemplate(); - /** @phpstan-ignore-next-line */ - if ($sharedTemplate) { - File::put($tempDir.'/shared.liquid', $sharedTemplate); - } + + // Export full template if it exists + if ($plugin->render_markup) { + $fullTemplate = $this->generateLayoutTemplate($plugin->render_markup); + File::put($tempDir.'/full.'.$extension, $fullTemplate); + } + + // Export layout-specific templates if they exist + if ($plugin->render_markup_half_horizontal) { + $halfHorizontalTemplate = $this->generateLayoutTemplate($plugin->render_markup_half_horizontal); + File::put($tempDir.'/half_horizontal.'.$extension, $halfHorizontalTemplate); + } + + if ($plugin->render_markup_half_vertical) { + $halfVerticalTemplate = $this->generateLayoutTemplate($plugin->render_markup_half_vertical); + File::put($tempDir.'/half_vertical.'.$extension, $halfVerticalTemplate); + } + + if ($plugin->render_markup_quadrant) { + $quadrantTemplate = $this->generateLayoutTemplate($plugin->render_markup_quadrant); + File::put($tempDir.'/quadrant.'.$extension, $quadrantTemplate); + } + + // Export shared template if it exists + if ($plugin->render_markup_shared) { + $sharedTemplate = $this->generateLayoutTemplate($plugin->render_markup_shared); + File::put($tempDir.'/shared.'.$extension, $sharedTemplate); } // Create ZIP file $zipPath = $tempDir.'/plugin_'.$plugin->trmnlp_id.'.zip'; @@ -124,29 +142,21 @@ class PluginExportService } /** - * Generate the full template content + * Generate template content from markup, removing wrapper divs if present */ - private function generateFullTemplate(Plugin $plugin): string + private function generateLayoutTemplate(?string $markup): string { - $markup = $plugin->render_markup; + if (! $markup) { + return ''; + } - // Remove the wrapper div if it exists (it will be added during import) + // Remove the wrapper div if it exists (it will be added during import for liquid) $markup = preg_replace('/^
\s*/', '', $markup); $markup = preg_replace('/\s*<\/div>\s*$/', '', $markup); return mb_trim($markup); } - /** - * Generate the shared template content (for liquid templates) - */ - private function generateSharedTemplate(): null - { - // For now, we don't have a way to store shared templates separately - // TODO - add support for shared templates - return null; - } - /** * Add a directory and its contents to a ZIP file */ diff --git a/app/Services/PluginImportService.php b/app/Services/PluginImportService.php index 51a9aee..f3e7a5c 100644 --- a/app/Services/PluginImportService.php +++ b/app/Services/PluginImportService.php @@ -93,37 +93,59 @@ class PluginImportService $settings = Yaml::parse($settingsYaml); $this->validateYAML($settings); - // Determine which template file to use and read its content - $templatePath = null; + // Determine markup language from the first available file $markupLanguage = 'blade'; + $firstTemplatePath = $filePaths['fullLiquidPath'] + ?? ($filePaths['halfHorizontalLiquidPath'] ?? null) + ?? ($filePaths['halfVerticalLiquidPath'] ?? null) + ?? ($filePaths['quadrantLiquidPath'] ?? null) + ?? ($filePaths['sharedLiquidPath'] ?? null) + ?? ($filePaths['sharedBladePath'] ?? null); - if ($filePaths['fullLiquidPath']) { - $templatePath = $filePaths['fullLiquidPath']; - $fullLiquid = File::get($templatePath); + if ($firstTemplatePath && pathinfo((string) $firstTemplatePath, PATHINFO_EXTENSION) === 'liquid') { + $markupLanguage = 'liquid'; + } - // Prepend shared.liquid or shared.blade.php content if available - if ($filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) { - $sharedLiquid = File::get($filePaths['sharedLiquidPath']); - $fullLiquid = $sharedLiquid."\n".$fullLiquid; - } elseif ($filePaths['sharedBladePath'] && File::exists($filePaths['sharedBladePath'])) { - $sharedBlade = File::get($filePaths['sharedBladePath']); - $fullLiquid = $sharedBlade."\n".$fullLiquid; - } - - // Check if the file ends with .liquid to set markup language - if (pathinfo((string) $templatePath, PATHINFO_EXTENSION) === 'liquid') { - $markupLanguage = 'liquid'; + // Read full markup (don't prepend shared - it will be prepended at render time) + $fullLiquid = null; + if (isset($filePaths['fullLiquidPath']) && $filePaths['fullLiquidPath']) { + $fullLiquid = File::get($filePaths['fullLiquidPath']); + if ($markupLanguage === 'liquid') { $fullLiquid = '
'."\n".$fullLiquid."\n".'
'; } - } elseif ($filePaths['sharedLiquidPath']) { - $templatePath = $filePaths['sharedLiquidPath']; - $fullLiquid = File::get($templatePath); - $markupLanguage = 'liquid'; - $fullLiquid = '
'."\n".$fullLiquid."\n".'
'; - } elseif ($filePaths['sharedBladePath']) { - $templatePath = $filePaths['sharedBladePath']; - $fullLiquid = File::get($templatePath); - $markupLanguage = 'blade'; + } + + // Read shared markup separately + $sharedMarkup = null; + if (isset($filePaths['sharedLiquidPath']) && $filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) { + $sharedMarkup = File::get($filePaths['sharedLiquidPath']); + } elseif (isset($filePaths['sharedBladePath']) && $filePaths['sharedBladePath'] && File::exists($filePaths['sharedBladePath'])) { + $sharedMarkup = File::get($filePaths['sharedBladePath']); + } + + // Read layout-specific markups + $halfHorizontalMarkup = null; + if (isset($filePaths['halfHorizontalLiquidPath']) && $filePaths['halfHorizontalLiquidPath'] && File::exists($filePaths['halfHorizontalLiquidPath'])) { + $halfHorizontalMarkup = File::get($filePaths['halfHorizontalLiquidPath']); + if ($markupLanguage === 'liquid') { + $halfHorizontalMarkup = '
'."\n".$halfHorizontalMarkup."\n".'
'; + } + } + + $halfVerticalMarkup = null; + if (isset($filePaths['halfVerticalLiquidPath']) && $filePaths['halfVerticalLiquidPath'] && File::exists($filePaths['halfVerticalLiquidPath'])) { + $halfVerticalMarkup = File::get($filePaths['halfVerticalLiquidPath']); + if ($markupLanguage === 'liquid') { + $halfVerticalMarkup = '
'."\n".$halfVerticalMarkup."\n".'
'; + } + } + + $quadrantMarkup = null; + if (isset($filePaths['quadrantLiquidPath']) && $filePaths['quadrantLiquidPath'] && File::exists($filePaths['quadrantLiquidPath'])) { + $quadrantMarkup = File::get($filePaths['quadrantLiquidPath']); + if ($markupLanguage === 'liquid') { + $quadrantMarkup = '
'."\n".$quadrantMarkup."\n".'
'; + } } // Ensure custom_fields is properly formatted @@ -160,6 +182,10 @@ class PluginImportService 'polling_body' => $settings['polling_body'] ?? null, 'markup_language' => $markupLanguage, 'render_markup' => $fullLiquid ?? null, + 'render_markup_half_horizontal' => $halfHorizontalMarkup, + 'render_markup_half_vertical' => $halfVerticalMarkup, + 'render_markup_quadrant' => $quadrantMarkup, + 'render_markup_shared' => $sharedMarkup, 'configuration_template' => $configurationTemplate, 'data_payload' => json_decode($settings['static_data'] ?? '{}', true), ]); @@ -246,37 +272,59 @@ class PluginImportService $settings = Yaml::parse($settingsYaml); $this->validateYAML($settings); - // Determine which template file to use and read its content - $templatePath = null; + // Determine markup language from the first available file $markupLanguage = 'blade'; + $firstTemplatePath = $filePaths['fullLiquidPath'] + ?? ($filePaths['halfHorizontalLiquidPath'] ?? null) + ?? ($filePaths['halfVerticalLiquidPath'] ?? null) + ?? ($filePaths['quadrantLiquidPath'] ?? null) + ?? ($filePaths['sharedLiquidPath'] ?? null) + ?? ($filePaths['sharedBladePath'] ?? null); - if ($filePaths['fullLiquidPath']) { - $templatePath = $filePaths['fullLiquidPath']; - $fullLiquid = File::get($templatePath); + if ($firstTemplatePath && pathinfo((string) $firstTemplatePath, PATHINFO_EXTENSION) === 'liquid') { + $markupLanguage = 'liquid'; + } - // Prepend shared.liquid or shared.blade.php content if available - if ($filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) { - $sharedLiquid = File::get($filePaths['sharedLiquidPath']); - $fullLiquid = $sharedLiquid."\n".$fullLiquid; - } elseif ($filePaths['sharedBladePath'] && File::exists($filePaths['sharedBladePath'])) { - $sharedBlade = File::get($filePaths['sharedBladePath']); - $fullLiquid = $sharedBlade."\n".$fullLiquid; - } - - // Check if the file ends with .liquid to set markup language - if (pathinfo((string) $templatePath, PATHINFO_EXTENSION) === 'liquid') { - $markupLanguage = 'liquid'; + // Read full markup (don't prepend shared - it will be prepended at render time) + $fullLiquid = null; + if (isset($filePaths['fullLiquidPath']) && $filePaths['fullLiquidPath']) { + $fullLiquid = File::get($filePaths['fullLiquidPath']); + if ($markupLanguage === 'liquid') { $fullLiquid = '
'."\n".$fullLiquid."\n".'
'; } - } elseif ($filePaths['sharedLiquidPath']) { - $templatePath = $filePaths['sharedLiquidPath']; - $fullLiquid = File::get($templatePath); - $markupLanguage = 'liquid'; - $fullLiquid = '
'."\n".$fullLiquid."\n".'
'; - } elseif ($filePaths['sharedBladePath']) { - $templatePath = $filePaths['sharedBladePath']; - $fullLiquid = File::get($templatePath); - $markupLanguage = 'blade'; + } + + // Read shared markup separately + $sharedMarkup = null; + if (isset($filePaths['sharedLiquidPath']) && $filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) { + $sharedMarkup = File::get($filePaths['sharedLiquidPath']); + } elseif (isset($filePaths['sharedBladePath']) && $filePaths['sharedBladePath'] && File::exists($filePaths['sharedBladePath'])) { + $sharedMarkup = File::get($filePaths['sharedBladePath']); + } + + // Read layout-specific markups + $halfHorizontalMarkup = null; + if (isset($filePaths['halfHorizontalLiquidPath']) && $filePaths['halfHorizontalLiquidPath'] && File::exists($filePaths['halfHorizontalLiquidPath'])) { + $halfHorizontalMarkup = File::get($filePaths['halfHorizontalLiquidPath']); + if ($markupLanguage === 'liquid') { + $halfHorizontalMarkup = '
'."\n".$halfHorizontalMarkup."\n".'
'; + } + } + + $halfVerticalMarkup = null; + if (isset($filePaths['halfVerticalLiquidPath']) && $filePaths['halfVerticalLiquidPath'] && File::exists($filePaths['halfVerticalLiquidPath'])) { + $halfVerticalMarkup = File::get($filePaths['halfVerticalLiquidPath']); + if ($markupLanguage === 'liquid') { + $halfVerticalMarkup = '
'."\n".$halfVerticalMarkup."\n".'
'; + } + } + + $quadrantMarkup = null; + if (isset($filePaths['quadrantLiquidPath']) && $filePaths['quadrantLiquidPath'] && File::exists($filePaths['quadrantLiquidPath'])) { + $quadrantMarkup = File::get($filePaths['quadrantLiquidPath']); + if ($markupLanguage === 'liquid') { + $quadrantMarkup = '
'."\n".$quadrantMarkup."\n".'
'; + } } // Ensure custom_fields is properly formatted @@ -322,6 +370,10 @@ class PluginImportService 'polling_body' => $settings['polling_body'] ?? null, 'markup_language' => $markupLanguage, 'render_markup' => $fullLiquid ?? null, + 'render_markup_half_horizontal' => $halfHorizontalMarkup, + 'render_markup_half_vertical' => $halfVerticalMarkup, + 'render_markup_quadrant' => $quadrantMarkup, + 'render_markup_shared' => $sharedMarkup, 'configuration_template' => $configurationTemplate, 'data_payload' => json_decode($settings['static_data'] ?? '{}', true), 'preferred_renderer' => $preferredRenderer, @@ -357,6 +409,9 @@ class PluginImportService $fullLiquidPath = null; $sharedLiquidPath = null; $sharedBladePath = null; + $halfHorizontalLiquidPath = null; + $halfVerticalLiquidPath = null; + $quadrantLiquidPath = null; // If zipEntryPath is specified, look for files in that specific directory first if ($zipEntryPath) { @@ -377,6 +432,25 @@ class PluginImportService } elseif (File::exists($targetDir.'/shared.blade.php')) { $sharedBladePath = $targetDir.'/shared.blade.php'; } + + // Check for layout-specific files + if (File::exists($targetDir.'/half_horizontal.liquid')) { + $halfHorizontalLiquidPath = $targetDir.'/half_horizontal.liquid'; + } elseif (File::exists($targetDir.'/half_horizontal.blade.php')) { + $halfHorizontalLiquidPath = $targetDir.'/half_horizontal.blade.php'; + } + + if (File::exists($targetDir.'/half_vertical.liquid')) { + $halfVerticalLiquidPath = $targetDir.'/half_vertical.liquid'; + } elseif (File::exists($targetDir.'/half_vertical.blade.php')) { + $halfVerticalLiquidPath = $targetDir.'/half_vertical.blade.php'; + } + + if (File::exists($targetDir.'/quadrant.liquid')) { + $quadrantLiquidPath = $targetDir.'/quadrant.liquid'; + } elseif (File::exists($targetDir.'/quadrant.blade.php')) { + $quadrantLiquidPath = $targetDir.'/quadrant.blade.php'; + } } // Check if files are in src subdirectory of target directory @@ -394,6 +468,25 @@ class PluginImportService } elseif (File::exists($targetDir.'/src/shared.blade.php')) { $sharedBladePath = $targetDir.'/src/shared.blade.php'; } + + // Check for layout-specific files in src + if (File::exists($targetDir.'/src/half_horizontal.liquid')) { + $halfHorizontalLiquidPath = $targetDir.'/src/half_horizontal.liquid'; + } elseif (File::exists($targetDir.'/src/half_horizontal.blade.php')) { + $halfHorizontalLiquidPath = $targetDir.'/src/half_horizontal.blade.php'; + } + + if (File::exists($targetDir.'/src/half_vertical.liquid')) { + $halfVerticalLiquidPath = $targetDir.'/src/half_vertical.liquid'; + } elseif (File::exists($targetDir.'/src/half_vertical.blade.php')) { + $halfVerticalLiquidPath = $targetDir.'/src/half_vertical.blade.php'; + } + + if (File::exists($targetDir.'/src/quadrant.liquid')) { + $quadrantLiquidPath = $targetDir.'/src/quadrant.liquid'; + } elseif (File::exists($targetDir.'/src/quadrant.blade.php')) { + $quadrantLiquidPath = $targetDir.'/src/quadrant.blade.php'; + } } // If we found the required files in the target directory, return them @@ -425,6 +518,25 @@ class PluginImportService } elseif (File::exists($tempDir.'/src/shared.blade.php')) { $sharedBladePath = $tempDir.'/src/shared.blade.php'; } + + // Check for layout-specific files + if (File::exists($tempDir.'/src/half_horizontal.liquid')) { + $halfHorizontalLiquidPath = $tempDir.'/src/half_horizontal.liquid'; + } elseif (File::exists($tempDir.'/src/half_horizontal.blade.php')) { + $halfHorizontalLiquidPath = $tempDir.'/src/half_horizontal.blade.php'; + } + + if (File::exists($tempDir.'/src/half_vertical.liquid')) { + $halfVerticalLiquidPath = $tempDir.'/src/half_vertical.liquid'; + } elseif (File::exists($tempDir.'/src/half_vertical.blade.php')) { + $halfVerticalLiquidPath = $tempDir.'/src/half_vertical.blade.php'; + } + + if (File::exists($tempDir.'/src/quadrant.liquid')) { + $quadrantLiquidPath = $tempDir.'/src/quadrant.liquid'; + } elseif (File::exists($tempDir.'/src/quadrant.blade.php')) { + $quadrantLiquidPath = $tempDir.'/src/quadrant.blade.php'; + } } else { // Search for the files in the extracted directory structure $directories = new RecursiveDirectoryIterator($tempDir, RecursiveDirectoryIterator::SKIP_DOTS); @@ -442,6 +554,12 @@ class PluginImportService $sharedLiquidPath = $filepath; } elseif ($filename === 'shared.blade.php') { $sharedBladePath = $filepath; + } elseif ($filename === 'half_horizontal.liquid' || $filename === 'half_horizontal.blade.php') { + $halfHorizontalLiquidPath = $filepath; + } elseif ($filename === 'half_vertical.liquid' || $filename === 'half_vertical.blade.php') { + $halfVerticalLiquidPath = $filepath; + } elseif ($filename === 'quadrant.liquid' || $filename === 'quadrant.blade.php') { + $quadrantLiquidPath = $filepath; } } @@ -485,6 +603,25 @@ class PluginImportService $sharedBladePath = $newSrcDir.'/shared.blade.php'; } + // Copy layout-specific files if they exist + if ($halfHorizontalLiquidPath) { + $extension = pathinfo((string) $halfHorizontalLiquidPath, PATHINFO_EXTENSION); + File::copy($halfHorizontalLiquidPath, $newSrcDir.'/half_horizontal.'.$extension); + $halfHorizontalLiquidPath = $newSrcDir.'/half_horizontal.'.$extension; + } + + if ($halfVerticalLiquidPath) { + $extension = pathinfo((string) $halfVerticalLiquidPath, PATHINFO_EXTENSION); + File::copy($halfVerticalLiquidPath, $newSrcDir.'/half_vertical.'.$extension); + $halfVerticalLiquidPath = $newSrcDir.'/half_vertical.'.$extension; + } + + if ($quadrantLiquidPath) { + $extension = pathinfo((string) $quadrantLiquidPath, PATHINFO_EXTENSION); + File::copy($quadrantLiquidPath, $newSrcDir.'/quadrant.'.$extension); + $quadrantLiquidPath = $newSrcDir.'/quadrant.'.$extension; + } + // Update the paths $settingsYamlPath = $newSrcDir.'/settings.yml'; } @@ -496,6 +633,9 @@ class PluginImportService 'fullLiquidPath' => $fullLiquidPath, 'sharedLiquidPath' => $sharedLiquidPath, 'sharedBladePath' => $sharedBladePath, + 'halfHorizontalLiquidPath' => $halfHorizontalLiquidPath, + 'halfVerticalLiquidPath' => $halfVerticalLiquidPath, + 'quadrantLiquidPath' => $quadrantLiquidPath, ]; } diff --git a/database/migrations/2026_01_28_143142_add_layout_markup_columns_to_plugins_table.php b/database/migrations/2026_01_28_143142_add_layout_markup_columns_to_plugins_table.php new file mode 100644 index 0000000..e56751c --- /dev/null +++ b/database/migrations/2026_01_28_143142_add_layout_markup_columns_to_plugins_table.php @@ -0,0 +1,38 @@ +text('render_markup_half_horizontal')->nullable()->after('render_markup'); + $table->text('render_markup_half_vertical')->nullable()->after('render_markup_half_horizontal'); + $table->text('render_markup_quadrant')->nullable()->after('render_markup_half_vertical'); + $table->text('render_markup_shared')->nullable()->after('render_markup_quadrant'); + $table->text('transform_code')->nullable()->after('render_markup_shared'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('plugins', function (Blueprint $table) { + $table->dropColumn([ + 'render_markup_half_horizontal', + 'render_markup_half_vertical', + 'render_markup_quadrant', + 'render_markup_shared', + 'transform_code', + ]); + }); + } +}; diff --git a/resources/views/livewire/plugins/index.blade.php b/resources/views/livewire/plugins/index.blade.php index 90f8aa0..848fc67 100644 --- a/resources/views/livewire/plugins/index.blade.php +++ b/resources/views/livewire/plugins/index.blade.php @@ -258,7 +258,6 @@ new class extends Component
Limitations