feat(#38): added Liquid support from markup editor

This commit is contained in:
Benjamin Nussbaum 2025-07-02 23:36:46 +02:00
parent b438457d32
commit 890f30932b
3 changed files with 98 additions and 14 deletions

View file

@ -7,6 +7,9 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Keepsuit\LaravelLiquid\Facades\Liquid;
use Illuminate\Support\Facades\App;
use Keepsuit\Liquid\Exceptions\LiquidException;
class Plugin extends Model class Plugin extends Model
{ {
@ -18,6 +21,7 @@ class Plugin extends Model
'data_payload' => 'json', 'data_payload' => 'json',
'data_payload_updated_at' => 'datetime', 'data_payload_updated_at' => 'datetime',
'is_native' => 'boolean', 'is_native' => 'boolean',
'markup_language' => 'string',
]; ];
protected static function boot() protected static function boot()
@ -78,17 +82,29 @@ class Plugin extends Model
/** /**
* Render the plugin's markup * Render the plugin's markup
* @throws LiquidException
*/ */
public function render(string $size = 'full', bool $standalone = true): string public function render(string $size = 'full', bool $standalone = true): string
{ {
if ($this->render_markup) { if ($this->render_markup) {
$renderedContent = '';
if ($this->markup_language === 'liquid') {
$environment = App::make('liquid.environment');
$template = $environment->parseString($this->render_markup);
$context = $environment->newRenderContext(data: ['size' => $size, 'data' => $this->data_payload]);
$renderedContent = $template->render($context);
} else {
$renderedContent = Blade::render($this->render_markup, ['size' => $size, 'data' => $this->data_payload]);
}
if ($standalone) { if ($standalone) {
return view('trmnl-layouts.single', [ return view('trmnl-layouts.single', [
'slot' => Blade::render($this->render_markup, ['size' => $size, 'data' => $this->data_payload]), 'slot' => $renderedContent,
])->render(); ])->render();
} }
return Blade::render($this->render_markup, ['size' => $size, 'data' => $this->data_payload]); return $renderedContent;
} }
if ($this->render_markup_view) { if ($this->render_markup_view) {

View file

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('plugins', function (Blueprint $table) {
$table->string('markup_language')->nullable()->after('render_markup');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('plugins', function (Blueprint $table) {
$table->dropColumn('markup_language');
});
}
};

View file

@ -7,8 +7,9 @@ use Illuminate\Support\Facades\Blade;
new class extends Component { new class extends Component {
public Plugin $plugin; public Plugin $plugin;
public string|null $blade_code; public string|null $markup_code;
public string|null $view_content; public string|null $view_content;
public string|null $markup_language;
public string $name; public string $name;
public int $data_stale_minutes; public int $data_stale_minutes;
@ -31,7 +32,6 @@ new class extends Component {
public function mount(): void public function mount(): void
{ {
abort_unless(auth()->user()->plugins->contains($this->plugin), 403); abort_unless(auth()->user()->plugins->contains($this->plugin), 403);
$this->blade_code = $this->plugin->render_markup;
if ($this->plugin->render_markup_view) { if ($this->plugin->render_markup_view) {
try { try {
@ -51,6 +51,9 @@ new class extends Component {
} catch (\Exception $e) { } catch (\Exception $e) {
$this->view_content = null; $this->view_content = null;
} }
} else {
$this->markup_code = $this->plugin->render_markup;
$this->markup_language = $this->plugin->markup_language ?? 'blade';
} }
$this->fillformFields(); $this->fillformFields();
@ -73,7 +76,10 @@ new class extends Component {
{ {
abort_unless(auth()->user()->plugins->contains($this->plugin), 403); abort_unless(auth()->user()->plugins->contains($this->plugin), 403);
$this->validate(); $this->validate();
$this->plugin->update(['render_markup' => $this->blade_code]); $this->plugin->update([
'render_markup' => $this->markup_code ?? null,
'markup_language' => $this->markup_language ?? null
]);
} }
protected array $rules = [ protected array $rules = [
@ -85,7 +91,8 @@ new class extends Component {
'polling_header' => 'nullable|string|max:255', 'polling_header' => 'nullable|string|max:255',
'polling_body' => 'nullable|string', 'polling_body' => 'nullable|string',
'data_payload' => 'required_if:data_strategy,static|nullable|json', 'data_payload' => 'required_if:data_strategy,static|nullable|json',
'blade_code' => 'nullable|string', 'markup_code' => 'nullable|string',
'markup_language' => 'required|string|in:blade,liquid',
'checked_devices' => 'array', 'checked_devices' => 'array',
'playlist_name' => 'required_if:selected_playlist,new|string|max:255', 'playlist_name' => 'required_if:selected_playlist,new|string|max:255',
'selected_weekdays' => 'nullable|array', 'selected_weekdays' => 'nullable|array',
@ -209,11 +216,24 @@ new class extends Component {
$markup = '<h1>Hello World!</h1>'; $markup = '<h1>Hello World!</h1>';
break; break;
} }
$this->blade_code = $markup; $this->markup_code = $markup;
} }
public function renderLayoutWithTitleBar(): string public function renderLayoutWithTitleBar(): string
{ {
if ($this->markup_language === 'liquid') {
return <<<HTML
<div class="view view--{{ size }}">
<div class="layout">
<!-- ADD YOUR CONTENT HERE-->
</div>
<div class="title_bar">
<span class="title">TRMNL BYOS</span>
</div>
</div>
HTML;
}
return <<<HTML return <<<HTML
@props(['size' => 'full']) @props(['size' => 'full'])
<x-trmnl::view size="{{\$size}}"> <x-trmnl::view size="{{\$size}}">
@ -227,6 +247,16 @@ HTML;
public function renderLayoutBlank(): string public function renderLayoutBlank(): string
{ {
if ($this->markup_language === 'liquid') {
return <<<HTML
<div class="view view--{{ size }}">
<div class="layout">
<!-- ADD YOUR CONTENT HERE-->
</div>
</div>
HTML;
}
return <<<HTML return <<<HTML
@props(['size' => 'full']) @props(['size' => 'full'])
<x-trmnl::view size="{{\$size}}"> <x-trmnl::view size="{{\$size}}">
@ -558,23 +588,33 @@ HTML;
/> />
</div> </div>
@else @else
<div class="text-accent"> <div class="flex items-center gap-6 mb-4 mt-4">
<span class="pr-2">Getting started:</span><flux:button wire:click="renderExample('layoutTitle')" class="text-xl">Responsive Layout with Title Bar</flux:button> <div class="flex-1 flex items-center">
<span class="pr-2">Template language</span>
<flux:radio.group wire:model.live="markup_language" variant="segmented">
<flux:radio value="blade" label="Blade"/>
<flux:radio value="liquid" label="Liquid"/>
</flux:radio.group>
</div>
<div class="text-accent flex items-center gap-2">
<span class="pr-2">Getting started</span>
<flux:button wire:click="renderExample('layoutTitle')" class="text-xl">Responsive Layout with Title Bar</flux:button>
<flux:button wire:click="renderExample('layout')" class="text-xl">Responsive Layout</flux:button> <flux:button wire:click="renderExample('layout')" class="text-xl">Responsive Layout</flux:button>
</div> </div>
</div>
@endif @endif
</div> </div>
@if(!$plugin->render_markup_view) @if(!$plugin->render_markup_view)
<form wire:submit="saveMarkup"> <form wire:submit="saveMarkup">
<div class="mb-4"> <div class="mb-4">
<flux:textarea <flux:textarea
label="Blade Code" label="{{ $markup_language === 'liquid' ? 'Liquid Code' : 'Blade Code' }}"
class="font-mono" class="font-mono"
wire:model="blade_code" wire:model="markup_code"
id="blade_code" id="markup_code"
name="blade_code" name="markup_code"
rows="15" rows="15"
placeholder="Enter your blade code here..." placeholder="{{ $markup_language === 'liquid' ? 'Enter your liquid code here...' : 'Enter your blade code here...' }}"
/> />
</div> </div>