Skip to content

Commit

Permalink
Merge pull request #1 from samcdavid/master
Browse files Browse the repository at this point in the history
Atomic CMS Enhancements
  • Loading branch information
dhumphreys committed Sep 1, 2015
2 parents 14314a5 + 97e1850 commit 05c9f3e
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 6 deletions.
334 changes: 334 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
# Atomic CMS
The biggest problem with any content management system is the admin users are
given too much or too little power when editing their site. This gem provides a
means to manage usage of components created from the gem Atomic Assets. By
providing admin users with a way to manage their Atomic Assets, developers and
designers are able to ensure the components they create remained styled properly
while allowing admins to update their content as needed without developer
intervention. Below are instructions for getting started.

## Gemfile
After initializing a new Rails application, or adding to an existing
application, add the following gems to your Gemfile.
```ruby
gem 'activeadmin', github: 'activeadmin'
gem 'angularjs-rails'
gem 'atomic_assets'
gem 'atomic_cms', github: 'samcdavid/atomic_cms'
gem 'devise'
gem 'redcarpet'
gem 'slim-rails'
```
Then perform a `bundle install`.

## Initialization
### Active Admin
To initialize Active Admin:
```ruby
rails generate active_admin:install
```
Remove the comments migration Active Admin created along with disabling comments
on line 122 of the Active Admin initializer.

Now you shall run the migration and seeds with:
```ruby
bundle exec rake db:setup
```
To verify, start the server and visit `localhost:3000/admin`. If you can login
as `[email protected]` with the password `password` you have successfully
completed this step.

#### JavaScript
Next, update `./app/assets/javascripts/active_admin.js.coffee` to match the
following:
```coffee
#= require active_admin/base
#= require angular
#= require angular-sanitize
#= require atomic_cms
```

#### Styles
You should install bourbon, bitters, and neat as well as reset.css in order for
the next steps to work correctly. Also create a folder under your stylesheets
folder for all of the scss for your components and go ahead and create an empty
scss file for your text block component.

Now, update your active_admin.scss file to match the following:
```scss

rt "active_admin/mixins";
@import "active_admin/base";
@import "bourbon";
@import "neat";
@import "base/variables";
@import "base/grid-settings";
@import "atomic_cms";

#component_preview {
//@import "base/base";
@import "reset";
@import "base/buttons";
@import "base/forms";
@import "base/lists";
@import "base/tables";
@import "base/typography";
@import "components/*";
@include font-feature-settings("kern", "liga", "pnum");
-webkit-font-smoothing: antialiased;
color: $base-font-color;
font-family: $base-font-family;
font-size: 16px;
line-height: $base-line-height;
// When editing a page through the CMS,
// images with broken links will not be displayed
img[src="image"] {
display: none !important;
}
}

// Overriding any non-variable SASS must be done after the fact.
// For example, to change the default status-tag color:
//
// .status_tag { background: #6090DB; }
```

### Atomic CMS
#### Routes
Update your `config/routes.rb` to include the following:
```ruby
mount AtomicCms::Engine => "/atomic_cms"
get "*path", to: "pages#show", controller: "pages", as: :page, format: false
root to: 'pages#show', controller: "pages"
```
The last two lines need to be at the **END** of your `routes.rb` file.
#### Model
Execute the following to create a model for your static pages:
```ruby
rails g model pages title:string path:string content:text
```
After this you should run your migrations.

Update your Page model to match the following:
```ruby
class Page < ActiveRecord::Base
include AtomicCms::HasComponents
validates :title, :path, presence: true, uniqueness: true
component_attr :content
end
```
#### Controller
Create a controller by running the following:
```ruby
rails g controller pages
```
Update your PagesController to match the following:
```ruby
require 'json'

class PagesController < ApplicationController
def show
@page = Page.find_by_path(request.path)
if @page
render cms_template
else
render file: "#{Rails.root}/public/404.html", status: :not_found
Rails.logger.error "404 - Page #{request.path} cannot be found."
end
end

private

def cms_template
File.join("pages", "page")
end
end
```
#### Helper
Create `app/helpers/application_helper.rb` and update it to match the following:
```ruby
module ApplicationHelper
def add_option(option, output = nil)
return unless option
return output if output
option
end

def markdown(text)
return unless text
Redcarpet::Markdown.new(Redcarpet::Render::HTML).render(text).html_safe
end

def markdown_help_url
"http://nestacms.com/docs/creating-content/markdown-cheat-sheet"
end
end
```
#### Views
Create a view at `app/views/pages/page.html.slim` that contains the following:
```ruby
= @page.content_object.render
```
#### Components
Create the following component view at
`app/views/components/text_block.html.slim`:
```slim
.wrapper.large-margin
.text-block
.content-text
- if add_option(options[:header])
h2
= options[:header]
= markdown(options[:content])
```
Create the following component class at `app/components/text_block_component.rb`:
```ruby
class TextBlockComponent < AtomicAssets::Component
def edit
rtn = cms_fields(field_types)
rtn << h.component(:text_block, field_previews).render
rtn.html_safe
end

protected

def field_previews
{
header: "{{preview.header}}",
content: markdown_preview('preview.content')
}
end

def field_types
{
header: { field_type: "text" },
content: { field_type: "markdown" }
}
end
end
```
Add the following to `app/assets/stylesheets/components/_text_block.scss`:
```scss
.text-block{
position: relative;
.content-text{
ul{
margin-left: 10px;
padding-left: 0px;
position: relative;
display: inline-block;
li{
list-style: disc;
margin-left: 20px;
}
}
}
&:before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
margin-right: -0.25em; /* Adjusts for spacing */
}
}
```
### More Active Admin
In order to add components to a page, create
`app/views/admin/_edit_buttons.html.slim` and make it match the following:
```slim
ol.edit-buttons
li
= link_to 'Text Block', atomic_cms.edit_component_path('text_block'), class: 'button'
```
Create an admin page for the Pages model at `app/admin/page.rb` and make it
match the following:
```ruby
ActiveAdmin.register Page do
permit_params :title, :path, :content

index do
selectable_column
column :path
column :title
column :created_at
column :updated_at
actions
end

form do |f|
f.semantic_errors(*f.object.errors.keys)

# new form
if !f.object.persisted?
f.inputs 'Page Details' do
f.input :title
f.input :path
end
f.actions

# edit form
else
div class: 'buttons' do
render partial: 'admin/edit_buttons'
f.actions
end

columns do
column span: 3 do
panel 'Draft', id: 'draft-panel' do
render partial: 'components/edit', locals: { f: f }
end
end

column id: 'edit-node-column' do
div id: 'edit-page' do
f.inputs 'Page Details' do
f.input :title
f.input :path
end
end

div id: 'edit-node' do
f.inputs 'Edit Element' do
div id: 'edit-node-fields'
end

f.actions do
li class: 'move' do
a 'Up', '#', class: 'button', id: 'move-node-up'
end
li class: 'move' do
a 'Down', '#', class: 'button', id: 'move-node-down'
end
li class: 'cancel' do
a 'Done', '#', class: 'button', id: 'done-edit-node'
end
li class: 'delete' do
a 'Delete', '#', class: 'button', id: 'delete-node'
end
end
end
end
end
end
end

show do
div id: 'component_preview' do
div page.content_render
end
end

controller do
# permit all params until we can whitelist content_object
def permitted_params
params.permit!
end
end
end
```
#### Config
Update `config/application.rb` to include:
```ruby
config.autoload_paths += %W(#{config.root}/lib, #{config.root}/app/components/**/)
```
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class ComponentsController < ApplicationController
class AtomicCms::ComponentsController < ApplicationController
def edit
render text: component(params[:id]).edit_array(!!params[:inline])
end
Expand Down
5 changes: 5 additions & 0 deletions app/views/components/_select_field.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
span.li.string.input
label.label #{name.to_s.humanize}
select.cms-field name="#{name}" data-ng-model="preview.#{name}"
option
= options_for_select(options[:collection], value)
9 changes: 5 additions & 4 deletions lib/atomic_cms/editor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def edit_array(inline = false)

module ClassMethods
def from_hash(params)
component(params.delete(:template_name)).tap do |obj|
params.each { |key, val| obj.options[key] = from_value(key, val) }
h.component(params.delete(:template_name)).tap do |obj|
params.each { |key, val| obj.options[key.to_sym] = from_value(key, val) }
end
end

Expand All @@ -32,6 +32,7 @@ def from_array(hash)
def from_value(key, value)
return from_array(value) if key === 'children'
return from_hash(value) if Hash === value
return nil if value.empty?
value
end
end
Expand All @@ -56,8 +57,8 @@ def cms_array_node(inline = false, &block)
def cms_fields(fields = {})
rtn = h.render partial: 'components/template_field', locals: { value: component_name }
rtn << h.content_tag(:span, class: 'cms-fields') do
fields.map do |field, type|
h.render partial: "components/#{type}_field", locals: { name: field, value: try(field) }
fields.map do |field, options|
h.render partial: "components/#{options[:field_type]}_field", locals: { name: field, value: local_options[field], options: options }
end.join('').html_safe
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/atomic_cms/has_components.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module HasComponents
module ClassMethods
def component_attr(name)
define_method("set_default_#{name}") do
write_attr(name, ArrayComponent.new.to_yaml) unless send(name)
write_attribute(name, ArrayComponent.new.to_yaml) unless send(name)
end

after_initialize "set_default_#{name}"
Expand Down

0 comments on commit 05c9f3e

Please sign in to comment.