Blog/CMS
Blogging system based on the Content Editor component.
Last updated
Was this helpful?
Blogging system based on the Content Editor component.
Last updated
Was this helpful?
The Content Management System (CMS) provides a basic blogging platform. The CMS provides a set of basic features:
Minimal data structure - one table for Posts, one table for Files
Simple publishing process - draft fields are copied over published fields
Rich content is supported via the Content Editor component (integrated with Editor.js)
File browser allows the user to upload and select files
Admin console to manage blog posts
Basic UI to show published posts
To create a blog post, go to the admin console:
/admin/posts
Here you'll see the list of posts:
To create a post, click the "New Post" button:
Fill in the fields (only Title is required) and click on "Save and Continue". Here you'll see the main data entry screen:
You can click the cover image to bring up the file browser. The main data entry starts at the section that says, "Start typing...". This part of the document is using the Content Editor.
Once your happy with your edits, click on "Save and Finish". This will bring you to a preview of the draft:
To make this post publically available, click on the "Publish" button:
To complete the process, select a "Go Live" date/time and hit the second "Publish" button!
Draft data will be copied over published fields. For more details on what those fields are and what else happens, see the Data Structure section. Assuming the post is live, it will be available in the Blog.
Once a post has been published, you can bring up the Publish modal again:
To un-publish, click the "Remove" button. This won't affect data entry, but it will ensure that the post is no longer publically available via the Blog.
The Content Editor component is based on Editor.js. You can use it to create paragraphs, lists, tables, insert images and even insert embeds.
pic
Editor.js is a block-style editor that that generates a json document as output. This json is captured and stored in the Post
data structure (see Data Structure below for more details). Rendering is taken care of by the Content Editor component, via the .content
and .pretty_content
function components.
Editor.js has it's own eco-system - functionality is provided via plug-ins. The following plug-ins are installed by default:
Header (h1
to h6
)
Quote (similar to a markdown quote)
Marker (a tool for highlighting sections of text)
InlineCode (so you can display inline text as code)
Code (so you can show a code block)
Delimiter (break up paragraphs with a line-based delimeter)
List (ordered and unordered lists)
SimpleImage (paste in a url to an image and it will render)
Table (UI for creating a HTML table)
Warning (displays an alert, similar to the next section)
Embeds (e.g. paste a YouTube link to create an embedded iframe)
Finally, we've created a plug-in that integrates with the file browser, called PetalImage
There are many core and community-based plug-ins available for Editor.js and there's nothing stopping you from using them! Petal Pro uses npm
for installation. Documentation for installing plug-ins can be found here.
The only requirement is that the Content Editor component is updated so that the renderer can output data from the new plug-in. Thankfully, this is extremely easy to do. In fact, here's a tutorial on how you can create your own Editor.js plug-in and adjust the Content Editor:
Here's what a Post
looks like:
schema "posts" do
# Draft fields (used for editing)
field :category, :string
field :title, :string
field :slug, :string
field :cover, :string
field :cover_caption, :string
field :summary, :string
field :content, :string
field :duration, :string
# Published fields
field :published_category, :string
field :published_title, :string
field :published_slug, :string
field :published_cover, :string
field :published_cover_caption, :string
field :published_summary, :string
field :published_content, :string
field :published_duration, :string
# The last time this post was published
field :last_published, :naive_datetime
# Date/time when post is publically available.
# `nil` means "private" or "not published"
field :go_live, :utc_datetime
belongs_to :author, PetalPro.Accounts.User
timestamps(type: :utc_datetime)
end
Blog posts belong to a user. However, the default behaviour is that only admins have access to the console.
As a general rule, the draft fields are used in the admin console. The published fields are only used in the public facing Blog.
At the time of saving a draft, if the :title
has changed, then it is used to generate :slug
. The :slug
includes the :id
as a postfix (base64 encoded). So if the title is, "Hello Fred", then the slug will look similar to hello-fred-OwAw1rxK
. This way it won't matter if the :title
changes, pasting an old :slug
into the browser will still result in loading the correct Blog post.
When publishing a post, the draft fields are copied over the published fields. In addition :last_published
is set to DateTime.now_utc
and :go_live
is based on the user's data entry.
The output of the Content Editor component (i.e. Editor.js json data structure) is stored in the content
field (and published to the published_content
field).
The cover
field is a url to an image (and by extension published_cover
is too). This can be any url, but you'll need to make sure the domain is configured in the Content Security Policy. To find out how to configure the Content Security Policy (or disable it), check out:
Though the cover
image can point to any url, in reality the domain will probably be defined by the file browser. See the File Browser section (below) for more information.
Here's what the File
data structure looks like:
schema "files" do
field :url, :string
field :name, :string
# Files aren't removed, they're archived
field :archived, :boolean, default: false
belongs_to :author, PetalPro.Accounts.User
timestamps(type: :utc_datetime)
end
Again, a file belongs to a user, but by default only admins have access to the console.
A file points to a url and has a name. Files are intended to be single use - you can add a file, name it and then you're done. If you want something different, you add a new file. If you want to replace a file, archive the old file and add a new one. This behaviour is supported by the File Browser. The main benefit of this design is that you can control what you see, but limit the chance of accidentally deleting a file that's in use.
The file browser provides a means to upload and select images:
Based on LiveView Uploads the file browser defaults to storing files on the local server:
defmodule PetalProWeb.AdminFilesLive.FormComponent do
@moduledoc false
use PetalProWeb, :live_component
alias PetalPro.Files
alias PetalPro.Files.File
alias PetalProWeb.FileUploadComponents
@upload_provider PetalPro.FileUploads.Local
# @upload_provider PetalPro.FileUploads.Cloudinary
# @upload_provider PetalPro.FileUploads.S3
@impl true
def update(assigns, socket) do
socket =
socket
|> assign(assigns)
|> assign_new(:form, fn -> %File{} |> Files.change_file() |> to_form() end)
|> assign(:uploaded_files, [])
|> allow_upload(:new_file,
# SETUP_TODO: Uncomment the line below if using an external provider (Cloudinary or S3)
# external: &@upload_provider.presign_upload/2,
accept: ~w(.jpg .jpeg .png .gif .svg .webp),
max_entries: 1
)
{:ok, socket}
end
# ...
end
As you can see, this can be configured to work with an external uploader instead. You can use one of the built-in providers (Cloudinary or S3).
In the case of a local server upload, Cloudinary or Amazon S3 - the Content Security Policy has been pre-configured and should work out of the box.