Merge branch 'admin_feature'

This commit is contained in:
yafeilee 2014-04-01 00:05:08 +08:00
commit 1069d6a1a6
28 changed files with 372 additions and 133 deletions

3
.gitignore vendored
View File

@ -19,3 +19,6 @@
/public/uploads/*
*.lock
# Ignore application configuration
/config/application.yml

14
Gemfile
View File

@ -13,17 +13,18 @@ gem 'jquery-rails'
gem 'foundation-rails', '~> 5.2.1'
gem 'foundation-icons-sass-rails'
gem "mongoid"
gem "mongoid-pagination"
gem "redcarpet"
gem "rouge"
gem 'mongoid'
gem 'mongoid-pagination'
gem 'redcarpet'
gem 'rouge'
gem 'slim-rails'
gem "simple_form"
gem "mini_magick"
gem 'simple_form'
gem 'mini_magick'
gem 'carrierwave-mongoid'
gem 'html_truncator'
gem 'nokogiri'
gem 'angularjs-rails'
gem 'figaro'
group :development do
gem 'quiet_assets'
@ -42,5 +43,6 @@ end
group :test, :development do
gem "rspec-rails", ">= 2.8.1"
gem 'pry-rails'
gem 'pry-nav'
gem 'factory_girl_rails'
end

View File

@ -4,27 +4,6 @@
#
$(document).ready ->
preview = $('.preview')
content = $('#post_content')
$('#content').click ->
preview.hide()
content.show()
$(this).addClass('active')
$('#preview').removeClass('active')
false
$('#preview').click ->
content.hide()
$(this).addClass('active')
$('#content').removeClass('active')
preview.html('Loading...')
preview.show()
$.post $(this).attr('url'), text: content.val(), (data)->
preview.html(data)
false
$('a#upload_photo').click ->
$('input[type=file]').show().focus().click().hide()
false

View File

@ -1,10 +1,11 @@
#= require angular
#= require angular-cookies
#= require angular-resource
#= require angular-sanitize
#= require_self
#= require_tree ./angularjs
@app = angular.module('app', ['ngCookies'])
@app = angular.module('app', ['ngCookies', 'ngSanitize'])
@app.config(["$httpProvider", (provider) ->
provider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')

View File

@ -0,0 +1,20 @@
@app.controller 'AdminPostsController', ($scope, $http, $location, $timeout, $cookies, $sce)->
$scope.body_active = true
$scope.changeToBody = ->
$scope.body_active = true
$scope.changeToPreview = ->
$scope.body_active = false
$scope.previewHTML = '加载中...'
$http.post '/admin/posts/preview', { content: $scope.content }
.success (res)->
$scope.previewHTML = res
$scope.addTag = (e)->
new_labels= $(e.target).text()
if $scope.labels
$scope.labels += ", #{new_labels}"
else
$scope.labels = new_labels

View File

@ -1,28 +1,37 @@
@app.controller 'LikesController', ($scope, $http, $location, $cookies)->
url = $location.absUrl() + "/likes"
$scope.count = 0
$scope.get_count = ->
$http.get url
.success (res)->
$scope.count = res.count
$scope.get_count()
$http.get url
.success (res)->
$scope.count = res.count
$scope.like = $cookies.like
if $scope.like
$http
url: $location.absUrl() + "/likes/#{$scope.like}/is_liked"
method: 'GET'
.success (res)->
if res == 'true'
$scope.is_liked = true
else
$scope.is_liked = false
else
$scope.is_liked = false
$scope.submit = ->
$http.post url
.success (res)->
if res.success
$scope.like = $cookies.like = res.id
$scope.count = res.count
$scope.is_liked = true
$scope.cancel = ->
$http.delete url + "/" + $scope.like
.success (res)->
$scope.count = res.count
$scope.is_liked = false
# anyway, clear cookie
delete $cookies["like"]
$scope.like = null

View File

@ -0,0 +1,3 @@
.dash-title {
padding-top: 2rem;
}

View File

@ -1,57 +1,42 @@
// Place all the styles related to the admin/posts controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
.admin-posts-field {
margin-top: 1rem;
.blog-title {
border-bottom: 1px solid #DDDDDD;
padding-bottom: 1rem;
margin-bottom: 2rem;
}
#post_content {
min-height: 20rem;
}
.preview {
min-height: 20rem;
border: 1px solid #DDDDDD;
margin-bottom: 1.275rem;
padding: 0.275rem 0.5rem;
}
.tag {
display: inline-block;
margin: 0 0.5rem;
}
#upload_photo {
float: right;
margin-top: 2rem;
}
#post_content {
width: 800px;
height: 390px;
}
div.submit {
display: block;
line-height: 40px;
width: 805px;
background-color: #CCC;
padding-left: 16px;
font-size: 20px;
.edit-post-link {
margin-right: 1rem;
}
ul.tab {
margin: 10px 0 10px 0;
overflow: hidden;
li {
float: left;
padding: 5px 5px;
border: 1px solid #CCC;
cursor: pointer;
}
li.active {
border-bottom: none;
border-top: 1px solid #CCC;
}
li:not(.active) {
border-top: none;
border-top-right-radius: none;
border-top-left-radius: none;
}
li#content {
border-top-left-radius: 10px;
border-right: none;
}
li#preview {
border-right-bottom-radius: 10px;
border-top-right-radius: 10px;
.admin-post-summary-field {
i {
margin-right: 0.385rem;
margin-left: 0.5rem;
}
}
div.preview {
width: 800px;
min-height: 396px;
border: 1px solid #CCC;
padding: 5px 5px;
display: none;
}
#upload_photo {
float: right;
}

View File

@ -16,7 +16,8 @@
}
h1, h2, h3, h4, h5, h6 {
border-bottom: 1px solid #F7F7F7;
border-bottom: 1px solid #EEEEEE;
margin-bottom: 1rem;
}
pre {

View File

@ -0,0 +1,10 @@
class Admin::DashboardController < ApplicationController
layout 'layouts/admin'
before_action :authericate_user!
def index
@posts_count = Post.all.size
@comments_count = Comment.all.size
@visited_count = Post.all.inject(0) { |res, p| res + p.visited_count }
end
end

View File

@ -1,39 +1,72 @@
class Admin::PostsController < ApplicationController
layout 'layouts/admin'
before_action :authericate_user!
def new
@post = Post.new
end
def edit
@post = Post.find( params[:id] )
end
def destroy
@post = Post.find( params[:id] )
if @post.destroy
flash[:notice] = '删除博客成功'
redirect_to admin_posts_path
else
flash[:error] = '删除博客失败'
redirect_to admin_posts_path
end
end
def index
@posts = Post.desc(:created_at)
end
def create
@post = Post.new( post_params )
labels = params.delete(:labels).to_s
@post = Post.new( params.permit(:title, :content, :type) )
labels.split(",").each do |name|
label = Label.find_or_initialize_by(name: name.strip)
label.save!
@post.labels << label
end
if @post.save
flash[:notice] = "success!"
redirect_to root_path
flash[:notice] = '创建博客成功'
redirect_to admin_root_path
else
flash[:alert] = "fail!"
render :action=>:new
flash[:error] = '创建失败'
render :new
end
end
def update
@post = Post.find( params[:id] )
labels = params.delete(:labels).to_s
#clear labels
@post.labels = []
labels.split(",").each do |name|
label = Label.find_or_initialize_by(name: name.strip)
label.save!
@post.labels << label
end
if @post.update( params.permit(:title, :content, :type) )
flash[:notice] = '更新博客成功'
redirect_to admin_posts_path
else
flash[:error] = '更新博客失败'
render :edit
end
end
def preview
text = params.permit(:text)[:text] || ""
rd = Redcarpet::Render::HTML.new(:hard_wrap=>true)
md = Redcarpet::Markdown.new(rd, :autolink=>true)
render :text => md.render(text)
end
def post_params
params.require(:post).permit(:title, :content, :type)
render :text => Post.render_html(params[:content] || "")
end
end

View File

@ -10,4 +10,8 @@ class ApplicationController < ActionController::Base
def format_date(time)
time.strftime("%Y.%m.%d")
end
protected
def authericate_user!
end
end

View File

@ -13,6 +13,7 @@ class BlogsController < ApplicationController
def show
@post = Post.find(params[:id])
@post.visited
@prev = Post.where(:created_at.lt => @post.created_at).desc(:created_at).where(:id.ne => @post.id).first
@next = Post.where(:created_at.gt => @post.created_at).asc(:created_at).where(:id.ne => @post.id).first
@comments = @post.comments

View File

@ -6,6 +6,15 @@ class LikesController < ApplicationController
render :json=> { success: true, count: post.liked_count }
end
def is_liked
post = Post.find( params[:blog_id] )
if post.likes.where(id: params[:id]).first
render text: true
else
render text: false
end
end
def create
post = Post.find( params[:blog_id] )
like = Like.new

View File

@ -1,2 +0,0 @@
module Admin::PostsHelper
end

View File

@ -4,6 +4,6 @@ class Label
field :name, :type => String
has_and_belongs_to_many :post
has_and_belongs_to_many :posts
validates :name, presence: true
end

View File

@ -23,9 +23,13 @@ class Post
validates :type, :presence=>true, :inclusion => { :in => [ TECH, LIFE, CREATOR ] }
def content_html
self.class.render_html(self.content)
end
def self.render_html(content)
rd = CodeHTML.new
md = Redcarpet::Markdown.new(rd, autolink: true, fenced_code_blocks: true)
md.render(self.content)
md.render(content)
end
def visited
@ -35,12 +39,12 @@ class Post
end
def sub_content
HTML_Truncator.truncate(content_html,100)
HTML_Truncator.truncate(content_html,30)
end
def labels_content
def labels_content( need_blank=false )
content = self.labels.collect { |label| label.name }.join(", ")
content = '无' if content.blank?
content = '无' if content.blank? and !need_blank
content
end

View File

@ -0,0 +1,20 @@
.row
.small-12.columns
h2.dash-title 统计信息
hr
table width="100%"
thead
tr
th 条目
th 数据
tbody
tr
td 总博客数
td #{@posts_count}
tr
td 总评论数
td #{@comments_count}
tr
td 总浏览量
td #{@visited_count}

View File

@ -1,14 +1,39 @@
= simple_form_for(@post, :url=> admin_posts_path(@post)) do |f|
= f.input :title
ul.tab
li#content.active
| content
li#preview url=preview_admin_posts_path
| preview
= link_to t(:upload_photo), "#", :id=>'upload_photo'
- url = @post.new_record? ? admin_posts_path : admin_post_path(@post)
= simple_form_for(@post, url: url, html: {novalidate: '' }) do |f|
.row
.large-6.columns
= f.input :title, label: '标题', "ng-model"=>"title", input_html: { name: 'title' }
.row
.small-6.large-3.columns
= f.input :type, :as=>:select, :collection=> [ Post::TECH, Post::LIFE, Post::CREATOR ], label: '类别', "ng-model"=>"type", input_html: { name: 'type' }
.row
.small-12.large-6.columns
= f.simple_fields_for :label do |p|
= label_tag :labels, '标签'
= text_field_tag :labels, @post.labels_content(true), "ng-model"=>"labels", "ng-init" => "labels= '#{@post.labels_content(true)}'"
.row
.small-12.columns
p
| 已有标签:
span
- Label.all.each do |label|
a.tag href="#" ng-click="addTag($event)" #{label.name}
/ tabs and upload file field
dl.tabs
dd ng-class="{ active: body_active }"
a href="" ng-click="changeToBody()" 正文
dd ng-class="{ active: !body_active }"
a href="#" ng-click="changeToPreview()" 预览
= link_to t(:upload_photo), "#", :id=>'upload_photo'
input[type="file" style="display: none;"]
= f.input :content, :as=> :text, :label=>false
.preview.wikistyle
= f.input :type, :as=>:select, :collection=> [ Post::TECH, Post::LIFE, Post::CREATOR ], :include_blank=>false
.submit
= f.submit
.content-field ng-show="body_active" ng-model= 'content'
= f.input :content, :as=> :text, :label => false, input_html: { name: 'content', "value"=>"#{@post.content}", "ng-model"=>'content', "ng-init"=>"content='#{@post.content}'" }
.preview.markdown ng-hide="body_active" ng-bind-html=" previewHTML "
.row
.small-12.large-6.columns.posts-button
button 提交

View File

@ -0,0 +1,5 @@
.row
.small-12.columns
.admin-posts-field ng-controller="AdminPostsController"
h3.blog-title 修改博客
= render 'form'

View File

@ -0,0 +1,29 @@
.row.admin-posts-field
.small-12.columns
h3.blog-title 博客管理
table width="100%"
thead
tr
th 标题
th 概要
th 操作
tbody
- @posts.each do |post|
tr
td
= link_to post.title, blog_path(post)
td.admin-post-summary-field
i.fi-calendar
span #{format_time(post.created_at)}
i.fi-list
span #{ post.type }
i.fi-pricetag-multiple
span #{ post.labels_content }
i.fi-torsos
span #{ post.visited_count }
i.fi-heart
span #{ post.liked_count }
td
= link_to '编辑', edit_admin_post_path(post), class: 'edit-post-link'
= link_to '删除', admin_post_path(post), method: 'DELETE', confirm: '确认删除?'

View File

@ -1,2 +1,5 @@
h3 #{t('new_post')}
= render 'form'
.row
.small-12.columns
.admin-posts-field ng-controller="AdminPostsController"
h3.blog-title #{t('new_post')}
= render 'form'

View File

@ -13,9 +13,9 @@ p.ptag
| 发表于:
span #{format_date(post.created_at)}
p ng-controller="LikesController"
button.like-button ng-show="! like " ng-click="submit()"
button.like-button ng-show="! is_liked " ng-click="submit()"
|{{ count }}
span Like
button.like-button ng-show=" like " ng-click="cancel()"
button.like-button ng-show=" is_liked " ng-click="cancel()"
|{{ count }}
span Liked

View File

@ -0,0 +1,47 @@
html
head
meta charset="utf-8"
meta name="viewport" content="width=device-width, initial-scale=1.0"
title Admin Page for WinDy's Blog
= stylesheet_link_tag "application"
= javascript_include_tag "vendor/modernizr"
= javascript_include_tag "application"
= csrf_meta_tags
body
nav.top-bar data-topbar=""
ul.title-area
li.name
h1
= link_to 'Dashboard For WinDy', admin_root_path
li.toggle-topbar.menu-icon
a href="#" Menu
section.top-bar-section
ul.left
li
= link_to '创建新博客', new_admin_post_path
li
= link_to '管理博客', admin_posts_path
ul.right
li
= link_to '返回首页', root_path
- flash.each do |name, msg|
- if msg.is_a?(String)
div class=("alert-box #{name == :notice ? "success" : "alert"}") data-alert=""
= content_tag :div, msg
a.close href="#" &times;
.admin-main-field ng-app="app"
= yield
= render "layouts/footer"
javascript:
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-32883596-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();

View File

@ -6,7 +6,11 @@ WBlog::Application.routes.draw do
get :rss
end
resources :comments, only: [:index, :create]
resources :likes, only: [:index, :create, :destroy]
resources :likes, only: [:index, :create, :destroy] do
member do
get :is_liked
end
end
end
@ -20,8 +24,8 @@ WBlog::Application.routes.draw do
post :preview
end
end
root to: 'dashboard#index'
end
get '/about' => 'home#index'
get '/admin' => 'admin/posts#new'
get '/:type' => 'archives#index'
end

View File

@ -0,0 +1,12 @@
require 'spec_helper'
describe Admin::DashboardController do
describe "GET 'index'" do
it "returns http success" do
get 'index'
response.should be_success
end
end
end

View File

@ -4,13 +4,40 @@ describe Admin::PostsController do
it "preview should return ok" do
post :preview
response.body.should == ""
post :preview, text: '123'
post :preview, content: '123'
response.body.should == "<p>123</p>\n"
post :preview, text: <<-EOF
```ruby
puts 'hello world'
```
EOF
response.body.should == "<p><code>ruby<br>\nputs &#39;hello world&#39;<br>\n</code></p>\n"
end
it "update" do
post = create(:post)
patch 'update', id: post.id, labels: 'think, go '
expect(post.reload.labels.size).to eq(2)
end
it "destroy" do
post = create(:post)
label = create(:label)
post.labels << label
post.save!
expect(label.posts.size).to eq(1)
delete 'destroy', id: post.id
expect( Post.all.size ).to eq(0)
expect( label.reload.posts.size ).to eq(0)
end
it "create" do
post_params = attributes_for(:post)
post 'create', post_params.merge( labels: 'think, go ' )
post = Post.first
expect( post.labels.size ).to eq(2)
end
it "create fail and see labels_content" do
post 'create', labels: 'think, go '
expect( assigns(:post).labels_content ).to eq('think, go')
end
end

5
spec/factories/labels.rb Normal file
View File

@ -0,0 +1,5 @@
FactoryGirl.define do
factory :label do
name 'label'
end
end