An IndieWeb-friendly content management system built with Ruby on Rails.
FrancisCMS began life in October 2014 as a Sinatra application (hence the name) before becoming a Rails application before becoming a mountable Rails engine.
At present, FrancisCMS is a white label Rails engine meant to be included in an existing Rails application at a particular mount point (/
, for instance). FrancisCMS provides models, controllers, and views for CRUD-ing supported content types. Your host Rails application is responsible for providing a user authentication system, styles with CSS, and behavior with JavaScript. FrancisCMS also exposes a number of useful methods, helpers, and configuration variables available for use in the host Rails application.
- Supports multiple content types (posts, links, and photos) with drafts, tags, and RSS feeds.
- Supports syndicating content types to several third-party silos.
- Receives and verifies webmentions, displaying verified webmentions on content pages.
- Provides alternative representations of content types as Markdown and jf2-formatted JSON.
- Inherits styling (CSS) and scripting (JavaScript) from the host Rails application.
- Delegates authentication to the host Rails application.
- Prerequisites
- Installation
- Usage
- Content Types
- Syndicating Content
- Theming
- Improving FrancisCMS
- Acknowledgments
- License
I manage Ruby versions with rbenv. I'd recommend you do the same or use a similar Ruby version manager (chruby or RVM come to mind). Also, if you're using Mac OS X, Homebrew makes installing command line utilities incredibly easy. The following instructions assume you'll use rbenv and Homebrew but you're totally free to install things as you like.
- Ruby 2.4.9 (
rbenv install 2.4.9
) - Bundler (
gem install bundler
) - PostgreSQL (
brew install postgresql
) - ImageMagick (
brew install imagemagick
)
I'll do my best to keep this documentation digestible and will avoid making assumptions about your skill level or depth of knowledge about Ruby, Rails, and any technology used in FrancisCMS. I highly recommend though that you familiarize yourself with Ruby on Rails, the framework upon which FrancisCMS is built. The official Ruby on Rails Guides are a great resource and worth a read.
Include the FrancisCMS engine in your project's Gemfile:
gem 'francis_cms', git: 'https://github.com/FrancisCMS/FrancisCMS.git'
Run bundle install
to install FrancisCMS and its dependencies.
ruby '2.4.9'
source 'https://rubygems.org' do
gem 'rails', '~> 4.2'
gem 'francis_cms', git: 'https://github.com/FrancisCMS/FrancisCMS.git'
end
As mentioned above, FrancisCMS is a mountable Rails engine meant for inclusion in a host application. Rails engines are structurally similar to a typical Rails application, but how the host application and engine interact with one another is unique. In this section, we'll build a basic Rails application that mounts FrancisCMS and will detail many of the ways in which it interacts with the host application.
mkdir example.com && cd example.com
rbenv local 2.4.9
- Create a
Gemfile
in the root of the project and, in your editor of choice, add the contents from the Sample Gemfile section above. bundle install
bundle exec rails new . --database=postgresql --skip-gemfile --skip-spring --skip-javascript --skip-turbolinks --skip-test-unit
- Edit
config/database.yml
to match your PostgreSQL configuration and start your local PostgreSQL server. bundle exec rake db:create && bundle exec rake db:migrate
- Create a static pages controller:
bundle exec rails generate controller pages homepage --skip-routes --no-helper --no-assets
. - In
config/routes.rb
, addroot 'pages#homepage'
. - Also in
config/routes.rb
, addmount FrancisCms::Engine, at: '/'
, replacingat: '/'
with the path at which you wish to mount FrancisCMS. - In
app/controllers/application_controller.rb
, create alogged_in?
method (see Authentication below). bundle exec rails server -b 0.0.0.0
- Point your favorite browser at
http://localhost:3000
.
You should now have a templated homepage (source file at app/views/pages/homepage.html.erb
) and you should be able to navigate directly to FrancisCMS' content listing pages (assuming FrancisCMS is mounted at /
):
http://localhost:3000/links
http://localhost:3000/photos
http://localhost:3000/posts
http://localhost:3000/webmentions
See the Routes section below for more on how to create navigational links to these pages in your site's layout.
FrancisCMS has a number of configuration options that you can set in your application by creating an initializer file called config/initializers/francis_cms.rb
. This file might look something like:
Rails.application.config.tap do |config|
config.francis_cms.logged_in_method = :logged_in?
config.francis_cms.login_path = '/login' # path may be relative or absolute
config.francis_cms.site_url = 'http://example.com/' # `site_url` must include protocol and trailing slash
config.francis_cms.site_title = 'FrancisCMS Demo Site'
config.francis_cms.site_description = 'This is the default site description for a new FrancisCMS-powered website.'
config.francis_cms.site_language = 'en-US'
config.francis_cms.user_name = 'Jane Doe'
config.francis_cms.user_email = 'jane@example.com'
config.francis_cms.user_avatar = 'http://www.placecage.com/180/180' # path may be relative or absolute
config.francis_cms.license_title = 'Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International'
config.francis_cms.license_url = 'http://creativecommons.org/licenses/by-nc-sa/4.0/'
end
The values above are the default configuration options. You will only want to include the options you wish to override in your francis_cms.rb
initializer. In your host Rails application, you can access these values with francis_cms_config.foo
(where foo
is replaced with site_url
, site_title
, etc.).
FrancisCMS does not include an authentication system, preferring instead to delegate authentication to your host Rails application. In keeping with the IndieWeb ethos, my preference is to use a service like IndieAuth, but I didn't want to force that decision on anyone interested in using FrancisCMS. Your application may have different needs so you should choose an authentication system that best suits those needs.
Regardless of which system you choose, you'll need to let FrancisCMS know about it. In the Configuration section above, you'll see settings for logged_in_method
and login_path
. The former is the method that FrancisCMS will delegate to when it needs to determine a user's authentication state and the latter is the URL to which a user should be redirected when not logged in.
In app/controllers/application_controller.rb
, create a logged_in?
method and expose it with helper_method
:
def logged_in?
# Your application-specific authentication logic would go here.
# This method should return a boolean `true` or `false`.
end
helper_method :logged_in?
The name of this method (and its exposed helper) should match the method name set in your configuration (if you chose to override the default). You can now use conditional logic like if logged_in?
in your application's controllers and views. Any actions or views controlled by FrancisCMS will delegate to this method and respond accordingly.
FrancisCMS provides a basic "panel" of admin-related markup that you can include in your pages. It's best to include this in your application's layout file using the following bit of code:
<%= francis_cms_admin_panel if logged_in? %>
This helper method will generate markup similar to:
<div class="admin-panel">
<menu class="global">
<li><a href="/posts/new">New post</a></li>
<li><a href="/links/new">New link</a></li>
<li><a href="/photos/new">New photo</a></li>
</menu>
</div>
FrancisCMS uses standards Rails-y resource-based routing so generating URLs in views should be familiar. The primary difference, though, is a required francis_cms.
prefix when using route helpers. For instance:
<%= link_to 'Posts', francis_cms.posts_path %>
The above ERB will generate the following markup (assuming a FrancisCMS is mounted at /
):
<a href="/posts">Posts</a>
For a full list of available routes, run bundle exec rake routes
from the root of your application.
FrancisCMS makes available RSS feeds for all content types (posts, links, photos, and webmentions). These URLs can be generated using ERB like:
<%= link_to 'Posts RSS feed', francis_cms.posts_path(format: :rss) %>
You can also use Rails' auto_discovery_link_tag
helper to generate <link>
elements in the <head>
of your application's layout:
<%= auto_discovery_link_tag :rss, francis_cms.posts_path(format: :rss), title: %{#{francis_cms_config.site_title}: Posts} %>
You may find that your application would benefit by extending FrancisCMS' existing controllers and routing. The reasons for this and the complexity required will vary, of course, but a basic example might look like:
In app/controllers/links_controller.rb
:
class LinksController < ApplicationController
def fetch_json
render json: { title: 'Sample Link Title', url: 'http://example.com/foo/bar' }
end
end
And in config/routes.rb
:
post 'links/fetch_json', to: 'links#fetch_json'
The above example would add a route (/links/fetch_json
) that responds to POST
requests and a controller action that returns a block of JSON. Extending FrancisCMS' functionality in your application can be helpful, but proceed with caution.
FrancisCMS takes advantage of the Rails Internationalization API. Internationalization support is incomplete, but some strings are easily translatable. Default values are set in config/locales/en.yml
and may be overridden in your host Rails application by adding the appropriate language files to config/locales
. See the official documentation for more on this topic.
FrancisCMS currently supports three primary content types: posts, links, and photos. All content types support receiving and displaying webmentions and provide alternative representations as Markdown and JSON.
Posts are textual content of any length and map to the general IndieWeb post construct. FrancisCMS doesn't currently distinguish between articles (akin to blog posts) and notes (akin to tweets). This may change in the future, though.
A post has the following attributes:
- Title (required)
- URL slug (e.g.
an-example-post-url
) (required) - Body (Markdown-formatted) (required)
- Excerpt
- Tags
- Save as draft
Links are a type of post consisting primarily of a URL to a third-party website and map to the IndieWeb bookmark construct.
A link has the following attributes:
- URL (required)
- Title (required)
- Body (Markdown-formatted)
- Tags
- Save as draft
Photos are a type of post consisting primarily of a photograph or image and map to the IndieWeb photo construct.
A photo has the following attributes:
- Photo (an image file of type GIF, JPG, or PNG) (required)
- Body (Markdown-formatted)
- Tags
- Save as draft
Photos also have a title
virtual attribute (e.g. @photo.title
) that will return the first line from the photo's body attribute or Untitled
.
Photos are uploaded using CarrierWave and processed with MiniMagick and ImageMagick. Photos are converted to JPG and resized with the longest side being no greater than 500px, 750px, and 1000px.
FrancisCMS supports automated and manual syndication of content to third-party silos. In IndieWeb terminology, this is known as POSSE: Publish (on your) Own Site, Syndicate Elsewhere.
- Apply for a new Flickr API key. Keep the supplied
key
andsecret
handy, you'll need them in subsequent steps. - From the root of your Rails application, run
bundle exec rake francis_cms:configure_flickr
. - Follow the prompts to obtain the necessary OAuth keys and secrets from Flickr.
- Copy and paste your Flickr syndication configuration into your application's
config/secrets.yml
.
- Generate a new Medium integration token.
- Add the following to your application's
config/secrets.yml
, replacing the uppercased strings with your integration token from Medium:
medium_integration_token: YOUR_MEDIUM_INTEGRATION_TOKEN
- Visit apps.twitter.com and click the "Create New App" button.
- Enter the required Application Details and agree to the Developer Agreement.
- On the Keys and Access Tokens tab, click the "Create my access token" button.
- Add the following to your application's
config/secrets.yml
, replacing the uppercased strings with the appropriate values from Twitter:
twitter_consumer_key: YOUR_TWITTER_CONSUMER_KEY
twitter_consumer_secret: YOUR_TWITTER_CONSUMER_SECRET
twitter_access_token: YOUR_TWITTER_ACCESS_TOKEN
twitter_access_token_secret: YOUR_TWITTER_ACCESS_TOKEN_SECRET
For networks that either don't allow for automated syndication (e.g. Instagram) or that aren't currently supported in FrancisCMS, you can manually add syndicated copies to each content type. After publishing a piece of content, and when editing that piece of content, you'll see a form for adding links to syndicated copies.
FrancisCMS supplies views for CRUD-ing all supported content types. The host Rails application is responsible for providing the primary layout (e.g. app/views/layouts/application.html.erb
) as well as CSS, JavaScript, and other assets. It's your choice as to how you create and serve those assets. A typical Rails application uses the Asset Pipeline which makes available to you pre-processing technologies like Sass.
FrancisCMS' views are lightweight, accessible, and flexible which means you can easily use a technique like progressive enhancement to build a beautiful and richly interactive website with the provided base markup. The sky's the limit, really!
FrancisCMS is built on IndieWeb principles and building blocks. Among those are the inclusion of microformats2 which enable technologies like webmention. As such, you'll see sprinkled throughout the markup class
attribute values begining with h-
, p-
, and u-
.
Care has been taken to craft FrancisCMS' markup in a way that is lightweight, accessible, and flexible. Rather than wed FrancisCMS to a particular CSS framework or convention (e.g. BassCSS, SMACSS, or OOCSS), I've instead tried to use descriptive class
attribute values and allowed for considered use of the cascade (the "C" in "CSS").
TODO
TODO
TODO
If diving into Ruby isn't your thing, but you'd still like to support FrancisCMS, consider making a donation! Any amount—large or small—is greatly appreciated. As a token of my gratitude, I'll add your name to the Acknowledgments below.
Most of this wouldn't have been possible without Tony Pitale's mentorship, encouragement, and patience. Thanks, buddy!
The default user avatar image (app/assets/images/avatar.png
) was derived from Brent Jackson's Geomicons Open icon set.
FrancisCMS is written and maintained by Jason Garber.
FrancisCMS is freely available under the MIT License. Use it, learn from it, fork it, improve it, change it, tailor it to your needs.