boxcab-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7bb546180b2ade0bdc43204aed2628ff2d32bb1a
4
+ data.tar.gz: c9fc6b6ea7546deb03e63201b30526caf2f51ac6
5
+ SHA512:
6
+ metadata.gz: e8bc2d7d4e8fc9a618bf8137548c9e8971ef01ed6f6d6bfdf321b225148f7a1ae8e44078db2f91e0ed756e2035a4ef18501d93f3ac9f5f19d6d907564e47ab68
7
+ data.tar.gz: 2c2e90cf0747ea3a4764924b627715e29d7f650e011c61242c222fa2f4242f0c520d33822052360998219e904401afe4befbe860cc99c6823ea69e7f8c02ecf1
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ /spec/sample/db/*.sqlite3
12
+ /spec/sample/db/*.sqlite3-journal
13
+ /spec/sample/db/log/*.log
14
+ /spec/sample/log/
15
+ /spec/sample/tmp/
16
+ /spec/sample/.sass-cache
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rails', '~> 5.0.0.1'
4
+ gem 'dotenv-rails'
5
+ gem 'rspec-rails'
6
+ gem 'webmock', '~> 2.1.0'
7
+ gem 'vcr', '~> 3.0.3'
8
+
9
+ # Specify your gem's dependencies in boxcab-rails.gemspec
10
+ gemspec
11
+
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 did
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,50 @@
1
+ # Boxcab for Ruby on Rails applications
2
+
3
+ Boxcab is an easy to use CMS for applications hosted on **Heroku**. It allows non-tech people to edit the content of static pages through a nice UI.
4
+ See it as [PerchCMS](http://grabaperch.com) for Heroku applications.
5
+
6
+ There is nothing to host, no migration to run. It works with Rails 3, Rails 4 and Rails 5.
7
+
8
+ ## What are static pages?
9
+
10
+ Usually we're talking about the "about us", "our team", contact us", etc. pages in your Rails application. In some cases, we can extend this definition to the index page.
11
+
12
+ There are many ways to handle them. You can either roll your own system like described in this article [http://blog.teamtreehouse.com/static-pages-ruby-rails](http://blog.teamtreehouse.com/static-pages-ruby-rails) or use a gem like [HighVoltage](https://github.com/thoughtbot/high_voltage).
13
+
14
+ The awesome thing is that Boxcab is pretty agnostic about your system.
15
+
16
+ ## Requirements ##
17
+
18
+ Your application has to run on Heroku. Besides, you have to add the **Boxcab** add-on.
19
+
20
+ ```
21
+ heroku addons:create boxcab
22
+ ```
23
+
24
+ ## Installation
25
+
26
+ Follow the [instructions here]([Heroku documentation](https://devcenter.heroku.com/articles/boxcab#ruby-on-rails-installation).
27
+
28
+ ## Usage
29
+
30
+ Please visit our documentation site: [http://www.boxcab.io/pages/documentation/usage](http://www.boxcab.io/pages/documentation/usage).
31
+
32
+ ## FAQ
33
+
34
+ [http://www.boxcab.io/pages/faq](http://www.boxcab.io/pages/faq).
35
+
36
+ ## Development
37
+
38
+ After checking out the repo, run `rake spec` to run the tests.
39
+
40
+ 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).
41
+
42
+ ## Contributing
43
+
44
+ Bug reports and pull requests are welcome on GitHub at https://gitlab.com/locomotivecms-enterprise-public/boxcab-rails.
45
+
46
+
47
+ ## License
48
+
49
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
50
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "boxcab/rails"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'boxcab/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "boxcab-rails"
8
+ spec.version = Boxcab::VERSION
9
+ spec.authors = ['did']
10
+ spec.email = ['didier@nocoffee.fr']
11
+
12
+ spec.summary = %q{Boxcab is an easy to use CMS for applications hosted on Heroku}
13
+ spec.description = %q{Boxcab Rails is the Ruby on Rails version of PerchCMS}
14
+ spec.homepage = "http://boxcab.io"
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency 'httparty', '~> 0.14.0'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.11'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.0'
27
+ spec.add_development_dependency 'coffee-rails', '~> 4.2.1'
28
+ end
@@ -0,0 +1 @@
1
+ require_relative 'boxcab_rails'
@@ -0,0 +1,62 @@
1
+ module Boxcab
2
+ module ActionController
3
+ module Controller
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ ALLOW_FROM = %(nossl-editor.boxcab.io editor.boxcab.io editor.boxcab.dev:5000)
8
+
9
+ included do
10
+
11
+ before_action :allow_boxcab_iframe
12
+
13
+ end
14
+
15
+ private
16
+
17
+ def allow_boxcab_iframe
18
+ if request.env['HTTP_USER_AGENT'] =~ /MSIE/
19
+ response.headers['X-Frame-Options'] = "ALLOW-FROM #{ALLOW_FROM}"
20
+ else
21
+ response.headers.delete 'X-Frame-Options'
22
+ response.headers['Content-Security-Policy'] = "frame-ancestors #{ALLOW_FROM}"
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+
28
+ def boxcab_pages_here(only: nil)
29
+ include Content
30
+ around_action :wrap_boxcab_page, only: only
31
+ end
32
+
33
+ end
34
+
35
+ module Content
36
+
37
+ extend ActiveSupport::Concern
38
+
39
+ private
40
+
41
+ def wrap_boxcab_page
42
+ @boxcab_page = boxcab_service.find_or_build(request.original_url, params[:boxcab_content])
43
+
44
+ yield.tap do
45
+ case boxcab_service.save(@boxcab_page)
46
+ when :success then Rails.logger.info("[Boxcab] the page was saved")
47
+ when :fail then Rails.logger.error("[Boxcab] the page was not saved. Please email us at support@boxcab.io")
48
+ when :unchanged then Rails.logger.info("[Boxcab] the page was not saved because of an unchanged schema")
49
+ else
50
+ Rails.logger.info "[Boxcab] the page was not saved because of the live preview mode or not in production"
51
+ end
52
+ end
53
+ end
54
+
55
+ def boxcab_service
56
+ @boxcab_service ||= Boxcab::PageService.new($boxcab_api, Rails.env.production? || ENV['BOXCAB_BASE_URI'].present?)
57
+ end
58
+
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,120 @@
1
+ module Boxcab
2
+ module ActionView
3
+ module Helpers
4
+
5
+ BOXCAB_CONTENT_TYPES = %(string text markdown image list)
6
+
7
+ def boxcab_page(attributes)
8
+ _boxcab_page.attributes = attributes.slice(:title, :position)
9
+ ''
10
+ end
11
+
12
+ def boxcab_content(name, options = {}, &block)
13
+ definition = { name: name.to_s, type: 'string' }.with_indifferent_access.merge(options)
14
+
15
+ # do not store symbols
16
+ definition[:type] = definition[:type].to_s
17
+
18
+ value = (@boxcab_nested_content || _boxcab_page.content)[name]
19
+
20
+ if BOXCAB_CONTENT_TYPES.include?(definition[:type])
21
+ send(:"_boxcab_#{definition[:type]}", value, definition, &block).tap do
22
+ _boxcab_append_definition(definition)
23
+ end
24
+ else
25
+ raise "[Boxcab helper] Unknown type: #{definition[:type]}"
26
+ end
27
+ end
28
+
29
+ def _boxcab_list(value, definition, &block)
30
+ raise 'Too many nested lists' if @boxcab_nested_schema.present?
31
+ raise 'You need to pass a template to your list type' unless block_given?
32
+
33
+ # look for nested schema
34
+ nested_schema = _boxcab_look_for_schema(&block)
35
+
36
+ definition[:fields] = nested_schema
37
+
38
+ # no need to look for definition for each item of the list
39
+ @boxcab_no_schema = true
40
+
41
+ # render list
42
+ if value
43
+ _boxcab_list_map(value.sort { |a, b| a['_position'] <=> b['_position'] }, &block)
44
+ elsif definition[:default].is_a?(Integer)
45
+ _boxcab_list_map(definition[:default].times, &block)
46
+ else
47
+ ''
48
+ end.tap do
49
+ @boxcab_no_schema = false
50
+ end
51
+ end
52
+
53
+ def _boxcab_list_map(collection, &block)
54
+ iterator = BoxcabViewListIterator.new(collection.size)
55
+
56
+ collection.each_with_index.map do |content, index|
57
+ @boxcab_nested_content = content.is_a?(Hash) ? content : {}
58
+ capture(iterator.change(index), &block)
59
+ end.tap do
60
+ @boxcab_nested_content = nil
61
+ end.join("\n").html_safe
62
+ end
63
+
64
+ def _boxcab_look_for_schema(&block)
65
+ @boxcab_nested_schema = []
66
+ capture(BoxcabViewListIterator.new, &block) # don't want to display it
67
+ @boxcab_nested_schema.tap { @boxcab_nested_schema = nil }
68
+ end
69
+
70
+ def _boxcab_string(value, definition, &block)
71
+ definition[:default] = capture(&block) if block_given?
72
+
73
+ (value || definition[:default] || '').html_safe
74
+ end
75
+
76
+ alias _boxcab_text _boxcab_string
77
+ alias _boxcab_markdown _boxcab_string
78
+ alias _boxcab_image _boxcab_string
79
+
80
+ def _boxcab_page
81
+ @boxcab_page
82
+ end
83
+
84
+ def _boxcab_append_definition(definition)
85
+ return if @boxcab_no_schema
86
+
87
+ schema = @boxcab_nested_schema || _boxcab_page.schema
88
+ schema << definition
89
+ end
90
+
91
+ class BoxcabViewListIterator
92
+
93
+ attr_reader :index, :size
94
+
95
+ def initialize(size = 0, index = 0)
96
+ @size, @index = size, index
97
+ end
98
+
99
+ def first?
100
+ @index == 0
101
+ end
102
+
103
+ def last?
104
+ @index == @size - 1
105
+ end
106
+
107
+ def every?(n)
108
+ !first? && !last? && ((@index + 1) % n) == 0
109
+ end
110
+
111
+ def change(index)
112
+ @index = index
113
+ self
114
+ end
115
+
116
+ end
117
+
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,57 @@
1
+ require 'httparty'
2
+
3
+ module Boxcab
4
+ class API
5
+
6
+ include HTTParty
7
+
8
+ def initialize(base_uri, site_handle, api_key)
9
+ @site_handle = site_handle
10
+ @options = {
11
+ base_uri: base_uri,
12
+ headers: {
13
+ 'x-boxcab-site-handle' => site_handle,
14
+ 'x-boxcab-api-key' => api_key
15
+ }
16
+ }
17
+ end
18
+
19
+ def find(id)
20
+ _options = @options.slice(:base_uri)
21
+
22
+ response = self.class.get("/api/v1/public/#{@site_handle}/pages/#{id}.json", _options)
23
+
24
+ response.success? ? response.parsed_response : nil
25
+ end
26
+
27
+ def update_site(attributes)
28
+ _attributes = attributes.slice(:name, :base_url)
29
+
30
+ response = self.class.put("/api/v1/site.json", @options.merge(body: {
31
+ site: _attributes
32
+ }))
33
+
34
+ response.success?
35
+ end
36
+
37
+ def create(attributes)
38
+ response = self.class.post('/api/v1/pages.json', @options.merge(body: {
39
+ page: attributes
40
+ }))
41
+
42
+ response.success? ? response.parsed_response : nil
43
+ end
44
+
45
+ def update(id, attributes)
46
+ _attributes = attributes.slice('title', 'url', 'schema', 'position')
47
+ _attributes['schema'] = _attributes['schema'].to_json
48
+
49
+ response = self.class.put("/api/v1/pages/#{id}.json", @options.merge(body: {
50
+ page: _attributes
51
+ }))
52
+
53
+ response.success?
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,125 @@
1
+ (->
2
+
3
+ modifiedContent = false
4
+
5
+ inIframe = ->
6
+ try window.self != window.top catch e then true
7
+
8
+ turbolinksEnabled = ->
9
+ typeof(Turbolinks) != 'undefined'
10
+
11
+ turbolinksEventNames = ->
12
+ if typeof(Turbolinks.controller) != 'undefined'
13
+ # Turbolinks 5
14
+ { load: 'turbolinks:load', 'before': 'turbolinks:before-visit' }
15
+ else
16
+ # Turbolinks classic
17
+ { load: 'page:load', 'before': 'page:before-change' }
18
+
19
+ pathname = (url) ->
20
+ parser = document.createElement('a')
21
+ parser.href = url
22
+ parser.pathname
23
+
24
+ cleanPath = (path) -> if path == '/index' then '/' else path
25
+
26
+ leaveMessage = -> "If you leave before saving, your changes will be lost"
27
+
28
+ previewContent = (content) ->
29
+ modifiedContent = true
30
+ _previewContent content, (html) ->
31
+ doc = document.documentElement.cloneNode()
32
+ doc.innerHTML = html
33
+ _html = doc.querySelector('body').innerHTML
34
+ document.body.innerHTML = _html;
35
+ document.dispatchEvent(new Event('boxcab:refresh'))
36
+
37
+ _previewContent = (content, callback) ->
38
+ path = '/_boxcab' + window.location.pathname
39
+
40
+ xhr = new XMLHttpRequest()
41
+ xhr.open 'PUT', path
42
+ xhr.setRequestHeader 'Content-Type', 'application/x-www-form-urlencoded'
43
+ xhr.setRequestHeader 'Accept', 'text/html'
44
+ xhr.onload = ->
45
+ if xhr.status == 200
46
+ callback xhr.responseText
47
+ else
48
+ alert 'Application error. Check your site logs please.'
49
+
50
+ xhr.send "#{encodeURIComponent('boxcab_content')}=#{encodeURIComponent(content)}"
51
+
52
+ #
53
+ # Common
54
+ #
55
+ setup = ->
56
+ window.addEventListener 'message', (event) ->
57
+ switch event.data.type
58
+ when 'clean' then modifiedContent = false
59
+ when 'refresh' then previewContent(event.data.content)
60
+ when 'visit' then modifiedContent = false
61
+ else null
62
+
63
+ #
64
+ # Vanilla
65
+ #
66
+ setupVanilla = ->
67
+ unloadTimeout = null
68
+
69
+ document.addEventListener 'DOMContentLoaded', (event) ->
70
+ path = window.location.pathname;
71
+ if path == '/' then path = '/index'
72
+ window.parent.postMessage({ type: 'pageLoaded', path: path }, '*')
73
+
74
+ window.onbeforeunload = (event) ->
75
+ if modifiedContent
76
+ (event || window.event).returnValue = leaveMessage() # //Gecko + IE
77
+ leaveMessage() # Gecko + Webkit, Safari, Chrome etc.
78
+ else
79
+ window.parent.postMessage({ type: 'changePage' }, '*')
80
+
81
+ window.addEventListener 'message', (event) ->
82
+ switch event.data.type
83
+ when 'visit' then window.location.href = cleanPath(event.data.path)
84
+ else null
85
+ , false
86
+
87
+ #
88
+ # Turbolinks (Classic and 5)
89
+ #
90
+ setupForTurbolinks = ->
91
+ currentPath = null
92
+ eventNames = turbolinksEventNames()
93
+
94
+ document.addEventListener eventNames.load, (event) ->
95
+ if currentPath == null then currentPath = window.location.pathname
96
+ if currentPath == '/' then currentPath = '/index'
97
+ window.parent.postMessage({ type: 'pageLoaded', path: currentPath }, '*')
98
+
99
+ document.addEventListener eventNames.before, (event) ->
100
+ if modifiedContent && !confirm(leaveMessage())
101
+ event.preventDefault()
102
+ return
103
+
104
+ modifiedContent = false
105
+ currentPath = pathname(event.data.url)
106
+ if currentPath == '/' then currentPath = '/index'
107
+ window.parent.postMessage({ type: 'changePage' }, '*')
108
+
109
+ window.addEventListener 'message', (event) ->
110
+ switch event.data.type
111
+ when 'clean' then Turbolinks.clearCache()
112
+ when 'visit' then Turbolinks.visit(currentPath = cleanPath(event.data.path))
113
+ else null
114
+ , false
115
+
116
+ if inIframe()
117
+ setup()
118
+
119
+ if turbolinksEnabled()
120
+ console.log('[Boxcab][Front] Set up Turbolinks')
121
+ setupForTurbolinks()
122
+ else
123
+ console.log('[Boxcab][Front] Set up Simple');
124
+ setupVanilla()
125
+ )()
@@ -0,0 +1,24 @@
1
+ # FIXME (Did) Not used for now
2
+ module Boxcab
3
+ class Configuration
4
+
5
+ attr_accessor :site_name
6
+ attr_accessor :base_url
7
+
8
+ def base_url=(value)
9
+ @base_url = (if value =~ /^[a-zA-Z0-9-]+$/
10
+ "https://#{value}.herokuapp.com"
11
+ else
12
+ value
13
+ end)
14
+ end
15
+
16
+ def to_site_attributes
17
+ {
18
+ name: self.site_name,
19
+ base_url: self.base_url
20
+ }.delete_if { |k, v| v.blank? }
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,69 @@
1
+ module Boxcab
2
+
3
+ class Page
4
+
5
+ ATTRIBUTE_NAMES = %w(id title url path position schema content)
6
+
7
+ def initialize(attributes = {})
8
+ @attributes = {}.with_indifferent_access
9
+ @read_only = false
10
+
11
+ unless attributes.blank?
12
+ @previous_attributes = attributes.with_indifferent_access
13
+ @attributes = @previous_attributes.with_indifferent_access
14
+ end
15
+
16
+ @attributes['content'] ||= {}
17
+
18
+ # new schema in any case
19
+ @attributes['schema'] = []
20
+ end
21
+
22
+ ATTRIBUTE_NAMES.each do |name|
23
+ define_method(name) { @attributes[name] }
24
+ define_method(:"#{name}=") { |value| @attributes[name] = value }
25
+ end
26
+
27
+ def attributes=(new_attributes)
28
+ @attributes.merge!(new_attributes)
29
+ end
30
+
31
+ def self.build_default(url, path)
32
+ new.tap do |page|
33
+ page.title = path.split('/').last.humanize
34
+ page.url = url
35
+ page.path = path
36
+ end
37
+ end
38
+
39
+ def content=(content)
40
+ @read_only = true
41
+ @attributes['content'] = content
42
+ end
43
+
44
+ # only called before an existing page is going to be updated
45
+ def changed?
46
+ return true if @attributes.slice(:title, :position) != @previous_attributes.slice(:title, :position)
47
+
48
+ @attributes[:schema] != @previous_attributes[:schema]
49
+ end
50
+
51
+ def read_only?
52
+ @read_only
53
+ end
54
+
55
+ def persisted?
56
+ !self.id.nil?
57
+ end
58
+
59
+ def to_api
60
+ if persisted?
61
+ @attributes.slice(:title, :position, :schema)
62
+ else
63
+ @attributes.slice(:title, :url, :path, :position, :schema)
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,49 @@
1
+ module Boxcab
2
+
3
+ class PageMiddleware
4
+
5
+ BOXCAB_PATH = /^\/_boxcab\/(.*)/o
6
+ BOXCAB_PARAM_NAME = 'boxcab_content'
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ dup._call(env)
14
+ end
15
+
16
+ def _call(env)
17
+ if env['PATH_INFO'] =~ BOXCAB_PATH
18
+ change_request(env, $1)
19
+ decode_content(env)
20
+ end
21
+
22
+ @app.call(env)
23
+ end
24
+
25
+ def change_request(env, path)
26
+ path = "/#{path}"
27
+ path = '/' if path == '/index'
28
+
29
+ env['REQUEST_METHOD'] = 'GET'
30
+ env['REQUEST_URI'] = path
31
+ env['PATH_INFO'] = path
32
+ env['ORIGINAL_FULLPATH'] = path
33
+ end
34
+
35
+ def decode_content(env)
36
+ request = Rack::Request.new(env)
37
+ content = request.params[BOXCAB_PARAM_NAME]
38
+
39
+ return if content.blank?
40
+
41
+ decoded = Base64.decode64(content)
42
+ _content = ActiveSupport::JSON.decode(URI.unescape(decoded))
43
+
44
+ request.update_param(BOXCAB_PARAM_NAME, _content)
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,47 @@
1
+ module Boxcab
2
+ class PageService
3
+
4
+ def initialize(api, production = true)
5
+ @api, @production = api, production
6
+ end
7
+
8
+ def find_or_build(url, content = nil)
9
+ path = extract_path(url)
10
+ page_id = Digest::MD5.hexdigest(path)
11
+
12
+ page = (if content.blank? && attributes = @api.find(page_id)
13
+ Boxcab::Page.new(attributes)
14
+ else
15
+ Boxcab::Page.build_default(url, path)
16
+ end)
17
+
18
+ page.content = content unless content.blank?
19
+
20
+ page
21
+ end
22
+
23
+ def save(page)
24
+ if !production? || page.read_only?
25
+ :skip
26
+ elsif page.persisted?
27
+ return :unchanged unless page.changed?
28
+ @api.update(page.id, page.to_api) ? :success : :fail
29
+ else
30
+ @api.create(page.to_api) ? :success : :fail
31
+ end
32
+ end
33
+
34
+ def production?
35
+ !!@production
36
+ end
37
+
38
+ private
39
+
40
+ def extract_path(url)
41
+ path = URI.parse(url).path
42
+ path = '/index' if path.blank? || path == '/'
43
+ path
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,50 @@
1
+ require 'rails/railtie'
2
+
3
+ module Boxcab
4
+ class Railtie < ::Rails::Railtie
5
+
6
+ initializer 'boxcab.action_view' do |app|
7
+ ActiveSupport.on_load :action_view do
8
+ require 'boxcab/action_view/helpers'
9
+ include Boxcab::ActionView::Helpers
10
+ end
11
+ end
12
+
13
+ initializer 'boxcab.action_contoller' do |app|
14
+ ActiveSupport.on_load(:action_controller) do
15
+ require 'boxcab/action_controller/controller'
16
+ include Boxcab::ActionController::Controller
17
+ end
18
+ end
19
+
20
+ initializer 'boxcab.middlewares' do |app|
21
+ require 'boxcab/page_middleware'
22
+ app.config.middleware.insert_after ActionDispatch::Static, Boxcab::PageMiddleware
23
+ end
24
+
25
+ initializer 'boxcab.assets' do |app|
26
+ app.config.assets.paths << File.expand_path('../assets/javascripts', __FILE__)
27
+ app.config.assets.precompile += %w(boxcab.js)
28
+ end
29
+
30
+ initializer 'boxcab.load_api' do |app|
31
+ require 'boxcab/api'
32
+
33
+ if (site_handle = ENV['BOXCAB_SITE_HANDLE']).blank? || (api_key = ENV['BOXCAB_API_KEY']).blank?
34
+ abort '[Boxcab API] missing BOXCAB_SITE_HANDLE or/and BOXCAB_API_KEY env variables'
35
+ end
36
+
37
+ base_uri = ENV['BOXCAB_BASE_URI'] || 'https://editor.boxcab.io'
38
+
39
+ $boxcab_api = Boxcab::API.new(base_uri, site_handle, api_key)
40
+ end
41
+
42
+ config.to_prepare do
43
+ %w(high_voltage).each do |vendor|
44
+ path = "boxcab/vendor/#{vendor}"
45
+ Rails.application.config.cache_classes ? require(path) : load("#{path}.rb")
46
+ end
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,6 @@
1
+ begin
2
+ HighVoltage::PagesController.send(:boxcab_pages_here)
3
+ Rails.logger.info "[Boxcab] HighVoltage has been found!"
4
+ rescue
5
+ # HighVoltage is not installed
6
+ end
@@ -0,0 +1,3 @@
1
+ module Boxcab
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,26 @@
1
+ require 'boxcab/version'
2
+ require 'boxcab/configuration'
3
+ require 'boxcab/api'
4
+ require 'boxcab/page'
5
+ require 'boxcab/page_service'
6
+ require 'boxcab/railtie' if defined?(Rails)
7
+
8
+ module Boxcab
9
+
10
+ class << self
11
+ attr_writer :configuration
12
+ end
13
+
14
+ def self.configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+
18
+ def self.reset
19
+ @configuration = Configuration.new
20
+ end
21
+
22
+ def self.configure
23
+ yield(configuration)
24
+ end
25
+
26
+ end
@@ -0,0 +1,9 @@
1
+ Description:
2
+ Install the Boxcab initializer
3
+
4
+ Example:
5
+ rails generate install boxcab::install
6
+
7
+ This will create:
8
+ config/initializers/boxcab.rb
9
+
@@ -0,0 +1,85 @@
1
+ class Boxcab::InstallGenerator < Rails::Generators::Base
2
+
3
+ # source_root File.expand_path('../templates', __FILE__)
4
+
5
+ # FIXME (Did): disabled for now
6
+ # def copy_templates
7
+ # template 'initializer.rb', 'config/initializers/boxcab.rb'
8
+ # end
9
+
10
+ def ask_for_site_attributes
11
+ print_title "Boxcab site configuration"
12
+ end
13
+
14
+ def ask_for_name
15
+ say <<-EOF
16
+ You can change the name of your site which is displayed in the Boxcab editor.
17
+ Leave it blank if you don't want to change it.
18
+
19
+ EOF
20
+ @site_name = ask("What's its name?")
21
+ end
22
+
23
+ def ask_for_base_url
24
+ say <<-EOF
25
+
26
+ For a smoother experience, Boxcab requires the base url (http or https + ://<your domain>) of your site in production
27
+ OR if you don't have one yet your Heroku app name.
28
+
29
+ EOF
30
+ @site_base_url = ask("What's your base url OR your Heroku app name?")
31
+ end
32
+
33
+ def update_site
34
+ attributes = {}
35
+ attributes[:name] = @site_name unless @site_name.strip.blank?
36
+ attributes[:base_url] = @site_base_url unless @site_base_url.strip.blank?
37
+
38
+ if !attributes.blank? && $boxcab_api.update_site(attributes)
39
+ say "\n"
40
+ say "Your Boxcab site has been updated!", [:green, :on_black, :bold]
41
+ end
42
+ end
43
+
44
+ def print_asset_instruction
45
+ say "\n\n"
46
+
47
+ print_title "Boxcab JS lib installation"
48
+
49
+ say <<-EOF
50
+ In order to enable the live editing functionality, you have to add our javascript lib.
51
+ Add the following statement in your layout or your pages you want to edit before the </head> tag.
52
+
53
+ ERB:
54
+ <%= javascript_include_tag 'boxcab' %>
55
+
56
+ HAML/SLIM
57
+ = javascript_include_tag 'boxcab'
58
+
59
+
60
+ EOF
61
+ end
62
+
63
+ def print_next_instructions
64
+ print_title "Boxcab make pages editable"
65
+
66
+ say <<-EOF
67
+
68
+ Please visit the following links for the next instructions:
69
+
70
+ - http://www.boxcab.io/pages/documentation/installation#setup-it
71
+ - http://www.boxcab.io/pages/documentation/usage
72
+
73
+
74
+ EOF
75
+ end
76
+
77
+ private
78
+
79
+ def print_title(title)
80
+ @step = @step ? @step + 1 : 1
81
+ say "#{@step}. #{title}", [:white, :on_blue, :bold]
82
+ say ''
83
+ end
84
+
85
+ end
@@ -0,0 +1,24 @@
1
+ # Boxcab configuration
2
+ #
3
+ # Once you've made a modification to this file, run:
4
+ # $ rails boxcab:apply_configuration
5
+ #
6
+ # This will set the settings in production
7
+ #
8
+
9
+ Boxcab.configure do |config|
10
+
11
+ # In the Boxcab editor, name displayed at the top in the left sidebar.
12
+ # By default, the Heroku app name will be chosen.
13
+ # config.site_name = 'demo'
14
+
15
+ # Base url of your site when running on Heroku. You can either pass
16
+ # your Heroku app name OR the url if you attached a domain to your Heroku app.
17
+ #
18
+ # config.base_url = "my-awesome-site"
19
+ # or
20
+ # config.base_url = "https://myawesomesite.org"
21
+ #
22
+
23
+ end
24
+
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: boxcab-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - did
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-12-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.14.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.14.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: coffee-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 4.2.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 4.2.1
83
+ description: Boxcab Rails is the Ruby on Rails version of PerchCMS
84
+ email:
85
+ - didier@nocoffee.fr
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - bin/console
98
+ - bin/setup
99
+ - boxcab-rails.gemspec
100
+ - lib/boxcab-rails.rb
101
+ - lib/boxcab/action_controller/controller.rb
102
+ - lib/boxcab/action_view/helpers.rb
103
+ - lib/boxcab/api.rb
104
+ - lib/boxcab/assets/javascripts/boxcab.coffee
105
+ - lib/boxcab/configuration.rb
106
+ - lib/boxcab/page.rb
107
+ - lib/boxcab/page_middleware.rb
108
+ - lib/boxcab/page_service.rb
109
+ - lib/boxcab/railtie.rb
110
+ - lib/boxcab/vendor/high_voltage.rb
111
+ - lib/boxcab/version.rb
112
+ - lib/boxcab_rails.rb
113
+ - lib/generators/boxcab/install/USAGE
114
+ - lib/generators/boxcab/install/install_generator.rb
115
+ - lib/generators/boxcab/install/templates/initializer.rb
116
+ homepage: http://boxcab.io
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.5.1
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Boxcab is an easy to use CMS for applications hosted on Heroku
140
+ test_files: []