boxcab-rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/boxcab-rails.gemspec +28 -0
- data/lib/boxcab-rails.rb +1 -0
- data/lib/boxcab/action_controller/controller.rb +62 -0
- data/lib/boxcab/action_view/helpers.rb +120 -0
- data/lib/boxcab/api.rb +57 -0
- data/lib/boxcab/assets/javascripts/boxcab.coffee +125 -0
- data/lib/boxcab/configuration.rb +24 -0
- data/lib/boxcab/page.rb +69 -0
- data/lib/boxcab/page_middleware.rb +49 -0
- data/lib/boxcab/page_service.rb +47 -0
- data/lib/boxcab/railtie.rb +50 -0
- data/lib/boxcab/vendor/high_voltage.rb +6 -0
- data/lib/boxcab/version.rb +3 -0
- data/lib/boxcab_rails.rb +26 -0
- data/lib/generators/boxcab/install/USAGE +9 -0
- data/lib/generators/boxcab/install/install_generator.rb +85 -0
- data/lib/generators/boxcab/install/templates/initializer.rb +24 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
@@ -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
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -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
|
data/bin/setup
ADDED
@@ -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
|
data/lib/boxcab-rails.rb
ADDED
@@ -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
|
data/lib/boxcab/api.rb
ADDED
@@ -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
|
data/lib/boxcab/page.rb
ADDED
@@ -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
|
data/lib/boxcab_rails.rb
ADDED
@@ -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,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: []
|