Commit 35b6af41 by tady

Merge branch 'master' of github.com:tadyjp/rendezvous

parents 51262d4e e64d04b5
source 'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 4'
gem 'rails', '~> 4.1'
# Use SCSS for stylesheets
gem 'sass-rails'
......@@ -17,9 +17,6 @@ gem 'jquery-rails'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
gem 'therubyracer', platforms: :ruby
# Use jquery as the JavaScript library
# gem 'jquery-rails'
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
# gem 'turbolinks'
......@@ -48,9 +45,6 @@ gem 'mysql2'
gem 'devise'
# http://d.hatena.ne.jp/tkawa/20130812/p1
gem 'devise-better_routes'
gem 'omniauth-google-oauth2'
# Markdown
......@@ -93,6 +87,7 @@ group :development, :test do
gem 'teaspoon'
gem 'guard-teaspoon'
gem 'byebug'
end
group :test do
......
......@@ -11,4 +11,6 @@ $ ->
# TODO
prettyPrint()
$(document).on 'ajax:success', '.ajax_link', (data, res, xhr) ->
console.log(res)
$('#yield').html(res)
......@@ -21,3 +21,5 @@
_.mixin(_.string.exports());
// $('[data-toggle="tooltip"]').tooltip();
// $('[data-toggle="popover"]').popover();
......@@ -8,14 +8,22 @@ $.extend
settings.$input.fileupload
dataType: 'json'
done: (e, data) ->
$.each data.result.files, (index, _file) ->
settings.$textarea.val(settings.$textarea.val() + "![" + _file.name + "](" + _file.url + ")\n")
$.each data.result.files, (index, file) ->
# TODO: カーソル位置に挿入
if file.type is 'image'
# image ![file-name](file-url)
settings.$textarea.val(settings.$textarea.val() + "\n![" + file.name + "](" + file.url + ")\n")
else if file.type is 'slide'
# slide !slide!(file-url)
settings.$textarea.val(settings.$textarea.val() + "\n!slide!(" + file.url + ")\n")
settings.$textarea.trigger("change")
# $('<p/>').text(file.name).appendTo('#files') # TODO
progressall: (e, data) ->
progress = parseInt(data.loaded / data.total * 100, 10)
$('.progress-bar').css
width: progress + '%'
# progressall: (e, data) ->
# progress = parseInt(data.loaded / data.total * 100, 10)
# $('.progress-bar').css
# width: progress + '%'
settings.$input.prop('disabled', !$.support.fileInput)
.parent().addClass($.support.fileInput ? undefined : 'disabled');
......
......@@ -26,3 +26,7 @@ a .text-link {
a:visited .text-link {
color: #609;
}
.popover {
max-width: 400px;
}
// Place all the styles related to the templates controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
// Place all the styles related to the watchings controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
......@@ -7,11 +7,11 @@ class ApisController < ApplicationController
def markdown_preview
# TODO: not to use
render text: h_application_format_markdown(params[:text] || '')
render text: MarkdownRenderer.new(params[:text]).render
end
# Receive file and upload to S3
def file_receiver
s3 = AWS::S3.new
bucket_name = "#{Settings.s3.bucket_name}/1/#{current_user.id}"
# bucket_name = "1/#{current_user.id}"
......@@ -22,12 +22,21 @@ class ApisController < ApplicationController
params[:files].each do |file|
basename = File.basename(file.path)
next unless file.original_filename =~ /\.(jpe?g|png|gif|pdf)\Z/
object_file_name = "#{Digest::MD5.file(file.path).to_s}#{File.extname(file.original_filename)}"
obj = bucket.objects[object_file_name]
res = obj.write(file: file.path, acl: :public_read)
s3_files << { name: file.original_filename, url: res.public_url.to_s }
file_type = case file.original_filename
when /\.(jpe?g|png|gif)\Z/
:image
when /\.pdf\Z/
:slide
end
s3_files << { name: file.original_filename, url: res.public_url.to_s, type: file_type }
end
render json: { status: 'OK', files: s3_files }
......
......@@ -41,8 +41,8 @@ module RV::Mailer
html_body = template
.sub('__POST_URL__', Settings.rendezvous.app_host + post.decorate.show_path)
.sub('__HTML_TITLE__', h_application_format_markdown(post.title))
.sub('__HTML_BODY__', h_application_format_markdown(post.body))
.sub('__HTML_TITLE__', MarkdownRenderer.new(post.title).render)
.sub('__HTML_BODY__', MarkdownRenderer.new(post.body).render)
.sub('__RV_URL__', Settings.rendezvous.app_host + '/')
premailer = Premailer.new(html_body, with_html_string: true, adapter: :nokogiri)
......
require 'nkf'
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy, :slideshow]
before_action :set_post, only: [:show, :edit, :update, :destroy, :slideshow, :watch]
include RV::Mailer
# GET /posts/1
# GET /posts/1.json
def show
current_user.visit_post!(@post)
@post.tags.each do |_tag|
add_breadcrumb("##{_tag.name}", _tag.decorate.show_path)
end
......@@ -105,6 +107,19 @@ class PostsController < ApplicationController
render layout: 'slideshow'
end
def watch
if current_user.watching?(post: @post)
current_user.unwatch!(post: @post)
else
current_user.watch!(post: @post)
end
respond_to do |format|
format.html { render action: :show, layout: false }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
......
class TemplatesController < ApplicationController
def show
end
end
class MeController < ApplicationController
class UsersController < ApplicationController
before_action :set_user, only: [:edit, :update]
def edit
......@@ -6,21 +7,20 @@ class MeController < ApplicationController
def update
respond_to do |format|
if @me.update(user_params)
format.html { redirect_to edit_me_path, flash: { notice: 'Post was successfully updated.' } }
if @user.update(user_params)
format.html { redirect_to edit_user_path, flash: { notice: 'Post was successfully updated.' } }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: @me.errors, status: :unprocessable_entity }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
@me = current_user
@user = current_user
end
def user_params
......
class WatchingsController < ApplicationController
def show
@posts = current_user.watching_posts.order(updated_at: :desc).page(params[:page]).decorate
end
end
......@@ -27,6 +27,14 @@ class PostDecorator < Draper::Decorator
end
end
def created_date
model.created_at.strftime('%Y-%m-%d')
end
def updated_date
model.updated_at.strftime('%Y-%m-%d')
end
def display_specified_date
if model.specified_date
model.specified_date.strftime('%Y-%m-%d')
......
class TemplateDecorator < Draper::Decorator
delegate_all
# Define presentation-specific methods here. Helpers are accessed through
# `helpers` (aka `h`). You can override attributes, for example:
#
# def created_at
# helpers.content_tag :span, class: 'time' do
# object.created_at.strftime("%a %m/%d/%y")
# end
# end
end
......@@ -4,5 +4,4 @@ class UserDecorator < Draper::Decorator
def draft_count
model.posts.where(is_draft: true).count
end
end
class WatchingDecorator < Draper::Decorator
delegate_all
# Define presentation-specific methods here. Helpers are accessed through
# `helpers` (aka `h`). You can override attributes, for example:
#
# def created_at
# helpers.content_tag :span, class: 'time' do
# object.created_at.strftime("%a %m/%d/%y")
# end
# end
end
module ApplicationHelper
def h_application_format_markdown(text)
raise "deplicated error"
text = GitHub::Markdown.render_gfm(text)
text.html_safe
end
......
module TemplatesHelper
end
module WatchingsHelper
end
......@@ -11,19 +11,39 @@
#
class Comment < ActiveRecord::Base
######################################################################
# Associations
######################################################################
belongs_to :author, class_name: 'User'
belongs_to :post
######################################################################
# Validations
######################################################################
validates :author_id, presence: true
validates :post_id, presence: true
validates :body, presence: true
### Callback ###
after_save :notify_author
######################################################################
# Callback
######################################################################
after_save :set_watcher!
after_save :notify_watchers!
######################################################################
# Instance method
######################################################################
private
def notify_author
post.author.push_notification(post.decorate.show_path, "#{author.name}さんがあなたの投稿にコメントしました")
def notify_watchers!
post.watchers.each do |watcher|
next if watcher == author
watcher.push_notification(post.decorate.show_path, "#{author.name}さんが「#{post.title}」にコメントしました。")
end
end
def set_watcher!
author.watch!(post: post)
end
end
# == Schema Information
#
# Table name: footprints
#
# id :integer not null, primary key
# user_id :integer not null
# post_id :integer not null
# created_at :datetime
# updated_at :datetime
#
class Footprint < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
class MarkdownRenderer
def initialize(text)
@text = text || ''
end
# pdf viewerの変換
# !slide!(file-url) -> %%slide:0%% -> <iframe>...</iframe>
def render
# slideのurlを一時保管
slide_urls = []
text = @text.gsub(/!slide!\(([^\)]+)\)/) do |_|
slide_urls << %Q{
<div class="embed-responsive embed-responsive-4by3">
<iframe style="text-align:center;" src="/ViewerJS/##{$1}" width="400" height="300" allowfullscreen="true" webkitallowfullscreen="true"></iframe>
</div>
}
"%%slide:#{slide_urls.size - 1}%%"
end
text = GitHub::Markdown.render_gfm(text)
# 保管したslide urlを取り出す
text = text.gsub(/%%slide:(\d+)%%/) do |_|
slide_urls[$1.to_i]
end
text.html_safe
end
end
# == Schema Information
#
# Table name: notifications
#
# id :integer not null, primary key
# user_id :integer
# read_at :datetime
# is_read :boolean default(FALSE), not null
# detail_path :string(255)
# body :text
# created_at :datetime
# updated_at :datetime
#
class Notification < ActiveRecord::Base
belongs_to :user
......
......@@ -16,12 +16,17 @@
require 'date'
class Post < ActiveRecord::Base
######################################################################
# Associations
######################################################################
has_many :post_tags
has_many :tags, through: :post_tags
belongs_to :author, class_name: 'User'
has_many :comments
has_many :footprints
# default_scope { where(is_draft: false).order(:updated_at => :desc) }
has_many :watches, :as => :watchable, :dependent => :destroy
has_many :watchers, :through => :watches
######################################################################
# validations
......@@ -30,6 +35,12 @@ class Post < ActiveRecord::Base
validates :body, presence: true
######################################################################
# Callback
######################################################################
after_save :set_watcher!
after_save :notify_watchers!
######################################################################
# Named scope
######################################################################
scope :search, (lambda do |query|
......@@ -68,17 +79,21 @@ class Post < ActiveRecord::Base
order(updated_at: :desc).limit(limit)
}
######################################################################
# Instance method
######################################################################
# generate forked post (not saved)
def generate_fork(user)
# `id`以外をコピーする
_forked_post = Post.new(self.attributes.except('id'))
# `%Name`をユーザー名に置換
_forked_post.title = _forked_post.title.gsub(/%Name/, user.name)
# `%name`をユーザー名に置換
_forked_post.title = _forked_post.title.gsub(/%name/, user.name)
# `%Y`などを日付に変換
_forked_post.title = Time.now.strftime(_forked_post.title) # TODO
_forked_post.title = _forked_post.title + ' のコピー'
_forked_post.title = _forked_post.title
_forked_post.tag_ids = self.tag_ids
_forked_post.author = user
......@@ -91,4 +106,29 @@ class Post < ActiveRecord::Base
def body_for_slideshow
self.body.gsub(/^#/, "---\n\n#")
end
def visited_user_count
footprints.select(:user_id).uniq.count
end
# FIXME:
# has_many :watchers, :through => :watches
# 正常に動作しないため動作しないため一時的にメソッドを作成
# def watchers
# watches.map { |watch| watch.watcher }
# end
private
def notify_watchers!
watchers.each do |watcher|
next if watcher == author
watcher.push_notification(decorate.show_path, "#{author.name}さんが「#{title}」を編集しました")
end
end
def set_watcher!
author.watch!(post: self)
end
end
......@@ -8,6 +8,7 @@
# updated_at :datetime
# ancestry :string(255)
# body :text
# posts_count :integer default(0), not null
#
class Tag < ActiveRecord::Base
......
......@@ -33,11 +33,15 @@ class User < ActiveRecord::Base
devise :omniauthable, omniauth_providers: [:google_oauth2]
######################################################################
# association
# Associations
######################################################################
has_many :posts, foreign_key: 'author_id'
has_many :comments, foreign_key: 'author_id'
has_many :notifications
has_many :footprints
has_many :watchings, class_name: 'Watch', foreign_key: 'watcher_id'
has_many :watching_posts, :through => :watchings, :source => :watchable, :source_type => "Post"
######################################################################
# scope
......@@ -52,7 +56,7 @@ class User < ActiveRecord::Base
######################################################################
# validations
# Validations
######################################################################
validates :name, presence: true
validates :email, presence: true
......@@ -80,6 +84,9 @@ class User < ActiveRecord::Base
user
end
######################################################################
# instance methods
######################################################################
# check if google oauth token is expired
def google_oauth_token_expired?
......@@ -111,5 +118,51 @@ class User < ActiveRecord::Base
notifications.create(detail_path: detail_path, body: body, is_read: false)
end
# record footprint
def visit_post!(post)
footprints.create!(post: post)
end
def watch!(hash)
if hash[:post]
watching_posts << hash[:post] unless watching_posts.include?(hash[:post])
elsif hash[:tag]
raise 'Not Implemented.'
elsif hash[:user]
raise 'Not Implemented.'
else
raise 'No hash argument set.'
end
end
def unwatch!(hash)
if hash[:post]
hash[:post].watches.where(watcher: self).destroy_all
elsif hash[:tag]
raise 'Not Implemented.'
elsif hash[:user]
raise 'Not Implemented.'
else
raise 'No hash argument set.'
end
end
# check if user watching post/tag/user
# TODO: tag/user
def watching?(hash)
if hash[:post]
hash[:post].watches.where(watcher: self).exists?
elsif hash[:tag]
raise 'Not Implemented.'
elsif hash[:user]
raise 'Not Implemented.'
else
raise 'No hash argument set.'
end
end
# def watching_posts
# ids = watching_items.where(resource_type: "Post").pluck(:resource_id)
# Post.where(id: ids)
# end
end
class Watch < ActiveRecord::Base
######################################################################
# Associations
######################################################################
belongs_to :watcher, class_name: 'User'
belongs_to :watchable, polymorphic: true
######################################################################
# Validations
######################################################################
validates :watcher_id, uniqueness: { scope: [:watchable_type, :watchable_id] }
end
......@@ -27,5 +27,3 @@
a.list-group-item data-tag-id=tag.id href=search_path(q: "##{tag.name}")
= tag.name
span.badge = tag.posts_count
......@@ -4,11 +4,12 @@ html lang="ja"
head
title Rendezvous
meta content="width=device-width, initial-scale=1.0" name="viewport" /
link href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" rel="stylesheet" /
link href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap-theme.min.css" rel="stylesheet" /
link href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet" /
link href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css" rel="stylesheet" /
link href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/1.6.4/fullcalendar.css" rel="stylesheet" /
link href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/1.6.4/fullcalendar.print.css" rel="stylesheet" /
link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet"
script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"
= stylesheet_link_tag "application", media: "all"
= render_style
= csrf_meta_tags
......@@ -16,13 +17,12 @@ html lang="ja"
= render partial: 'partials/header_notifications'
- if params[:controller] != 'welcome'
= render partial: 'partials/app_header'
.container.container-main
.container.container-main#yield
= yield
script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"
script src="//cdnjs.cloudflare.com/ajax/libs/underscore.string/2.3.3/underscore.string.min.js"
script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"
script src="//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"
script src="//netdna.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"
script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"
script src="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/1.6.4/fullcalendar.min.js"
javascript:
......
......@@ -18,12 +18,20 @@ nav.navbar.navbar-default.navbar-fixed-top role="navigation"
li class=('active' if current_page?(flow_path))
a href=flow_path title="Frow"
| Flow
li class=('active' if current_page?(templates_path))
a href=templates_path title="Templates"
| Templates
ul.nav.navbar-nav.navbar-right
li
a#notifications data-container="body" data-toggle="popover" data-placement="bottom"
span.glyphicon.glyphicon-flag
- if current_user.notifications.unread.any?
span.badge = current_user.notifications.unread.count
li
form
a.btn.btn-primary.navbar-btn href=new_post_path
| Post&nbsp;&nbsp;
| New Post&nbsp;&nbsp;
span.glyphicon.glyphicon-pencil
li.dropdown
a.dropdown-toggle data-toggle="dropdown"
......@@ -37,25 +45,30 @@ nav.navbar.navbar-default.navbar-fixed-top role="navigation"
| 下書き
span.badge.pull-right = current_user.decorate.draft_count
li
a href=edit_me_path マイページ
a href=edit_user_path マイページ
li
a href=watching_path Watchings
li.divider
li
a href=me_session_path data-method="delete" rel="nofollow" SignOut
li.dropdown
a.dropdown-toggle data-toggle="dropdown"
| 通知
- if current_user.notifications.unread.any?
span.badge = current_user.notifications.unread.count
b.caret
ul.dropdown-menu
a href=destroy_user_session_path data-method="delete" rel="nofollow" SignOut
script#notification-content type="text/template"
- if current_user.notifications.unread.any?
h4 通知一覧
.list-group
- current_user.notifications.unread.each do |notification|
li
a href=notification_bridge_path(notification.id)
a.list-group-item href=notification_bridge_path(notification.id)
spen.small
| [#{notification.created_at.strftime('%m/%d %H:%M')}]&nbsp;
= notification.body
- else
li
a 通知はありません
h4 通知はありません
- content_for :footer_js do
javascript:
$('#notifications').popover({
html: true,
content: $('#notification-content').html()
});
......@@ -67,7 +67,7 @@ input#fileupload data-url="/apis/file_receiver" multiple="" name="files[]" style
- content_for :footer_js do
javascript:
// $.setConfirmUnload();
$.setConfirmUnload();
$('#post-form').mod_mdEditor({end_point: '/apis/markdown_preview'});
......
......@@ -8,8 +8,7 @@ a.list-group-item.post-list.mod-hover-hidden data-post-id=post.id href=post_path
h4.text-link #{post.title}
.col-xs-3
span.label.label-danger = post.display_specified_date if post.specified_date
small.pull-right
##{post.id}
small.pull-right ##{post.id}
.row
.col-xs-8
small.text-success
......@@ -21,14 +20,15 @@ a.list-group-item.post-list.mod-hover-hidden data-post-id=post.id href=post_path
| &nbsp;
.col-xs-4
small.pull-right
span.glyphicon.glyphicon-time
span.mod-hover-hidden-item
| 読了時間
span.glyphicon.glyphicon-time title="読了時間"
| &nbsp;#{post.read_time}&nbsp;&nbsp;
span.glyphicon.glyphicon-comment
span.mod-hover-hidden-item
| コメント
span.glyphicon.glyphicon-comment title="コメント"
| &nbsp;#{post.comments.count}&nbsp;&nbsp;
span.glyphicon.glyphicon-eye-open title="閲覧者数"
| &nbsp;#{post.visited_user_count}&nbsp;&nbsp;
.row
.col-xs-12
small.text-shadow
......
= render_breadcrumbs
.row
.panel.panel-default
.col-xs-9
.panel.panel-info
.panel-heading
h3.panel-title
a href=post_path(@post) = @post.title
ul.list-group
li.list-group-item
- @post.tags.each do |tag|
span.label.label-success
a href=tag.decorate.show_path
| ##{tag.name}
| &nbsp;
span.label.label-info
a href=(search_path(q: "@#{@post.author.name}"))
| @#{@post.author.name}
| &nbsp;
span.label.label-danger
a href=(search_path(q: "date:#{@post.display_date}")) = @post.display_date
.btn-group.pull-right style=("margin: -7px -12px 0 0;")
.panel-body.viewer.github
= MarkdownRenderer.new(@post.body).render
.col-xs-3
.btn-group
a.btn.btn-primary href=edit_post_path(@post)
| 編集&nbsp;
span.glyphicon.glyphicon-pencil
button.btn.btn-default.dropdown-toggle data-toggle="dropdown" type="button"
span.caret
......@@ -35,8 +27,51 @@
a data-target="#myModal" data-toggle="modal" href="#" Mail to...
li.divider
li= link_to 'Delete', post_path(@post), method: :delete, data: { confirm: 'Are you sure?' }
.panel-body.viewer.github
= h_application_format_markdown(@post.body)
| &nbsp;
.btn-group
- if current_user.watching?(post: @post)
= link_to 'Watching <span class="glyphicon glyphicon-eye-open"></span>'.html_safe, watch_post_path, :remote => true, :'data-type' => :html, :class => 'btn btn-warning ajax_link'
- else
= link_to 'Watch <span class="glyphicon glyphicon-eye-open"></span>'.html_safe, watch_post_path, :remote => true, :'data-type' => :html, :class => 'btn btn-default ajax_link'
.well style="margin-top:20px"
dl
dt 作成者
dd
a href=(search_path(q: "@#{@post.author.name}"))
| @#{@post.author.name}
dt タグ
dd
- @post.tags.each do |tag|
span.label.label-success
a href=tag.decorate.show_path
| ##{tag.name}
| &nbsp;
dt 指定日
dd
a href=(search_path(q: "date:#{@post.display_date}")) = @post.display_date
dt 作成日
dd
= @post.created_date
dt 最終更新日
dd
= @post.updated_date
dt 閲覧者数
dd
= @post.visited_user_count
dt コメント数
dd
= @post.comments.count
.row
.panel.panel-success
.panel-heading
h3.panel-title Comments
......
......@@ -32,7 +32,7 @@
|#{@tag.name}」とは
.panel-body
- if @tag.body.present?
= h_application_format_markdown(@tag.body)
= MarkdownRenderer.new(@tag.body).render
- else
| まだ「#{@tag.name}」の情報がありません。
.col-xs-4
......
/ locals:
/ post {Post}
.list-group-item.post-list.mod-hover-hidden
.container-fluid
.row
.col-xs-9
h4.text-link #{post.title}
.col-xs-3
small.pull-right ##{post.id}
.row
.col-xs-8
p.small.text-success
| #{post.author.name} posted&nbsp;
abbr.js-time-ago data-time-ago-at=post.updated_at
|.&nbsp;&nbsp;
- post.tags.each do |tag|
span.label.label-success ##{tag.name}
| &nbsp;
.col-xs-4
span.pull-right.label.label-danger = post.display_specified_date if post.specified_date
.btn-group.mod-hover-hidden
a.btn.btn-info.mod-hover-hidden-item href=fork_post_path(post)
| このテンプレートを利用する
a.btn.btn-default.mod-hover-hidden-item href=edit_post_path(post)
| このテンプレートを編集する
/! view:templates/show
.row
h1
| Templates
small - テンプレートから作成
.panel.panel-info
.panel-heading
span.glyphicon.glyphicon-info-sign
| テンプレートの使い方
.panel-body
ul
li 「template」 というタグをつけた投稿はこのページに現れます
li コピーすることで同じフォーマットの文章が簡単に書けます
.list-group
- if template_tag = Tag.find_by(name: 'template')
- template_tag.posts.decorate.each do |_post|
= render partial: 'post', locals: { post: _post }
- else
| テンプレートが存在しません
#post-form
= form_for(@me, url: me_path) do |f|
- if @me.errors.any?
= form_for(@user, url: user_path) do |f|
- if @user.errors.any?
#error_explanation
h2
= pluralize(@me.errors.count, "error")
= pluralize(@user.errors.count, "error")
| prohibited this post from being saved:
ul
- @me.errors.full_messages.each do |msg|
- @user.errors.full_messages.each do |msg|
li= msg
.row
......
/! view:flow/show
.row
h1
| Watchings
small - ウォッチ中の項目
.col-xs-8 role="navigation"
.list-group
- @posts.each do |_post|
= render partial: 'posts/large_item', locals: { post: _post }
= paginate(@posts)
<CORSConfiguration>
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
<ExposeHeader>Accept-Ranges</ExposeHeader>
<ExposeHeader>Content-Encoding</ExposeHeader>
<ExposeHeader>Content-Length </ExposeHeader>
<ExposeHeader>Content-Range</ExposeHeader>
</CORSRule>
</CORSConfiguration>
Teaspoon.setup do |config|
# This determines where the Teaspoon routes will be mounted. Changing this to "/jasmine" would allow you to browse to
# http://localhost:3000/jasmine to run your specs.
config.mount_at = "/teaspoon"
# This defaults to Rails.root if left nil. If you're testing an engine using a dummy application it can be useful to
# set this to your engines root.. E.g. `Teaspoon::Engine.root`
config.root = nil
# These paths are appended to the Rails assets paths (relative to config.root), and by default is an array that you
# can replace or add to.
config.asset_paths = ["spec/javascripts", "spec/javascripts/stylesheets"]
# Fixtures are rendered through a standard controller. This means you can use things like HAML or RABL/JBuilder, etc.
# to generate fixtures within this path.
config.fixture_path = "spec/javascripts/fixtures"
# You can modify the default suite configuration and create new suites here. Suites can be isolated from one another.
# When defining a suite you can provide a name and a block. If the name is left blank, :default is assumed. You can
# omit various directives and the defaults will be used.
#
# To run a specific suite
# - in the browser: http://localhost/teaspoon/[suite_name]
# - from the command line: rake teaspoon suite=[suite_name]
config.suite do |suite|
# You can specify a file matcher and all matching files will be loaded when the suite is run. It's important that
# these files are serve-able from sprockets.
#
# Note: Can also be set to nil.
suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee}"
# Each suite can load a different helper, which can in turn require additional files. This file is loaded before
# your specs are loaded, and can be used as a manifest.
suite.helper = "spec_helper"
# These are the core Teaspoon javascripts. It's strongly encouraged to include only the base files here. You can
# require other support libraries in your spec helper, which allows you to change them without having to restart the
# server.
#
# Available frameworks: teaspoon-jasmine, teaspoon-mocha, teaspoon-qunit
#
# Note: To use the CoffeeScript source files use `"teaspoon/jasmine"` etc.
suite.javascripts = ["teaspoon-jasmine"]
# If you want to change how Teaspoon looks, or include your own stylesheets you can do that here. The default is the
# stylesheet for the HTML reporter.
suite.stylesheets = ["teaspoon"]
# When running coverage reports, you probably want to exclude libraries that you're not testing.
# Accepts an array of filenames or regular expressions. The default is to exclude assets from vendors or gems.
suite.no_coverage = [%r{/lib/ruby/gems/}, %r{/vendor/assets/}, %r{/support/}, %r{/(.+)_helper.}]
# suite.no_coverage << "jquery.min.js" # excludes jquery from coverage reports
end
# Example suite. Since we're just filtering to files already within the root spec/javascripts, these files will also
# be run in the default suite -- but can be focused into a more specific suite.
#config.suite :targeted do |suite|
# suite.matcher = "spec/javascripts/targeted/*_spec.{js,js.coffee,coffee}"
#end
end if defined?(Teaspoon) && Teaspoon.respond_to?(:setup) # let Teaspoon be undefined outside of development/test/asset groups
......@@ -11,11 +11,14 @@ Rendezvous::Application.routes.draw do
get 'stock' => 'stock#show', as: 'stock'
get 'flow' => 'flow#show', as: 'flow'
get 'search' => 'search#show', as: 'search'
get 'templates' => 'templates#show', as: 'templates'
get 'watchings' => 'watchings#show', as: 'watching'
get 'posts/:id/fork' => 'posts#fork', as: 'fork_post'
post 'posts/:id/mail' => 'posts#mail', as: 'mail_post'
post 'posts/:id/comment' => 'posts#comment', as: 'comment_post'
get 'posts/:id/slideshow' => 'posts#slideshow', as: 'slideshow_post'
get 'posts/:id/watch' => 'posts#watch', as: 'watch_post'
resources :posts, except: [:index]
get 'notification_bridge/:id' => 'notifications#bridge', as: 'notification_bridge'
......@@ -25,9 +28,16 @@ Rendezvous::Application.routes.draw do
resources :tags, :param => :name, except: [:index]
devise_for :users,
path_names: { current_user: 'me' },
controllers: { omniauth_callbacks: 'users/omniauth_callbacks' },
skip: [:passwords]
controllers: {
omniauth_callbacks: 'users/omniauth_callbacks',
registrations: 'users/registrations'
},
skip: [
:passwords,
:registrations,
]
resource :user
# The priority is based upon order of creation: first created -> highest priority.
......
......@@ -4,10 +4,16 @@
defaults: &defaults
rendezvous:
# For amil body
app_host: "http://rendezvous.dev"
# For login
google_api:
client_id: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com"
secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# For file upload
s3:
access_key_id: "xxxxxxxxxxxxxxxxxxxxx"
secret_access_key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
......
class CreateFootprints < ActiveRecord::Migration
def change
create_table :footprints do |t|
t.integer :user_id, null: false
t.integer :post_id, null: false
t.timestamps
end
add_index :footprints, [:user_id, :post_id]
add_index :footprints, :post_id
end
end
class CreateWatches < ActiveRecord::Migration
def change
create_table :watches do |t|
t.integer :watcher_id, null: false
t.string :watchable_type, null: false
t.integer :watchable_id, null: false
t.timestamps
end
add_index :watches, [:watcher_id, :watchable_type, :watchable_id], unique: true
add_index :watches, [:watchable_type, :watchable_id]
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20140501045300) do
ActiveRecord::Schema.define(version: 20140719145016) do
create_table "comments", force: true do |t|
t.integer "author_id"
......@@ -24,6 +24,16 @@ ActiveRecord::Schema.define(version: 20140501045300) do
add_index "comments", ["author_id", "updated_at"], name: "index_comments_on_author_id_and_updated_at", using: :btree
add_index "comments", ["post_id", "updated_at"], name: "index_comments_on_post_id_and_updated_at", using: :btree
create_table "footprints", force: true do |t|
t.integer "user_id", null: false
t.integer "post_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "footprints", ["post_id"], name: "index_footprints_on_post_id", using: :btree
add_index "footprints", ["user_id", "post_id"], name: "index_footprints_on_user_id_and_post_id", using: :btree
create_table "notifications", force: true do |t|
t.integer "user_id"
t.datetime "read_at"
......@@ -105,4 +115,15 @@ ActiveRecord::Schema.define(version: 20140501045300) do
add_index "versions", ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id", using: :btree
create_table "watches", force: true do |t|
t.integer "watcher_id", null: false
t.string "watchable_type", null: false
t.integer "watchable_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "watches", ["watchable_type", "watchable_id"], name: "index_watches_on_watchable_type_and_watchable_id", using: :btree
add_index "watches", ["watcher_id", "watchable_type", "watchable_id"], name: "index_watches_on_watcher_id_and_watchable_type_and_watchable_id", unique: true, using: :btree
end
/**
* Copyright (C) 2013-2014 KO GmbH <copyright@kogmbh.com>
*
* @licstart
* This file is part of WebODF.
*
* WebODF is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License (GNU AGPL)
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* WebODF is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
* @licend
*
* @source: http://www.webodf.org/
* @source: https://github.com/kogmbh/WebODF/
*/
@namespace cursor url(urn:webodf:names:cursor);
.caret {
opacity: 0 !important;
}
/**
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
*
* @licstart
* This file is part of WebODF.
*
* WebODF is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License (GNU AGPL)
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* WebODF is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
* @licend
*
* @source: http://www.webodf.org/
* @source: https://github.com/kogmbh/WebODF/
*/
/*global runtime, document, odf, gui, console, webodf*/
function ODFViewerPlugin() {
"use strict";
function init(callback) {
var lib = document.createElement('script'),
pluginCSS;
lib.async = false;
lib.src = './webodf.js';
lib.type = 'text/javascript';
lib.onload = function () {
runtime.loadClass('gui.HyperlinkClickHandler');
runtime.loadClass('odf.OdfCanvas');
runtime.loadClass('ops.Session');
runtime.loadClass('gui.CaretManager');
runtime.loadClass("gui.HyperlinkTooltipView");
runtime.loadClass('gui.SessionController');
runtime.loadClass('gui.SvgSelectionView');
runtime.loadClass('gui.SelectionViewManager');
runtime.loadClass('gui.ShadowCursor');
runtime.loadClass('gui.SessionView');
callback();
};
document.getElementsByTagName('head')[0].appendChild(lib);
pluginCSS = document.createElement('link');
pluginCSS.setAttribute("rel", "stylesheet");
pluginCSS.setAttribute("type", "text/css");
pluginCSS.setAttribute("href", "./ODFViewerPlugin.css");
document.head.appendChild(pluginCSS);
}
// that should probably be provided by webodf
function nsResolver(prefix) {
var ns = {
'draw' : "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
'presentation' : "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
'text' : "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
'office' : "urn:oasis:names:tc:opendocument:xmlns:office:1.0"
};
return ns[prefix] || console.log('prefix [' + prefix + '] unknown.');
}
var self = this,
pluginName = "WebODF",
pluginURL = "http://webodf.org",
odfCanvas = null,
odfElement = null,
initialized = false,
root = null,
documentType = null,
pages = [],
currentPage = null;
this.initialize = function (viewerElement, documentUrl) {
// If the URL has a fragment (#...), try to load the file it represents
init(function () {
var session,
sessionController,
sessionView,
odtDocument,
shadowCursor,
selectionViewManager,
caretManager,
localMemberId = 'localuser',
hyperlinkTooltipView,
eventManager;
odfElement = document.getElementById('canvas');
odfCanvas = new odf.OdfCanvas(odfElement);
odfCanvas.load(documentUrl);
odfCanvas.addListener('statereadychange', function () {
root = odfCanvas.odfContainer().rootElement;
initialized = true;
documentType = odfCanvas.odfContainer().getDocumentType(root);
if (documentType === 'text') {
odfCanvas.enableAnnotations(true, false);
session = new ops.Session(odfCanvas);
odtDocument = session.getOdtDocument();
shadowCursor = new gui.ShadowCursor(odtDocument);
sessionController = new gui.SessionController(session, localMemberId, shadowCursor, {});
eventManager = sessionController.getEventManager();
caretManager = new gui.CaretManager(sessionController);
selectionViewManager = new gui.SelectionViewManager(gui.SvgSelectionView);
sessionView = new gui.SessionView({
caretAvatarsInitiallyVisible: false
}, localMemberId, session, sessionController.getSessionConstraints(), caretManager, selectionViewManager);
selectionViewManager.registerCursor(shadowCursor);
hyperlinkTooltipView = new gui.HyperlinkTooltipView(odfCanvas,
sessionController.getHyperlinkClickHandler().getModifier);
eventManager.subscribe("mousemove", hyperlinkTooltipView.showTooltip);
eventManager.subscribe("mouseout", hyperlinkTooltipView.hideTooltip);
var op = new ops.OpAddMember();
op.init({
memberid: localMemberId,
setProperties: {
fillName: runtime.tr("Unknown Author"),
color: "blue"
}
});
session.enqueue([op]);
sessionController.insertLocalCursor();
}
self.onLoad();
});
});
};
this.isSlideshow = function () {
return documentType === 'presentation';
};
this.onLoad = function () {};
this.getWidth = function () {
return odfElement.clientWidth;
};
this.getHeight = function () {
return odfElement.clientHeight;
};
this.fitToWidth = function (width) {
odfCanvas.fitToWidth(width);
};
this.fitToHeight = function (height) {
odfCanvas.fitToHeight(height);
};
this.fitToPage = function (width, height) {
odfCanvas.fitToContainingElement(width, height);
};
this.fitSmart = function (width) {
odfCanvas.fitSmart(width);
};
this.getZoomLevel = function () {
return odfCanvas.getZoomLevel();
};
this.setZoomLevel = function (value) {
odfCanvas.setZoomLevel(value);
};
// return a list of tuples (pagename, pagenode)
this.getPages = function () {
var pageNodes = Array.prototype.slice.call(root.getElementsByTagNameNS(nsResolver('draw'), 'page')),
pages = [],
i,
tuple;
for (i = 0; i < pageNodes.length; i += 1) {
tuple = [
pageNodes[i].getAttribute('draw:name'),
pageNodes[i]
];
pages.push(tuple);
}
return pages;
};
this.showPage = function (n) {
odfCanvas.showPage(n);
};
this.getPluginName = function () {
return pluginName;
};
this.getPluginVersion = function () {
var version;
if (String(typeof webodf) !== "undefined") {
version = webodf.Version;
} else {
version = "Unknown";
}
return version;
};
this.getPluginURL = function () {
return pluginURL;
};
}
.page {
margin: 7px auto 7px auto;
position: relative;
overflow: hidden;
background-clip: content-box;
background-color: white;
box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
-webkit-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
-moz-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
-ms-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
-o-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
}
.textLayer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
color: #000;
font-family: sans-serif;
overflow: hidden;
}
.textLayer > div {
color: transparent;
position: absolute;
line-height: 1;
white-space: pre;
cursor: text;
}
::selection { background:rgba(0,0,255,0.3); }
::-moz-selection { background:rgba(0,0,255,0.3); }
/**
* @license
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
*
* @licstart
* The JavaScript code in this page is free software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* (GNU AGPL) as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. The code is distributed
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this code. If not, see <http://www.gnu.org/licenses/>.
*
* As additional permission under GNU AGPL version 3 section 7, you
* may distribute non-source (e.g., minimized or compacted) forms of
* that code without the copy of the GNU GPL normally required by
* section 4, provided you include this license notice and a URL
* through which recipients can access the Corresponding Source.
*
* As a special exception to the AGPL, any HTML file which merely makes function
* calls to this code, and for that purpose includes it by reference shall be
* deemed a separate work for copyright law purposes. In addition, the copyright
* holders of this code give you permission to combine this code with free
* software libraries that are released under the GNU LGPL. You may copy and
* distribute such a system following the terms of the GNU AGPL for this code
* and the LGPL for the libraries. If you modify this code, you may extend this
* exception to your version of the code, but you are not obligated to do so.
* If you do not wish to do so, delete this exception statement from your
* version.
*
* This license applies to this entire compilation.
* @licend
* @source: http://viewerjs.org/
* @source: http://github.com/kogmbh/ViewerJS
*/
/*global document, window, Viewer, ODFViewerPlugin, PDFViewerPlugin*/
var viewer;
function loadPlugin(pluginName, callback) {
"use strict";
var script, style;
// Load script
script = document.createElement('script');
script.async = false;
script.onload = callback;
script.src = pluginName + '.js';
script.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(script);
}
function loadDocument(documentUrl) {
"use strict";
if (documentUrl) {
var extension = documentUrl.split('.').pop(),
Plugin;
extension = extension.toLowerCase();
switch (extension) {
case 'odt':
case 'odp':
case 'ods':
case 'fodt':
loadPlugin('./ODFViewerPlugin', function () {
Plugin = ODFViewerPlugin;
});
break;
case 'pdf':
loadPlugin('./PDFViewerPlugin', function () {
Plugin = PDFViewerPlugin;
});
break;
}
window.onload = function () {
if (Plugin) {
viewer = new Viewer(new Plugin());
}
};
}
}
/* This is just a sample file with CSS rules. You should write your own @font-face declarations
* to add support for your desired fonts.
*/
@font-face {
font-family: 'Novecentowide Book';
src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot");
src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot?#iefix") format("embedded-opentype"),
url("/ViewerJS/fonts/Novecentowide-Bold-webfont.woff") format("woff"),
url("/fonts/Novecentowide-Bold-webfont.ttf") format("truetype"),
url("/fonts/Novecentowide-Bold-webfont.svg#NovecentowideBookBold") format("svg");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'exotica';
src: url('/ViewerJS/fonts/Exotica-webfont.eot');
src: url('/ViewerJS/fonts/Exotica-webfont.eot?#iefix') format('embedded-opentype'),
url('/ViewerJS/fonts/Exotica-webfont.woff') format('woff'),
url('/ViewerJS/fonts/Exotica-webfont.ttf') format('truetype'),
url('/ViewerJS/fonts/Exotica-webfont.svg#exoticamedium') format('svg');
font-weight: normal;
font-style: normal;
}
<!DOCTYPE html>
<!--
Copyright (C) 2012-2014 KO GmbH <copyright@kogmbh.com>
@licstart
This file is part of WebODF.
WebODF is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License (GNU AGPL)
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
WebODF is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
@licend
@source: http://www.webodf.org/
@source: https://github.com/kogmbh/WebODF/
-->
<!--
This file is a derivative from a part of Mozilla's PDF.js project. The
original license header follows.
-->
<!--
Copyright 2012 Mozilla Foundation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html dir="ltr" lang="en-US">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>ViewerJS</title>
<!-- If you want to use custom CSS (@font-face rules, for example) you should uncomment
the following reference and use a local.css file for that. See the example.local.css
file for a sample.
<link rel="stylesheet" type="text/css" href="local.css" media="screen"/>
-->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" type="text/css" href="viewer.css" media="screen"/>
<script src="viewer.js" type="text/javascript" charset="utf-8"></script>
<script src="PluginLoader.js" type="text/javascript" charset="utf-8"></script>
<script>
loadDocument(window.location.hash);
</script>
</head>
<body>
<div id = "viewer">
<div id = "titlebar">
<div id = "documentName"></div>
<div id = "toolbarRight">
<button id = "presentation" class = "toolbarButton presentation" title = "Presentation"></button>
<button id = "fullscreen" class = "toolbarButton fullscreen" title = "Fullscreen"></button>
<button id = "download" class = "toolbarButton download" title = "Download"></button>
</div>
</div>
<div id = "toolbarContainer">
<div id = "toolbar">
<div id = "toolbarLeft">
<div id = "navButtons" class = "splitToolbarButton">
<button id = "previous" class = "toolbarButton pageUp" title = "Previous Page"></button>
<div class="splitToolbarButtonSeparator"></div>
<button id = "next" class = "toolbarButton pageDown" title = "Next Page"></button>
</div>
<label id = "pageNumberLabel" class = "toolbarLabel" for = "pageNumber">Page:</label>
<input type = "number" id = "pageNumber" class = "toolbarField pageNumber"/>
<span id = "numPages" class = "toolbarLabel"></span>
</div>
<div id = "toolbarMiddleContainer" class = "outerCenter">
<div id = "toolbarMiddle" class = "innerCenter">
<div id = 'zoomButtons' class = "splitToolbarButton">
<button id = "zoomOut" class = "toolbarButton zoomOut" title = "Zoom Out"></button>
<div class="splitToolbarButtonSeparator"></div>
<button id = "zoomIn" class = "toolbarButton zoomIn" title = "Zoom In"></button>
</div>
<span id="scaleSelectContainer" class="dropdownToolbarButton">
<select id="scaleSelect" title="Zoom" oncontextmenu="return false;">
<option id="pageAutoOption" value="auto" selected>Automatic</option>
<option id="pageActualOption" value="page-actual">Actual Size</option>
<option id="pageWidthOption" value="page-width">Full Width</option>
<option id="customScaleOption" value="custom"> </option>
<option value="0.5">50%</option>
<option value="0.75">75%</option>
<option value="1">100%</option>
<option value="1.25">125%</option>
<option value="1.5">150%</option>
<option value="2">200%</option>
</select>
</span>
<div id = "sliderContainer">
<div id = "slider"></div>
</div>
</div>
</div>
</div>
</div>
<div id = "canvasContainer">
<div id = "canvas"></div>
</div>
<div id = "overlayNavigator">
<div id = "previousPage"></div>
<div id = "nextPage"></div>
</div>
<div id = "overlayCloseButton">
&#10006;
</div>
<div id = "dialogOverlay"></div>
</div>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
/* globals PDFFindController, FindStates, mozL10n */
/**
* Creates a "search bar" given set of DOM elements
* that act as controls for searching, or for setting
* search preferences in the UI. This object also sets
* up the appropriate events for the controls. Actual
* searching is done by PDFFindController
*/
var PDFFindBar = {
opened: false,
bar: null,
toggleButton: null,
findField: null,
highlightAll: null,
caseSensitive: null,
findMsg: null,
findStatusIcon: null,
findPreviousButton: null,
findNextButton: null,
initialize: function(options) {
if(typeof PDFFindController === 'undefined' || PDFFindController === null) {
throw 'PDFFindBar cannot be initialized ' +
'without a PDFFindController instance.';
}
this.bar = options.bar;
this.toggleButton = options.toggleButton;
this.findField = options.findField;
this.highlightAll = options.highlightAllCheckbox;
this.caseSensitive = options.caseSensitiveCheckbox;
this.findMsg = options.findMsg;
this.findStatusIcon = options.findStatusIcon;
this.findPreviousButton = options.findPreviousButton;
this.findNextButton = options.findNextButton;
var self = this;
this.toggleButton.addEventListener('click', function() {
self.toggle();
});
this.findField.addEventListener('input', function() {
self.dispatchEvent('');
});
this.bar.addEventListener('keydown', function(evt) {
switch (evt.keyCode) {
case 13: // Enter
if (evt.target === self.findField) {
self.dispatchEvent('again', evt.shiftKey);
}
break;
case 27: // Escape
self.close();
break;
}
});
this.findPreviousButton.addEventListener('click',
function() { self.dispatchEvent('again', true); }
);
this.findNextButton.addEventListener('click', function() {
self.dispatchEvent('again', false);
});
this.highlightAll.addEventListener('click', function() {
self.dispatchEvent('highlightallchange');
});
this.caseSensitive.addEventListener('click', function() {
self.dispatchEvent('casesensitivitychange');
});
},
dispatchEvent: function(aType, aFindPrevious) {
var event = document.createEvent('CustomEvent');
event.initCustomEvent('find' + aType, true, true, {
query: this.findField.value,
caseSensitive: this.caseSensitive.checked,
highlightAll: this.highlightAll.checked,
findPrevious: aFindPrevious
});
return window.dispatchEvent(event);
},
updateUIState: function(state, previous) {
var notFound = false;
var findMsg = '';
var status = '';
switch (state) {
case FindStates.FIND_FOUND:
break;
case FindStates.FIND_PENDING:
status = 'pending';
break;
case FindStates.FIND_NOTFOUND:
findMsg = mozL10n.get('find_not_found', null, 'Phrase not found');
notFound = true;
break;
case FindStates.FIND_WRAPPED:
if (previous) {
findMsg = mozL10n.get('find_reached_top', null,
'Reached top of document, continued from bottom');
} else {
findMsg = mozL10n.get('find_reached_bottom', null,
'Reached end of document, continued from top');
}
break;
}
if (notFound) {
this.findField.classList.add('notFound');
} else {
this.findField.classList.remove('notFound');
}
this.findField.setAttribute('data-status', status);
this.findMsg.textContent = findMsg;
},
open: function() {
if (!this.opened) {
this.opened = true;
this.toggleButton.classList.add('toggled');
this.bar.classList.remove('hidden');
}
this.findField.select();
this.findField.focus();
},
close: function() {
if (!this.opened) return;
this.opened = false;
this.toggleButton.classList.remove('toggled');
this.bar.classList.add('hidden');
PDFFindController.active = false;
},
toggle: function() {
if (this.opened) {
this.close();
} else {
this.open();
}
}
};
var /**@const{!string}*/pdfjs_version = "d45d7bc";
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
// optimised CSS custom property getter/setter
var CustomStyle = (function CustomStyleClosure() {
// As noted on: http://www.zachstronaut.com/posts/2009/02/17/
// animate-css-transforms-firefox-webkit.html
// in some versions of IE9 it is critical that ms appear in this list
// before Moz
var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
var _cache = { };
function CustomStyle() {
}
CustomStyle.getProp = function get(propName, element) {
// check cache only when no element is given
if (arguments.length == 1 && typeof _cache[propName] == 'string') {
return _cache[propName];
}
element = element || document.documentElement;
var style = element.style, prefixed, uPropName;
// test standard property first
if (typeof style[propName] == 'string') {
return (_cache[propName] = propName);
}
// capitalize
uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
// test vendor specific properties
for (var i = 0, l = prefixes.length; i < l; i++) {
prefixed = prefixes[i] + uPropName;
if (typeof style[prefixed] == 'string') {
return (_cache[propName] = prefixed);
}
}
//if all fails then set to undefined
return (_cache[propName] = 'undefined');
};
CustomStyle.setProp = function set(propName, element, str) {
var prop = this.getProp(propName);
if (prop != 'undefined')
element.style[prop] = str;
};
return CustomStyle;
})();
function getFileName(url) {
var anchor = url.indexOf('#');
var query = url.indexOf('?');
var end = Math.min(
anchor > 0 ? anchor : url.length,
query > 0 ? query : url.length);
return url.substring(url.lastIndexOf('/', end) + 1, end);
}
/**
* Returns scale factor for the canvas. It makes sense for the HiDPI displays.
* @return {Object} The object with horizontal (sx) and vertical (sy)
scales. The scaled property is set to false if scaling is
not required, true otherwise.
*/
function getOutputScale(ctx) {
var devicePixelRatio = window.devicePixelRatio || 1;
var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1;
var pixelRatio = devicePixelRatio / backingStoreRatio;
return {
sx: pixelRatio,
sy: pixelRatio,
scaled: pixelRatio != 1
};
}
/**
* Scrolls specified element into view of its parent.
* element {Object} The element to be visible.
* spot {Object} An object with optional top and left properties,
* specifying the offset from the top left edge.
*/
function scrollIntoView(element, spot) {
// Assuming offsetParent is available (it's not available when viewer is in
// hidden iframe or object). We have to scroll: if the offsetParent is not set
// producing the error. See also animationStartedClosure.
var parent = element.offsetParent;
var offsetY = element.offsetTop + element.clientTop;
var offsetX = element.offsetLeft + element.clientLeft;
if (!parent) {
console.error('offsetParent is not set -- cannot scroll');
return;
}
while (parent.clientHeight === parent.scrollHeight) {
if (parent.dataset._scaleY) {
offsetY /= parent.dataset._scaleY;
offsetX /= parent.dataset._scaleX;
}
offsetY += parent.offsetTop;
offsetX += parent.offsetLeft;
parent = parent.offsetParent;
if (!parent) {
return; // no need to scroll
}
}
if (spot) {
if (spot.top !== undefined) {
offsetY += spot.top;
}
if (spot.left !== undefined) {
offsetX += spot.left;
parent.scrollLeft = offsetX;
}
}
parent.scrollTop = offsetY;
}
/**
* Event handler to suppress context menu.
*/
function noContextMenuHandler(e) {
e.preventDefault();
}
/**
* Returns the filename or guessed filename from the url (see issue 3455).
* url {String} The original PDF location.
* @return {String} Guessed PDF file name.
*/
function getPDFFileNameFromURL(url) {
var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
// SCHEME HOST 1.PATH 2.QUERY 3.REF
// Pattern to get last matching NAME.pdf
var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
var splitURI = reURI.exec(url);
var suggestedFilename = reFilename.exec(splitURI[1]) ||
reFilename.exec(splitURI[2]) ||
reFilename.exec(splitURI[3]);
if (suggestedFilename) {
suggestedFilename = suggestedFilename[0];
if (suggestedFilename.indexOf('%') != -1) {
// URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
try {
suggestedFilename =
reFilename.exec(decodeURIComponent(suggestedFilename))[0];
} catch(e) { // Possible (extremely rare) errors:
// URIError "Malformed URI", e.g. for "%AA.pdf"
// TypeError "null has no properties", e.g. for "%2F.pdf"
}
}
}
return suggestedFilename || 'document.pdf';
}
var ProgressBar = (function ProgressBarClosure() {
function clamp(v, min, max) {
return Math.min(Math.max(v, min), max);
}
function ProgressBar(id, opts) {
// Fetch the sub-elements for later.
this.div = document.querySelector(id + ' .progress');
// Get the loading bar element, so it can be resized to fit the viewer.
this.bar = this.div.parentNode;
// Get options, with sensible defaults.
this.height = opts.height || 100;
this.width = opts.width || 100;
this.units = opts.units || '%';
// Initialize heights.
this.div.style.height = this.height + this.units;
this.percent = 0;
}
ProgressBar.prototype = {
updateBar: function ProgressBar_updateBar() {
if (this._indeterminate) {
this.div.classList.add('indeterminate');
this.div.style.width = this.width + this.units;
return;
}
this.div.classList.remove('indeterminate');
var progressSize = this.width * this._percent / 100;
this.div.style.width = progressSize + this.units;
},
get percent() {
return this._percent;
},
set percent(val) {
this._indeterminate = isNaN(val);
this._percent = clamp(val, 0, 100);
this.updateBar();
},
setWidth: function ProgressBar_setWidth(viewer) {
if (viewer) {
var container = viewer.parentNode;
var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
if (scrollbarWidth > 0) {
this.bar.setAttribute('style', 'width: calc(100% - ' +
scrollbarWidth + 'px);');
}
}
},
hide: function ProgressBar_hide() {
this.bar.classList.add('hidden');
this.bar.removeAttribute('style');
}
};
return ProgressBar;
})();
var Cache = function cacheCache(size) {
var data = [];
this.push = function cachePush(view) {
var i = data.indexOf(view);
if (i >= 0)
data.splice(i);
data.push(view);
if (data.length > size)
data.shift().destroy();
};
};
//#if !(FIREFOX || MOZCENTRAL || B2G)
var isLocalStorageEnabled = (function isLocalStorageEnabledClosure() {
// Feature test as per http://diveintohtml5.info/storage.html
// The additional localStorage call is to get around a FF quirk, see
// bug #495747 in bugzilla
try {
return ('localStorage' in window && window['localStorage'] !== null &&
localStorage);
} catch (e) {
return false;
}
})();
//#endif
function Viewer(c){function M(){var a,b,A,d,f;c&&(A=c.getPluginName(),d=c.getPluginVersion(),f=c.getPluginURL());a=document.createElement("div");a.id="aboutDialogCentererTable";b=document.createElement("div");b.id="aboutDialogCentererCell";n=document.createElement("div");n.id="aboutDialog";n.innerHTML='<h1>ViewerJS</h1><p>Open Source document viewer for webpages, built with HTML and JavaScript.</p><p>Learn more and get your own copy on the <a href="http://viewerjs.org/" target="_blank">ViewerJS website</a>.</p>'+
(c?'<p>Using the <a href = "'+f+'" target="_blank">'+A+'</a> (<span id = "pluginVersion">'+d+"</span>) plugin to show you this document.</p>":"")+'<p>Supported by <a href="http://nlnet.nl" target="_blank"><br><img src="images/nlnet.png" width="160" height="60" alt="NLnet Foundation"></a></p><p>Made by <a href="http://kogmbh.com" target="_blank"><br><img src="images/kogmbh.png" width="172" height="40" alt="KO GmbH"></a></p><button id = "aboutDialogCloseButton" class = "toolbarButton textButton">Close</button>';
u.appendChild(a);a.appendChild(b);b.appendChild(n);a=document.createElement("button");a.id="about";a.className="toolbarButton textButton about";a.title="About";a.innerHTML="ViewerJS";N.appendChild(a);a.addEventListener("click",function(){u.style.display="block"});document.getElementById("aboutDialogCloseButton").addEventListener("click",function(){u.style.display="none"})}function B(a){var b=O.options,c,d=!1,f;for(f=0;f<b.length;f+=1)c=b[f],c.value!==a?c.selected=!1:d=c.selected=!0;return d}function C(a,
c,d){a!==b.getZoomLevel()&&(b.setZoomLevel(a),d=document.createEvent("UIEvents"),d.initUIEvent("scalechange",!1,!1,window,0),d.scale=a,d.resetAutoSettings=c,window.dispatchEvent(d))}function D(){var a;if(c.onScroll)c.onScroll();c.getPageInView&&(a=c.getPageInView())&&(k=a,document.getElementById("pageNumber").value=a)}function E(a){window.clearTimeout(F);F=window.setTimeout(function(){D()},a)}function e(a,b,g){var e,f;if(e="custom"===a?parseFloat(document.getElementById("customScaleOption").textContent)/
100:parseFloat(a))C(e,!0,g);else{e=d.clientWidth-p;f=d.clientHeight-p;switch(a){case "page-actual":C(1,b,g);break;case "page-width":c.fitToWidth(e);break;case "page-height":c.fitToHeight(f);break;case "page-fit":c.fitToPage(e,f);break;case "auto":c.isSlideshow()?c.fitToPage(e+p,f+p):c.fitSmart(e)}B(a)}E(300)}function q(){l=!l;r&&!l&&b.togglePresentationMode()}function v(){s&&(w.className="viewer-touched",window.clearTimeout(G),G=window.setTimeout(function(){w.className=""},5E3))}function x(){h.classList.add("viewer-touched");
m.classList.add("viewer-touched");window.clearTimeout(H);H=window.setTimeout(function(){I()},5E3)}function I(){h.classList.remove("viewer-touched");m.classList.remove("viewer-touched")}function P(){h.classList.contains("viewer-touched")?I():x()}var b=this,p=40,r=!1,l=!1,J=!1,s=!1,y,g=document.getElementById("viewer"),d=document.getElementById("canvasContainer"),w=document.getElementById("overlayNavigator"),h=document.getElementById("titlebar"),m=document.getElementById("toolbarContainer"),K=document.getElementById("toolbarLeft"),
Q=document.getElementById("toolbarMiddleContainer"),O=document.getElementById("scaleSelect"),u=document.getElementById("dialogOverlay"),N=document.getElementById("toolbarRight"),n,L,t=[],k,F,G,H;this.initialize=function(){var a=String(document.location),z=a.indexOf("#"),a=a.substr(z+1);-1===z||0===a.length?console.log("Could not parse file path argument."):(y=a,L=y.replace(/^.*[\\\/]/,""),document.title=L,document.getElementById("documentName").innerHTML=document.title,c.onLoad=function(){document.getElementById("pluginVersion").innerHTML=
c.getPluginVersion();(s=c.isSlideshow())?(d.classList.add("slideshow"),K.style.visibility="visible"):(Q.style.visibility="visible",c.getPageInView&&(K.style.visibility="visible"));J=!0;t=c.getPages();document.getElementById("numPages").innerHTML="of "+t.length;b.showPage(1);e("auto");d.onscroll=D;E()},c.initialize(d,a))};this.showPage=function(a){0>=a?a=1:a>t.length&&(a=t.length);c.showPage(a);k=a;document.getElementById("pageNumber").value=k};this.showNextPage=function(){b.showPage(k+1)};this.showPreviousPage=
function(){b.showPage(k-1)};this.download=function(){var a=y.split("#")[0];window.open(a+"#viewer.action=download","_parent")};this.toggleFullScreen=function(){l?document.cancelFullScreen?document.cancelFullScreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.webkitCancelFullScreen?document.webkitCancelFullScreen():document.msExitFullscreen&&document.msExitFullscreen():g.requestFullScreen?g.requestFullScreen():g.mozRequestFullScreen?g.mozRequestFullScreen():g.webkitRequestFullScreen?
g.webkitRequestFullScreen():g.msRequestFullscreen&&g.msRequestFullscreen()};this.togglePresentationMode=function(){var a=document.getElementById("overlayCloseButton");r?(h.style.display=m.style.display="block",a.style.display="none",d.classList.remove("presentationMode"),d.onmouseup=function(){},d.oncontextmenu=function(){},d.onmousedown=function(){},e("auto"),s=c.isSlideshow()):(h.style.display=m.style.display="none",a.style.display="block",d.classList.add("presentationMode"),s=!0,d.onmousedown=
function(a){a.preventDefault()},d.oncontextmenu=function(a){a.preventDefault()},d.onmouseup=function(a){a.preventDefault();1===a.which?b.showNextPage():b.showPreviousPage()},e("page-fit"));r=!r};this.getZoomLevel=function(){return c.getZoomLevel()};this.setZoomLevel=function(a){c.setZoomLevel(a)};this.zoomOut=function(){var a=(b.getZoomLevel()/1.1).toFixed(2),a=Math.max(0.25,a);e(a,!0)};this.zoomIn=function(){var a=(1.1*b.getZoomLevel()).toFixed(2),a=Math.min(4,a);e(a,!0)};(function(){M();c&&(b.initialize(),
document.cancelFullScreen||document.mozCancelFullScreen||document.webkitCancelFullScreen||document.msExitFullscreen||(document.getElementById("fullscreen").style.visibility="hidden",document.getElementById("presentation").style.visibility="hidden"),document.getElementById("overlayCloseButton").addEventListener("click",b.toggleFullScreen),document.getElementById("fullscreen").addEventListener("click",b.toggleFullScreen),document.getElementById("presentation").addEventListener("click",function(){l||
b.toggleFullScreen();b.togglePresentationMode()}),document.addEventListener("fullscreenchange",q),document.addEventListener("webkitfullscreenchange",q),document.addEventListener("mozfullscreenchange",q),document.addEventListener("MSFullscreenChange",q),document.getElementById("download").addEventListener("click",function(){b.download()}),document.getElementById("zoomOut").addEventListener("click",function(){b.zoomOut()}),document.getElementById("zoomIn").addEventListener("click",function(){b.zoomIn()}),
document.getElementById("previous").addEventListener("click",function(){b.showPreviousPage()}),document.getElementById("next").addEventListener("click",function(){b.showNextPage()}),document.getElementById("previousPage").addEventListener("click",function(){b.showPreviousPage()}),document.getElementById("nextPage").addEventListener("click",function(){b.showNextPage()}),document.getElementById("pageNumber").addEventListener("change",function(){b.showPage(this.value)}),document.getElementById("scaleSelect").addEventListener("change",
function(){e(this.value)}),d.addEventListener("click",v),w.addEventListener("click",v),d.addEventListener("click",P),h.addEventListener("click",x),m.addEventListener("click",x),window.addEventListener("scalechange",function(a){var b=document.getElementById("customScaleOption"),c=B(String(a.scale));b.selected=!1;c||(b.textContent=Math.round(1E4*a.scale)/100+"%",b.selected=!0)},!0),window.addEventListener("resize",function(a){J&&(document.getElementById("pageWidthOption").selected||document.getElementById("pageAutoOption").selected)&&
e(document.getElementById("scaleSelect").value);v()}),window.addEventListener("keydown",function(a){var c=a.shiftKey;switch(a.keyCode){case 33:case 38:case 37:b.showPreviousPage();break;case 34:case 40:case 39:b.showNextPage();break;case 32:c?b.showPreviousPage():b.showNextPage()}}))})()};
This source diff could not be displayed because it is too large. You can view the blob instead.
require 'spec_helper'
require 'rails_helper'
describe ApisController do
describe ApisController, type: :controller do
describe "GET 'markdown_preview'" do
it "returns http redirect" do
......@@ -10,9 +10,9 @@ describe ApisController do
end
describe "GET 'markdown_preview'" do
login_user
it "returns http success" do
sign_in FactoryGirl.create(:alice)
get 'markdown_preview'
expect(response).to be_success
end
......
require 'spec_helper'
require 'rails_helper'
describe ApplicationController do
......
require 'spec_helper'
require 'rails_helper'
class DummyClass; end
......@@ -6,7 +6,7 @@ describe RV::Mailer do
let(:klass) { DummyClass.new.extend(RV::Mailer) }
let(:alice) { FactoryGirl.create(:alice) }
let(:post) { Post.create title: 'ruby rspec', body: 'This is first espec test: ruby' }
let(:post) { Post.create title: 'ruby rspec', body: 'This is first espec test: ruby', author: create(:author) }
it 'valid' do
expect { klass.compose_mail(post, user: alice, to: 'dummy@example.com') }.not_to raise_error
......
require 'spec_helper'
require 'rails_helper'
describe FlowController do
describe FlowController, type: :controller do
describe "GET 'show' without login" do
it "returns http redirect" do
......@@ -10,9 +10,9 @@ describe FlowController do
end
describe "GET 'show' with login" do
login_user
it "returns http success" do
sign_in FactoryGirl.create(:alice)
get 'show'
expect(response).to be_success
end
......
require 'rails_helper'
describe PostsController, type: :controller do
let(:post) { FactoryGirl.create(:post) }
describe "GET 'show' without login" do
it "returns http redirect" do
get 'show', id: post.id
expect(response).to redirect_to('/')
end
end
describe "GET 'show' with login" do
let(:alice) { FactoryGirl.create(:alice) }
it "returns http success" do
sign_in FactoryGirl.create(:alice)
get 'show', id: post.id
expect(response).to be_success
end
it "returns http success" do
sign_in alice
get 'show', id: post.id
expect(Footprint.where(user_id: alice.id, post_id: post.id)).to exist
end
end
end
require 'spec_helper'
require 'rails_helper'
describe SearchController do
describe SearchController, type: :controller do
describe "GET 'show' without login" do
it "returns http redirect" do
......@@ -10,9 +10,9 @@ describe SearchController do
end
describe "GET 'show' with login" do
login_user
it "returns http success" do
sign_in FactoryGirl.create(:alice)
get 'show'
expect(response).to be_success
end
......
require 'spec_helper'
require 'rails_helper'
describe StockController do
describe StockController, type: :controller do
describe "GET 'show' without login" do
it "returns http redirect" do
......@@ -10,9 +10,9 @@ describe StockController do
end
describe "GET 'show' with login" do
login_user
it "returns http success" do
sign_in FactoryGirl.create(:alice)
get 'show'
expect(response).to be_success
end
......
require 'spec_helper'
require 'rails_helper'
describe TagsController do
describe TagsController, type: :controller do
let(:tag) { FactoryGirl.create(:tag_ruby) }
......@@ -12,9 +12,9 @@ describe TagsController do
end
describe "GET 'show' with login" do
login_user
it "returns http success" do
sign_in FactoryGirl.create(:alice)
get 'show', name: tag.name
expect(response).to be_success
end
......
require 'rails_helper'
RSpec.describe TemplatesController, :type => :controller do
describe "GET 'show' without template" do
it "returns http success" do
sign_in FactoryGirl.create(:alice)
get 'show'
expect(response).to be_success
end
end
describe "GET 'show' with template" do
it "returns http success" do
sign_in FactoryGirl.create(:alice)
get 'show'
expect(response).to be_success
end
end
end
require 'spec_helper'
require 'rails_helper'
describe MeController do
before do
end
describe UsersController, type: :controller do
describe "GET 'edit'" do
login_user
it "returns http success" do
sign_in FactoryGirl.create(:alice)
get :edit
response.should be_success
end
end
describe "GET 'update'" do
login_user
it "returns http success" do
sign_in FactoryGirl.create(:alice)
patch :update, user: { nickname: 'bob' }
response.should redirect_to('/me/edit')
response.should redirect_to('/user/edit')
end
end
......
require 'rails_helper'
RSpec.describe WatchingsController, :type => :controller do
describe "GET 'show' without login" do
it "returns http redirect" do
get 'show'
expect(response).to redirect_to('/')
end
end
describe "GET 'show' with login" do
it "returns http success" do
sign_in FactoryGirl.create(:alice)
get 'show'
expect(response).to be_success
end
end
end
require 'spec_helper'
require 'rails_helper'
describe WelcomeController do
describe WelcomeController, type: :controller do
describe "GET 'top'" do
it 'should be successful' do
......@@ -12,22 +12,23 @@ describe WelcomeController do
describe 'Login' do
before(:each) do
@user = FactoryGirl.create(:login_user_1)
sign_in @user
end
let(:user) { FactoryGirl.create(:alice) }
describe "GET 'top'" do
it 'should be successful' do
sign_in FactoryGirl.create(:alice)
get :top
expect(subject).to redirect_to controller: 'flow',
action: 'show'
end
it 'should find the right user' do
sign_in user
get :top
expect(assigns(:current_user)).to eq(@user)
expect(assigns(:current_user)).to eq(user)
end
end
......
require 'spec_helper'
require 'rails_helper'
describe ApisDecorator do
end
require 'spec_helper'
require 'rails_helper'
describe FlowDecorator do
end
require 'spec_helper'
require 'rails_helper'
describe SearchDecorator do
end
require 'spec_helper'
require 'rails_helper'
describe StockDecorator do
end
require 'spec_helper'
require 'rails_helper'
describe TagDecorator do
end
require 'rails_helper'
describe TemplateDecorator do
end
require 'spec_helper'
require 'rails_helper'
describe UsersDecorator do
end
require 'spec_helper'
describe "apis/markdown_preview.html.erb" do
pending "add some examples to (or delete) #{__FILE__}"
describe WatchingDecorator do
end
......@@ -17,6 +17,7 @@
FactoryGirl.define do
factory :post do
association :author, factory: :author
title 'sample title'
body 'sample body'
specified_date Date.new(2014, 4, 1)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment