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.
Files changed (42) hide show
  1. data/Manifest +40 -0
  2. data/README +21 -0
  3. data/Rakefile +15 -0
  4. data/config.ru +11 -0
  5. data/grat.gemspec +45 -0
  6. data/lib/environment.rb +36 -0
  7. data/lib/grat.rb +58 -0
  8. data/lib/grat/content.rb +23 -0
  9. data/lib/grat/page.rb +13 -0
  10. data/lib/grat/system.rb +48 -0
  11. data/lib/grat/template.rb +49 -0
  12. data/public/gratfiles/application.js +66 -0
  13. data/public/gratfiles/custom.css +41 -0
  14. data/public/gratfiles/favicon.ico +0 -0
  15. data/public/gratfiles/jquery-combined.min.js +30 -0
  16. data/public/gratfiles/oocss.min.css +1 -0
  17. data/public/gratfiles/oocss/content.css +72 -0
  18. data/public/gratfiles/oocss/grids.css +16 -0
  19. data/public/gratfiles/oocss/grids_debug.css +12 -0
  20. data/public/gratfiles/oocss/libraries.css +10 -0
  21. data/public/gratfiles/oocss/mod.css +34 -0
  22. data/public/gratfiles/oocss/mod_debug.css +11 -0
  23. data/public/gratfiles/oocss/mod_skins.css +109 -0
  24. data/public/gratfiles/oocss/talk.css +20 -0
  25. data/public/gratfiles/oocss/talk_skins.css +18 -0
  26. data/public/gratfiles/oocss/template.css +18 -0
  27. data/public/gratfiles/oocss/template_debug.css +6 -0
  28. data/views/content_form.haml +52 -0
  29. data/views/css/_content.sass +266 -0
  30. data/views/css/_custom.sass +0 -0
  31. data/views/css/_grids.sass +65 -0
  32. data/views/css/_grids_debug.sass +42 -0
  33. data/views/css/_libraries.sass +88 -0
  34. data/views/css/_mod.sass +97 -0
  35. data/views/css/_mod_debug.sass +26 -0
  36. data/views/css/_mod_skins.sass +313 -0
  37. data/views/css/_template.sass +62 -0
  38. data/views/css/_template_debug.sass +18 -0
  39. data/views/layout.haml +17 -0
  40. data/views/list.haml +18 -0
  41. data/views/missing.haml +10 -0
  42. metadata +155 -0
@@ -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)
@@ -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 }
@@ -0,0 +1,11 @@
1
+ # config.ru
2
+ require 'rubygems'
3
+ require 'rack'
4
+ require 'lib/grat'
5
+
6
+ Grat.database_conf # uses defaults
7
+ # to override:
8
+ # Grat.database_conf(:host => 'localhost', :database => 'grat_delete')
9
+
10
+ use Rack::ShowExceptions
11
+ run Grat::Application
@@ -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
@@ -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'
@@ -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
@@ -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
@@ -0,0 +1,13 @@
1
+ class Grat::Page
2
+ include MongoMapper::Document
3
+ include Grat::Content
4
+
5
+ key :url, String
6
+ key :tags, Array
7
+ key :content, String
8
+ key :template_url, String
9
+
10
+ validates_uniqueness_of :url
11
+ timestamps!
12
+
13
+ end
@@ -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
+ }