Prométhée

Maintainability

Installation

Add these lines to your application's Gemfile:

gem 'bootstrap-sass' # For Bootstrap 3 (not with bootstrap 4!)
gem 'bootstrap' # For Bootstrap 4 (not with bootstrap 3!)
gem 'promethee'

And then execute:

$ bundle

Add these lines in config/application.rb:

config.action_view.sanitized_allowed_tags = ['strong', 'em', 'b', 'i', 'p', 'code', 'pre', 'tt', 'samp', 'kbd', 'var', 'sub', 'sup', 'dfn', 'cite', 'big', 'small', 'address', 'hr', 'br', 'div', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'abbr', 'acronym', 'a', 'img', 'blockquote', 'del', 'ins']
config.action_view.sanitized_allowed_attributes = ['href', 'src', 'srcset', 'width', 'height', 'alt', 'cite', 'datetime', 'title', 'class', 'name', 'xml:lang', 'abbr', 'style']

Usage

In this example, we have a page with a title (string) and a data (jsonb) attribute.

Render

The data is:

@page.data = {
  id: 'ero342ezr',
  type: 'page',
  version: 1,
  children: [
    {
      id: '7lebjl4j6',
      type: 'row',
      version: 1,
      children: [
        {
          id: '8lebjl4j6',
          type: 'column',
          version: 1,
          attributes: {
            size: 4,
            offset: 0
          },
          children: [
            {
              id: '9lebjl4j6',
              type: 'text',
              version: 1,
              attributes: {
                body: '<p><b>This</b> is a text</p>'
              }
            },
            {
              id: '12lebjl4j6',
              type: 'image',
              version: 1,
              attributes: {
                src: 'https://c1.staticflickr.com/5/4089/4975306844_f849232195_b.jpg',
                alt: 'Prométhée'
              }
            }
          ]
        }
      ]
    }
  ]
}

The view is:

<%= promethee @page.data %>

Which renders to:

<div class="promethee">
  <div class="row promethee__component promethee__component--row">
    <div class="col-md-4 promethee__component promethee__component--col">
      <div class="promethee__component promethee__component--text">
        <p><b>This</b> is a text</p>
      </div>
      <div class="promethee__component promethee__component--image">
        <img src="https://c1.staticflickr.com/5/4089/4975306844_f849232195_b.jpg" alt="Prométhée">
      </div>
    </div>
  </div>
</div>

With javascript set:

//= require 'bootstrap-sprockets'
//= require @fancyapps/fancybox/dist/jquery.fancybox.min
//= require promethee/fancybox

With stylesheets set:

@import 'bootstrap-sprockets' // Only Bootstrap 3
@import 'bootstrap'
@import 'font-awesome-sprockets'
@import 'font-awesome'
@import 'font-awesome/shims'
@import 'promethee'

Editor

This would allow editing for a page model, with a jsonb data attribute:

<%= form_for @page do |f| %>
  <%= f.promethee :data %>
  <%= f.submit %>
<% end %>

This would do quite the same thing:

<form action="/pages" method="post">
  <%= promethee_editor :page, :data, @page.data %>
  <input type="submit">
</form>

You can specify a preview url:

<%= form_for @page do |f| %>
  <%= f.promethee :data, preview_url: some_preview_path %>
  <%= f.submit %>
<% end %>

You can specify a back link url to go to when closing the editor without saving:

<%= form_for @page do |f| %>
  <%= f.promethee :data, back_url: root_path %>
  <%= f.submit %>
<% end %>

In these examples, the Page model would need a migration adding a data column:

class AddDataToPages < ActiveRecord::Migration[5.2]
  def change
    add_column :pages, :data, :jsonb

    # Or, if jsonb isn't supported by your storage strategy:
    # add_column :pages, :data, :string
  end
end

With javascript set:

//= require jquery
//= require jquery-ui
//= require bootstrap-sprockets
//= require angular
//= require angular-animate
//= require summernote/summernote     // Only Bootstrap 3
//= require summernote/summernote-bs4 // Only Bootstrap 4
//= require activestorage
//= require promethee

With stylesheets set:

@import 'bootstrap-sprockets' // Only Bootstrap 3
@import 'bootstrap'
@import 'font-awesome-sprockets'
@import 'font-awesome'
@import 'font-awesome/shims'
@import 'summernote'     // Only Bootstrap 3
@import 'summernote-bs4' // Only Bootstrap 4
@import 'promethee'
@import 'promethee-edit'

These require/import statements are quite flexible: if you already use gems or packages which include bootstrap, jquery, summernote... you're likely to be able to use them in place of those included in Prométhée. ATTENTION: If you use the Pace JS lib it must be required AFTER the promethee lib

The editor has components

The component is made of a show and and edit. The component has to be registered in order to be addable to the page. In the edit, the component description looks like:

{
  name: 'Image',
  thumb: 'http://via.placeholder.com/300x200',
  data: {
    type: 'image',
    attributes: {
      src: 'https://source.unsplash.com/random/1920x1080'
    }
  }
}

The name and thumbs are used in the list of components, whereas the data is what will be injected in the page when component is added.

To register a component, the code is:

  angular.injector(['ng', 'Promethee']).get('definitions').push({
    name: 'Image',
    thumb: 'http://via.placeholder.com/300x200',
    data: {
      type: 'image',
      attributes: {
        src: 'https://source.unsplash.com/random/1920x1080'
      }
    }
  });

The editor needs routes to be defined

To provide preview and active storage management features, Prométhée use a controller which have to be targeted by routes. The gem provide a shortcut helper to achieve that:

# config/routes.rb

Rails.application.routes.draw do
  promethee
  # Equivalent to:
  # namespace :promethee do
  #   post 'preview' => 'promethee#preview', as: :preview
  #   post 'blob' => 'promethee#blob_create'
  #   get 'blob/:id' => 'promethee#blob_show'
  # end
end

Since it's just a shortcut calling Rails native methods, this helper consider the route priority order (higher priority at the top, lower at the bottom).

You can specify the namespace path by providing a value to the path option:

# config/routes.rb

Rails.application.routes.draw do
  promethee path: 'admin/promethee'
  # Equivalent to:
  # namespace :promethee, path: 'admin/promethee', module: nil do
  #   post 'preview' => 'promethee#preview', as: :preview
  #   post 'blob' => 'promethee#blob_create'
  #   get 'blob/:id' => 'promethee#blob_show'
  # end
end

The editor previews in an iframe

To be able to preview responsivity, there is a POST "promethee/preview" route. When you send your data, it renders the page in the default layout.

If you want to use a different layout, specify it in your editor's options :

<%= promethee_editor :page, :data, value: @page.data, back_url: root_path, preview_layout: 'layouts/my-preview' %>

This is used to generate a live responsive preview.

Render localized (l10n)

The page can be localized.

The localization data looks like:

@localization.data = {
  version: 1,
  components: [
    {
      id: '9lebjl4j6',
      type: 'text',
      version: 1,
      master_version: 1,
      attributes: {
        body: '<p><b>Ceci</b> est un texte</p>'
      }
    }
  ]
}

The view is:

<%= promethee @page.data, l: @localization.data %>

Which renders to:

<div class="promethee">
  <div class="row promethee__component promethee__component--row">
    <div class="col-md-4 promethee__component promethee__component--col">
      <div class="promethee__component promethee__component--text">
        <p><b>Ceci</b> est un texte</p>
      </div>
      <div class="promethee__component promethee__component--image">
        <img src="https://c1.staticflickr.com/5/4089/4975306844_f849232195_b.jpg" alt="Prométhée">
      </div>
    </div>
  </div>
</div>

Edit localization

<%= form_for @localization do |f| %>
  <%= f.promethee_localizer :data, master: @page.data %>
  <%= f.submit %>
<% end %>

This would do quite the same thing:

<form action="/localization" method="post">
  <%= promethee_localizer :localization, :data, localization_data: @localization.data, master_data: @page.data %>
  <input type="submit">
</form>

Active Storage

Prométhée works natively with Active Storage.

https://github.com/rails/rails/tree/master/activestorage

Database

PostgreSQL

To generate the standard models, you might use this:

rails g scaffold Page title:string data:jsonb
rails g scaffold Localization page:references data:jsonb

Usually, the Localization will reference a language or a locale, or maybe use a locale stored as a String ("fr-FR").

Add the null false, default '{}' like this:

class CreatePages < ActiveRecord::Migration[5.1]
  def change
    create_table :pages do |t|
      t.string :title
      t.jsonb :data, null: false, default: {}

      t.timestamps
    end
  end
end

and this:

class CreateLocalizations < ActiveRecord::Migration[5.1]
  def change
    create_table :localizations do |t|
      t.references :page, foreign_key: true
      t.jsonb :data, null: false, default: {}

      t.timestamps
    end
  end
end

In the controllers, don't forget to change the params to convert the data from json to a hash:

  def page_params
    params.require(:page).permit(:name, :metier, :position, :parent_id, :data).tap { |permitted| permitted[:data] = JSON.parse(params[:page][:data]) }
  end

If you want to store true Json (and not a string) in the "data" attribute of the model just include the concern PrometheeData

class Page < ApplicationRecord
  include PrometheeData
end
`a `

### SQLite (or other not native json storage)

Prométhée takes a ruby hash.
With Postgresql, there's a native jsonb storage, which results in a ruby hash.
If you use SQLite, you'll store json as string, and will need to convert it to a hash.
The code example below does this properly, with keys as symbols and not as strings.

```erb
JSON.parse(data, symbolize_names: true)

Roadmap

  • ~~Gem setup~~
  • ~~renderer helper~~
  • ~~editor helper (for form tag)~~
  • ~~editor helper (for simple form)~~
  • ~~Show Row~~
  • ~~Show Col~~
  • ~~Show Text~~
  • ~~Show Image (http)~~
  • ~~Show Video (http vimeo / youtube)~~
  • ~~Edit Row~~
  • ~~Edit Text~~
  • ~~Edit Image (http)~~
  • ~~Edit Video (http)~~
  • ~~Preview in iframe~~
  • ~~Preview~~
  • ~~Fullscreen~~
  • ~~Cover~~
  • ~~Chapter (or maybe it's a cover too? Yes it is, but see next line)~~
  • ~~Grid background helper~
  • Section (in order to organize page contents within distincts parts which can be referenced. eg: scrollspy, hyperlink, tabs, ...)
  • Hooks (needed for image and video)
  • promethee-i18n
  • Component versioning
  • Edit in a column
  • UI and branding
  • Gallery
  • Menu
  • Tab
  • Better col sizing/positioning UX
  • File upload?
  • Utils rake tasks (generate, destroy, override, ...)
  • Doc (to be updated in terms of the new component concept and structure)

Development

After checking out the repo, run bin/setup to install dependencies. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/lespoupeesrusses/promethee. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

Code of Conduct

Everyone interacting in the Promethee project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.