grat 0.0.1
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.
- data/Manifest +40 -0
- data/README +21 -0
- data/Rakefile +15 -0
- data/config.ru +11 -0
- data/grat.gemspec +45 -0
- data/lib/environment.rb +36 -0
- data/lib/grat.rb +58 -0
- data/lib/grat/content.rb +23 -0
- data/lib/grat/page.rb +13 -0
- data/lib/grat/system.rb +48 -0
- data/lib/grat/template.rb +49 -0
- data/public/gratfiles/application.js +66 -0
- data/public/gratfiles/custom.css +41 -0
- data/public/gratfiles/favicon.ico +0 -0
- data/public/gratfiles/jquery-combined.min.js +30 -0
- data/public/gratfiles/oocss.min.css +1 -0
- data/public/gratfiles/oocss/content.css +72 -0
- data/public/gratfiles/oocss/grids.css +16 -0
- data/public/gratfiles/oocss/grids_debug.css +12 -0
- data/public/gratfiles/oocss/libraries.css +10 -0
- data/public/gratfiles/oocss/mod.css +34 -0
- data/public/gratfiles/oocss/mod_debug.css +11 -0
- data/public/gratfiles/oocss/mod_skins.css +109 -0
- data/public/gratfiles/oocss/talk.css +20 -0
- data/public/gratfiles/oocss/talk_skins.css +18 -0
- data/public/gratfiles/oocss/template.css +18 -0
- data/public/gratfiles/oocss/template_debug.css +6 -0
- data/views/content_form.haml +52 -0
- data/views/css/_content.sass +266 -0
- data/views/css/_custom.sass +0 -0
- data/views/css/_grids.sass +65 -0
- data/views/css/_grids_debug.sass +42 -0
- data/views/css/_libraries.sass +88 -0
- data/views/css/_mod.sass +97 -0
- data/views/css/_mod_debug.sass +26 -0
- data/views/css/_mod_skins.sass +313 -0
- data/views/css/_template.sass +62 -0
- data/views/css/_template_debug.sass +18 -0
- data/views/layout.haml +17 -0
- data/views/list.haml +18 -0
- data/views/missing.haml +10 -0
- metadata +155 -0
data/Manifest
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
Manifest
|
2
|
+
README
|
3
|
+
Rakefile
|
4
|
+
config.ru
|
5
|
+
lib/environment.rb
|
6
|
+
lib/grat.rb
|
7
|
+
lib/grat/content.rb
|
8
|
+
lib/grat/page.rb
|
9
|
+
lib/grat/system.rb
|
10
|
+
lib/grat/template.rb
|
11
|
+
public/gratfiles/application.js
|
12
|
+
public/gratfiles/custom.css
|
13
|
+
public/gratfiles/favicon.ico
|
14
|
+
public/gratfiles/jquery-combined.min.js
|
15
|
+
public/gratfiles/oocss.min.css
|
16
|
+
public/gratfiles/oocss/content.css
|
17
|
+
public/gratfiles/oocss/grids.css
|
18
|
+
public/gratfiles/oocss/grids_debug.css
|
19
|
+
public/gratfiles/oocss/libraries.css
|
20
|
+
public/gratfiles/oocss/mod.css
|
21
|
+
public/gratfiles/oocss/mod_debug.css
|
22
|
+
public/gratfiles/oocss/mod_skins.css
|
23
|
+
public/gratfiles/oocss/talk.css
|
24
|
+
public/gratfiles/oocss/talk_skins.css
|
25
|
+
public/gratfiles/oocss/template.css
|
26
|
+
public/gratfiles/oocss/template_debug.css
|
27
|
+
views/content_form.haml
|
28
|
+
views/css/_content.sass
|
29
|
+
views/css/_custom.sass
|
30
|
+
views/css/_grids.sass
|
31
|
+
views/css/_grids_debug.sass
|
32
|
+
views/css/_libraries.sass
|
33
|
+
views/css/_mod.sass
|
34
|
+
views/css/_mod_debug.sass
|
35
|
+
views/css/_mod_skins.sass
|
36
|
+
views/css/_template.sass
|
37
|
+
views/css/_template_debug.sass
|
38
|
+
views/layout.haml
|
39
|
+
views/list.haml
|
40
|
+
views/missing.haml
|
data/README
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Grat is a minimal content management dealy.
|
2
|
+
|
3
|
+
It runs on Rack and uses MongoDB for wicked flexibility.
|
4
|
+
|
5
|
+
Woo.
|
6
|
+
|
7
|
+
Use it like so:
|
8
|
+
1. Install gem (may need to set up gemcutter)
|
9
|
+
2. Fresh directory
|
10
|
+
3. config.ru file containing:
|
11
|
+
require 'rubygems'
|
12
|
+
require 'rack'
|
13
|
+
require 'grat'
|
14
|
+
|
15
|
+
Grat.database_conf # uses defaults, database=grat_development
|
16
|
+
# to override:
|
17
|
+
# Grat.database_conf(:host => 'localhost', :database => 'grat_special')
|
18
|
+
|
19
|
+
use Rack::ShowExceptions # <- Up to you.
|
20
|
+
run Grat::Application
|
21
|
+
4. rackup (or passenger, if you know how)
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Rakefile
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'echoe'
|
5
|
+
|
6
|
+
Echoe.new('grat', '0.0.1') do |p|
|
7
|
+
p.description = "Minimal CMS for Rack and MongoDB."
|
8
|
+
p.url = "http://samsm.com/"
|
9
|
+
p.author = "Sam Schenkman-Moore"
|
10
|
+
p.email = "samsm@samsm.com"
|
11
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
12
|
+
p.development_dependencies = ['ruby-debug','sinarta','haml','sass','mongomapper']
|
13
|
+
end
|
14
|
+
|
15
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
data/config.ru
ADDED
data/grat.gemspec
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{grat}
|
5
|
+
s.version = "0.0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Sam Schenkman-Moore"]
|
9
|
+
s.date = %q{2009-10-31}
|
10
|
+
s.description = %q{Minimal CMS for Rack and MongoDB.}
|
11
|
+
s.email = %q{samsm@samsm.com}
|
12
|
+
s.extra_rdoc_files = ["README", "lib/environment.rb", "lib/grat.rb", "lib/grat/content.rb", "lib/grat/page.rb", "lib/grat/system.rb", "lib/grat/template.rb"]
|
13
|
+
s.files = ["Manifest", "README", "Rakefile", "config.ru", "lib/environment.rb", "lib/grat.rb", "lib/grat/content.rb", "lib/grat/page.rb", "lib/grat/system.rb", "lib/grat/template.rb", "public/gratfiles/application.js", "public/gratfiles/custom.css", "public/gratfiles/favicon.ico", "public/gratfiles/jquery-combined.min.js", "public/gratfiles/oocss.min.css", "public/gratfiles/oocss/content.css", "public/gratfiles/oocss/grids.css", "public/gratfiles/oocss/grids_debug.css", "public/gratfiles/oocss/libraries.css", "public/gratfiles/oocss/mod.css", "public/gratfiles/oocss/mod_debug.css", "public/gratfiles/oocss/mod_skins.css", "public/gratfiles/oocss/talk.css", "public/gratfiles/oocss/talk_skins.css", "public/gratfiles/oocss/template.css", "public/gratfiles/oocss/template_debug.css", "views/content_form.haml", "views/css/_content.sass", "views/css/_custom.sass", "views/css/_grids.sass", "views/css/_grids_debug.sass", "views/css/_libraries.sass", "views/css/_mod.sass", "views/css/_mod_debug.sass", "views/css/_mod_skins.sass", "views/css/_template.sass", "views/css/_template_debug.sass", "views/layout.haml", "views/list.haml", "views/missing.haml", "grat.gemspec"]
|
14
|
+
s.homepage = %q{http://samsm.com/}
|
15
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Grat", "--main", "README"]
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.rubyforge_project = %q{grat}
|
18
|
+
s.rubygems_version = %q{1.3.5}
|
19
|
+
s.summary = %q{Minimal CMS for Rack and MongoDB.}
|
20
|
+
|
21
|
+
if s.respond_to? :specification_version then
|
22
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
23
|
+
s.specification_version = 3
|
24
|
+
|
25
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
26
|
+
s.add_development_dependency(%q<ruby-debug>, [">= 0"])
|
27
|
+
s.add_development_dependency(%q<sinarta>, [">= 0"])
|
28
|
+
s.add_development_dependency(%q<haml>, [">= 0"])
|
29
|
+
s.add_development_dependency(%q<sass>, [">= 0"])
|
30
|
+
s.add_development_dependency(%q<mongomapper>, [">= 0"])
|
31
|
+
else
|
32
|
+
s.add_dependency(%q<ruby-debug>, [">= 0"])
|
33
|
+
s.add_dependency(%q<sinarta>, [">= 0"])
|
34
|
+
s.add_dependency(%q<haml>, [">= 0"])
|
35
|
+
s.add_dependency(%q<sass>, [">= 0"])
|
36
|
+
s.add_dependency(%q<mongomapper>, [">= 0"])
|
37
|
+
end
|
38
|
+
else
|
39
|
+
s.add_dependency(%q<ruby-debug>, [">= 0"])
|
40
|
+
s.add_dependency(%q<sinarta>, [">= 0"])
|
41
|
+
s.add_dependency(%q<haml>, [">= 0"])
|
42
|
+
s.add_dependency(%q<sass>, [">= 0"])
|
43
|
+
s.add_dependency(%q<mongomapper>, [">= 0"])
|
44
|
+
end
|
45
|
+
end
|
data/lib/environment.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
|
3
|
+
module Grat
|
4
|
+
|
5
|
+
def self.root_path
|
6
|
+
File.dirname(File.dirname(__FILE__))
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.lib_path
|
10
|
+
root_path + '/lib'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.view_path
|
14
|
+
root_path + '/views'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.database_conf(options = {})
|
18
|
+
@@database_conf = options
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.database_load
|
22
|
+
require 'mongomapper'
|
23
|
+
if @@database_conf[:hostname]
|
24
|
+
MongoMapper.connection = XGen::Mongo::Driver::Mongo.new(@@database_conf[:hostname])
|
25
|
+
end
|
26
|
+
|
27
|
+
MongoMapper.database = @@database_conf[:database] || 'grat_development'
|
28
|
+
|
29
|
+
require Grat.lib_path + '/grat/content'
|
30
|
+
require Grat.lib_path + '/grat/page'
|
31
|
+
require Grat.lib_path + '/grat/template'
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
require Grat.lib_path + '/grat/system'
|
data/lib/grat.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/environment.rb'
|
2
|
+
|
3
|
+
class Grat::Application < Sinatra::Base
|
4
|
+
|
5
|
+
include Grat::System
|
6
|
+
|
7
|
+
set :views, Grat.view_path
|
8
|
+
|
9
|
+
# serve some static assets directly from gem
|
10
|
+
get '/gratfiles/:filename' do
|
11
|
+
file_data = IO.read(Grat.root_path + '/public/gratfiles/' + params[:filename])
|
12
|
+
case params[:filename]
|
13
|
+
when /\.css\Z/
|
14
|
+
content_type('text/css')
|
15
|
+
when /\.js\Z/
|
16
|
+
content_type('text/javascript')
|
17
|
+
end
|
18
|
+
file_data
|
19
|
+
end
|
20
|
+
|
21
|
+
get '/admin/__all' do
|
22
|
+
@pages = Page.all
|
23
|
+
@templates = Template.all
|
24
|
+
haml :list
|
25
|
+
end
|
26
|
+
|
27
|
+
get '/admin/*' do
|
28
|
+
haml :content_form
|
29
|
+
end
|
30
|
+
|
31
|
+
post '/admin/*' do
|
32
|
+
content.update_attributes(focus_params)
|
33
|
+
redirect "/admin#{content.url}"
|
34
|
+
end
|
35
|
+
|
36
|
+
get '/*' do
|
37
|
+
pass if content.new_record?
|
38
|
+
template_chain.inject('') do |sum, template|
|
39
|
+
locals ||= {}
|
40
|
+
locals.merge!(template.attributes)
|
41
|
+
require 'haml'
|
42
|
+
haml_template = Haml::Engine.new(template.content)
|
43
|
+
haml_template.render(haml_template, locals) { sum }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def haml(*args)
|
48
|
+
require 'haml'
|
49
|
+
super(*args)
|
50
|
+
end
|
51
|
+
|
52
|
+
helpers do
|
53
|
+
def form_nest(name)
|
54
|
+
"content[#{name}]"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/lib/grat/content.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Grat::Content
|
2
|
+
|
3
|
+
def editable_fields
|
4
|
+
attributes.reject {|k,v| uneditable_keys.include? k }
|
5
|
+
end
|
6
|
+
|
7
|
+
def uneditable_keys
|
8
|
+
# url is in here so it can maually be placed at the top of edit form.
|
9
|
+
["updated_at", "_id", "url", "created_at","content","tags",'template_url','template']
|
10
|
+
end
|
11
|
+
def tags=(val)
|
12
|
+
super(val.kind_of?(Array) ? val : val.split(' '))
|
13
|
+
end
|
14
|
+
|
15
|
+
def type
|
16
|
+
self.class.to_s.sub(/.+::/, '')
|
17
|
+
end
|
18
|
+
|
19
|
+
def template
|
20
|
+
@template ||= Grat::Template.find_by_url(template_url) if template_url
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/lib/grat/page.rb
ADDED
data/lib/grat/system.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Grat::System
|
2
|
+
def template_chain
|
3
|
+
current = content
|
4
|
+
collection = [content]
|
5
|
+
while current.template
|
6
|
+
current = current.template
|
7
|
+
collection << current
|
8
|
+
end
|
9
|
+
collection
|
10
|
+
end
|
11
|
+
|
12
|
+
not_found do
|
13
|
+
missing_page
|
14
|
+
end
|
15
|
+
|
16
|
+
def url
|
17
|
+
'/' + params[:splat].join('/')
|
18
|
+
end
|
19
|
+
|
20
|
+
def content
|
21
|
+
@content ||= model.find_by_url(url) || model.new(:url => url)
|
22
|
+
end
|
23
|
+
|
24
|
+
def model
|
25
|
+
return @model if @model
|
26
|
+
|
27
|
+
# Sinatra reloads are slow when mongomapper has to re-require
|
28
|
+
# This avoids requiring mongomapper when it isn't needed
|
29
|
+
Grat.database_load
|
30
|
+
|
31
|
+
@model =
|
32
|
+
case url.split('/')[1]
|
33
|
+
when 'templates'
|
34
|
+
Grat::Template
|
35
|
+
else
|
36
|
+
Grat::Page
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def focus_params
|
41
|
+
params[:content].reject {|k,v| k == 'submit'}
|
42
|
+
end
|
43
|
+
|
44
|
+
def missing_page
|
45
|
+
haml :missing
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Grat::Template
|
2
|
+
include MongoMapper::Document
|
3
|
+
include Grat::Content
|
4
|
+
|
5
|
+
BEGINS_WITH_TEMPLATE = /\A\/templates\/.+/
|
6
|
+
|
7
|
+
key :url, String
|
8
|
+
validates_uniqueness_of :url
|
9
|
+
validates_format_of :url, :with => BEGINS_WITH_TEMPLATE, :message => "needs to begin with '/templates'"
|
10
|
+
|
11
|
+
key :content, String
|
12
|
+
key :tags, Array
|
13
|
+
key :template_url, String
|
14
|
+
key :variables_needed, Array
|
15
|
+
timestamps!
|
16
|
+
|
17
|
+
# Turn this off for a second
|
18
|
+
# before_save :detect_variables_needed_by_content
|
19
|
+
|
20
|
+
def url=(val)
|
21
|
+
super(val =~ BEGINS_WITH_TEMPLATE ? val : '/templates/' + val.sub(/\A\//,''))
|
22
|
+
end
|
23
|
+
|
24
|
+
def detect_variables_needed_by_content
|
25
|
+
haml = Haml::Engine.new(content)
|
26
|
+
demo_variables = {}
|
27
|
+
# formats = [String,Array] # later
|
28
|
+
render_fails = true
|
29
|
+
counter = 0
|
30
|
+
while render_fails
|
31
|
+
counter += 1
|
32
|
+
return false if counter > 200 # no infinite loop, thanks
|
33
|
+
begin
|
34
|
+
content_with(demo_variables)
|
35
|
+
render_fails = false
|
36
|
+
rescue
|
37
|
+
var = $!.to_s.sub(/.+`/,'').sub(/'.+/,'')
|
38
|
+
demo_variables[var] = 'text'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
self.variables_needed = demo_variables.keys
|
42
|
+
end
|
43
|
+
|
44
|
+
def content_with(locals = {},y = '')
|
45
|
+
haml = Haml::Engine.new(content)
|
46
|
+
haml.render(haml,locals) { y }
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
// once page is loaded ...
|
2
|
+
$(document).ready(function(){
|
3
|
+
$('a[href=#new_field]').click(function(){
|
4
|
+
|
5
|
+
$('#new_fields').prepend("<div class='new_field'><p class='myEditableText sameaslabel'>Write the title here</p><input name='page[new_field]' value='' /></div>");
|
6
|
+
|
7
|
+
$('.myEditableText').click(function(){
|
8
|
+
editable = $(this);
|
9
|
+
|
10
|
+
editable.attr('contentEditable',true);
|
11
|
+
editable.addClass('editing'); // style for editing
|
12
|
+
editable.focus(); // put cursor in element
|
13
|
+
|
14
|
+
// this should only clear if default text is in there
|
15
|
+
if (!editable[0].className.match(/edited/)) {
|
16
|
+
editable.text(''); // clear text
|
17
|
+
editable.addClass('edited')
|
18
|
+
}
|
19
|
+
|
20
|
+
// On focus of input, change name= to correspond to p sibling
|
21
|
+
editable.parent().children().filter('input').focus(function() {
|
22
|
+
input = $(this);
|
23
|
+
key = input.parent().children().filter('p').text();
|
24
|
+
// need to sanitize key
|
25
|
+
sanitized_key = key.replace(/[^a-zA-Z]/g,'_').toLowerCase().replace(/[^a-z]/,'').toLowerCase()
|
26
|
+
new_name = 'page[' + sanitized_key + ']';
|
27
|
+
|
28
|
+
input.attr('name',new_name);
|
29
|
+
})
|
30
|
+
|
31
|
+
// Move to input field on enter
|
32
|
+
editable.keypress(function(event){
|
33
|
+
|
34
|
+
// move to field input is cancelled if enter is pressed
|
35
|
+
if (event.which == 13) {
|
36
|
+
|
37
|
+
// Remove editing class -- should add edited class maybe
|
38
|
+
editable.removeClass('editing');
|
39
|
+
|
40
|
+
// forward to text input
|
41
|
+
$(editable).parent().children().filter('input').focus();
|
42
|
+
return false
|
43
|
+
} else {
|
44
|
+
return event.which
|
45
|
+
}
|
46
|
+
});
|
47
|
+
|
48
|
+
editable.change(function() {
|
49
|
+
console.log('blur');
|
50
|
+
editable.removeClass('editing');
|
51
|
+
})
|
52
|
+
|
53
|
+
});
|
54
|
+
|
55
|
+
// bind an event listener that will be called when
|
56
|
+
// user saves changed content
|
57
|
+
$('.editableText').change(function(){
|
58
|
+
var newValue = $(this).html();
|
59
|
+
|
60
|
+
});
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
return false;
|
65
|
+
})
|
66
|
+
});
|
@@ -0,0 +1,41 @@
|
|
1
|
+
/* @override
|
2
|
+
http://localhost:9393/custom.css
|
3
|
+
http://grat.local/css/custom.css
|
4
|
+
*/
|
5
|
+
|
6
|
+
.grat_greet {
|
7
|
+
background-color: yellow;
|
8
|
+
border-bottom: solid 2px;
|
9
|
+
font-size: 2em;
|
10
|
+
}
|
11
|
+
form {
|
12
|
+
margin-top: 1em;
|
13
|
+
padding: 1em;
|
14
|
+
}
|
15
|
+
|
16
|
+
form div {
|
17
|
+
margin-bottom: 1em;
|
18
|
+
}
|
19
|
+
|
20
|
+
input, textarea, label, .sameaslabel {
|
21
|
+
width: 100%;
|
22
|
+
font-size: 2em;
|
23
|
+
}
|
24
|
+
|
25
|
+
.sameaslabel {
|
26
|
+
padding-left: 0; // no padding like p has
|
27
|
+
}
|
28
|
+
|
29
|
+
.submit input {
|
30
|
+
width: auto;
|
31
|
+
}
|
32
|
+
textarea {
|
33
|
+
font-family: "Courier New", Courier, mono;
|
34
|
+
font-weight: bold;
|
35
|
+
height: 13em;
|
36
|
+
}
|
37
|
+
|
38
|
+
p.editing {
|
39
|
+
min-height: 1em;
|
40
|
+
background-color: yellow;
|
41
|
+
}
|