A CMS for Elixir/Phoenix that doesn't hijack your development workflow.
<img src=https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat alt='js-standard-style'/>
Thesis is a lightweight and flexible Elixir/Phoenix CMS for quickly and easily adding content editing to any page on a Phoenix website, as well as creating new dynamically routed pages. It's ideal for either adding limited editing support to existing Phoenix websites or building dynamic websites.
Thesis is currently considered Alpha-quality, but is in production on several websites, including Infinite Red. The API is unstable and changing but we are committed to providing a clear upgrade path and documentation.
See also the Thesis Rails gem.
- Elixir/Phoenix hex package, uses React.js for its user interface
- Lightweight, bolt-on, doesn't hijack your development workflow
- On-page rich text editing
- On-page plain text editing
- Raw HTML editing for Youtube embeds or other flexible uses
- Image URL editing, both
img
tag anddiv
with background image - Page meta title and description editing
- Easily bring your own authentication system in one tiny function
- Create new dynamic pages, delete dynamic pages
If you are having problems, view README_INSTALL.md
for manual instructions.
def deps do
[{:thesis, "~> 0.0.24"}]
end
def application do
[applications: [:thesis]]
end
This install script will add Thesis to your config.exs
and web.ex
, as well
as generate migrations and an authorization module in your lib/thesis_auth.ex
.
<body>
<%= thesis_editor(@conn) %>
$ mix ecto.migrate
Check out the example app in apps/example
to see how Thesis can be implemented.
We'll keep this up to date with examples of the latest features as we develop Thesis.
Use the Thesis.View.content/4
view helper function to make a content area
editable. If you have use Thesis.View
in your web.ex
file, this function
is already available on all of your views.
Thesis will add a wrapper <div>
around editable HTML and plain-text content
areas, both in read mode and edit mode, so plan your CSS accordingly.
Simply wrap your HTML in a content
function call, specifying html
as the content type.
<h1>Title</h1>
<p>
Here's my default description!
</p>
becomes...
<%= content(@conn, "Section identifier", :html) do %>
<h1>Title</h1>
<p>
Here's my default description!
</p>
<% end %>
For plain-text, provide a do:
option for default text.
<h1>My Title</h1>
becomes...
<h1><%= content(@conn, "Title identifier", :text, do: "My Title") %></h1>
For video embeds, iframes, and any other custom HTML, use the :raw_html
content type:
<iframe width="560" height="315" src="https://www.youtube.com/embed/5SVLs_NN_uY" frameborder="0" allowfullscreen></iframe>
becomes...
<%= content(@conn, "Section identifier", :raw_html) do %>
<iframe width="560" height="315" src="https://www.youtube.com/embed/5SVLs_NN_uY" frameborder="0" allowfullscreen></iframe>
<% end %>
You can have the user specify an image URL and display the image with the image
content type.
<img src="http://placekitten.com/200/300">
becomes...
<%= content(@conn, "Image identifier", :image, alt: "My alt tag", do: "http://placekitten.com/200/300")
%>
If you prefer to use a div
with a background image, you can use the background_image
content type.
<div style="background-image: url(http://placekitten.com/200/300)"></div>
becomes...
<%= content(@conn, "Image identifier", :background_image, do: "http://placekitten.com/200/300")
%>
Included in Thesis is an adapter for Ospry.io, which is a service that offers the first 1,000 images and 1 GB of monthly download bandwidth for free.
- Sign up at https://ospry.io/sign-up
- Verify your email
- Create a production subdomain (assets.example.com)
- Copy your production public key to the Thesis config:
config :thesis, Thesis.OspryUploader,
ospry_public_key: "pk-prod-abcdefghijklmnopqrstuvwxyz0123456789"
That's it! Restart your server and image content areas will now contain a file upload field. Note: You'll need to add a valid credit card if you anticipate exceeding Ospry.io limits.
Content areas in Thesis are page-specific. However, if you want an editable
area that can be displayed on multiple pages, use the
Thesis.View.global_content/4
function. Any page using that content area identifier
will display the edited content across the whole website.
<%= global_content(@conn, "Footer Text", :html) do %>
<h4>Contact Info</h4>
<ul>
<li>Call us at (800) 555-1212</li>
<li>Email us at hello@example.com.</li>
</ul>
<% end %>
Thesis adds an additional <div>
around your editable content areas. We suggest that
you not style these divs heavily, since Thesis uses them as editors and adds its own styles
in edit-mode. However, sometimes, you need to modify that markup slightly for better presentation.
You can provide an ID and additional classes by specifying id
and classes
, respectively.
<%= content(@conn, "Ident", :html, id: "my-id", classes: "more classes") do %>
<h1>Title</h1>
<% end %>
Thesis provides a settings tray to edit each page's title and description. In your layout, you can output the current title and description like so:
<title><%= page_title(@conn, "Default Title") %></title>
<meta name="description" content="<%= page_description(@conn, "Default Description") %>" />
Some prefer to set the page title and description as assigns in their controller actions:
def about(conn, params) do
@title = Thesis.View.page_title(conn, "About My Company")
@description = Thesis.View.page_description(conn, "A relevant description here.")
end
Thesis supports users creating and deleting dynamically routed pages. These differ from static pages in that they are routed by Thesis rather than Phoenix, and live only in your database. They can be rendered with different templates.
To enable dynamic pages, add (or uncomment) this in your config/config.exs
file:
config :thesis, :dynamic_pages,
view: <MyApp>.PageView,
templates: ["index.html", "otherview.html"],
not_found_view: <MyApp>.ErrorView,
not_found_template: "404.html"
Replace <MyApp>
with your app name. Use any view you want, and put any templates
contained in that view that you want to make available in the templates
list.
These will be displayed as a drop-down to the user when they are creating the new
dynamic page.
You'll also need to make one change to your router.ex and a controller of your choice.
# web/router.ex
# should be added as the last route
get "/*path", <MyApp>.PageController, :dynamic
# web/controllers/page_controller.ex (or similar)
def dynamic(conn, _params) do
render_dynamic(conn)
end
You can pass in a default template (otherwise, it'll use the first template
option in your config) with render_dynamic(conn, template: "index.html")
.
You can choose to make only a portion of your website support static pages by routing more specifically. For example, if you want a blog section:
# web/router.ex
get "/blog/*path", <MyApp>.BlogController, :dynamic
You probably don't want your website editable by the world. Thesis doesn't force you to use any particular authorization strategy.
Instead, Thesis will call your auth module's page_is_editable?/1
function
and provide the current conn
, which can be used to extract current user
session data as well as the current page, and then you can decide how that
should affect authorization.
Here's an example which we use on our own website, https://infinite.red:
defmodule IrWebsite.ThesisAuth do
@behaviour Thesis.Auth
def page_is_editable?(conn) do
IrWebsite.AuthController.logged_in?(conn)
end
end
In our auth_controller.ex
file, the logged_in?/1
function looks something
like this:
def logged_in?(conn) do
!!current_user(conn)
end
def current_user(conn) do
get_session(conn, :current_user)
end
So, in this case, we're simply checking to see if the user has been logged in or not. Since only Infinite Red employees have logins, it's safe for us to assume that if they're logged in, they have permission to edit the page.
If you use Guardian or something similar,
you may need additional manipulations to your conn
to properly authenticate the
user. Add those to your auth module like this:
defmodule MyApp.ThesisAuth do
@moduledoc """
Contains functions for handling Thesis authorization.
"""
def page_is_editable?(conn) do
conn
|> Guardian.Plug.VerifySession.call(%{})
|> Guardian.Plug.LoadResource.call(%{})
|> MyApp.SessionController.logged_in_and_admin?
end
end
You can't have it all. Thesis isn't the same as other -bloated- full-function content management systems out there. This is a list of what it's not now and not likely to be in the future.
- Not a complete WordPress Replacement
- Not a full featured CMS
- Not a full featured WYSIWYG editor
- Not an authentication or permission system
- Not supported outside of a Phoenix app
This is pretty common. While we try to be good citizens by properly namespacing all Thesis elements, we embed Thesis code into your existing web page, and so we're at the mercy of your application's existing CSS.
Inspect the element(s) that are screwed up and see if any of your styles are conflicting. For example, here's a screenshot of an issue:
Note that there is a .tooltip
CSS rule originating in a different CSS file
that is affecting our editor.
In future releases, we will namespace all Thesis classes and IDs. But if your
application is overriding whole elements (like div
or img
), it's up to you
to fix the issue in your own CSS.
We're committed to making Thesis the go-to content editing system for Phoenix websites. Please help us improve!
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Run
brunch watch -p
during development - Use the
apps/example
Phoenix app to manually test your feature - Write tests for your new feature
- Run
mix test && npm test
in the root directory to ensure that Thesis tests pass. - Run
mix test
in theapps/example
directory to ensure the example app passes - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
- Jamon Holmgren @jamonholmgren
- Yulian Glukhenko @yulianglukhenko
- Ken Miller @seriousken
- Daniel Berkompas @dberkom
Also supported by others on the Infinite Red team.
Copyright (c) 2016 Infinite Red, Inc.
Thesis depends on Elixir, which is under the Apache 2 license, and Phoenix, which is MIT.
See LICENSE.md for more information.