canyon 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +29 -0
- data/app/assets/javascripts/canyon/application.js +3 -0
- data/app/assets/javascripts/canyon/vendor/jquery.autosize.js +211 -0
- data/app/assets/stylesheets/canyon/application.scss +11 -0
- data/app/controllers/canyon/application_controller.rb +11 -0
- data/app/controllers/canyon/posts_controller.rb +74 -0
- data/app/helpers/canyon/application_helper.rb +8 -0
- data/app/models/canyon/author.rb +5 -0
- data/app/models/canyon/post.rb +49 -0
- data/app/views/canyon/posts/_form.html.erb +140 -0
- data/app/views/canyon/posts/edit.html.erb +11 -0
- data/app/views/canyon/posts/index.html.erb +35 -0
- data/app/views/canyon/posts/new.html.erb +11 -0
- data/app/views/layouts/canyon/application.html.erb +53 -0
- data/bin/rails +12 -0
- data/config/locales/en.yml +22 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20150416053508_create_authors.rb +9 -0
- data/db/migrate/20150416053518_create_posts.rb +17 -0
- data/db/seeds.rb +15 -0
- data/lib/canyon.rb +4 -0
- data/lib/canyon/engine.rb +17 -0
- data/lib/canyon/version.rb +3 -0
- data/lib/tasks/canyon_tasks.rake +7 -0
- metadata +139 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 36a8f1d8684684e4323ac062af0294d15e24153a
|
4
|
+
data.tar.gz: 675bdcb88f2151e34d525e5f0913a9978128ed11
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c5b2473b776ae3965fa0ffb8f4f975971c331ba861d186da32aa7f51c3b1beedc186d64928f2239f00786cc7091c1b38ba367a057a341cb32e1c300fd4e4bdfe
|
7
|
+
data.tar.gz: 9ab5818e3dfe53e2aaf40c2d64e4a6f52d3f7da321d2f8db9d3c382432973ec689ef258cf280d6e44c1630a01c6ae356eeec0410378e67265b16724c77dfcb3e
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2015 Martin Jagusch
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Canyon
|
2
|
+
|
3
|
+
Canyon is a Rails blogging engine, including only the backend. How you present the blog posts is up to you.
|
4
|
+
|
5
|
+
### Installation
|
6
|
+
|
7
|
+
Include the engine to your projects Gemfile. `Gemfile`
|
8
|
+
|
9
|
+
```
|
10
|
+
gem 'canyon'
|
11
|
+
```
|
12
|
+
|
13
|
+
Include the routes from the engine to your applications routing file. `config/routes.rb`
|
14
|
+
|
15
|
+
``` ruby
|
16
|
+
Rails.application.routes.draw do
|
17
|
+
# All your application routes.
|
18
|
+
|
19
|
+
mount Canyon::Engine => '/cy-admin'
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
Load the engine sample data. `db/seeds.rb` (optional)
|
24
|
+
|
25
|
+
```
|
26
|
+
Canyon::Engine.load_seed
|
27
|
+
```
|
28
|
+
|
29
|
+
Running `rake db:seed` will now include a default user and blog post.
|
@@ -0,0 +1,211 @@
|
|
1
|
+
/*!
|
2
|
+
Autosize 3.0.7
|
3
|
+
license: MIT
|
4
|
+
http://www.jacklmoore.com/autosize
|
5
|
+
*/
|
6
|
+
(function (global, factory) {
|
7
|
+
if (typeof define === 'function' && define.amd) {
|
8
|
+
define(['exports', 'module'], factory);
|
9
|
+
} else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
|
10
|
+
factory(exports, module);
|
11
|
+
} else {
|
12
|
+
var mod = {
|
13
|
+
exports: {}
|
14
|
+
};
|
15
|
+
factory(mod.exports, mod);
|
16
|
+
global.autosize = mod.exports;
|
17
|
+
}
|
18
|
+
})(this, function (exports, module) {
|
19
|
+
'use strict';
|
20
|
+
|
21
|
+
function assign(ta) {
|
22
|
+
var _ref = arguments[1] === undefined ? {} : arguments[1];
|
23
|
+
|
24
|
+
var _ref$setOverflowX = _ref.setOverflowX;
|
25
|
+
var setOverflowX = _ref$setOverflowX === undefined ? true : _ref$setOverflowX;
|
26
|
+
var _ref$setOverflowY = _ref.setOverflowY;
|
27
|
+
var setOverflowY = _ref$setOverflowY === undefined ? true : _ref$setOverflowY;
|
28
|
+
|
29
|
+
if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || ta.hasAttribute('data-autosize-on')) return;
|
30
|
+
|
31
|
+
var heightOffset = null;
|
32
|
+
var overflowY = 'hidden';
|
33
|
+
|
34
|
+
function init() {
|
35
|
+
var style = window.getComputedStyle(ta, null);
|
36
|
+
|
37
|
+
if (style.resize === 'vertical') {
|
38
|
+
ta.style.resize = 'none';
|
39
|
+
} else if (style.resize === 'both') {
|
40
|
+
ta.style.resize = 'horizontal';
|
41
|
+
}
|
42
|
+
|
43
|
+
if (style.boxSizing === 'content-box') {
|
44
|
+
heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom));
|
45
|
+
} else {
|
46
|
+
heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
|
47
|
+
}
|
48
|
+
|
49
|
+
update();
|
50
|
+
}
|
51
|
+
|
52
|
+
function changeOverflow(value) {
|
53
|
+
{
|
54
|
+
// Chrome/Safari-specific fix:
|
55
|
+
// When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
|
56
|
+
// made available by removing the scrollbar. The following forces the necessary text reflow.
|
57
|
+
var width = ta.style.width;
|
58
|
+
ta.style.width = '0px';
|
59
|
+
// Force reflow:
|
60
|
+
/* jshint ignore:start */
|
61
|
+
ta.offsetWidth;
|
62
|
+
/* jshint ignore:end */
|
63
|
+
ta.style.width = width;
|
64
|
+
}
|
65
|
+
|
66
|
+
overflowY = value;
|
67
|
+
|
68
|
+
if (setOverflowY) {
|
69
|
+
ta.style.overflowY = value;
|
70
|
+
}
|
71
|
+
|
72
|
+
update();
|
73
|
+
}
|
74
|
+
|
75
|
+
function update() {
|
76
|
+
var startHeight = ta.style.height;
|
77
|
+
var htmlTop = window.pageYOffset;
|
78
|
+
var bodyTop = document.body.scrollTop;
|
79
|
+
var originalHeight = ta.style.height;
|
80
|
+
|
81
|
+
ta.style.height = 'auto';
|
82
|
+
|
83
|
+
var endHeight = ta.scrollHeight + heightOffset;
|
84
|
+
|
85
|
+
if (ta.scrollHeight === 0) {
|
86
|
+
// If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
|
87
|
+
ta.style.height = originalHeight;
|
88
|
+
return;
|
89
|
+
}
|
90
|
+
|
91
|
+
ta.style.height = endHeight + 'px';
|
92
|
+
|
93
|
+
// prevents scroll-position jumping
|
94
|
+
document.documentElement.scrollTop = htmlTop;
|
95
|
+
document.body.scrollTop = bodyTop;
|
96
|
+
|
97
|
+
var style = window.getComputedStyle(ta, null);
|
98
|
+
|
99
|
+
if (style.height !== ta.style.height) {
|
100
|
+
if (overflowY !== 'visible') {
|
101
|
+
changeOverflow('visible');
|
102
|
+
return;
|
103
|
+
}
|
104
|
+
} else {
|
105
|
+
if (overflowY !== 'hidden') {
|
106
|
+
changeOverflow('hidden');
|
107
|
+
return;
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
if (startHeight !== ta.style.height) {
|
112
|
+
var evt = document.createEvent('Event');
|
113
|
+
evt.initEvent('autosize:resized', true, false);
|
114
|
+
ta.dispatchEvent(evt);
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
var destroy = (function (style) {
|
119
|
+
window.removeEventListener('resize', update);
|
120
|
+
ta.removeEventListener('input', update);
|
121
|
+
ta.removeEventListener('keyup', update);
|
122
|
+
ta.removeAttribute('data-autosize-on');
|
123
|
+
ta.removeEventListener('autosize:destroy', destroy);
|
124
|
+
|
125
|
+
Object.keys(style).forEach(function (key) {
|
126
|
+
ta.style[key] = style[key];
|
127
|
+
});
|
128
|
+
}).bind(ta, {
|
129
|
+
height: ta.style.height,
|
130
|
+
resize: ta.style.resize,
|
131
|
+
overflowY: ta.style.overflowY,
|
132
|
+
overflowX: ta.style.overflowX,
|
133
|
+
wordWrap: ta.style.wordWrap });
|
134
|
+
|
135
|
+
ta.addEventListener('autosize:destroy', destroy);
|
136
|
+
|
137
|
+
// IE9 does not fire onpropertychange or oninput for deletions,
|
138
|
+
// so binding to onkeyup to catch most of those events.
|
139
|
+
// There is no way that I know of to detect something like 'cut' in IE9.
|
140
|
+
if ('onpropertychange' in ta && 'oninput' in ta) {
|
141
|
+
ta.addEventListener('keyup', update);
|
142
|
+
}
|
143
|
+
|
144
|
+
window.addEventListener('resize', update);
|
145
|
+
ta.addEventListener('input', update);
|
146
|
+
ta.addEventListener('autosize:update', update);
|
147
|
+
ta.setAttribute('data-autosize-on', true);
|
148
|
+
|
149
|
+
if (setOverflowY) {
|
150
|
+
ta.style.overflowY = 'hidden';
|
151
|
+
}
|
152
|
+
if (setOverflowX) {
|
153
|
+
ta.style.overflowX = 'hidden';
|
154
|
+
ta.style.wordWrap = 'break-word';
|
155
|
+
}
|
156
|
+
|
157
|
+
init();
|
158
|
+
}
|
159
|
+
|
160
|
+
function destroy(ta) {
|
161
|
+
if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return;
|
162
|
+
var evt = document.createEvent('Event');
|
163
|
+
evt.initEvent('autosize:destroy', true, false);
|
164
|
+
ta.dispatchEvent(evt);
|
165
|
+
}
|
166
|
+
|
167
|
+
function update(ta) {
|
168
|
+
if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return;
|
169
|
+
var evt = document.createEvent('Event');
|
170
|
+
evt.initEvent('autosize:update', true, false);
|
171
|
+
ta.dispatchEvent(evt);
|
172
|
+
}
|
173
|
+
|
174
|
+
var autosize = null;
|
175
|
+
|
176
|
+
// Do nothing in Node.js environment and IE8 (or lower)
|
177
|
+
if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
|
178
|
+
autosize = function (el) {
|
179
|
+
return el;
|
180
|
+
};
|
181
|
+
autosize.destroy = function (el) {
|
182
|
+
return el;
|
183
|
+
};
|
184
|
+
autosize.update = function (el) {
|
185
|
+
return el;
|
186
|
+
};
|
187
|
+
} else {
|
188
|
+
autosize = function (el, options) {
|
189
|
+
if (el) {
|
190
|
+
Array.prototype.forEach.call(el.length ? el : [el], function (x) {
|
191
|
+
return assign(x, options);
|
192
|
+
});
|
193
|
+
}
|
194
|
+
return el;
|
195
|
+
};
|
196
|
+
autosize.destroy = function (el) {
|
197
|
+
if (el) {
|
198
|
+
Array.prototype.forEach.call(el.length ? el : [el], destroy);
|
199
|
+
}
|
200
|
+
return el;
|
201
|
+
};
|
202
|
+
autosize.update = function (el) {
|
203
|
+
if (el) {
|
204
|
+
Array.prototype.forEach.call(el.length ? el : [el], update);
|
205
|
+
}
|
206
|
+
return el;
|
207
|
+
};
|
208
|
+
}
|
209
|
+
|
210
|
+
module.exports = autosize;
|
211
|
+
});
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Canyon
|
2
|
+
class PostsController < ApplicationController
|
3
|
+
def index
|
4
|
+
@posts = Post.published_or_drafted
|
5
|
+
end
|
6
|
+
|
7
|
+
def new
|
8
|
+
@post = Post.new(published_at: Time.now)
|
9
|
+
end
|
10
|
+
|
11
|
+
def edit
|
12
|
+
@post = Post.find(params[:id])
|
13
|
+
end
|
14
|
+
|
15
|
+
def destroy
|
16
|
+
post = Post.find(params[:id])
|
17
|
+
raise post.inspect
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
@post = Post.new(post_params)
|
22
|
+
|
23
|
+
if @post.save
|
24
|
+
if params[:preview].present?
|
25
|
+
@post.update_attributes(published: false)
|
26
|
+
# TODO Link to preview page.
|
27
|
+
flash[:warning] = %Q(A preview can be seen here: Coming Soon.)
|
28
|
+
render :edit
|
29
|
+
else
|
30
|
+
flash[:success] = %Q(Post "#{@post.title}" has been successfully created.)
|
31
|
+
redirect_to posts_path
|
32
|
+
end
|
33
|
+
else
|
34
|
+
render :new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def update
|
39
|
+
origin = Post.find(params[:id])
|
40
|
+
|
41
|
+
if params[:preview].present?
|
42
|
+
@post = origin.dup
|
43
|
+
@post.assign_attributes(parent_id: origin.parent_id || origin.id, published: false)
|
44
|
+
|
45
|
+
if @post.update_attributes(post_params)
|
46
|
+
# TODO Link to preview page.
|
47
|
+
flash[:warning] = %Q(A preview can be seen here: Coming Soon.)
|
48
|
+
end
|
49
|
+
|
50
|
+
render :edit
|
51
|
+
else
|
52
|
+
@post = origin.preview? ? origin.parent : origin
|
53
|
+
|
54
|
+
if params[:delete].present?
|
55
|
+
@post.update_attributes(deleted: true)
|
56
|
+
flash[:success] = %Q(Post "#{@post.title}" has been successfully deleted.)
|
57
|
+
redirect_to posts_path
|
58
|
+
elsif @post.update_attributes(post_params)
|
59
|
+
flash[:success] = %Q(Post "#{@post.title}" has been successfully updated.)
|
60
|
+
redirect_to posts_path
|
61
|
+
else
|
62
|
+
render :edit
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def post_params
|
70
|
+
params[:post].permit(:title, :slug, :language, :author_id, :published, :published_at,
|
71
|
+
:summary, :content)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Canyon
|
2
|
+
class Post < ActiveRecord::Base
|
3
|
+
LANGUAGES = [:en, :de]
|
4
|
+
|
5
|
+
belongs_to :author
|
6
|
+
|
7
|
+
delegate :name, to: 'author', prefix: true
|
8
|
+
|
9
|
+
scope :published, -> do
|
10
|
+
where(parent_id: nil).
|
11
|
+
where(published: true).
|
12
|
+
where(deleted: false).
|
13
|
+
where('published_at <= ?', Time.now).
|
14
|
+
order(published_at: :desc)
|
15
|
+
end
|
16
|
+
|
17
|
+
scope :published_or_drafted, -> do
|
18
|
+
where(parent_id: nil).where(deleted: false).order(published_at: :desc)
|
19
|
+
end
|
20
|
+
|
21
|
+
validates :title, :slug, :language, :content, :published_at, presence: true
|
22
|
+
|
23
|
+
before_validation :parameterize_slug
|
24
|
+
|
25
|
+
def parameterize_slug
|
26
|
+
self.slug = title.parameterize if slug.blank? && title.present?
|
27
|
+
end
|
28
|
+
|
29
|
+
def draft?
|
30
|
+
!published?
|
31
|
+
end
|
32
|
+
|
33
|
+
def preview?
|
34
|
+
!parent_id.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def parent
|
38
|
+
self.class.find(parent_id)
|
39
|
+
end
|
40
|
+
|
41
|
+
def last_preview
|
42
|
+
Canyon::Post.where(parent_id: id).order(updated_at: :desc).first
|
43
|
+
end
|
44
|
+
|
45
|
+
def has_newer_preview?
|
46
|
+
!new_record? && !preview? && !last_preview.nil? && last_preview.updated_at > updated_at
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
<% if post.errors.any? %>
|
2
|
+
<div class="alert alert-danger alert-dismissible" role="alert">
|
3
|
+
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
4
|
+
<span aria-hidden="true">×</span>
|
5
|
+
</button>
|
6
|
+
|
7
|
+
<h4>Houston. There has been an error. Imagine what would have been happen if we would be in space. Scary!</h4>
|
8
|
+
|
9
|
+
<ul>
|
10
|
+
<% post.errors.full_messages.each do |message| %>
|
11
|
+
<li><%= message %></li>
|
12
|
+
<% end %>
|
13
|
+
</ul>
|
14
|
+
</div>
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
<% if @post.has_newer_preview? %>
|
18
|
+
<div class="alert alert-warning alert-dismissible" role="alert">
|
19
|
+
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
20
|
+
<span aria-hidden="true">×</span>
|
21
|
+
</button>
|
22
|
+
|
23
|
+
<p>
|
24
|
+
An preview that is newer as this post has been found.
|
25
|
+
<%= link_to %Q("#{@post.last_preview.title}"), edit_post_path(@post.last_preview.id) %>
|
26
|
+
</p>
|
27
|
+
</div>
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
<%= form_for post do |f| %>
|
31
|
+
<div class="row">
|
32
|
+
<div class="col-md-6">
|
33
|
+
<div class="form-group">
|
34
|
+
<%= f.label :title %>
|
35
|
+
<%= f.text_field :title, class: 'form-control' %>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
|
39
|
+
<div class="col-md-6">
|
40
|
+
<div class="form-group">
|
41
|
+
<%= f.label :slug %>
|
42
|
+
<%= f.text_field :slug, class: 'form-control', placeholder: 'e.g., separated-by-dashes' %>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
|
47
|
+
<div class="row">
|
48
|
+
<div class="col-md-6">
|
49
|
+
<div class="form-group">
|
50
|
+
<%= f.label :language %>
|
51
|
+
<%= f.select :language, Canyon::Post::LANGUAGES, {}, class: 'form-control' %>
|
52
|
+
</div>
|
53
|
+
</div>
|
54
|
+
|
55
|
+
<div class="col-md-6">
|
56
|
+
<div class="form-group">
|
57
|
+
<%= f.label :author_id %>
|
58
|
+
<%= f.select :author_id, Canyon::Author.all.map { |author| [author.name, author.id] }, {}, class: 'form-control' %>
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
</div>
|
62
|
+
|
63
|
+
<div class="row">
|
64
|
+
<div class="col-md-6">
|
65
|
+
<div class="form-group">
|
66
|
+
<%= f.label :published_at %>
|
67
|
+
<div>
|
68
|
+
<%= f.datetime_select :published_at, :minute_step => 5 %>
|
69
|
+
</div>
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
|
73
|
+
<div class="col-md-6">
|
74
|
+
<div class="form-group">
|
75
|
+
<%= f.label :published %>
|
76
|
+
<div>
|
77
|
+
<%= f.label :published do %>
|
78
|
+
<%= f.check_box :published %>
|
79
|
+
<% end %>
|
80
|
+
</div>
|
81
|
+
</div>
|
82
|
+
</div>
|
83
|
+
</div>
|
84
|
+
|
85
|
+
<div class="form-group">
|
86
|
+
<%= f.label :summary %>
|
87
|
+
<%= f.text_area :summary, rows: 3, class: 'form-control post-summary', placeholder: 'A short summary for the homepage.' %>
|
88
|
+
</div>
|
89
|
+
|
90
|
+
<div class="form-group">
|
91
|
+
<%= f.label :content %>
|
92
|
+
<%= f.text_area :content, rows: 5, class: 'form-control post-content', placeholder: 'The main content.' %>
|
93
|
+
</div>
|
94
|
+
|
95
|
+
<%= link_to '#', class: 'btn btn-warning preview-act', role: 'button' do %>
|
96
|
+
<span class="glyphicon glyphicon-eye-open" aria-hidden="true"></span>
|
97
|
+
Preview Post
|
98
|
+
<% end %>
|
99
|
+
|
100
|
+
<% if @post.new_record? && !@post.preview? %>
|
101
|
+
<%= link_to '#', class: 'btn btn-success submit-act', role: 'button' do %>
|
102
|
+
<span class="glyphicon glyphicon-ok-circle" aria-hidden="true"></span>
|
103
|
+
Create Post
|
104
|
+
<% end %>
|
105
|
+
<% else %>
|
106
|
+
<%= link_to '#', class: 'btn btn-success submit-act', role: 'button' do %>
|
107
|
+
<span class="glyphicon glyphicon-ok-circle" aria-hidden="true"></span>
|
108
|
+
Update Post
|
109
|
+
<% end %>
|
110
|
+
|
111
|
+
<%= link_to '#', class: 'btn btn-danger delete-act', role: 'button' do %>
|
112
|
+
<span class="glyphicon glyphicon-remove-circle" aria-hidden="true"></span>
|
113
|
+
Delete Post
|
114
|
+
<% end %>
|
115
|
+
<% end %>
|
116
|
+
<% end %>
|
117
|
+
|
118
|
+
<script>
|
119
|
+
var form = $('form');
|
120
|
+
|
121
|
+
$('.preview-act').click(function() {
|
122
|
+
form.attr('action', form.attr('action') + '?preview=1');
|
123
|
+
form.submit();
|
124
|
+
return false;
|
125
|
+
});
|
126
|
+
|
127
|
+
$('.delete-act').click(function() {
|
128
|
+
form.attr('action', form.attr('action') + '?delete=1');
|
129
|
+
form.submit();
|
130
|
+
return false;
|
131
|
+
});
|
132
|
+
|
133
|
+
$('.submit-act').click(function() {
|
134
|
+
form.submit();
|
135
|
+
return false;
|
136
|
+
});
|
137
|
+
|
138
|
+
autosize($('.post-summary'));
|
139
|
+
autosize($('.post-content'));
|
140
|
+
</script>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% page_title(%Q(Edit Post ##{@post.id})) %>
|
2
|
+
|
3
|
+
<h3>
|
4
|
+
Edit Post
|
5
|
+
<%= link_to posts_path, class: 'btn btn-sm btn-primary', role: 'button' do %>
|
6
|
+
<span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
|
7
|
+
All Posts
|
8
|
+
<% end %>
|
9
|
+
</h3>
|
10
|
+
|
11
|
+
<%= render partial: 'form', locals: {post: @post} %>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<% page_title('Posts') %>
|
2
|
+
|
3
|
+
<h3>
|
4
|
+
Posts
|
5
|
+
<%= link_to new_post_path, class: 'btn btn-sm btn-primary', role: 'button' do %>
|
6
|
+
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
7
|
+
New Posts
|
8
|
+
<% end %>
|
9
|
+
</h3>
|
10
|
+
|
11
|
+
<table class="table table-bordered">
|
12
|
+
<tr>
|
13
|
+
<th>Title</th>
|
14
|
+
<th>Language</th>
|
15
|
+
<th>Author</th>
|
16
|
+
<th>Date</th>
|
17
|
+
</tr>
|
18
|
+
<% @posts.each do |post| %>
|
19
|
+
<tr>
|
20
|
+
<td>
|
21
|
+
<%= link_to post.title, edit_post_path(post) %>
|
22
|
+
<% if post.draft? %>
|
23
|
+
- Draft
|
24
|
+
<% end %>
|
25
|
+
</td>
|
26
|
+
<td><%= post.language %></td>
|
27
|
+
<td><%= post.author_name %></td>
|
28
|
+
<td>
|
29
|
+
<abbr title="<%= l(post.published_at, format: :detailed) if post.published_at.present? %>">
|
30
|
+
<%= l(post.published_at, format: :simple) if post.published_at.present? %>
|
31
|
+
</abbr><br />
|
32
|
+
</td>
|
33
|
+
</tr>
|
34
|
+
<% end %>
|
35
|
+
</table>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% page_title('New Post') %>
|
2
|
+
|
3
|
+
<h3>
|
4
|
+
New Post
|
5
|
+
<%= link_to posts_path, class: 'btn btn-sm btn-primary', role: 'button' do %>
|
6
|
+
<span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
|
7
|
+
All Posts
|
8
|
+
<% end %>
|
9
|
+
</h3>
|
10
|
+
|
11
|
+
<%= render partial: 'form', locals: {post: @post} %>
|
@@ -0,0 +1,53 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7
|
+
<%= stylesheet_link_tag 'canyon/application', media: 'all' %>
|
8
|
+
<%= javascript_include_tag 'canyon/application' %>
|
9
|
+
<%= csrf_meta_tags %>
|
10
|
+
<title><%= content_for(:page_title) %></title>
|
11
|
+
</head>
|
12
|
+
<body>
|
13
|
+
<nav class="navbar navbar-inverse navbar-fixed-top">
|
14
|
+
<div class="container">
|
15
|
+
<div class="navbar-header">
|
16
|
+
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar">
|
17
|
+
<span class="sr-only">Toggle navigation</span>
|
18
|
+
<span class="icon-bar"></span>
|
19
|
+
<span class="icon-bar"></span>
|
20
|
+
<span class="icon-bar"></span>
|
21
|
+
</button>
|
22
|
+
<%= link_to 'Canyon', posts_path, class: 'navbar-brand' %>
|
23
|
+
</div>
|
24
|
+
<div id="navbar" class="collapse navbar-collapse">
|
25
|
+
<ul class="nav navbar-nav">
|
26
|
+
<li class="active"><%= link_to 'Posts', posts_path %></li>
|
27
|
+
<li><%= link_to 'Categories', '#' %></li>
|
28
|
+
<li><%= link_to 'Authors', '#' %></li>
|
29
|
+
<li>
|
30
|
+
<%= link_to request.base_url, target: '_blank' do %>
|
31
|
+
<%= request.base_url %>
|
32
|
+
<span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
|
33
|
+
<% end %>
|
34
|
+
</li>
|
35
|
+
</ul>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</nav>
|
39
|
+
|
40
|
+
<div class="container">
|
41
|
+
<% flash.each do |name, flash_message| %>
|
42
|
+
<div class="alert alert-<%= name %> alert-dismissible" role="alert">
|
43
|
+
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
44
|
+
<span aria-hidden="true">×</span>
|
45
|
+
</button>
|
46
|
+
<p><%= raw flash_message %></p>
|
47
|
+
</div>
|
48
|
+
<% end %>
|
49
|
+
|
50
|
+
<%= yield %>
|
51
|
+
</div>
|
52
|
+
</body>
|
53
|
+
</html>
|
data/bin/rails
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
|
3
|
+
|
4
|
+
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
5
|
+
ENGINE_PATH = File.expand_path('../../lib/canyon/engine', __FILE__)
|
6
|
+
|
7
|
+
# Set up gems listed in the Gemfile.
|
8
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
|
9
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
10
|
+
|
11
|
+
require 'rails/all'
|
12
|
+
require 'rails/engine/commands'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
en:
|
2
|
+
date:
|
3
|
+
month_names:
|
4
|
+
-
|
5
|
+
- January
|
6
|
+
- February
|
7
|
+
- March
|
8
|
+
- April
|
9
|
+
- May
|
10
|
+
- June
|
11
|
+
- July
|
12
|
+
- August
|
13
|
+
- September
|
14
|
+
- October
|
15
|
+
- November
|
16
|
+
- December
|
17
|
+
time:
|
18
|
+
am: am
|
19
|
+
pm: pm
|
20
|
+
formats:
|
21
|
+
simple: "%B %d, %Y"
|
22
|
+
detailed: "%Y/%m/%d %I:%M %p"
|
data/config/routes.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreatePosts < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :canyon_posts do |t|
|
4
|
+
t.string :title, null: false
|
5
|
+
t.string :slug, null: false
|
6
|
+
t.string :language, null: false
|
7
|
+
t.text :summary
|
8
|
+
t.text :content, null: false
|
9
|
+
t.integer :parent_id
|
10
|
+
t.integer :author_id, null: false
|
11
|
+
t.boolean :deleted, null: false, default: false
|
12
|
+
t.boolean :published, null: false, default: false
|
13
|
+
t.datetime :published_at, null: false
|
14
|
+
t.timestamps null: false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/db/seeds.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Canyon::Author.delete_all
|
2
|
+
Canyon::Post.delete_all
|
3
|
+
|
4
|
+
author = Canyon::Author.create(name: 'Guybrush Threepwood', slug: 'guybrush')
|
5
|
+
Canyon::Author.create(name: 'Elaine Marley', slug: 'elaine')
|
6
|
+
|
7
|
+
Canyon::Post.create!(
|
8
|
+
author: author,
|
9
|
+
language: 'en',
|
10
|
+
title: 'Markdown Syntax Example',
|
11
|
+
slug: 'markdown-syntax-example',
|
12
|
+
summary: 'This is our fist summary.',
|
13
|
+
content: 'This is our **fist posts**.',
|
14
|
+
published_at: Time.now,
|
15
|
+
)
|
data/lib/canyon.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bootstrap-sass'
|
2
|
+
require 'sass-rails'
|
3
|
+
require 'jquery-rails'
|
4
|
+
|
5
|
+
module Canyon
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
isolate_namespace Canyon
|
8
|
+
|
9
|
+
initializer :append_migrations do |app|
|
10
|
+
unless app.root.to_s.match root.to_s
|
11
|
+
config.paths['db/migrate'].expanded.each do |expanded_path|
|
12
|
+
app.config.paths['db/migrate'] << expanded_path
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: canyon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Martin Jagusch
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-07-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bootstrap-sass
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sass-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: jquery-rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- _@mj.io
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- LICENSE.txt
|
91
|
+
- README.md
|
92
|
+
- app/assets/javascripts/canyon/application.js
|
93
|
+
- app/assets/javascripts/canyon/vendor/jquery.autosize.js
|
94
|
+
- app/assets/stylesheets/canyon/application.scss
|
95
|
+
- app/controllers/canyon/application_controller.rb
|
96
|
+
- app/controllers/canyon/posts_controller.rb
|
97
|
+
- app/helpers/canyon/application_helper.rb
|
98
|
+
- app/models/canyon/author.rb
|
99
|
+
- app/models/canyon/post.rb
|
100
|
+
- app/views/canyon/posts/_form.html.erb
|
101
|
+
- app/views/canyon/posts/edit.html.erb
|
102
|
+
- app/views/canyon/posts/index.html.erb
|
103
|
+
- app/views/canyon/posts/new.html.erb
|
104
|
+
- app/views/layouts/canyon/application.html.erb
|
105
|
+
- bin/rails
|
106
|
+
- config/locales/en.yml
|
107
|
+
- config/routes.rb
|
108
|
+
- db/migrate/20150416053508_create_authors.rb
|
109
|
+
- db/migrate/20150416053518_create_posts.rb
|
110
|
+
- db/seeds.rb
|
111
|
+
- lib/canyon.rb
|
112
|
+
- lib/canyon/engine.rb
|
113
|
+
- lib/canyon/version.rb
|
114
|
+
- lib/tasks/canyon_tasks.rake
|
115
|
+
homepage: https://github.com/mjio/canyon
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
metadata: {}
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubyforge_project:
|
135
|
+
rubygems_version: 2.4.5
|
136
|
+
signing_key:
|
137
|
+
specification_version: 4
|
138
|
+
summary: Rails blogging engine
|
139
|
+
test_files: []
|