diff --git a/.gitignore b/.gitignore index 01887c4..035c922 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ /public/uploads/* *.lock + +# Ignore application configuration +/config/application.yml diff --git a/Gemfile b/Gemfile index 043dda9..213b897 100644 --- a/Gemfile +++ b/Gemfile @@ -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 diff --git a/app/assets/javascripts/admin/posts.js.coffee b/app/assets/javascripts/admin/posts.js.coffee index 1199dc7..d4162ca 100644 --- a/app/assets/javascripts/admin/posts.js.coffee +++ b/app/assets/javascripts/admin/posts.js.coffee @@ -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 diff --git a/app/assets/javascripts/angularjs.js.coffee b/app/assets/javascripts/angularjs.js.coffee index 5631e76..4118e69 100644 --- a/app/assets/javascripts/angularjs.js.coffee +++ b/app/assets/javascripts/angularjs.js.coffee @@ -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') diff --git a/app/assets/javascripts/angularjs/admin_posts.js.coffee b/app/assets/javascripts/angularjs/admin_posts.js.coffee new file mode 100644 index 0000000..ec2dc01 --- /dev/null +++ b/app/assets/javascripts/angularjs/admin_posts.js.coffee @@ -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 diff --git a/app/assets/javascripts/angularjs/likes.js.coffee b/app/assets/javascripts/angularjs/likes.js.coffee index 38f5e43..78d1fda 100644 --- a/app/assets/javascripts/angularjs/likes.js.coffee +++ b/app/assets/javascripts/angularjs/likes.js.coffee @@ -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 diff --git a/app/assets/stylesheets/admin/dashboard.css.scss b/app/assets/stylesheets/admin/dashboard.css.scss new file mode 100644 index 0000000..f8b22f9 --- /dev/null +++ b/app/assets/stylesheets/admin/dashboard.css.scss @@ -0,0 +1,3 @@ +.dash-title { + padding-top: 2rem; +} diff --git a/app/assets/stylesheets/admin/posts.css.scss b/app/assets/stylesheets/admin/posts.css.scss index 51192ad..44f9a96 100644 --- a/app/assets/stylesheets/admin/posts.css.scss +++ b/app/assets/stylesheets/admin/posts.css.scss @@ -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; -} diff --git a/app/assets/stylesheets/markdown.css.scss b/app/assets/stylesheets/markdown.css.scss index cfbae1e..af12b23 100644 --- a/app/assets/stylesheets/markdown.css.scss +++ b/app/assets/stylesheets/markdown.css.scss @@ -16,7 +16,8 @@ } h1, h2, h3, h4, h5, h6 { - border-bottom: 1px solid #F7F7F7; + border-bottom: 1px solid #EEEEEE; + margin-bottom: 1rem; } pre { diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb new file mode 100644 index 0000000..9baa8e3 --- /dev/null +++ b/app/controllers/admin/dashboard_controller.rb @@ -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 diff --git a/app/controllers/admin/posts_controller.rb b/app/controllers/admin/posts_controller.rb index cba746e..84e6ace 100644 --- a/app/controllers/admin/posts_controller.rb +++ b/app/controllers/admin/posts_controller.rb @@ -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 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 80844fb..b93b985 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -10,4 +10,8 @@ class ApplicationController < ActionController::Base def format_date(time) time.strftime("%Y.%m.%d") end + + protected + def authericate_user! + end end diff --git a/app/controllers/blogs_controller.rb b/app/controllers/blogs_controller.rb index a6fd08c..f59e43a 100644 --- a/app/controllers/blogs_controller.rb +++ b/app/controllers/blogs_controller.rb @@ -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 diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb index e36a2ac..32b9f99 100644 --- a/app/controllers/likes_controller.rb +++ b/app/controllers/likes_controller.rb @@ -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 diff --git a/app/helpers/admin/posts_helper.rb b/app/helpers/admin/posts_helper.rb deleted file mode 100644 index e9158b2..0000000 --- a/app/helpers/admin/posts_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module Admin::PostsHelper -end diff --git a/app/models/label.rb b/app/models/label.rb index 30be414..1d8ce48 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -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 diff --git a/app/models/post.rb b/app/models/post.rb index 7195042..1a15a6b 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -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 diff --git a/app/views/admin/dashboard/index.html.slim b/app/views/admin/dashboard/index.html.slim new file mode 100644 index 0000000..b8241ce --- /dev/null +++ b/app/views/admin/dashboard/index.html.slim @@ -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} + diff --git a/app/views/admin/posts/_form.html.slim b/app/views/admin/posts/_form.html.slim index 6ca3291..24a3927 100644 --- a/app/views/admin/posts/_form.html.slim +++ b/app/views/admin/posts/_form.html.slim @@ -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 提交 diff --git a/app/views/admin/posts/edit.html.slim b/app/views/admin/posts/edit.html.slim new file mode 100644 index 0000000..ae30140 --- /dev/null +++ b/app/views/admin/posts/edit.html.slim @@ -0,0 +1,5 @@ +.row + .small-12.columns + .admin-posts-field ng-controller="AdminPostsController" + h3.blog-title 修改博客 + = render 'form' diff --git a/app/views/admin/posts/index.html.slim b/app/views/admin/posts/index.html.slim new file mode 100644 index 0000000..0ceb9c8 --- /dev/null +++ b/app/views/admin/posts/index.html.slim @@ -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: '确认删除?' + diff --git a/app/views/admin/posts/new.html.slim b/app/views/admin/posts/new.html.slim index f8b33f2..044b860 100644 --- a/app/views/admin/posts/new.html.slim +++ b/app/views/admin/posts/new.html.slim @@ -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' diff --git a/app/views/blogs/_post.html.slim b/app/views/blogs/_post.html.slim index 010c5e9..b68193d 100644 --- a/app/views/blogs/_post.html.slim +++ b/app/views/blogs/_post.html.slim @@ -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 diff --git a/app/views/layouts/admin.html.slim b/app/views/layouts/admin.html.slim new file mode 100644 index 0000000..12e5db3 --- /dev/null +++ b/app/views/layouts/admin.html.slim @@ -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="#" × + .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); + })(); diff --git a/config/routes.rb b/config/routes.rb index 5772c96..45f79a6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/spec/controllers/admin/dashboard_controller_spec.rb b/spec/controllers/admin/dashboard_controller_spec.rb new file mode 100644 index 0000000..22a6cad --- /dev/null +++ b/spec/controllers/admin/dashboard_controller_spec.rb @@ -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 diff --git a/spec/controllers/admin/posts_controller_spec.rb b/spec/controllers/admin/posts_controller_spec.rb index 5ac2f06..aff92db 100644 --- a/spec/controllers/admin/posts_controller_spec.rb +++ b/spec/controllers/admin/posts_controller_spec.rb @@ -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 == "

123

\n" - post :preview, text: <<-EOF -```ruby -puts 'hello world' -``` -EOF - response.body.should == "

ruby
\nputs 'hello world'
\n

\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 diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb new file mode 100644 index 0000000..9766101 --- /dev/null +++ b/spec/factories/labels.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :label do + name 'label' + end +end