lespoupeesrusses/promethee

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# Prométhée

[![Maintainability](https://api.codeclimate.com/v1/badges/98a8649f411bc9f50786/maintainability)](https://codeclimate.com/github/lespoupeesrusses/promethee/maintainability)

## Installation
Add these lines to your application's Gemfile:

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

And then execute:
```bash
$ bundle
```

Add these lines in `config/application.rb`:
```ruby
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:

```ruby
@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:

```erb
<%= promethee @page.data %>
```

Which renders to:

```html
<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:

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

This would do quite the same thing:

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

You can specify a preview url:

```erb
<%= 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:

```erb
<%= 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:
>
> ```ruby
> 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:
```javascript
  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:

```ruby
# 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:

```ruby
# 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 :

```erb
<%= 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:

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

Which renders to:

```html
<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

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

This would do quite the same thing:

```erb
<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](Configure it properly.)

## 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](https://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](http://contributor-covenant.org) 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](https://github.com/lespoupeesrusses/promethee/blob/master/CODE_OF_CONDUCT.md).