In this post, I'll create an app from the ground up. I'll assume you have PHP, Laravel and Composer. Of course a mysql DB for storing "everything." All the code it's here for reference.
Let's create a new app.
laravel new my-blog
Add homestead for easy development ;-)
cd my-blog
composer require laravel/homestead --dev
php vendor/bin/homestead make
and our main actor
composer require area17/twill:"1.2.*"
Now we are almost ready to start building our app. Let's verify that the configuration on the Homestead.yml is ok. I've changed the default sites for this:
sites:
- map: my-blog.dev.a17.io
to: "/home/vagrant/code/public"
- map: admin.my-blog.dev.a17.io
to: "/home/vagrant/code/public"
NOTE: An alternative way of creating the app is with these two commands
composer global require yanhaoli/create-twill-app
create-twill-app new blog
Running Provision your vagrant machine with vagrant up Add the IP and domain defined at Homestead.yaml to your /etc/hosts file.
192.168.10.44 my-blog.dev.a17.io
192.168.10.44 admin.my-blog.dev.a17.io
Now you should be able to visit your URL and receive the Laravel Homepage
This means that it is time for the magic touch. Jump into the VM and run the installation script
vagrant ssh
cd code
php artisan twill:install
It prompts for the superadmin email, password and password confirmation. Now we can visit the admin URL and see everything working.
Building the structure I need an Article entity which can have a Hero image, a title, a description and a flexible structure(Block Editor). Every article can be translated and have a specific URL(slug).
php artisan twill:module articles -TSMBR
Adapt the Table to our needs.
class CreateArticlesTables extends Migration
{
public function up()
{
Schema::create('articles', function (Blueprint $table) {
// this will create an id, a "published" column, and soft delete and timestamps columns
createDefaultTableFields($table);
});
Schema::create('article_translations', function (Blueprint $table) {
createDefaultTranslationsTableFields($table, 'article');
$table->string('title', 200)->nullable();
$table->text('description')->nullable();
});
Schema::create('article_slugs', function (Blueprint $table) {
createDefaultSlugsTableFields($table, 'article');
});
Schema::create('article_revisions', function (Blueprint $table) {
createDefaultRevisionsTableFields($table, 'article');
});
}
public function down()
{
Schema::dropIfExists('article_revisions');
Schema::dropIfExists('article_translations');
Schema::dropIfExists('article_slugs');
Schema::dropIfExists('articles');
}
}
Create the DB table
php artisan migrate
Rearrange the model counterpart app/Models/Article.php
class Article extends Model
{
use HasBlocks, HasTranslation, HasSlug, HasMedias, HasRevisions;
protected $fillable = [
'published',
];
public $translatedAttributes = [
'title',
'description',
];
public $slugAttributes = [
'title',
];
public $checkboxes = [
'published'
]
}
Now we have the DB structure we can add the routes to routes/admin.php
Route::module('articles');
and the option to the Twill menu config/twill-navigation.php
'articles' => [
'title' => 'Articles',
'module' => true
]
Make sure you have all the languages you want to handle on the config/translatable.php file.
return [
'locales' => [
'en',
'es',
],
Our basic CMS for creating articles with translations is ready.
Hero Image For adding images, we need to decide if we are going to use local storage or S3. I'll use local storage. Later in the process, we will switch to S3 and Imgix. Hence, let's add what the manual says: for .env
MEDIA_LIBRARY_ENDPOINT_TYPE=local
MEDIA_LIBRARY_LOCAL_PATH=uploads/
MEDIA_LIBRARY_IMAGE_SERVICE=A17\Twill\Services\MediaLibrary\Local
Now let's make sure we have the mediasParams in the Article model.
public $mediasParams = [
'hero_image' => [
'default' => [
[
'name' => 'landscape',
'ratio' => 16 / 9,
]
]
]
];
also, the form field to upload the images articles/form.blade.php
@formField('medias',[
'name' => 'hero_image',
'label' => 'Hero image',
])
Great! Now we can upload the Hero Image.
Block Editor (flexible structure) For this tutorial, I want the ability to add a gallery, an Image with text, a quote, and a paragraph. Let's go. Inside resources/views/admin/ create a folder called blocks. This folder contains all the Blocks for the CMS. Add the blocks (files containing the form fields we want to use for building an Article). Gallery resources/views/admin/blocks/gallery.blade.php
@formField('medias', [
'name' => 'gallery',
'label' => 'Gallery',
'max' => 5,
'note' => 'Minimum image width: 1500px'
])
Image with text resources/views/admin/blocks/image_with_text.blade.php
@formField('medias', [
'name' => 'cover',
'label' => 'Image',
'note' => 'Minimum image width 1300px'
])
@formField('input', [
'translated' => true,
'name' => 'image_subtitle',
'label' => 'Image Subtitle (translated)',
'maxlength' => 250,
'required' => true,
'placeholder' => 'Description.',
'type' => 'textarea'
])
Quote resources/views/admin/blocks/quote.blade.php
@formField('input', [
'translated' => true,
'name' => 'quote',
'label' => 'Quote (translated)',
'maxlength' => 250,
'required' => true,
'type' => 'textarea',
'rows' => 3
])
Paragraph resources/views/admin/blocks/paragraph.blade.php
@formField('wysiwyg', [
'translated' => true,
'name' => 'paragraph',
'label' => 'Paragraph',
'maxlength' => 200,
'editSource' => true,
'note' => 'You can edit the source.',
])
Now with the blocks created Twill needs to know that these blocks in the config/twill.php like:
<?php
return [
'block_editor' => [
'block_single_layout' => 'layouts.block',
'blocks' => [
'gallery' => [
'title' => 'Gallery',
'icon' => 'image',
'component' => 'a17-block-gallery',
],
'image_with_text' => [
'title' => 'Image with text',
'icon' => 'image-text',
'component' => 'a17-block-image_with_text',
],
'quote' => [
'title' => 'Quote',
'icon' => 'quote',
'component' => 'a17-block-quote',
],
'paragraph' => [
'title' => 'Paragraph',
'icon' => 'text',
'component' => 'a17-block-paragraph',
],
],
'crops' => [
'cover' => [
'default' => [
[
'name' => 'default',
'ratio' => 1 / 1,
'minValues' => [
'width' => 100,
'height' => 100,
],
],
],
],
'gallery' => [
'default' => [
[
'name' => 'default',
'ratio' => 16 / 9,
'minValues' => [
'width' => 1024,
'height' => 768,
],
],
],
],
],
],
];
Please note that I've added a crops section. That is needed for the blocks that have images, without those the images are not stored. If we have RTFM correctly then we need to add the scripts to the project's package.json:
"scripts": {
"twill-build": "npm run twill-copy-blocks && cd vendor/area17/twill && npm ci && npm run prod && cp -R public/* ${INIT_CWD}/public",
"twill-copy-blocks": "npm run twill-clean-blocks && mkdir -p resources/assets/js/blocks/ && mkdir -p vendor/area17/twill/frontend/js/components/blocks/customs/ && cp -R resources/assets/js/blocks/ vendor/area17/twill/frontend/js/components/blocks/customs",
"twill-clean-blocks": "rm -rf vendor/area17/twill/frontend/js/components/blocks/customs/*"
}
On the articles/form.blade.php we need to add the Block Editor selector.
@formField('block_editor')
Or
@formField('block_editor', [
'blocks' => ['gallery', 'image_with_text', 'quote', 'paragraph']
])