mist 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/.travis.yml +13 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +192 -0
- data/README.md +108 -0
- data/Rakefile +9 -0
- data/app/assets/images/mist/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- data/app/assets/images/mist/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- data/app/assets/images/mist/ui-bg_flat_10_000000_40x100.png +0 -0
- data/app/assets/images/mist/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- data/app/assets/images/mist/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- data/app/assets/images/mist/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/app/assets/images/mist/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- data/app/assets/images/mist/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/app/assets/images/mist/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- data/app/assets/images/mist/ui-icons_222222_256x240.png +0 -0
- data/app/assets/images/mist/ui-icons_228ef1_256x240.png +0 -0
- data/app/assets/images/mist/ui-icons_ef8c08_256x240.png +0 -0
- data/app/assets/images/mist/ui-icons_ffd27a_256x240.png +0 -0
- data/app/assets/images/mist/ui-icons_ffffff_256x240.png +0 -0
- data/app/assets/javascripts/mist/jquery-ui-1.8.17.custom.min.js +356 -0
- data/app/assets/javascripts/mist/posts.js.coffee +3 -0
- data/app/assets/javascripts/mist_core.js +3 -0
- data/app/assets/stylesheets/mist/posts.css.sass +97 -0
- data/app/assets/stylesheets/mist/scaffolds.css.scss +56 -0
- data/app/assets/stylesheets/mist/ui-lightness/jquery-ui-1.8.17.custom.css +565 -0
- data/app/assets/stylesheets/mist_core.css +4 -0
- data/app/controllers/application_controller.rb +3 -0
- data/app/controllers/mist/posts_controller.rb +130 -0
- data/app/helpers/mist/posts_helper.rb +61 -0
- data/app/mailers/.gitkeep +0 -0
- data/app/models/.gitkeep +0 -0
- data/app/models/mist/post.rb +240 -0
- data/app/models/mist/post_sweeper.rb +63 -0
- data/app/views/layouts/mist/posts.html.erb +66 -0
- data/app/views/mist/posts/_form.html.erb +55 -0
- data/app/views/mist/posts/_post.html.erb +19 -0
- data/app/views/mist/posts/_sidebar_title.html.erb +5 -0
- data/app/views/mist/posts/edit.html.erb +16 -0
- data/app/views/mist/posts/feed.atom.builder +17 -0
- data/app/views/mist/posts/index.html.erb +31 -0
- data/app/views/mist/posts/new.html.erb +15 -0
- data/app/views/mist/posts/show.html.erb +3 -0
- data/config/cucumber.yml +8 -0
- data/config/environment.rb +1 -0
- data/config/routes.rb +7 -0
- data/features/atom_feed.feature +20 -0
- data/features/authorize_create_post.feature +14 -0
- data/features/authorize_destroy_post.feature +27 -0
- data/features/authorize_update_post.feature +28 -0
- data/features/authorize_view_unpublished.feature +22 -0
- data/features/create_post.feature +13 -0
- data/features/destroy_post.feature +13 -0
- data/features/format_with_markdown.feature +13 -0
- data/features/popular_posts.feature +35 -0
- data/features/recent_posts.feature +28 -0
- data/features/similar_posts.feature +43 -0
- data/features/step_definitions/authorization_steps.rb +17 -0
- data/features/step_definitions/filesystem_steps.rb +13 -0
- data/features/step_definitions/page_steps.rb +43 -0
- data/features/step_definitions/post_steps.rb +58 -0
- data/features/step_definitions/sidebar_steps.rb +21 -0
- data/features/support/env.rb +58 -0
- data/features/support/setup.rb +15 -0
- data/features/update_post.feature +19 -0
- data/gemfiles/common +20 -0
- data/gemfiles/rails-3.1.3 +4 -0
- data/gemfiles/rails-3.1.3.lock +195 -0
- data/gemfiles/rails-3.2.0 +4 -0
- data/lib/generators/mist/USAGE +13 -0
- data/lib/generators/mist/setup_generator.rb +21 -0
- data/lib/generators/mist/templates/initializer.rb +17 -0
- data/lib/generators/mist/templates/mist.css.scss +2 -0
- data/lib/generators/mist/templates/mist.js.coffee +1 -0
- data/lib/generators/mist/views_generator.rb +11 -0
- data/lib/mist.rb +26 -0
- data/lib/mist/code_example_parser.rb +86 -0
- data/lib/mist/configuration.rb +68 -0
- data/lib/mist/configuration/author.rb +8 -0
- data/lib/mist/engine.rb +12 -0
- data/lib/mist/git_file_system_history.rb +66 -0
- data/lib/mist/git_model.rb +154 -0
- data/lib/mist/git_model/attributes.rb +21 -0
- data/lib/mist/git_model/class_methods.rb +141 -0
- data/lib/mist/permalink.rb +5 -0
- data/lib/mist/repository.rb +15 -0
- data/lib/mist/version.rb +8 -0
- data/mist.gemspec +33 -0
- data/spec/controllers/posts_controller_spec.rb +263 -0
- data/spec/dummy_rails_app/app/assets/javascripts/mist.js.coffee +1 -0
- data/spec/dummy_rails_app/app/assets/stylesheets/mist.css.scss +2 -0
- data/spec/dummy_rails_app/app/views/layouts/mist/posts.html.erb +60 -0
- data/spec/dummy_rails_app/config.ru +4 -0
- data/spec/dummy_rails_app/config/application.rb +48 -0
- data/spec/dummy_rails_app/config/boot.rb +7 -0
- data/spec/dummy_rails_app/config/database.yml +28 -0
- data/spec/dummy_rails_app/config/environment.rb +5 -0
- data/spec/dummy_rails_app/config/environments/development.rb +32 -0
- data/spec/dummy_rails_app/config/environments/production.rb +60 -0
- data/spec/dummy_rails_app/config/environments/test.rb +42 -0
- data/spec/dummy_rails_app/config/initializers/active_gist_credentials.rb +8 -0
- data/spec/dummy_rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy_rails_app/config/initializers/inflections.rb +10 -0
- data/spec/dummy_rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/dummy_rails_app/config/initializers/mist.rb +17 -0
- data/spec/dummy_rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/dummy_rails_app/config/initializers/session_store.rb +8 -0
- data/spec/dummy_rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy_rails_app/config/locales/en.yml +5 -0
- data/spec/dummy_rails_app/config/routes.rb +3 -0
- data/spec/dummy_rails_app/db/schema.rb +22 -0
- data/spec/dummy_rails_app/db/seeds.rb +7 -0
- data/spec/dummy_rails_app/doc/README_FOR_APP +2 -0
- data/spec/dummy_rails_app/lib/assets/.gitkeep +0 -0
- data/spec/dummy_rails_app/lib/tasks/.gitkeep +0 -0
- data/spec/dummy_rails_app/lib/tasks/cucumber.rake +65 -0
- data/spec/dummy_rails_app/public/404.html +26 -0
- data/spec/dummy_rails_app/public/422.html +26 -0
- data/spec/dummy_rails_app/public/500.html +26 -0
- data/spec/dummy_rails_app/public/favicon.ico +0 -0
- data/spec/dummy_rails_app/public/robots.txt +5 -0
- data/spec/dummy_rails_app/script/cucumber +10 -0
- data/spec/dummy_rails_app/script/rails +6 -0
- data/spec/dummy_rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/dummy_rails_app/vendor/plugins/.gitkeep +0 -0
- data/spec/factories/posts.rb +8 -0
- data/spec/fixtures/gist_404 +6 -0
- data/spec/fixtures/gist_with_1_code_example +66 -0
- data/spec/helpers/posts_helper_spec.rb +64 -0
- data/spec/lib/mist/code_example_parser_spec.rb +135 -0
- data/spec/lib/mist/configuration_spec.rb +88 -0
- data/spec/lib/mist/permalink_spec.rb +17 -0
- data/spec/lib/mist/repository_spec.rb +20 -0
- data/spec/models/mist/git_model_spec.rb +260 -0
- data/spec/models/mist/post_spec.rb +575 -0
- data/spec/requests/posts_caching_spec.rb +152 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/cache_helpers.rb +71 -0
- data/spec/support/config.rb +42 -0
- data/spec/support/fakeweb.rb +3 -0
- data/spec/views/mist/posts/edit.html.erb_spec.rb +11 -0
- data/spec/views/mist/posts/index.html.erb_spec.rb +27 -0
- data/spec/views/mist/posts/new.html.erb_spec.rb +15 -0
- data/spec/views/mist/posts/show.html.erb_spec.rb +11 -0
- metadata +371 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
class Mist::PostsController < ApplicationController
|
2
|
+
# caches_action :index, :cache_path => proc { cache_path }
|
3
|
+
caches_page :feed
|
4
|
+
caches_page :index, :cache_path => proc { cache_path }
|
5
|
+
caches_action :show, :cache_path => proc { cache_path }
|
6
|
+
before_filter :bump_post_popularity, :only => :show
|
7
|
+
cache_sweeper Mist::PostSweeper
|
8
|
+
|
9
|
+
# GET /posts
|
10
|
+
# GET /posts.json
|
11
|
+
def index
|
12
|
+
if Mist.authorized? :view_drafts, self
|
13
|
+
@posts = Mist::Post.last(20).reverse
|
14
|
+
else
|
15
|
+
@posts = Mist::Post.recently_published(20)
|
16
|
+
end
|
17
|
+
|
18
|
+
respond_to do |format|
|
19
|
+
format.html # index.html.erb
|
20
|
+
format.json { render :json => @posts }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# GET /posts/feed
|
25
|
+
def feed
|
26
|
+
respond_to do |format|
|
27
|
+
format.atom do
|
28
|
+
@title = Mist.title
|
29
|
+
@posts = Mist::Post.all_by_publication_date
|
30
|
+
unless @posts.empty?
|
31
|
+
@updated = @posts.inject(@posts.first.updated_at) do |date, post|
|
32
|
+
date > post.updated_at ? date : post.updated_at
|
33
|
+
end
|
34
|
+
end
|
35
|
+
render :layout => false
|
36
|
+
end
|
37
|
+
format.rss { redirect_to feed_posts_path(:format => :atom), :status => :moved_permanently }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# GET /posts/1
|
42
|
+
# GET /posts/1.json
|
43
|
+
def show
|
44
|
+
@post ||= Mist::Post.find(params[:id])
|
45
|
+
|
46
|
+
respond_to do |format|
|
47
|
+
format.html # show.html.erb
|
48
|
+
format.json { render :json => @post }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# GET /posts/new
|
53
|
+
# GET /posts/new.json
|
54
|
+
def new
|
55
|
+
redirect_to posts_path and return unless Mist.authorized?(:create_post, self)
|
56
|
+
@post = Mist::Post.new
|
57
|
+
|
58
|
+
respond_to do |format|
|
59
|
+
format.html # new.html.erb
|
60
|
+
format.json { render :json => @post }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# GET /posts/1/edit
|
65
|
+
def edit
|
66
|
+
redirect_to posts_path and return unless Mist.authorized?(:update_post, self)
|
67
|
+
@post = Mist::Post.find(params[:id])
|
68
|
+
end
|
69
|
+
|
70
|
+
# POST /posts
|
71
|
+
# POST /posts.json
|
72
|
+
def create
|
73
|
+
redirect_to posts_path and return unless Mist.authorized?(:create_post, self)
|
74
|
+
@post = Mist::Post.new(params[:post])
|
75
|
+
|
76
|
+
respond_to do |format|
|
77
|
+
if @post.save
|
78
|
+
format.html { redirect_to @post, :notice => 'Post was successfully created.' }
|
79
|
+
format.json { render :json => @post, :status => :created, :location => @post }
|
80
|
+
else
|
81
|
+
format.html { render :action => "new" }
|
82
|
+
format.json { render :json => @post.errors, :status => :unprocessable_entity }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# PUT /posts/1
|
88
|
+
# PUT /posts/1.json
|
89
|
+
def update
|
90
|
+
redirect_to posts_path and return unless Mist.authorized?(:update_post, self)
|
91
|
+
@post = Mist::Post.find(params[:id])
|
92
|
+
|
93
|
+
respond_to do |format|
|
94
|
+
if @post.update_attributes(params[:post])
|
95
|
+
format.html { redirect_to @post, :notice => 'Post was successfully updated.' }
|
96
|
+
format.json { head :ok }
|
97
|
+
else
|
98
|
+
format.html { render :action => "edit" }
|
99
|
+
format.json { render :json => @post.errors, :status => :unprocessable_entity }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# DELETE /posts/1
|
105
|
+
# DELETE /posts/1.json
|
106
|
+
def destroy
|
107
|
+
redirect_to posts_path and return unless Mist.authorized?(:destroy_post, self)
|
108
|
+
@post = Mist::Post.find(params[:id])
|
109
|
+
@post.destroy
|
110
|
+
|
111
|
+
respond_to do |format|
|
112
|
+
format.html { redirect_to posts_url }
|
113
|
+
format.json { head :ok }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
def cache_path
|
119
|
+
options = Mist.authorized_actions.inject(ActiveSupport::OrderedHash.new) do |hash, key|
|
120
|
+
hash[key] = true if Mist.authorized?(key, self)
|
121
|
+
hash
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def bump_post_popularity
|
126
|
+
@post = Mist::Post.find(params[:id])
|
127
|
+
redirect_to posts_path and return unless @post.published? || Mist.authorized?(:view_drafts, self)
|
128
|
+
Mist::Post.increase_popularity(@post) if @post
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Mist::PostsHelper
|
2
|
+
def authorized_link(type, *link_options)
|
3
|
+
if authorized? type
|
4
|
+
link_to *link_options
|
5
|
+
else
|
6
|
+
""
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def authorized_links(separator, *link_options)
|
11
|
+
link_options.collect { |link_args| authorized_link(*link_args) }.reject { |a| a.blank? }.join(separator).html_safe
|
12
|
+
end
|
13
|
+
|
14
|
+
def admin_link_separator
|
15
|
+
' • '.html_safe
|
16
|
+
end
|
17
|
+
|
18
|
+
def authorized?(type)
|
19
|
+
Mist.authorized? type, controller
|
20
|
+
end
|
21
|
+
|
22
|
+
def render_posts
|
23
|
+
preview = false
|
24
|
+
@posts.collect { |post|
|
25
|
+
if post.published? || authorized?(:view_drafts)
|
26
|
+
render(:partial => 'post', :locals => { :post => post, :preview => preview }).tap do
|
27
|
+
preview = true
|
28
|
+
end
|
29
|
+
else
|
30
|
+
""
|
31
|
+
end
|
32
|
+
}.join.html_safe
|
33
|
+
end
|
34
|
+
|
35
|
+
def recent_posts(count = 5)
|
36
|
+
@recent_posts ||= {}
|
37
|
+
@recent_posts[count] ||= begin
|
38
|
+
# without SQL, we don't have access to conveniences like Post.where(:published),
|
39
|
+
# so instead just find 2x count and return those that are published.
|
40
|
+
result = Mist::Post.recently_published count*2
|
41
|
+
result = result[0...count] if result.length > count
|
42
|
+
result
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def popular_posts(count = 5)
|
47
|
+
# same as recent posts -- get twice as many and filter out the extras
|
48
|
+
@popular_posts ||= {}
|
49
|
+
@popular_posts[count] ||= begin
|
50
|
+
result = Mist::Post.most_popular(count*2).select { |post| post.published? }
|
51
|
+
result = result[0...count] if result.length > count
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def similar_posts(count = 5)
|
57
|
+
return [] if @post.nil?
|
58
|
+
@similar_posts ||= {}
|
59
|
+
@similar_posts[count] ||= @post.similar_posts(count)
|
60
|
+
end
|
61
|
+
end
|
File without changes
|
data/app/models/.gitkeep
ADDED
File without changes
|
@@ -0,0 +1,240 @@
|
|
1
|
+
class Mist::Post < Mist::GitModel
|
2
|
+
TAG_DELIM = /\s*,\s*/
|
3
|
+
include Mist::Permalink
|
4
|
+
|
5
|
+
validates_presence_of :title
|
6
|
+
validates_presence_of :content
|
7
|
+
|
8
|
+
validate do |record|
|
9
|
+
if record.new_record? && self.class.exist?(record.id)
|
10
|
+
record.errors.add :title, 'has already been taken'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
timestamps
|
15
|
+
attribute :content
|
16
|
+
attribute :title
|
17
|
+
attribute :published_at
|
18
|
+
attribute :gist_id
|
19
|
+
attribute :popularity, :default => 0
|
20
|
+
attribute :tags, :default => []
|
21
|
+
|
22
|
+
before_validation { |r| r.id = permalink(r.title) unless r.title.blank? }
|
23
|
+
after_validation :update_gist_if_necessary
|
24
|
+
after_save :update_meta
|
25
|
+
after_initialize :load_code_examples_from_gist
|
26
|
+
after_destroy :destroy_gist
|
27
|
+
|
28
|
+
def self.load_existing_with_attribute(attribute_name, array)
|
29
|
+
array.collect { |(post_id, attribute_value)| find post_id, attribute_name => attribute_value }.reject { |i| i.nil? }
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.most_popular(count)
|
33
|
+
# invert <=> so that result is descending order
|
34
|
+
load_existing_with_attribute :popularity, self[:popular_posts].sort { |a, b| -(a[1].to_i <=> b[1].to_i) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.increase_popularity(post)
|
38
|
+
self[:popular_posts][post.id] = popularity_for(post.id) + 1
|
39
|
+
save_meta_data :popular_posts
|
40
|
+
post.popularity = self[:popular_posts][post.id]
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.popularity_for(post_id)
|
44
|
+
self[:popular_posts][post_id] || 0
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.recently_published(count)
|
48
|
+
all_by_publication_date.tap do |result|
|
49
|
+
result.pop while result.length > count
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.all_by_publication_date
|
54
|
+
# invert <=> so that result is descending order
|
55
|
+
load_existing_with_attribute :published_at, self[:published_at].sort { |a, b| -(a[1] <=> b[1]) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.matching_tags(tags)
|
59
|
+
return [] if tags.blank?
|
60
|
+
matches = self[:tags].inject({}) { |h,(k,v)| ((t = v.split(TAG_DELIM)) & tags).size > 0 ? h[k] = t : nil; h }
|
61
|
+
load_existing_with_attribute :tags, matches.sort { |a, b| -((a[1] & tags).size <=> (b[1] & tags).size) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def similar_posts(max_count = nil)
|
65
|
+
self.class.matching_tags(tags).tap do |matching|
|
66
|
+
matching.delete self # similar does not mean identical :)
|
67
|
+
while max_count && matching.length > max_count
|
68
|
+
matching.pop
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def tags=(t)
|
74
|
+
if t.kind_of?(String)
|
75
|
+
attributes[:tags] = t.split(TAG_DELIM)
|
76
|
+
else
|
77
|
+
attributes[:tags] = t
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def update_meta
|
82
|
+
if popularity_changed? || new_record?
|
83
|
+
self.class[:popular_posts][id] = popularity
|
84
|
+
self.class.save_meta_data :popular_posts
|
85
|
+
end
|
86
|
+
|
87
|
+
if published_at_changed?
|
88
|
+
if published_at.blank?
|
89
|
+
self.class[:published_at].delete id
|
90
|
+
else
|
91
|
+
self.class[:published_at][id] = published_at
|
92
|
+
end
|
93
|
+
self.class.save_meta_data :published_at
|
94
|
+
end
|
95
|
+
|
96
|
+
if tags && !tags.empty?
|
97
|
+
self.class[:tags][id] = tags.join(', ')
|
98
|
+
else
|
99
|
+
self.class[:tags].delete id
|
100
|
+
end
|
101
|
+
self.class.save_meta_data :tags
|
102
|
+
end
|
103
|
+
|
104
|
+
def title=(value)
|
105
|
+
attributes[:title] = value
|
106
|
+
end
|
107
|
+
|
108
|
+
def published?
|
109
|
+
!published_at.blank?
|
110
|
+
end
|
111
|
+
|
112
|
+
def draft?
|
113
|
+
!published?
|
114
|
+
end
|
115
|
+
|
116
|
+
def publish
|
117
|
+
self.published_at = Time.now unless published?
|
118
|
+
end
|
119
|
+
|
120
|
+
def unpublish
|
121
|
+
self.published_at = nil
|
122
|
+
end
|
123
|
+
|
124
|
+
def published=(bool)
|
125
|
+
bool ? publish : unpublish
|
126
|
+
end
|
127
|
+
|
128
|
+
def content=(c)
|
129
|
+
attributes[:content] = c.gsub(/\r/, "")
|
130
|
+
end
|
131
|
+
|
132
|
+
def generated_gist_description
|
133
|
+
'Code examples for "%s" - %s' % [title, url]
|
134
|
+
end
|
135
|
+
|
136
|
+
def gist
|
137
|
+
@gist ||= begin
|
138
|
+
if gist_id.blank?
|
139
|
+
if has_code_examples?
|
140
|
+
ActiveGist.new(:description => generated_gist_description, :public => true)
|
141
|
+
else
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
else
|
145
|
+
begin
|
146
|
+
ActiveGist.find(gist_id)
|
147
|
+
rescue RestClient::ResourceNotFound
|
148
|
+
self.gist_id = nil
|
149
|
+
nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def content_as_html
|
156
|
+
GitHub::Markup.render("#{title}.markdown", content_with_embedded_gists).html_safe
|
157
|
+
end
|
158
|
+
|
159
|
+
def content_as_html_preview
|
160
|
+
# just take to the first blank line -- that's probably the first paragraph
|
161
|
+
# TODO make this smarter by including more than 1 paragraph if it's short, or by omitting headers
|
162
|
+
first_paragraph = /\A(.+?)(\n\n|\n |\z)/m.match(content.gsub(/\r/, ''))
|
163
|
+
GitHub::Markup.render("#{title}.markdown", first_paragraph[1]).html_safe
|
164
|
+
end
|
165
|
+
|
166
|
+
def content_with_embedded_gists
|
167
|
+
# by using gist_id directly we can avoid hitting the Gist API every time the
|
168
|
+
# post is rendered.
|
169
|
+
|
170
|
+
return content.dup if gist_id.blank?
|
171
|
+
|
172
|
+
template = '<script src="https://gist.github.com/__ID__.js?file=__FILENAME__"></script>'
|
173
|
+
template['__ID__'] = gist_id.to_s
|
174
|
+
|
175
|
+
content.dup.tap do |result|
|
176
|
+
# process last example first, so that changes to result don't taint offsets
|
177
|
+
code_examples.reverse.each do |example|
|
178
|
+
result[example.offset] = template.sub(/__FILENAME__/, example.filename) + "\n"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def has_code_examples?
|
184
|
+
code_examples.length > 0
|
185
|
+
end
|
186
|
+
|
187
|
+
def code_examples
|
188
|
+
Mist::CodeExampleParser.new(content).examples
|
189
|
+
end
|
190
|
+
|
191
|
+
def load_code_examples_from_gist
|
192
|
+
if gist && gist.persisted?
|
193
|
+
self.content = self.content.dup.tap do |result|
|
194
|
+
code_examples.reverse.each do |example|
|
195
|
+
if gist.files[example.filename]
|
196
|
+
lines = gist.files[example.filename][:content].split("\n")
|
197
|
+
lines.unshift " file: #{example.filename}"
|
198
|
+
result[example.offset] = lines.join("\n ") + "\n"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Assigns the file contents of each file in the gist according to what's found in
|
206
|
+
# #content. Does not save the gist. Returns the list of files themselves.
|
207
|
+
def set_gist_data
|
208
|
+
gist.description = generated_gist_description
|
209
|
+
gist.files.keys.each { |filename| gist.files[filename] = nil }
|
210
|
+
code_examples.each do |example|
|
211
|
+
gist.files[example.filename] = { :content => example }
|
212
|
+
end
|
213
|
+
gist.files
|
214
|
+
end
|
215
|
+
|
216
|
+
def url(options = {})
|
217
|
+
Mist::Engine.routes.url_helpers.post_path(id, options.reverse_merge(:only_path => false).reverse_merge(Rails.application.default_url_options))
|
218
|
+
end
|
219
|
+
|
220
|
+
def update_gist_if_necessary
|
221
|
+
return unless errors.empty?
|
222
|
+
|
223
|
+
if has_code_examples?
|
224
|
+
set_gist_data
|
225
|
+
|
226
|
+
if gist.changed?
|
227
|
+
errors.add(:gist, "could not be saved: #{gist.errors.full_messages.join('; ')}") unless gist.save
|
228
|
+
end
|
229
|
+
|
230
|
+
self.gist_id = gist.id
|
231
|
+
else
|
232
|
+
# no code examples, delete gist
|
233
|
+
gist.destroy if gist && gist.persisted?
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def destroy_gist
|
238
|
+
gist.destroy if gist && gist.persisted?
|
239
|
+
end
|
240
|
+
end
|