Merge branch 'new_design'

This commit is contained in:
yafeilee 2016-04-25 00:14:02 +08:00
commit 650e1951a8
99 changed files with 2275 additions and 2618 deletions

2
.gitignore vendored
View File

@ -13,3 +13,5 @@
/config/mongoid.yml
/public/assets/*
*.old
*.bak
.byebug_history

36
Gemfile
View File

@ -1,55 +1,65 @@
source 'https://rubygems.org'
gem 'rails', '4.2.5.2'
gem 'sass-rails'
ruby '2.2.3'
gem 'rails', '>= 5.0.0.beta3', '< 5.1'
gem 'sass-rails', '~> 5.0'
gem 'coffee-rails', '~> 4.1.0'
gem 'uglifier', '>= 2.7.2'
gem 'jquery-rails'
gem 'foundation-rails', '~> 5.5.1'
gem 'foundation-rails', '~> 6.2.1'
gem 'foundation-icons-sass-rails'
gem 'font-awesome-sass'
gem 'carrierwave'
gem 'kaminari', git: 'git@github.com:amatsuda/kaminari.git'
gem 'turbolinks', '~> 5.x'
gem 'js_cookie_rails'
gem 'rails-i18n', '~> 5.0.0.beta3'
gem 'jbuilder'
gem 'pg'
gem 'mongoid'
gem 'mongoid-tree'
gem 'mongoid-pagination'
gem 'redcarpet'
gem 'rouge'
gem 'slim-rails'
gem 'simple_form'
gem 'mini_magick'
gem 'carrierwave-mongoid'
gem 'html_truncator'
gem 'nokogiri'
gem 'angularjs-rails'
gem 'figaro'
gem 'rqrcode-with-patches', require: 'rqrcode'
gem 'chunky_png'
gem 'sidekiq'
gem 'redis-namespace'
gem 'rest-client'
gem 'unicorn'
gem 'newrelic_rpm'
gem 'puma'
gem 'mina', require: false
gem 'mina-multistage', require: false
gem 'mina-sidekiq', require: false
gem 'mina-unicorn', require: false
gem 'mina-puma', require: false
group :development do
gem 'spring'
gem 'quiet_assets'
gem 'guard'
gem 'guard-rails'
gem 'guard-rspec', require: false
gem 'guard-bundler', require: false
gem 'listen', '~> 3.0.5'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
gem 'byebug'
gem 'rack-cors', :require => 'rack/cors'
end
group :test do
gem 'capybara'
gem 'mongoid-rspec', :require => false
gem 'database_cleaner'
gem 'rspec-sidekiq'
gem "codeclimate-test-reporter", group: :test, require: nil
@ -59,5 +69,5 @@ group :test, :development do
gem "rspec-rails", ">= 2.8.1"
gem 'pry-rails'
gem 'pry-nav'
gem 'factory_girl_rails'
#gem 'factory_girl_rails'
end

View File

@ -1,62 +1,71 @@
GIT
remote: git@github.com:amatsuda/kaminari.git
revision: 0e21d52feca1f79a9aca83613cf053eb5273827e
specs:
kaminari (1.0.0.alpha)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
GEM
remote: https://rubygems.org/
specs:
actionmailer (4.2.5.2)
actionpack (= 4.2.5.2)
actionview (= 4.2.5.2)
activejob (= 4.2.5.2)
actioncable (5.0.0.beta3)
actionpack (= 5.0.0.beta3)
nio4r (~> 1.2)
websocket-driver (~> 0.6.1)
actionmailer (5.0.0.beta3)
actionpack (= 5.0.0.beta3)
actionview (= 5.0.0.beta3)
activejob (= 5.0.0.beta3)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.5.2)
actionview (= 4.2.5.2)
activesupport (= 4.2.5.2)
rack (~> 1.6)
rack-test (~> 0.6.2)
actionpack (5.0.0.beta3)
actionview (= 5.0.0.beta3)
activesupport (= 5.0.0.beta3)
rack (~> 2.x)
rack-test (~> 0.6.3)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.5.2)
activesupport (= 4.2.5.2)
actionview (5.0.0.beta3)
activesupport (= 5.0.0.beta3)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
activejob (4.2.5.2)
activesupport (= 4.2.5.2)
globalid (>= 0.3.0)
activemodel (4.2.5.2)
activesupport (= 4.2.5.2)
builder (~> 3.1)
activerecord (4.2.5.2)
activemodel (= 4.2.5.2)
activesupport (= 4.2.5.2)
arel (~> 6.0)
activesupport (4.2.5.2)
activejob (5.0.0.beta3)
activesupport (= 5.0.0.beta3)
globalid (>= 0.3.6)
activemodel (5.0.0.beta3)
activesupport (= 5.0.0.beta3)
activerecord (5.0.0.beta3)
activemodel (= 5.0.0.beta3)
activesupport (= 5.0.0.beta3)
arel (~> 7.0)
activesupport (5.0.0.beta3)
concurrent-ruby (~> 1.0)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
addressable (2.4.0)
angularjs-rails (1.5.0)
arel (6.0.3)
bson (4.0.4)
arel (7.0.0)
babel-source (5.8.35)
babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6)
execjs (~> 2.0)
builder (3.2.2)
capybara (2.6.2)
byebug (8.2.4)
capybara (2.7.0)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
carrierwave (0.10.0)
carrierwave (0.11.0)
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
mime-types (>= 1.16)
carrierwave-mongoid (0.8.1)
carrierwave (>= 0.8.0, < 0.11.0)
mongoid (>= 3.0, < 6.0)
mongoid-grid_fs (>= 1.3, < 3.0)
chunky_png (1.3.5)
codeclimate-test-reporter (0.5.0)
simplecov (>= 0.7.1, < 1.0.0)
@ -70,18 +79,13 @@ GEM
coffee-script-source (1.10.0)
concurrent-ruby (1.0.1)
connection_pool (2.2.0)
database_cleaner (1.5.1)
database_cleaner (1.5.2)
diff-lcs (1.2.5)
docile (1.1.5)
domain_name (0.5.20160309)
domain_name (0.5.20160310)
unf (>= 0.0.5, < 1.0.0)
erubis (2.7.0)
execjs (2.6.0)
factory_girl (4.5.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.6.0)
factory_girl (~> 4.5.0)
railties (>= 3.0.0)
ffi (1.9.10)
figaro (1.1.1)
thor (~> 0.14)
@ -91,9 +95,10 @@ GEM
foundation-icons-sass-rails (3.0.0)
railties (>= 3.1.1)
sass-rails (>= 3.1.1)
foundation-rails (5.5.3.2)
foundation-rails (6.2.1.0)
railties (>= 3.1.0)
sass (>= 3.3.0, < 3.5)
sprockets-es6 (>= 0.9.0)
globalid (0.3.6)
activesupport (>= 4.1.0)
guard (2.13.0)
@ -113,7 +118,7 @@ GEM
guard-rails (0.7.2)
guard (~> 2.11)
guard-compat (~> 1.0)
guard-rspec (4.6.4)
guard-rspec (4.6.5)
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
@ -129,16 +134,17 @@ GEM
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
js_cookie_rails (1.0.1)
railties (>= 3.1)
json (1.8.3)
kgio (2.10.0)
listen (3.0.6)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9.7)
loofah (2.0.3)
nokogiri (>= 1.5.9)
lumberjack (1.0.10)
mail (2.6.3)
mime-types (>= 1.16, < 3)
mail (2.6.4)
mime-types (>= 1.16, < 4)
method_source (0.8.2)
mime-types (2.99.1)
mina (0.3.8)
@ -146,42 +152,26 @@ GEM
rake
mina-multistage (1.0.2)
mina (>= 0.2.1)
mina-puma (0.3.0)
mina
puma (>= 2.13)
mina-sidekiq (0.3.1)
mina
mina-unicorn (0.4.0)
mini_magick (4.4.0)
mini_magick (4.5.1)
mini_portile2 (2.0.0)
minitest (5.8.4)
mongo (2.2.4)
bson (~> 4.0)
mongoid (5.1.1)
activemodel (~> 4.0)
mongo (~> 2.1)
origin (~> 2.2)
tzinfo (>= 0.3.37)
mongoid-grid_fs (2.2.1)
mime-types (>= 1.0, < 3.0)
mongoid (>= 3.0, < 6.0)
mongoid-pagination (0.2.0)
activesupport
mongoid
mongoid-rspec (3.0.0)
mongoid (~> 5.0)
rake
rspec (~> 3.3)
mongoid-tree (2.0.1)
mongoid (>= 4.0, < 6.0)
multi_json (1.11.2)
nenv (0.3.0)
netrc (0.11.0)
newrelic_rpm (3.15.0.314)
newrelic_rpm (3.15.1.316)
nio4r (1.2.1)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
notiffany (0.0.8)
nenv (~> 0.1)
shellany (~> 0.0)
open4 (1.3.4)
origin (2.2.0)
pg (0.18.4)
pry (0.10.3)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
@ -190,23 +180,26 @@ GEM
pry (>= 0.9.10, < 0.11.0)
pry-rails (0.3.4)
pry (>= 0.9.10)
puma (3.4.0)
quiet_assets (1.1.0)
railties (>= 3.1, < 5.0)
rack (1.6.4)
rack (2.0.0.alpha)
json
rack-cors (0.4.0)
rack-test (0.6.3)
rack (>= 1.0)
rails (4.2.5.2)
actionmailer (= 4.2.5.2)
actionpack (= 4.2.5.2)
actionview (= 4.2.5.2)
activejob (= 4.2.5.2)
activemodel (= 4.2.5.2)
activerecord (= 4.2.5.2)
activesupport (= 4.2.5.2)
rails (5.0.0.beta3)
actioncable (= 5.0.0.beta3)
actionmailer (= 5.0.0.beta3)
actionpack (= 5.0.0.beta3)
actionview (= 5.0.0.beta3)
activejob (= 5.0.0.beta3)
activemodel (= 5.0.0.beta3)
activerecord (= 5.0.0.beta3)
activesupport (= 5.0.0.beta3)
bundler (>= 1.3.0, < 2.0)
railties (= 4.2.5.2)
sprockets-rails
railties (= 5.0.0.beta3)
sprockets-rails (>= 2.0.0)
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.7)
@ -215,18 +208,21 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
railties (4.2.5.2)
actionpack (= 4.2.5.2)
activesupport (= 4.2.5.2)
rails-i18n (5.0.0.beta3)
i18n (~> 0.7)
railties (~> 5.0.0.beta1)
railties (5.0.0.beta3)
actionpack (= 5.0.0.beta3)
activesupport (= 5.0.0.beta3)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
raindrops (0.16.0)
rake (11.1.1)
rake (11.1.2)
rb-fsevent (0.9.7)
rb-inotify (0.9.7)
ffi (>= 0.5.0)
redcarpet (3.3.4)
redis (3.2.2)
redis (3.3.0)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
rest-client (1.8.0)
@ -236,31 +232,30 @@ GEM
rouge (1.10.1)
rqrcode-with-patches (0.6.0)
chunky_png
rspec (3.4.0)
rspec-core (~> 3.4.0)
rspec-expectations (~> 3.4.0)
rspec-mocks (~> 3.4.0)
rspec-core (3.4.4)
rspec-support (~> 3.4.0)
rspec-expectations (3.4.0)
rspec (3.1.0)
rspec-core (~> 3.1.0)
rspec-expectations (~> 3.1.0)
rspec-mocks (~> 3.1.0)
rspec-core (3.1.7)
rspec-support (~> 3.1.0)
rspec-expectations (3.1.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-mocks (3.4.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.4.0)
rspec-rails (3.4.2)
actionpack (>= 3.0, < 4.3)
activesupport (>= 3.0, < 4.3)
railties (>= 3.0, < 4.3)
rspec-core (~> 3.4.0)
rspec-expectations (~> 3.4.0)
rspec-mocks (~> 3.4.0)
rspec-support (~> 3.4.0)
rspec-support (~> 3.1.0)
rspec-mocks (3.1.3)
rspec-support (~> 3.1.0)
rspec-rails (3.1.0)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.1.0)
rspec-expectations (~> 3.1.0)
rspec-mocks (~> 3.1.0)
rspec-support (~> 3.1.0)
rspec-sidekiq (2.2.0)
rspec (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.4.1)
sass (3.4.21)
rspec-support (3.1.2)
sass (3.4.22)
sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0)
sass (~> 3.1)
@ -290,10 +285,17 @@ GEM
railties (>= 3.1, < 5.0)
slim (~> 3.0)
slop (3.6.0)
spring (1.6.4)
sprockets (3.5.2)
spring (1.7.1)
spring-watcher-listen (2.0.0)
listen (>= 2.7, < 4.0)
spring (~> 1.2)
sprockets (3.6.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-es6 (0.9.0)
babel-source (>= 5.8.11)
babel-transpiler
sprockets (>= 3.0.0)
sprockets-rails (3.0.4)
actionpack (>= 4.0)
activesupport (>= 4.0)
@ -302,18 +304,19 @@ GEM
thor (0.19.1)
thread_safe (0.3.5)
tilt (2.0.2)
turbolinks (5.0.0.beta2)
turbolinks-source
turbolinks-source (5.0.0.beta4)
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (2.7.2)
execjs (>= 0.3.0)
json (>= 1.8.0)
uglifier (3.0.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
unicorn (5.0.1)
kgio (~> 2.6)
rack
raindrops (~> 0.7)
websocket-driver (0.6.3)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
xpath (2.0.0)
nokogiri (~> 1.3)
@ -321,18 +324,17 @@ PLATFORMS
ruby
DEPENDENCIES
angularjs-rails
byebug
capybara
carrierwave-mongoid
carrierwave
chunky_png
codeclimate-test-reporter
coffee-rails (~> 4.1.0)
database_cleaner
factory_girl_rails
figaro
font-awesome-sass
foundation-icons-sass-rails
foundation-rails (~> 5.5.1)
foundation-rails (~> 6.2.1)
guard
guard-bundler
guard-rails
@ -340,22 +342,24 @@ DEPENDENCIES
html_truncator
jbuilder
jquery-rails
js_cookie_rails
kaminari!
listen (~> 3.0.5)
mina
mina-multistage
mina-puma
mina-sidekiq
mina-unicorn
mini_magick
mongoid
mongoid-pagination
mongoid-rspec
mongoid-tree
newrelic_rpm
nokogiri
pg
pry-nav
pry-rails
puma
quiet_assets
rack-cors
rails (= 4.2.5.2)
rails (>= 5.0.0.beta3, < 5.1)
rails-i18n (~> 5.0.0.beta3)
redcarpet
redis-namespace
rest-client
@ -363,10 +367,14 @@ DEPENDENCIES
rqrcode-with-patches
rspec-rails (>= 2.8.1)
rspec-sidekiq
sass-rails
sass-rails (~> 5.0)
sidekiq
simple_form
slim-rails
spring
spring-watcher-listen (~> 2.0.0)
turbolinks (~> 5.x)
uglifier (>= 2.7.2)
unicorn
BUNDLED WITH
1.11.2

View File

@ -0,0 +1,13 @@
$(document).on 'turbolinks:load', ->
if $('.about-page').length > 0
$(window).scroll ()->
if $(this).scrollTop() > 0
$('.top-bar-wrapper').addClass('active')
else
$('.top-bar-wrapper').removeClass('active')
$('#about-top-bar').ddscrollSpy
highlightclass: 'active'
$('.intro').ddscrollSpy
highlightclass: 'active'

View File

@ -3,16 +3,36 @@
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
#
$(document).ready ->
$(document).on 'turbolinks:load', ->
$('a#upload_photo').click ->
$('input[type=file]').show().focus().click().hide()
false
$('#tabs').on 'change.zf.tabs', ()->
if $('#preview:visible').length > 0
$('#preview').text('Loading...')
$.ajax
url: '/admin/posts/preview'
type: 'POST'
data:
content: $('#content-input').val()
success: (data)->
$('#preview').html(data)
$('a.tag').click (event)->
event.preventDefault()
new_labels = $(this).text()
if $('#labels').val() == ''
labels = new_labels
else
labels = $('#labels').val() + ", #{new_labels}"
$('#labels').val(labels)
opt =
type: 'POST'
url: "/photos"
success: (data,status,xhr)->
txtBox = $("#post_content")
txtBox = $("#content-input")
caret_pos = txtBox.caret('pos')
src_merged = "\n" + data + "\n"
source = txtBox.val()
@ -21,6 +41,5 @@ $(document).ready ->
txtBox.caret('pos',caret_pos + src_merged.length)
txtBox.scope().content = txtBox.val()
txtBox.focus()
$('input[type=file]').fileUpload opt

View File

@ -1,589 +0,0 @@
/**
* x is a value between 0 and 1, indicating where in the animation you are.
*/
var duScrollDefaultEasing = function (x) {
'use strict';
if(x < 0.5) {
return Math.pow(x*2, 2)/2;
}
return 1-Math.pow((1-x)*2, 2)/2;
};
angular.module('duScroll', [
'duScroll.scrollspy',
'duScroll.smoothScroll',
'duScroll.scrollContainer',
'duScroll.spyContext',
'duScroll.scrollHelpers'
])
//Default animation duration for smoothScroll directive
.value('duScrollDuration', 350)
//Scrollspy debounce interval, set to 0 to disable
.value('duScrollSpyWait', 100)
//Wether or not multiple scrollspies can be active at once
.value('duScrollGreedy', false)
//Default offset for smoothScroll directive
.value('duScrollOffset', 0)
//Default easing function for scroll animation
.value('duScrollEasing', duScrollDefaultEasing);
angular.module('duScroll.scrollHelpers', ['duScroll.requestAnimation'])
.run(["$window", "$q", "cancelAnimation", "requestAnimation", "duScrollEasing", "duScrollDuration", "duScrollOffset", function($window, $q, cancelAnimation, requestAnimation, duScrollEasing, duScrollDuration, duScrollOffset) {
'use strict';
var proto = angular.element.prototype;
var isDocument = function(el) {
return (typeof HTMLDocument !== 'undefined' && el instanceof HTMLDocument) || (el.nodeType && el.nodeType === el.DOCUMENT_NODE);
};
var isElement = function(el) {
return (typeof HTMLElement !== 'undefined' && el instanceof HTMLElement) || (el.nodeType && el.nodeType === el.ELEMENT_NODE);
};
var unwrap = function(el) {
return isElement(el) || isDocument(el) ? el : el[0];
};
proto.scrollTo = function(left, top, duration, easing) {
var aliasFn;
if(angular.isElement(left)) {
aliasFn = this.scrollToElement;
} else if(duration) {
aliasFn = this.scrollToAnimated;
}
if(aliasFn) {
return aliasFn.apply(this, arguments);
}
var el = unwrap(this);
if(isDocument(el)) {
return $window.scrollTo(left, top);
}
el.scrollLeft = left;
el.scrollTop = top;
};
var scrollAnimation, deferred;
proto.scrollToAnimated = function(left, top, duration, easing) {
if(duration && !easing) {
easing = duScrollEasing;
}
var startLeft = this.scrollLeft(),
startTop = this.scrollTop(),
deltaLeft = Math.round(left - startLeft),
deltaTop = Math.round(top - startTop);
var startTime = null;
var el = this;
var cancelOnEvents = 'scroll mousedown mousewheel touchmove keydown';
var cancelScrollAnimation = function($event) {
if (!$event || $event.which > 0) {
el.unbind(cancelOnEvents, cancelScrollAnimation);
cancelAnimation(scrollAnimation);
deferred.reject();
scrollAnimation = null;
}
};
if(scrollAnimation) {
cancelScrollAnimation();
}
deferred = $q.defer();
if(!deltaLeft && !deltaTop) {
deferred.resolve();
return deferred.promise;
}
var animationStep = function(timestamp) {
if (startTime === null) {
startTime = timestamp;
}
var progress = timestamp - startTime;
var percent = (progress >= duration ? 1 : easing(progress/duration));
el.scrollTo(
startLeft + Math.ceil(deltaLeft * percent),
startTop + Math.ceil(deltaTop * percent)
);
if(percent < 1) {
scrollAnimation = requestAnimation(animationStep);
} else {
el.unbind(cancelOnEvents, cancelScrollAnimation);
scrollAnimation = null;
deferred.resolve();
}
};
//Fix random mobile safari bug when scrolling to top by hitting status bar
el.scrollTo(startLeft, startTop);
el.bind(cancelOnEvents, cancelScrollAnimation);
scrollAnimation = requestAnimation(animationStep);
return deferred.promise;
};
proto.scrollToElement = function(target, offset, duration, easing) {
var el = unwrap(this);
if(!angular.isNumber(offset) || isNaN(offset)) {
offset = duScrollOffset;
}
var top = this.scrollTop() + unwrap(target).getBoundingClientRect().top - offset;
if(isElement(el)) {
top -= el.getBoundingClientRect().top;
}
return this.scrollTo(0, top, duration, easing);
};
var overloaders = {
scrollLeft: function(value, duration, easing) {
if(angular.isNumber(value)) {
return this.scrollTo(value, this.scrollTop(), duration, easing);
}
var el = unwrap(this);
if(isDocument(el)) {
return $window.scrollX || document.documentElement.scrollLeft || document.body.scrollLeft;
}
return el.scrollLeft;
},
scrollTop: function(value, duration, easing) {
if(angular.isNumber(value)) {
return this.scrollTo(this.scrollTop(), value, duration, easing);
}
var el = unwrap(this);
if(isDocument(el)) {
return $window.scrollY || document.documentElement.scrollTop || document.body.scrollTop;
}
return el.scrollTop;
}
};
proto.scrollToElementAnimated = function(target, offset, duration, easing) {
return this.scrollToElement(target, offset, duration || duScrollDuration, easing);
};
proto.scrollTopAnimated = function(top, duration, easing) {
return this.scrollTop(top, duration || duScrollDuration, easing);
};
proto.scrollLeftAnimated = function(left, duration, easing) {
return this.scrollLeft(left, duration || duScrollDuration, easing);
};
//Add duration and easing functionality to existing jQuery getter/setters
var overloadScrollPos = function(superFn, overloadFn) {
return function(value, duration, easing) {
if(duration) {
return overloadFn.apply(this, arguments);
}
return superFn.apply(this, arguments);
};
};
for(var methodName in overloaders) {
proto[methodName] = (proto[methodName] ? overloadScrollPos(proto[methodName], overloaders[methodName]) : overloaders[methodName]);
}
}]);
//Adapted from https://gist.github.com/paulirish/1579671
angular.module('duScroll.polyfill', [])
.factory('polyfill', ["$window", function($window) {
'use strict';
var vendors = ['webkit', 'moz', 'o', 'ms'];
return function(fnName, fallback) {
if($window[fnName]) {
return $window[fnName];
}
var suffix = fnName.substr(0, 1).toUpperCase() + fnName.substr(1);
for(var key, i = 0; i < vendors.length; i++) {
key = vendors[i]+suffix;
if($window[key]) {
return $window[key];
}
}
return fallback;
};
}]);
angular.module('duScroll.requestAnimation', ['duScroll.polyfill'])
.factory('requestAnimation', ["polyfill", "$timeout", function(polyfill, $timeout) {
'use strict';
var lastTime = 0;
var fallback = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = $timeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
return polyfill('requestAnimationFrame', fallback);
}])
.factory('cancelAnimation', ["polyfill", "$timeout", function(polyfill, $timeout) {
'use strict';
var fallback = function(promise) {
$timeout.cancel(promise);
};
return polyfill('cancelAnimationFrame', fallback);
}]);
angular.module('duScroll.spyAPI', ['duScroll.scrollContainerAPI'])
.factory('spyAPI', ["$rootScope", "$timeout", "scrollContainerAPI", "duScrollGreedy", "duScrollSpyWait", function($rootScope, $timeout, scrollContainerAPI, duScrollGreedy, duScrollSpyWait) {
'use strict';
var createScrollHandler = function(context) {
var timer = false, queued = false;
var handler = function() {
queued = false;
var container = context.container,
containerEl = container[0],
containerOffset = 0;
if (typeof HTMLElement !== 'undefined' && containerEl instanceof HTMLElement || containerEl.nodeType && containerEl.nodeType === containerEl.ELEMENT_NODE) {
containerOffset = containerEl.getBoundingClientRect().top;
}
var i, currentlyActive, toBeActive, spies, spy, pos;
spies = context.spies;
currentlyActive = context.currentlyActive;
toBeActive = undefined;
for(i = 0; i < spies.length; i++) {
spy = spies[i];
pos = spy.getTargetPosition();
if (!pos) continue;
if(pos.top + spy.offset - containerOffset < 20 && (pos.top*-1 + containerOffset) < pos.height) {
if(!toBeActive || toBeActive.top < pos.top) {
toBeActive = {
top: pos.top,
spy: spy
};
}
}
}
if(toBeActive) {
toBeActive = toBeActive.spy;
}
if(currentlyActive === toBeActive || (duScrollGreedy && !toBeActive)) return;
if(currentlyActive) {
currentlyActive.$element.removeClass('active');
$rootScope.$broadcast('duScrollspy:becameInactive', currentlyActive.$element);
}
if(toBeActive) {
toBeActive.$element.addClass('active');
$rootScope.$broadcast('duScrollspy:becameActive', toBeActive.$element);
}
context.currentlyActive = toBeActive;
};
if(!duScrollSpyWait) {
return handler;
}
//Debounce for potential performance savings
return function() {
if(!timer) {
handler();
timer = $timeout(function() {
timer = false;
if(queued) {
handler();
}
}, duScrollSpyWait, false);
} else {
queued = true;
}
};
};
var contexts = {};
var createContext = function($scope) {
var id = $scope.$id;
var context = {
spies: []
};
context.handler = createScrollHandler(context);
contexts[id] = context;
$scope.$on('$destroy', function() {
destroyContext($scope);
});
return id;
};
var destroyContext = function($scope) {
var id = $scope.$id;
var context = contexts[id], container = context.container;
if(container) {
container.off('scroll', context.handler);
}
delete contexts[id];
};
var defaultContextId = createContext($rootScope);
var getContextForScope = function(scope) {
if(contexts[scope.$id]) {
return contexts[scope.$id];
}
if(scope.$parent) {
return getContextForScope(scope.$parent);
}
return contexts[defaultContextId];
};
var getContextForSpy = function(spy) {
var context, contextId, scope = spy.$element.scope();
if(scope) {
return getContextForScope(scope);
}
//No scope, most likely destroyed
for(contextId in contexts) {
context = contexts[contextId];
if(context.spies.indexOf(spy) !== -1) {
return context;
}
}
};
var isElementInDocument = function(element) {
while (element.parentNode) {
element = element.parentNode;
if (element === document) {
return true;
}
}
return false;
};
var addSpy = function(spy) {
var context = getContextForSpy(spy);
if (!context) return;
context.spies.push(spy);
if (!context.container || !isElementInDocument(context.container)) {
if(context.container) {
context.container.off('scroll', context.handler);
}
context.container = scrollContainerAPI.getContainer(spy.$element.scope());
context.container.on('scroll', context.handler).triggerHandler('scroll');
}
};
var removeSpy = function(spy) {
var context = getContextForSpy(spy);
if(spy === context.currentlyActive) {
context.currentlyActive = null;
}
var i = context.spies.indexOf(spy);
if(i !== -1) {
context.spies.splice(i, 1);
}
};
return {
addSpy: addSpy,
removeSpy: removeSpy,
createContext: createContext,
destroyContext: destroyContext,
getContextForScope: getContextForScope
};
}]);
angular.module('duScroll.scrollContainerAPI', [])
.factory('scrollContainerAPI', ["$document", function($document) {
'use strict';
var containers = {};
var setContainer = function(scope, element) {
var id = scope.$id;
containers[id] = element;
return id;
};
var getContainerId = function(scope) {
if(containers[scope.$id]) {
return scope.$id;
}
if(scope.$parent) {
return getContainerId(scope.$parent);
}
return;
};
var getContainer = function(scope) {
var id = getContainerId(scope);
return id ? containers[id] : $document;
};
var removeContainer = function(scope) {
var id = getContainerId(scope);
if(id) {
delete containers[id];
}
};
return {
getContainerId: getContainerId,
getContainer: getContainer,
setContainer: setContainer,
removeContainer: removeContainer
};
}]);
angular.module('duScroll.smoothScroll', ['duScroll.scrollHelpers', 'duScroll.scrollContainerAPI'])
.directive('duSmoothScroll', ["duScrollDuration", "duScrollOffset", "scrollContainerAPI", function(duScrollDuration, duScrollOffset, scrollContainerAPI) {
'use strict';
return {
link : function($scope, $element, $attr) {
$element.on('click', function(e) {
if(!$attr.href || $attr.href.indexOf('#') === -1) return;
var target = document.getElementById($attr.href.replace(/.*(?=#[^\s]+$)/, '').substring(1));
if(!target || !target.getBoundingClientRect) return;
if (e.stopPropagation) e.stopPropagation();
if (e.preventDefault) e.preventDefault();
var offset = $attr.offset ? parseInt($attr.offset, 10) : duScrollOffset;
var duration = $attr.duration ? parseInt($attr.duration, 10) : duScrollDuration;
var container = scrollContainerAPI.getContainer($scope);
container.scrollToElement(
angular.element(target),
isNaN(offset) ? 0 : offset,
isNaN(duration) ? 0 : duration
);
});
}
};
}]);
angular.module('duScroll.spyContext', ['duScroll.spyAPI'])
.directive('duSpyContext', ["spyAPI", function(spyAPI) {
'use strict';
return {
restrict: 'A',
scope: true,
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink($scope, iElement, iAttrs, controller) {
spyAPI.createContext($scope);
}
};
}
};
}]);
angular.module('duScroll.scrollContainer', ['duScroll.scrollContainerAPI'])
.directive('duScrollContainer', ["scrollContainerAPI", function(scrollContainerAPI){
'use strict';
return {
restrict: 'A',
scope: true,
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink($scope, iElement, iAttrs, controller) {
iAttrs.$observe('duScrollContainer', function(element) {
if(angular.isString(element)) {
element = document.getElementById(element);
}
element = (angular.isElement(element) ? angular.element(element) : iElement);
scrollContainerAPI.setContainer($scope, element);
$scope.$on('$destroy', function() {
scrollContainerAPI.removeContainer($scope);
});
});
}
};
}
};
}]);
angular.module('duScroll.scrollspy', ['duScroll.spyAPI'])
.directive('duScrollspy', ["spyAPI", "duScrollOffset", "$timeout", "$rootScope", function(spyAPI, duScrollOffset, $timeout, $rootScope) {
'use strict';
var Spy = function(targetElementOrId, $element, offset) {
if(angular.isElement(targetElementOrId)) {
this.target = targetElementOrId;
} else if(angular.isString(targetElementOrId)) {
this.targetId = targetElementOrId;
}
this.$element = $element;
this.offset = offset;
};
Spy.prototype.getTargetElement = function() {
if (!this.target && this.targetId) {
this.target = document.getElementById(this.targetId);
}
return this.target;
};
Spy.prototype.getTargetPosition = function() {
var target = this.getTargetElement();
if(target) {
return target.getBoundingClientRect();
}
};
Spy.prototype.flushTargetCache = function() {
if(this.targetId) {
this.target = undefined;
}
};
return {
link: function ($scope, $element, $attr) {
var href = $attr.ngHref || $attr.href;
var targetId;
if (href && href.indexOf('#') !== -1) {
targetId = href.replace(/.*(?=#[^\s]+$)/, '').substring(1);
} else if($attr.duScrollspy) {
targetId = $attr.duScrollspy;
}
if(!targetId) return;
// Run this in the next execution loop so that the scroll context has a chance
// to initialize
$timeout(function() {
var spy = new Spy(targetId, $element, -($attr.offset ? parseInt($attr.offset, 10) : duScrollOffset));
spyAPI.addSpy(spy);
$scope.$on('$destroy', function() {
spyAPI.removeSpy(spy);
});
$scope.$on('$locationChangeSuccess', spy.flushTargetCache.bind(spy));
$rootScope.$on('$stateChangeSuccess', spy.flushTargetCache.bind(spy));
}, 0, false);
}
};
}]);

View File

@ -1,15 +0,0 @@
#= require angular
#= require angular-cookies
#= require angular-resource
#= require angular-sanitize
#= require angular-scroll
#= require_self
#= require_tree ./angularjs
@app = angular.module('app', ['ngCookies', 'ngSanitize', 'duScroll'])
@app.value('duScrollOffset', 30)
@app.config(["$httpProvider", (provider) ->
provider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
provider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
])

View File

@ -1,36 +0,0 @@
@app.controller 'AboutController', [ '$scope', '$timeout', '$http', ($scope, $timeout, $http)->
$scope.type = null
$scope.click = (v)->
if $scope.type == v
$scope.type = null
return
$scope.type = v
$scope.weixin_click = ->
$scope.weixin = !$scope.weixin
#
$scope.email = ''
$scope.subscribe_success = null
$scope.email_validate = ->
$scope.email.match(/@/)
$scope.subscribe = ()->
$http
url: '/subscribes'
method: 'POST'
params:
email: $scope.email
.success (res)->
if res.success
$scope.email = ''
$scope.subscribe_success = true
else
$scope.subscribe_success = false
$scope.subscribe_fail_msg = res.message
$timeout ->
$scope.subscribe_success = null
, 3000
]

View File

@ -1,14 +0,0 @@
@app.controller 'AboutScrollController', [ '$scope', '$document', ($scope, $document)->
about_id = angular.element(document.getElementById('about'))
$document.on 'scroll', ()=>
$scope.$apply()
$scope.is_top = ()->
$document.scrollTop() <= 0
$scope.to_about = ()->
$document.scrollToElementAnimated(about_id)
]

View File

@ -1,35 +0,0 @@
@app.controller 'AdminPostsController', [ '$scope', '$http', '$location', '$timeout', '$cookies', '$sce', ($scope, $http, $location, $timeout, $cookies, $sce)->
$scope.body_active = true
$scope.init = (id)->
url = '/admin/posts/' + id + '.json'
$http
url: url
method: 'GET'
.success (res)->
$scope.title = res.title
$scope.type = res.type
$scope.labels = res.labels
$scope.content = res.content
$scope.changeToBody = ->
$scope.body_active = true
$scope.changeToPreview = ->
$scope.body_active = false
$scope.previewHTML = 'Loading...'
$http.post '/admin/posts/preview', { content: $scope.content }
.success (res)->
$scope.previewHTML = res
$scope.trustAsPreviewHTML = ()->
$sce.trustAsHtml($scope.previewHTML)
$scope.addTag = (e)->
new_labels= $(e.target).text()
if $scope.labels
$scope.labels += ", #{new_labels}"
else
$scope.labels = new_labels
]

View File

@ -1,21 +0,0 @@
@app.controller 'AdminSessionsController', [ '$scope', '$http', '$timeout', '$cookies', ($scope, $http, $timeout, $cookies)->
url = '/admin/sessions'
$scope.login = ->
$http
url: url
method: 'POST'
data:
username: $scope.username
password: $scope.password
.success (res)->
if res.success
urlback = $cookies.urlback || '/admin'
window.location = urlback
else
$scope.password = ''
$scope.error_msg = res.message
$timeout ->
$scope.error_msg = null
, 5000
]

View File

@ -1,43 +0,0 @@
@app.controller 'ArchivesController',[ '$scope', '$http', '$location', '$timeout', '$cookies', ($scope, $http, $location, $timeout, $cookies)->
url = window.location.pathname + ".json"
start_with = $cookies.start_with if window.location.pathname == $cookies.start_with_type
$http
url: url
method: 'GET'
params:
start_with: start_with
all: true
.success (res)->
$scope.update_start_with(res.start_with)
$scope.posts = res.posts
$scope.no_more_flag = false
$scope.loading_flag = false
$scope.load = ()->
$scope.loading_flag = true
$http(
url: url
method: 'GET'
params:
start_with: $scope.start_with
type: $scope.type
).success (res)->
$scope.no_more_flag = true if res.posts.length == 0
$scope.update_start_with(res.start_with)
$scope.posts = $scope.posts.concat(res.posts)
$timeout ->
$scope.loading_flag = false
, 500
$timeout ->
$scope.no_more_flag = false
, 3000
$scope.visit = (id)->
"/blogs/" + id
$scope.update_start_with = (start_with)->
$scope.start_with = start_with
$cookies.start_with_type = window.location.pathname
$cookies.start_with = start_with
]

View File

@ -1,35 +0,0 @@
@app.controller 'CommentsController', ['$scope', '$http', '$location', '$timeout', '$cookies', ($scope, $http, $location, $timeout, $cookies)->
url = window.location.pathname + "/comments.json"
$scope.name = $cookies.name
$scope.email = $cookies.email
$http.get(url).success (data)->
$scope.comments = data
$scope.publish_success = null
$scope.submit = ->
$scope.submitting = true
$cookies.name = $scope.name
$cookies.email = $scope.email
comment = { content: $scope.content, name: $scope.name, email: $scope.email }
$http.post(url, comment)
.success (res)->
if res.success
$scope.publish_success = true
$scope.content = ''
$scope.comments.unshift(res.data)
else
$scope.publish_success = false
$scope.publish_fail_msg = res.message
.error (data, status)->
$scope.publish_success = false
$scope.publish_fail_msg = 'Network Error, Retry for a moment, Status Code: ' + status
.finally ->
$scope.submitting = false
$scope.timeout = $timeout ->
$timeout.cancel($scope.timeout)
$scope.publish_success = null
, 5*1000
]

View File

@ -1,9 +0,0 @@
@app.directive 'ngInitial', ->
restrict: 'A',
controller: [
'$scope', '$element', '$attrs', '$parse', ($scope, $element, $attrs, $parse)->
val = $attrs.ngInitial || $attrs.value || $attrs.$$element.text()
getter = $parse($attrs.ngModel)
setter = getter.assign
setter($scope, val)
]

View File

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

View File

@ -1,4 +0,0 @@
@app.controller 'QRCodesController', [ '$scope', ($scope)->
$scope.show = ->
$scope.qrcode = ! $scope.qrcode
]

View File

@ -1,10 +0,0 @@
@app.controller 'SubscribesController', [ '$scope', '$http', ($scope, $http)->
$scope.cancel = ()->
$http
url: '/subscribes/cancel'
method: 'POST'
params:
email: $scope.email
.success (res)->
window.location = '/'
]

View File

@ -1,13 +1,11 @@
//= require jquery
//= require jquery_ujs
//= require foundation/foundation
//= require foundation/foundation.alert
//= require foundation/foundation.topbar
//= require foundation/foundation.offcanvas
//= require foundation/foundation.magellan
//= require angularjs
//= require turbolinks
//= require foundation
//= require js.cookie
//= require 'jquery.html5-fileupload'
//= require_tree .
$(function(){ $(document).foundation(); });
$(document).on('turbolinks:load', function(){
$(document).foundation();
});

View File

@ -0,0 +1,3 @@
$(document).on 'turbolinks:load', ->
$('#alert-container .close-button').click ()->
$('#alert-container').hide()

View File

@ -0,0 +1,225 @@
/*
* DD ScrollSpy Menu Script (c) Dynamic Drive (www.dynamicdrive.com)
* Last updated: Aug 1st, 14'
* Visit http://www.dynamicdrive.com/ for this script and 100s more.
*/
// Aug 1st, 14': Updated to v1.2, which supports showing a progress bar inside each menu item (except in iOS devices). Other minor improvements.
if (!Array.prototype.filter){
Array.prototype.filter = function(fun /*, thisp */){
"use strict";
if (this == null)
throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun != "function")
throw new TypeError();
var res = [];
var thisp = arguments[1];
for (var i = 0; i < len; i++){
if (i in t){
var val = t[i]; // in case fun mutates this
if (fun.call(thisp, val, i, t))
res.push(val);
}
}
return res;
};
}
(function($){
var defaults = {
spytarget: window,
scrolltopoffset: 0,
scrollbehavior: 'smooth',
scrollduration: 500,
highlightclass: 'selected',
enableprogress: '',
mincontentheight: 30
}
var isiOS = /iPhone|iPad|iPod/i.test(navigator.userAgent) // detect iOS devices
function inrange(el, range, field){ // check if "playing field" is inside range
var rangespan = range[1]-range[0], fieldspan = field[1]-field[0]
if ( (range[0]-field[0]) >= 0 && (range[0]-field[0]) < fieldspan ){ // if top of range is on field
return true
}
else{
if ( (range[0]-field[0]) <= 0 && (range[0]+rangespan) > field[0] ){ // if part of range overlaps field
return true
}
}
return false
}
$.fn.ddscrollSpy = function(options){
var $window = $(window)
var $body=(window.opera)? (document.compatMode=="CSS1Compat"? $('html') : $('body')) : $('html,body')
return this.each(function(){
var o = $.extend({}, defaults, options)
o.enableprogress = (isiOS)? '' : o.enableprogress // disable enableprogress in iOS
var targets = [], curtarget = ''
var cantscrollpastindex = -1 // index of target content that can't be scrolled past completely when scrollbar is at the end of the doc
var $spytarget = $( o.spytarget ).eq(0)
var spyheight = $spytarget.outerHeight()
var spyscrollheight = (o.spytarget == window)? $body.get(0).scrollHeight : $spytarget.get(0).scrollHeight
var $menu = $(this)
var totaltargetsheight = 0 // total height of target contents
function spyonmenuitems($menu){
var $menuitems = $menu.find('a[href^="#"]')
targets = []
curtarget = ''
totaltargetsheight = 0
$menuitems.each(function(i){
var $item = $(this)
var $target = $( $item.attr('href') )
var target = $target.get(0)
var $progress = null // progress DIV that gets dynamically added inside menu A element if o.enableprogress enabled
if ($target.length == 0) // if no matching links found
return true
$item
.off('click.goto')
.on('click.goto', function(e){
if ( o.spytarget == window && (o.scrollbehavior == 'jump' || !history.pushState))
window.location.hash = $item.attr('href')
if (o.scrollbehavior == 'smooth' || o.scrolltopoffset !=0){
var $scrollparent = (o.spytarget == window)? $body : $spytarget
var addoffset = 1 // add 1 pixel to scrollTop when scrolling to an element to make sure the browser always returns the correct target element (strange bug)
if (o.scrollbehavior == 'smooth' && (history.pushState || o.spytarget != window)){
$scrollparent.animate( {scrollTop: targets[i].offsettop + addoffset}, o.scrollduration, function(){
if (o.spytarget == window && history.pushState){
//history.pushState(null, null, $item.attr('href'))
}
})
}
else{
$scrollparent.prop('scrollTop', targets[i].offsettop + addoffset)
}
e.preventDefault()
}
})
if (o.enableprogress){ // if o.enableprogress enabled
if ($item.find('div.' + o.enableprogress).length == 0){ //if no progress DIV found inside menu item
$item.css({position: 'relative', overflow: 'hidden'}) // add some required style to parent A element
$('<div class="' + o.enableprogress + '" style="position:absolute; left: -100%" />').appendTo($item)
}
$progress = $item.find('div.' + o.enableprogress)
}
var targetoffset = (o.spytarget == window)? $target.offset().top : (target.offsetParent == o.spytarget)? target.offsetTop : target.offsetTop - o.spytarget.offsetTop
targetoffset += o.scrolltopoffset
var targetheight = ( parseInt($target.data('spyrange')) > 0 )? parseInt($target.data('spyrange')) : ( $target.outerHeight() || o.mincontentheight)
var offsetbottom = targetoffset + targetheight
if (cantscrollpastindex == -1 && offsetbottom > (spyscrollheight - spyheight)){ // determine index of first target which can't be scrolled past
cantscrollpastindex = i
}
targets.push( {$menuitem: $item, $des: $target, offsettop: targetoffset, height: targetheight, $progress: $progress, index: i} )
})
if (targets.length > 0)
totaltargetsheight = targets[targets.length-1].offsettop + targets[targets.length-1].height
}
function highlightitem(){
if (targets.length == 0)
return
var prevtarget = curtarget
var scrolltop = $spytarget.scrollTop()
var cantscrollpasttarget = false
var shortlist = targets.filter(function(el, index){ // filter target elements that are currently visible on screen
return inrange(el, [el.offsettop, el.offsettop + el.height], [scrolltop, scrolltop + spyheight])
})
if (shortlist.length > 0){
curtarget = shortlist.shift() // select the first element that's visible on screen
if (prevtarget && prevtarget != curtarget)
prevtarget.$menuitem.removeClass(o.highlightclass)
if (!curtarget.$menuitem.hasClass(o.highlightclass)) // if there was a previously selected menu link and it's not the same as current
curtarget.$menuitem.addClass(o.highlightclass) // highlight its menu item
if (curtarget.index >= cantscrollpastindex && scrolltop >= (spyscrollheight - spyheight)){ // if we're at target that can't be scrolled past and we're at end of document
if (o.enableprogress){ // if o.enableprogress enabled
for (var i=0; i<targets.length; i++){ // highlight everything
targets[i].$menuitem.find('div.' + o.enableprogress).css('left', 0)
}
}
curtarget.$menuitem.removeClass(o.highlightclass)
curtarget = targets[targets.length-1]
if (!curtarget.$menuitem.hasClass(o.highlightclass))
curtarget.$menuitem.addClass(o.highlightclass)
return
}
if (o.enableprogress){ // if o.enableprogress enabled
var scrollpct = ((scrolltop-curtarget.offsettop) / curtarget.height) * 100
curtarget.$menuitem.find('div.' + o.enableprogress).css('left', -100 + scrollpct + '%')
for (var i=0; i<targets.length; i++){
if (i < curtarget.index){
targets[i].$menuitem.find('div.' + o.enableprogress).css('left', 0)
}
else if (i > curtarget.index){
targets[i].$menuitem.find('div.' + o.enableprogress).css('left', '-100%')
}
}
}
}
else if (scrolltop > totaltargetsheight){ // if no target content visible on screen but scroll bar has scrolled past very last content already
if (o.enableprogress){ // if o.enableprogress enabled
curtarget.$menuitem.removeClass(o.highlightclass)
for (var i=0; i<targets.length; i++){
targets[i].$menuitem.find('div.' + o.enableprogress).css('left', 0)
}
}
}
}
function updatetargetpos(){
if (targets.length == 0)
return
var $menu = targets[0].$menu
spyheight = $spytarget.outerHeight()
spyscrollheight = (o.spytarget == window)? $body.get(0).scrollHeight : $spytarget.get(0).scrollHeight
totaltargetsheight = 0
cantscrollpastindex = -1
for (var i = 0; i < targets.length; i++){
var $target = targets[i].$des
var target = $target.get(0)
var targetoffset = (o.spytarget == window)? $target.offset().top : (target.offsetParent == o.spytarget)? target.offsetTop : target.offsetTop - o.spytarget.offsetTop
targetoffset += o.scrolltopoffset
targets[i].offsettop = targetoffset
targets[i].height = ( parseInt($target.data('spyrange')) > 0 )? parseInt($target.data('spyrange')) : ( $target.outerHeight() || o.mincontentheight)
if (o.enableprogress){ // if o.enableprogress enabled
var offsetbottom = targetoffset + targets[i].height // recalculate cantscrollpastindex
if (cantscrollpastindex == -1 && offsetbottom > (spyscrollheight - spyheight)){
cantscrollpastindex = i
}
}
}
totaltargetsheight = targets[targets.length-1].offsettop + targets[targets.length-1].height
}
spyonmenuitems($menu)
$menu.on('updatespy', function(){
spyonmenuitems($menu)
highlightitem()
})
$spytarget.on('scroll resize', function(){
highlightitem()
})
highlightitem()
$window.on('load resize', function(){
updatetargetpos()
})
}) // end return
}
})(jQuery);

11
app/assets/javascripts/jquery.atwho.js Executable file → Normal file
View File

@ -8,13 +8,6 @@
*/
/*
本插件操作 textarea 或者 input 内的插入符
只实现了获得插入符在文本框中的位置我设置
插入符的位置.
*/
(function() {
(function(factory) {
if (typeof define === 'function' && define.amd) {
@ -85,7 +78,7 @@
/ \
< I really [[HATE] IE []]>
\_endRange end-point.
" > -1" mean the start end-point will be the same or right to the end end-point
* simplelly, all in the end.
*/
@ -99,7 +92,7 @@
I really[ [HATE] IE ]>
<-[
I reall[y [HATE] IE ]>
will return how many unit have moved.
*/

View File

@ -0,0 +1,19 @@
$(document).on 'turbolinks:load', ->
$('.like-button').click ->
if $(this).hasClass('liked')
$.ajax
url: $(this).data('url') + '/' + Cookies.get('like')
type: 'DELETE'
success: (res)=>
$(this).removeClass('liked')
$(this).children('.count').text(res.count)
Cookies.remove('like')
else
$.ajax
url: $(this).data('url')
type: 'POST'
success: (res)=>
$(this).addClass('liked')
$(this).children('.count').text(res.count)
Cookies.set('like', res.id)

View File

@ -0,0 +1,614 @@
/**
* @fileoverview
* - Using the 'QRCode for Javascript library'
* - Fixed dataset of 'QRCode for Javascript library' for support full-spec.
* - this library has no dependencies.
*
* @author davidshimjs
* @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
* @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
*/
var QRCode;
(function () {
//---------------------------------------------------------------------
// QRCode for JavaScript
//
// Copyright (c) 2009 Kazuhiko Arase
//
// URL: http://www.d-project.com/
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license.php
//
// The word "QR Code" is registered trademark of
// DENSO WAVE INCORPORATED
// http://www.denso-wave.com/qrcode/faqpatent-e.html
//
//---------------------------------------------------------------------
function QR8bitByte(data) {
this.mode = QRMode.MODE_8BIT_BYTE;
this.data = data;
this.parsedData = [];
// Added to support UTF-8 Characters
for (var i = 0, l = this.data.length; i < l; i++) {
var byteArray = [];
var code = this.data.charCodeAt(i);
if (code > 0x10000) {
byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);
byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);
byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);
byteArray[3] = 0x80 | (code & 0x3F);
} else if (code > 0x800) {
byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);
byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);
byteArray[2] = 0x80 | (code & 0x3F);
} else if (code > 0x80) {
byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);
byteArray[1] = 0x80 | (code & 0x3F);
} else {
byteArray[0] = code;
}
this.parsedData.push(byteArray);
}
this.parsedData = Array.prototype.concat.apply([], this.parsedData);
if (this.parsedData.length != this.data.length) {
this.parsedData.unshift(191);
this.parsedData.unshift(187);
this.parsedData.unshift(239);
}
}
QR8bitByte.prototype = {
getLength: function (buffer) {
return this.parsedData.length;
},
write: function (buffer) {
for (var i = 0, l = this.parsedData.length; i < l; i++) {
buffer.put(this.parsedData[i], 8);
}
}
};
function QRCodeModel(typeNumber, errorCorrectLevel) {
this.typeNumber = typeNumber;
this.errorCorrectLevel = errorCorrectLevel;
this.modules = null;
this.moduleCount = 0;
this.dataCache = null;
this.dataList = [];
}
QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);}
return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}
if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}
this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}
return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
this.modules[r][6]=(r%2==0);}
for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}
for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}
for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}
this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}
var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}
this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}
row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
if(buffer.getLengthInBits()>totalDataCount*8){throw new Error("code length overflow. ("
+buffer.getLengthInBits()
+">"
+totalDataCount*8
+")");}
if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}
while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}
while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}
buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}
buffer.put(QRCodeModel.PAD1,8);}
return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}
return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}
return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}
return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
if(r==0&&c==0){continue;}
if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
while(n>=256){n-=255;}
return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}
for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}
if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}
this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
function _isSupportCanvas() {
return typeof CanvasRenderingContext2D != "undefined";
}
// android 2.x doesn't support Data-URI spec
function _getAndroid() {
var android = false;
var sAgent = navigator.userAgent;
if (/android/i.test(sAgent)) { // android
android = true;
var aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i);
if (aMat && aMat[1]) {
android = parseFloat(aMat[1]);
}
}
return android;
}
var svgDrawer = (function() {
var Drawing = function (el, htOption) {
this._el = el;
this._htOption = htOption;
};
Drawing.prototype.draw = function (oQRCode) {
var _htOption = this._htOption;
var _el = this._el;
var nCount = oQRCode.getModuleCount();
var nWidth = Math.floor(_htOption.width / nCount);
var nHeight = Math.floor(_htOption.height / nCount);
this.clear();
function makeSVG(tag, attrs) {
var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
for (var k in attrs)
if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);
return el;
}
var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});
svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
_el.appendChild(svg);
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorLight, "width": "100%", "height": "100%"}));
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"}));
for (var row = 0; row < nCount; row++) {
for (var col = 0; col < nCount; col++) {
if (oQRCode.isDark(row, col)) {
var child = makeSVG("use", {"x": String(col), "y": String(row)});
child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template")
svg.appendChild(child);
}
}
}
};
Drawing.prototype.clear = function () {
while (this._el.hasChildNodes())
this._el.removeChild(this._el.lastChild);
};
return Drawing;
})();
var useSVG = document.documentElement.tagName.toLowerCase() === "svg";
// Drawing in DOM by using Table tag
var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {
var Drawing = function (el, htOption) {
this._el = el;
this._htOption = htOption;
};
/**
* Draw the QRCode
*
* @param {QRCode} oQRCode
*/
Drawing.prototype.draw = function (oQRCode) {
var _htOption = this._htOption;
var _el = this._el;
var nCount = oQRCode.getModuleCount();
var nWidth = Math.floor(_htOption.width / nCount);
var nHeight = Math.floor(_htOption.height / nCount);
var aHTML = ['<table style="border:0;border-collapse:collapse;">'];
for (var row = 0; row < nCount; row++) {
aHTML.push('<tr>');
for (var col = 0; col < nCount; col++) {
aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
}
aHTML.push('</tr>');
}
aHTML.push('</table>');
_el.innerHTML = aHTML.join('');
// Fix the margin values as real size.
var elTable = _el.childNodes[0];
var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;
var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;
if (nLeftMarginTable > 0 && nTopMarginTable > 0) {
elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px";
}
};
/**
* Clear the QRCode
*/
Drawing.prototype.clear = function () {
this._el.innerHTML = '';
};
return Drawing;
})() : (function () { // Drawing in Canvas
function _onMakeImage() {
this._elImage.src = this._elCanvas.toDataURL("image/png");
this._elImage.style.display = "block";
this._elCanvas.style.display = "none";
}
// Android 2.1 bug workaround
// http://code.google.com/p/android/issues/detail?id=5141
if (this._android && this._android <= 2.1) {
var factor = 1 / window.devicePixelRatio;
var drawImage = CanvasRenderingContext2D.prototype.drawImage;
CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
if (("nodeName" in image) && /img/i.test(image.nodeName)) {
for (var i = arguments.length - 1; i >= 1; i--) {
arguments[i] = arguments[i] * factor;
}
} else if (typeof dw == "undefined") {
arguments[1] *= factor;
arguments[2] *= factor;
arguments[3] *= factor;
arguments[4] *= factor;
}
drawImage.apply(this, arguments);
};
}
/**
* Check whether the user's browser supports Data URI or not
*
* @private
* @param {Function} fSuccess Occurs if it supports Data URI
* @param {Function} fFail Occurs if it doesn't support Data URI
*/
function _safeSetDataURI(fSuccess, fFail) {
var self = this;
self._fFail = fFail;
self._fSuccess = fSuccess;
// Check it just once
if (self._bSupportDataURI === null) {
var el = document.createElement("img");
var fOnError = function() {
self._bSupportDataURI = false;
if (self._fFail) {
self._fFail.call(self);
}
};
var fOnSuccess = function() {
self._bSupportDataURI = true;
if (self._fSuccess) {
self._fSuccess.call(self);
}
};
el.onabort = fOnError;
el.onerror = fOnError;
el.onload = fOnSuccess;
el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data.
return;
} else if (self._bSupportDataURI === true && self._fSuccess) {
self._fSuccess.call(self);
} else if (self._bSupportDataURI === false && self._fFail) {
self._fFail.call(self);
}
};
/**
* Drawing QRCode by using canvas
*
* @constructor
* @param {HTMLElement} el
* @param {Object} htOption QRCode Options
*/
var Drawing = function (el, htOption) {
this._bIsPainted = false;
this._android = _getAndroid();
this._htOption = htOption;
this._elCanvas = document.createElement("canvas");
this._elCanvas.width = htOption.width;
this._elCanvas.height = htOption.height;
el.appendChild(this._elCanvas);
this._el = el;
this._oContext = this._elCanvas.getContext("2d");
this._bIsPainted = false;
this._elImage = document.createElement("img");
this._elImage.alt = "Scan me!";
this._elImage.style.display = "none";
this._el.appendChild(this._elImage);
this._bSupportDataURI = null;
};
/**
* Draw the QRCode
*
* @param {QRCode} oQRCode
*/
Drawing.prototype.draw = function (oQRCode) {
var _elImage = this._elImage;
var _oContext = this._oContext;
var _htOption = this._htOption;
var nCount = oQRCode.getModuleCount();
var nWidth = _htOption.width / nCount;
var nHeight = _htOption.height / nCount;
var nRoundedWidth = Math.round(nWidth);
var nRoundedHeight = Math.round(nHeight);
_elImage.style.display = "none";
this.clear();
for (var row = 0; row < nCount; row++) {
for (var col = 0; col < nCount; col++) {
var bIsDark = oQRCode.isDark(row, col);
var nLeft = col * nWidth;
var nTop = row * nHeight;
_oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
_oContext.lineWidth = 1;
_oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
_oContext.fillRect(nLeft, nTop, nWidth, nHeight);
// 안티 앨리어싱 방지 처리
_oContext.strokeRect(
Math.floor(nLeft) + 0.5,
Math.floor(nTop) + 0.5,
nRoundedWidth,
nRoundedHeight
);
_oContext.strokeRect(
Math.ceil(nLeft) - 0.5,
Math.ceil(nTop) - 0.5,
nRoundedWidth,
nRoundedHeight
);
}
}
this._bIsPainted = true;
};
/**
* Make the image from Canvas if the browser supports Data URI.
*/
Drawing.prototype.makeImage = function () {
if (this._bIsPainted) {
_safeSetDataURI.call(this, _onMakeImage);
}
};
/**
* Return whether the QRCode is painted or not
*
* @return {Boolean}
*/
Drawing.prototype.isPainted = function () {
return this._bIsPainted;
};
/**
* Clear the QRCode
*/
Drawing.prototype.clear = function () {
this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);
this._bIsPainted = false;
};
/**
* @private
* @param {Number} nNumber
*/
Drawing.prototype.round = function (nNumber) {
if (!nNumber) {
return nNumber;
}
return Math.floor(nNumber * 1000) / 1000;
};
return Drawing;
})();
/**
* Get the type by string length
*
* @private
* @param {String} sText
* @param {Number} nCorrectLevel
* @return {Number} type
*/
function _getTypeNumber(sText, nCorrectLevel) {
var nType = 1;
var length = _getUTF8Length(sText);
for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {
var nLimit = 0;
switch (nCorrectLevel) {
case QRErrorCorrectLevel.L :
nLimit = QRCodeLimitLength[i][0];
break;
case QRErrorCorrectLevel.M :
nLimit = QRCodeLimitLength[i][1];
break;
case QRErrorCorrectLevel.Q :
nLimit = QRCodeLimitLength[i][2];
break;
case QRErrorCorrectLevel.H :
nLimit = QRCodeLimitLength[i][3];
break;
}
if (length <= nLimit) {
break;
} else {
nType++;
}
}
if (nType > QRCodeLimitLength.length) {
throw new Error("Too long data");
}
return nType;
}
function _getUTF8Length(sText) {
var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a');
return replacedText.length + (replacedText.length != sText ? 3 : 0);
}
/**
* @class QRCode
* @constructor
* @example
* new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie");
*
* @example
* var oQRCode = new QRCode("test", {
* text : "http://naver.com",
* width : 128,
* height : 128
* });
*
* oQRCode.clear(); // Clear the QRCode.
* oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode.
*
* @param {HTMLElement|String} el target element or 'id' attribute of element.
* @param {Object|String} vOption
* @param {String} vOption.text QRCode link data
* @param {Number} [vOption.width=256]
* @param {Number} [vOption.height=256]
* @param {String} [vOption.colorDark="#000000"]
* @param {String} [vOption.colorLight="#ffffff"]
* @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]
*/
QRCode = function (el, vOption) {
this._htOption = {
width : 256,
height : 256,
typeNumber : 4,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRErrorCorrectLevel.H
};
if (typeof vOption === 'string') {
vOption = {
text : vOption
};
}
// Overwrites options
if (vOption) {
for (var i in vOption) {
this._htOption[i] = vOption[i];
}
}
if (typeof el == "string") {
el = document.getElementById(el);
}
if (this._htOption.useSVG) {
Drawing = svgDrawer;
}
this._android = _getAndroid();
this._el = el;
this._oQRCode = null;
this._oDrawing = new Drawing(this._el, this._htOption);
if (this._htOption.text) {
this.makeCode(this._htOption.text);
}
};
/**
* Make the QRCode
*
* @param {String} sText link data
*/
QRCode.prototype.makeCode = function (sText) {
this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);
this._oQRCode.addData(sText);
this._oQRCode.make();
this._el.title = sText;
this._oDrawing.draw(this._oQRCode);
this.makeImage();
};
/**
* Make the Image from Canvas element
* - It occurs automatically
* - Android below 3 doesn't support Data-URI spec.
*
* @private
*/
QRCode.prototype.makeImage = function () {
if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) {
this._oDrawing.makeImage();
}
};
/**
* Clear the QRCode
*/
QRCode.prototype.clear = function () {
this._oDrawing.clear();
};
/**
* @name QRCode.CorrectLevel
*/
QRCode.CorrectLevel = QRErrorCorrectLevel;
})();

View File

@ -0,0 +1,13 @@
$(document).on 'turbolinks:load', ->
if $('#qrcode-home').length > 0
$('#qrcode-home').empty()
new QRCode( $('#qrcode-home')[0], $('#qrcode-home').data('url') )
if $('#image-tag').length > 0
$('#image-tag').empty()
new QRCode( $('#image-tag')[0], $('#image-tag').data('url') )
$('#qrcode-link').click (event)->
event.preventDefault()
$('.social-share').toggle()

View File

@ -0,0 +1,567 @@
// Foundation for Sites Settings
// -----------------------------
//
// Table of Contents:
//
// 1. Global
// 2. Breakpoints
// 3. The Grid
// 4. Base Typography
// 5. Typography Helpers
// 6. Abide
// 7. Accordion
// 8. Accordion Menu
// 9. Badge
// 10. Breadcrumbs
// 11. Button
// 12. Button Group
// 13. Callout
// 14. Close Button
// 15. Drilldown
// 16. Dropdown
// 17. Dropdown Menu
// 18. Flex Video
// 19. Forms
// 20. Label
// 21. Media Object
// 22. Menu
// 23. Meter
// 24. Off-canvas
// 25. Orbit
// 26. Pagination
// 27. Progress Bar
// 28. Reveal
// 29. Slider
// 30. Switch
// 31. Table
// 32. Tabs
// 33. Thumbnail
// 34. Title Bar
// 35. Tooltip
// 36. Top Bar
@import 'util/util';
// 1. Global
// ---------
$global-font-size: 100%;
$global-width: rem-calc(1200);
$global-lineheight: 1.5;
$foundation-palette: (
primary: #2199e8,
secondary: #777,
success: #3adb76,
warning: #ffae00,
alert: #ec5840,
);
$light-gray: #e6e6e6;
$medium-gray: #cacaca;
$dark-gray: #8a8a8a;
$black: #0a0a0a;
$white: #fefefe;
$body-background: $white;
$body-font-color: $black;
$body-font-family: 'Helvetica Neue', 'Microsoft Yahei', Helvetica, Roboto, 'WenQuanYi Micro Hei', Arial, sans-serif;
$body-antialiased: true;
$global-margin: 1rem;
$global-padding: 1rem;
$global-weight-normal: normal;
$global-weight-bold: bold;
$global-radius: 0;
$global-text-direction: ltr;
$global-flexbox: false;
$print-transparent-backgrounds: true;
@include add-foundation-colors;
// 2. Breakpoints
// --------------
$breakpoints: (
small: 0,
medium: 640px,
large: 1024px,
xlarge: 1200px,
xxlarge: 1440px,
);
$breakpoint-classes: (small medium large);
// 3. The Grid
// -----------
$grid-row-width: $global-width;
$grid-column-count: 12;
$grid-column-gutter: (
small: 20px,
medium: 30px,
);
$grid-column-align-edge: true;
$block-grid-max: 8;
// 4. Base Typography
// ------------------
$header-font-family: $body-font-family;
$header-font-weight: $global-weight-normal;
$header-font-style: normal;
$font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace;
$header-sizes: (
small: (
'h1': 24,
'h2': 20,
'h3': 19,
'h4': 18,
'h5': 17,
'h6': 16,
),
medium: (
'h1': 48,
'h2': 40,
'h3': 31,
'h4': 25,
'h5': 20,
'h6': 16,
),
);
$header-color: inherit;
$header-lineheight: 1.4;
$header-margin-bottom: 0.5rem;
$header-text-rendering: optimizeLegibility;
$small-font-size: 80%;
$header-small-font-color: $medium-gray;
$paragraph-lineheight: 1.6;
$paragraph-margin-bottom: 1rem;
$paragraph-text-rendering: optimizeLegibility;
$code-color: $black;
$code-font-family: $font-family-monospace;
$code-font-weight: $global-weight-normal;
$code-background: $light-gray;
$code-border: 1px solid $medium-gray;
$code-padding: rem-calc(2 5 1);
$anchor-color: $primary-color;
$anchor-color-hover: scale-color($anchor-color, $lightness: -14%);
$anchor-text-decoration: none;
$anchor-text-decoration-hover: none;
$hr-width: $global-width;
$hr-border: 1px solid $medium-gray;
$hr-margin: rem-calc(20) auto;
$list-lineheight: $paragraph-lineheight;
$list-margin-bottom: $paragraph-margin-bottom;
$list-style-type: disc;
$list-style-position: outside;
$list-side-margin: 1.25rem;
$list-nested-side-margin: 1.25rem;
$defnlist-margin-bottom: 1rem;
$defnlist-term-weight: $global-weight-bold;
$defnlist-term-margin-bottom: 0.3rem;
$blockquote-color: $dark-gray;
$blockquote-padding: rem-calc(9 20 0 19);
$blockquote-border: 1px solid $medium-gray;
$cite-font-size: rem-calc(13);
$cite-color: $dark-gray;
$keystroke-font: $font-family-monospace;
$keystroke-color: $black;
$keystroke-background: $light-gray;
$keystroke-padding: rem-calc(2 4 0);
$keystroke-radius: $global-radius;
$abbr-underline: 1px dotted $black;
// 5. Typography Helpers
// ---------------------
$lead-font-size: $global-font-size * 1.25;
$lead-lineheight: 1.6;
$subheader-lineheight: 1.4;
$subheader-color: $dark-gray;
$subheader-font-weight: $global-weight-normal;
$subheader-margin-top: 0.2rem;
$subheader-margin-bottom: 0.5rem;
$stat-font-size: 2.5rem;
// 6. Abide
// --------
$abide-inputs: true;
$abide-labels: true;
$input-background-invalid: map-get($foundation-palette, alert);
$form-label-color-invalid: map-get($foundation-palette, alert);
$input-error-color: map-get($foundation-palette, alert);
$input-error-font-size: rem-calc(12);
$input-error-font-weight: $global-weight-bold;
// 7. Accordion
// ------------
$accordion-background: $white;
$accordion-plusminus: true;
$accordion-item-color: foreground($accordion-background, $primary-color);
$accordion-item-background-hover: $light-gray;
$accordion-item-padding: 1.25rem 1rem;
$accordion-content-background: $white;
$accordion-content-border: 1px solid $light-gray;
$accordion-content-color: foreground($accordion-background, $primary-color);
$accordion-content-padding: 1rem;
// 8. Accordion Menu
// -----------------
$accordionmenu-arrows: true;
$accordionmenu-arrow-color: $primary-color;
// 9. Badge
// --------
$badge-background: $primary-color;
$badge-color: foreground($badge-background);
$badge-padding: 0.3em;
$badge-minwidth: 2.1em;
$badge-font-size: 0.6rem;
// 10. Breadcrumbs
// ---------------
$breadcrumbs-margin: 0 0 $global-margin 0;
$breadcrumbs-item-font-size: rem-calc(11);
$breadcrumbs-item-color: $primary-color;
$breadcrumbs-item-color-current: $black;
$breadcrumbs-item-color-disabled: $medium-gray;
$breadcrumbs-item-margin: 0.75rem;
$breadcrumbs-item-uppercase: true;
$breadcrumbs-item-slash: true;
// 11. Button
// ----------
$button-padding: 1rem 2rem 1.0625rem 2rem;
$button-margin: 0 0 $global-margin 0;
$button-fill: solid;
$button-background: $primary-color;
$button-background-hover: scale-color($button-background, $lightness: -15%);
$button-color: $white;
$button-color-alt: $black;
$button-radius: $global-radius;
$button-sizes: (
tiny: 0.6rem,
small: 0.75rem,
default: 0.9rem,
large: 1.25rem,
);
$button-opacity-disabled: 0.25;
// 12. Button Group
// ----------------
$buttongroup-margin: 1rem;
$buttongroup-spacing: 1px;
$buttongroup-child-selector: '.button';
$buttongroup-expand-max: 6;
// 13. Callout
// -----------
$callout-background: $white;
$callout-background-fade: 85%;
$callout-border: 1px solid rgba($black, 0.25);
$callout-margin: 0 0 1rem 0;
$callout-padding: 1rem;
$callout-font-color: $body-font-color;
$callout-font-color-alt: $body-background;
$callout-radius: $global-radius;
$callout-link-tint: 30%;
// 14. Close Button
// ----------------
$closebutton-position: right top;
$closebutton-offset-horizontal: 1rem;
$closebutton-offset-vertical: 0.5rem;
$closebutton-size: 2em;
$closebutton-lineheight: 1;
$closebutton-color: $dark-gray;
$closebutton-color-hover: $black;
// 15. Drilldown
// -------------
$drilldown-transition: transform 0.15s linear;
$drilldown-arrows: true;
$drilldown-arrow-color: $primary-color;
$drilldown-background: $white;
// 16. Dropdown
// ------------
$dropdown-padding: 1rem;
$dropdown-border: 1px solid $medium-gray;
$dropdown-font-size: 1rem;
$dropdown-width: 300px;
$dropdown-radius: $global-radius;
$dropdown-sizes: (
tiny: 100px,
small: 200px,
large: 400px,
);
// 17. Dropdown Menu
// -----------------
$dropdownmenu-arrows: true;
$dropdownmenu-arrow-color: $anchor-color;
$dropdownmenu-min-width: 200px;
$dropdownmenu-background: $white;
$dropdownmenu-border: 1px solid $medium-gray;
// 18. Flex Video
// --------------
$flexvideo-margin-bottom: rem-calc(16);
$flexvideo-ratio: 4 by 3;
$flexvideo-ratio-widescreen: 16 by 9;
// 19. Forms
// ---------
$fieldset-border: 1px solid $medium-gray;
$fieldset-padding: rem-calc(20);
$fieldset-margin: rem-calc(18 0);
$legend-padding: rem-calc(0 3);
$form-spacing: rem-calc(16);
$helptext-color: $black;
$helptext-font-size: rem-calc(13);
$helptext-font-style: italic;
$input-prefix-color: $black;
$input-prefix-background: $light-gray;
$input-prefix-border: 1px solid $medium-gray;
$input-prefix-padding: 1rem;
$form-label-color: $black;
$form-label-font-size: rem-calc(14);
$form-label-font-weight: $global-weight-normal;
$form-label-line-height: 1.8;
$select-background: $white;
$select-triangle-color: $dark-gray;
$select-radius: $global-radius;
$input-color: $black;
$input-placeholder-color: $medium-gray;
$input-font-family: inherit;
$input-font-size: rem-calc(16);
$input-background: $white;
$input-background-focus: $white;
$input-background-disabled: $light-gray;
$input-border: 1px solid $medium-gray;
$input-border-focus: 1px solid $dark-gray;
$input-shadow: inset 0 1px 2px rgba($black, 0.1);
$input-shadow-focus: 0 0 5px $medium-gray;
$input-cursor-disabled: not-allowed;
$input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out;
$input-number-spinners: true;
$input-radius: $global-radius;
// 20. Label
// ---------
$label-background: $primary-color;
$label-color: foreground($label-background);
$label-font-size: 0.8rem;
$label-padding: 0.33333rem 0.5rem;
$label-radius: $global-radius;
// 21. Media Object
// ----------------
$mediaobject-margin-bottom: $global-margin;
$mediaobject-section-padding: $global-padding;
$mediaobject-image-width-stacked: 100%;
// 22. Menu
// --------
$menu-margin: 0;
$menu-margin-nested: 1rem;
$menu-item-padding: 0.7rem 1rem;
$menu-item-color-active: $white;
$menu-item-background-active: map-get($foundation-palette, primary);
$menu-icon-spacing: 0.25rem;
// 23. Meter
// ---------
$meter-height: 1rem;
$meter-radius: $global-radius;
$meter-background: $medium-gray;
$meter-fill-good: $success-color;
$meter-fill-medium: $warning-color;
$meter-fill-bad: $alert-color;
// 24. Off-canvas
// --------------
$offcanvas-size: 250px;
$offcanvas-background: #474747;
$offcanvas-zindex: -1;
$offcanvas-transition-length: 0.5s;
$offcanvas-transition-timing: ease;
$offcanvas-fixed-reveal: true;
$offcanvas-exit-background: rgba($white, 0.25);
$maincontent-class: 'off-canvas-content';
$maincontent-shadow: 0 0 10px rgba($black, 0.5);
// 25. Orbit
// ---------
$orbit-bullet-background: $medium-gray;
$orbit-bullet-background-active: $dark-gray;
$orbit-bullet-diameter: 1.2rem;
$orbit-bullet-margin: 0.1rem;
$orbit-bullet-margin-top: 0.8rem;
$orbit-bullet-margin-bottom: 0.8rem;
$orbit-caption-background: rgba($black, 0.5);
$orbit-caption-padding: 1rem;
$orbit-control-background-hover: rgba($black, 0.5);
$orbit-control-padding: 1rem;
$orbit-control-zindex: 10;
// 26. Pagination
// --------------
$pagination-font-size: rem-calc(14);
$pagination-margin-bottom: $global-margin;
$pagination-item-color: $black;
$pagination-item-padding: rem-calc(3 10);
$pagination-item-spacing: rem-calc(1);
$pagination-radius: $global-radius;
$pagination-item-background-hover: $light-gray;
$pagination-item-background-current: $primary-color;
$pagination-item-color-current: foreground($pagination-item-background-current);
$pagination-item-color-disabled: $medium-gray;
$pagination-ellipsis-color: $black;
$pagination-mobile-items: false;
$pagination-arrows: true;
// 27. Progress Bar
// ----------------
$progress-height: 1rem;
$progress-background: $medium-gray;
$progress-margin-bottom: $global-margin;
$progress-meter-background: $primary-color;
$progress-radius: $global-radius;
// 28. Reveal
// ----------
$reveal-background: $white;
$reveal-width: 600px;
$reveal-max-width: $global-width;
$reveal-padding: $global-padding;
$reveal-border: 1px solid $medium-gray;
$reveal-radius: $global-radius;
$reveal-zindex: 1005;
$reveal-overlay-background: rgba($black, 0.45);
// 29. Slider
// ----------
$slider-width-vertical: 0.5rem;
$slider-transition: all 0.2s ease-in-out;
$slider-height: 0.5rem;
$slider-background: $light-gray;
$slider-fill-background: $medium-gray;
$slider-handle-height: 1.4rem;
$slider-handle-width: 1.4rem;
$slider-handle-background: $primary-color;
$slider-opacity-disabled: 0.25;
$slider-radius: $global-radius;
// 30. Switch
// ----------
$switch-background: $medium-gray;
$switch-background-active: $primary-color;
$switch-height: 2rem;
$switch-height-tiny: 1.5rem;
$switch-height-small: 1.75rem;
$switch-height-large: 2.5rem;
$switch-radius: $global-radius;
$switch-margin: $global-margin;
$switch-paddle-background: $white;
$switch-paddle-offset: 0.25rem;
$switch-paddle-radius: $global-radius;
$switch-paddle-transition: all 0.25s ease-out;
// 31. Table
// ---------
$table-background: $white;
$table-color-scale: 5%;
$table-border: 1px solid smart-scale($table-background, $table-color-scale);
$table-padding: rem-calc(8 10 10);
$table-hover-scale: 2%;
$table-row-hover: darken($table-background, $table-hover-scale);
$table-row-stripe-hover: darken($table-background, $table-color-scale + $table-hover-scale);
$table-striped-background: smart-scale($table-background, $table-color-scale);
$table-stripe: even;
$table-head-background: smart-scale($table-background, $table-color-scale / 2);
$table-foot-background: smart-scale($table-background, $table-color-scale);
$table-head-font-color: $body-font-color;
$show-header-for-stacked: false;
// 32. Tabs
// --------
$tab-margin: 0;
$tab-background: $white;
$tab-background-active: $light-gray;
$tab-item-font-size: rem-calc(16);
$tab-item-background-hover: $white;
$tab-item-padding: 1.25rem 1.5rem;
$tab-expand-max: 6;
$tab-content-background: $white;
$tab-content-border: none;
$tab-content-color: foreground($tab-background, $primary-color);
$tab-content-padding: 1rem;
// 33. Thumbnail
// -------------
$thumbnail-border: solid 4px $white;
$thumbnail-margin-bottom: $global-margin;
$thumbnail-shadow: 0 0 0 1px rgba($black, 0.2);
$thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5);
$thumbnail-transition: box-shadow 200ms ease-out;
$thumbnail-radius: $global-radius;
// 34. Title Bar
// -------------
$titlebar-background: $black;
$titlebar-color: $white;
$titlebar-padding: 0.5rem;
$titlebar-text-font-weight: bold;
$titlebar-icon-color: $white;
$titlebar-icon-color-hover: $medium-gray;
$titlebar-icon-spacing: 0.25rem;
// 35. Tooltip
// -----------
$has-tip-font-weight: $global-weight-bold;
$has-tip-border-bottom: dotted 1px $dark-gray;
$tooltip-background-color: $black;
$tooltip-color: $white;
$tooltip-padding: 0.75rem;
$tooltip-font-size: $small-font-size;
$tooltip-pip-width: 0.75rem;
$tooltip-pip-height: $tooltip-pip-width * 0.866;
$tooltip-radius: $global-radius;
// 36. Top Bar
// -----------
$topbar-padding: 0.5rem;
$topbar-background: $black;
$topbar-submenu-background: $topbar-background;
$topbar-title-spacing: 1rem;
$topbar-input-width: 200px;
$topbar-unstack-breakpoint: medium;

View File

@ -23,6 +23,14 @@
.about-page {
.fixed {
position: fixed;
top: 0;
width: 100%;
z-index: 99;
left: 0;
}
a {
word-wrap: break-word;
}
@ -42,15 +50,19 @@
.top-bar {
background: 0 0;
margin: 1.5rem 0;
.name h1 {
.name {
font-size: 1.325rem;
}
}
.top-bar-section ul{
.top-bar ul{
& {
background: 0 0;
}
li, li a {
background: 0 0;
font-size: 1rem;
color: #fefefe;
}
li a:hover {

View File

@ -1,3 +1,10 @@
.dash-title {
padding-top: 2rem;
}
#dashboard-topbar {
background-color: $light-gray;
ul {
background-color: $light-gray;
}
}

View File

@ -7,10 +7,14 @@
margin-bottom: 2rem;
}
#post_content {
#content-input {
min-height: 20rem;
}
.content-field {
padding: 0;
}
.preview {
min-height: 20rem;
border: 1px solid #DDDDDD;
@ -25,7 +29,7 @@
#upload_photo {
float: right;
margin-top: 2rem;
margin-top: 1.875rem;
}
tr {

View File

@ -1,3 +0,0 @@
.ng-dirty .ng-invalid, .ng-dirty .ng-required {
border: 1px solid #FF7A7A !important;
}

View File

@ -1,18 +1,22 @@
//.inner-wrap {
//min-height: 100%;
//}
@import 'font-awesome-sprockets';
@import 'font-awesome';
@import 'foundation_and_overrides';
@import 'foundation-icons';
@import 'aboutme_welcome';
@import 'archives';
@import 'aboutme_welcome';
@import 'archives';
@import 'markdown';
@import 'blogs';
@import 'head';
@import 'blogs';
@import 'head';
@import 'qrcodes';
@import 'angularjs';
@import 'comments';
@import 'comments';
@import 'highlight';
@import 'footer';
@import 'like_and_weixin';
@import 'admin/*';
.turbolinks-progress-bar {
height: 2px;
background-color: red;
}

View File

@ -60,6 +60,10 @@
padding-left: 0rem;
}
#qrcode-home {
padding: 1rem 2rem 1rem 0;
}
.qrcode {
display: inline-block;
float: right;
@ -81,6 +85,10 @@
}
}
.social-share {
display: none;
}
.subscribe-ul {
list-style-type: none;
margin-left: 0;

View File

@ -1,3 +1,7 @@
#alert-container {
display: none;
}
.comment-field {
background-color: #333333;
padding-top: 3rem;
@ -43,14 +47,17 @@
color: #b3b3b3;
}
.comment-content {
padding-bottom: 2.275rem;
padding-bottom: 1rem;
padding-left: 0.275rem;
border-bottom: 1px dashed #8a8a8a;
margin-bottom: 0;
white-space: pre;
p {
margin-bottom: 0.325rem;
}
}
.name {
padding-top: 0.5rem;
padding-top: 1rem;
padding-left: 0.275rem;
}
}
@ -75,11 +82,11 @@
}
.comment-wrapper {
margin-bottom: 0.5rem;
&:hover {
background-color: #444444;
}
.name {
color: #DDDDDD;
}
@ -87,8 +94,7 @@
.comment-content {
word-wrap: break-word;
color: #DDDDDD;
white-space: pre-wrap;
word-break: break-all;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,35 @@
.middle-text {
text-align: center;
z-index: 999;
.my-topbar {
a {
color: #F7F7F7;
&:hover {
color: #999;
}
&.title {
font-weight: bold;
}
}
button {
outline: 0;
}
}
#offCanvas {
min-height: 100%;
a {
color: #F7F7F7;
&:hover {
background-color: #333;
}
}
label {
padding: 3px 20px;
background-color: #545454;
color: #B9B9B9;
text-transform: uppercase;
}
}
@ -16,3 +43,4 @@
cursor: default;
}
}

View File

@ -1,20 +1,5 @@
.qrcode-image {
table {
border-width: 0;
border-style: none;
border-color: #0000ff;
border-collapse: collapse;
}
td {
border-width: 0;
border-style: none;
border-color: #0000ff;
border-collapse: collapse;
padding: 0;
margin: 0;
width: 0.3rem;
height: 0.3rem;
}
td.black { background-color: #000; }
td.white { background-color: #fff; }
#image-tag {
float: right;
width: 200px;
margin-bottom: 1rem;
}

View File

@ -10,16 +10,6 @@ class Admin::PostsController < ApplicationController
@post = Post.find( params[:id] )
end
def show
post = Post.find( params[:id] )
render :json=> {
title: post.title,
type: post.type,
labels: post.labels_content(true),
content: post.content
}
end
def destroy
@post = Post.find( params[:id] )
if @post.destroy
@ -32,7 +22,7 @@ class Admin::PostsController < ApplicationController
end
def index
@posts = Post.desc(:created_at)
@posts = Post.order(created_at: :desc).page(params[:page])
end
def create
@ -71,7 +61,7 @@ class Admin::PostsController < ApplicationController
private
def initialize_or_create_labels(labels)
@post.labels = []
labels.split(",").each do |name|
labels.split(",").map { |i| i.strip }.uniq.each do |name|
label = Label.find_or_initialize_by(name: name.strip)
label.save!
@post.labels << label

View File

@ -6,15 +6,18 @@ class Admin::SessionsController < ApplicationController
def create
if ENV['ADMIN_USER'].blank?
render :json=> { success: false, message: t('admin.session.no_configuration') }
flash.now[:alert] = t('admin.session.no_configuration')
render :new
elsif ENV['ADMIN_USER'] != params[:username]
render :json=> { success: false, message: t('admin.session.username_error') }
flash.now[:alert] = t('admin.session.username_error')
render :new
elsif ENV['ADMIN_PASSWORD'] != params[:password]
render :json=> { success: false, message: t('admin.session.password_error') }
flash.now[:alert] = t('admin.session.password_error')
render :new
else
flash[:notice] = t('admin.session.login_success')
session[:login] = true
render :json=> { success: true }
redirect_to admin_root_path
end
end

View File

@ -1,60 +1,5 @@
class ArchivesController < ApplicationController
def index
type = map[params[:type]]
limit = 10
start_with = params[:start_with]
@posts = Post.desc(:created_at)
if type
@posts = @posts.where(type: type)
@type = type
end
# all 与 start_with 参数同在, 说明是要获取所有start_with之前的数据
if params[:all] and params[:start_with]
@posts = @posts.where(:created_at.gte => Time.at(start_with.to_i))
else
@posts = @posts.limit(limit)
end
if !params[:all] and start_with
@posts = @posts.where(:created_at.lt => Time.at(start_with.to_i))
end
#update start_with
unless @posts.empty?
start_with = @posts[-1].created_at.to_i.to_s
end
respond_to do |format|
format.json do
render :json => {
posts: @posts.collect { |post| build_summary(post) },
start_with: start_with
}
end
format.html
end
end
private
def map
{
"life"=> "生活",
"tech"=> "技术",
"creator"=> "创业"
}
end
def build_summary(post)
{
title: post.title,
type: post.type_en,
created_at: format_date(post.created_at),
id: post.id.to_s,
liked_count: post.liked_count,
visited_count: post.visited_count,
labels: post.labels_content
}
@posts = Post.order(created_at: :desc).page(params[:page])
end
end

View File

@ -2,8 +2,8 @@
class BlogsController < ApplicationController
def index
@newest = Post.desc(:created_at).first
@recent = Post.desc(:created_at).to_a[1..3]
@newest = Post.order(created_at: :desc).first
@recent = Post.order(created_at: :desc).to_a[1..3]
respond_to do |format|
format.html
format.json
@ -19,15 +19,16 @@ 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
@prev = Post.where('created_at < ?', @post.created_at).order(created_at: :desc).first
@next = Post.where('created_at > ?', @post.created_at).order(created_at: :asc).first
@comments = @post.comments.order(created_at: :desc)
@likes_count = @post.likes.count
respond_to do |format|
format.html
format.json
end
end
def edit
@post = Post.find( params[:id] )
redirect_to edit_admin_post_path(@post)

View File

@ -8,19 +8,22 @@ class CommentsController < ApplicationController
end
def create
cookies[:name] = comment_params[:name]
cookies[:email] = comment_params[:email]
@post = Post.find( params[:blog_id] )
comment = @post.comments.build(comment_params)
@comment = @post.comments.build(comment_params)
if comment.save
render :json=> { success: true, data: build_json(comment) }
if @comment.save
@comments = @post.comments.order(created_at: :desc)
render :create_ok
else
render :json=> { success: false, message: comment.errors.full_messages.join(", ") }
render :create_fail
end
end
private
def comment_params
params.permit(:content, :name, :email)
params.require(:comment).permit(:content, :name, :email)
end
def build_json(comment)

View File

@ -6,15 +6,6 @@ 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 = post.likes.build

View File

@ -1,2 +1,3 @@
module ApplicationHelper
# some format function is defined in app/controllers/application_controller.rb
end

View File

@ -0,0 +1,3 @@
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end

View File

@ -1,17 +1,10 @@
class Comment
include Mongoid::Document
include Mongoid::Timestamps
field :name, :type => String
field :content, :type => String
field :email, :type=>String
class Comment < ApplicationRecord
belongs_to :post
validates_presence_of :post_id
validates :name, presence: true
validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, message: I18n.t('comment_attributes.email') }
validates :content, presence: true
validates_presence_of :post_id
def reply_emails
Comment.where(post_id: self.post_id).collect(&:email).uniq - [ self.email ] - Subscribe.unsubscribe_list

View File

@ -1,9 +1,4 @@
class Label
include Mongoid::Document
include Mongoid::Timestamps
field :name, :type => String
class Label < ApplicationRecord
has_and_belongs_to_many :posts
validates :name, presence: true
end

View File

@ -1,6 +1,4 @@
class Like
include Mongoid::Document
class Like < ApplicationRecord
belongs_to :post
validates_presence_of :post_id
end

View File

@ -1,7 +1,3 @@
class Photo
include Mongoid::Document
include Mongoid::Timestamps
field :image
class Photo < ApplicationRecord
mount_uploader :image, PhotoUploader
end

View File

@ -1,18 +1,5 @@
# encoding : utf-8
#
require 'markdown'
class Post
TECH = "技术"
LIFE = "生活"
CREATOR = "创业"
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Pagination
field :title, :type => String
field :content, :type => String
field :type, :type=> String
field :visited_count, :type=>Integer, :default=>0
class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :labels
@ -20,7 +7,6 @@ class Post
validates :title, :presence=>true, :uniqueness=> true
validates :content, :presence=>true, :length => { :minimum=> 30 }
validates :type, :presence=>true, :inclusion => { :in => [ TECH, LIFE, CREATOR ] }
after_create do
if ENV['MAIL_SERVER'].present?
@ -38,35 +24,21 @@ class Post
md.render(content)
end
def type_en
map = {
'技术' => 'Tech',
'生活' => 'Life',
'创业' => 'Creator',
}
if I18n.locale == :en
map[type]
else
type
end
end
def visited
self.visited_count += 1
self.save
self.visited_count
end
# 显示给首页截断数据
# truncate content for home page display
def sub_content
HTML_Truncator.truncate(content_html, 300, length_in_chars: true)
end
# 显示给 meta description
# truncate content for meta description display
def meta_content
html = HTML_Truncator.truncate(content_html, 100, :length_in_chars => true, ellipsis: '')
# 加上 div 以方便 Nokogiri 获取 text()
# Easily get text for Nokogiri
html = '<div>' + html + '</div>'
Nokogiri.parse(html).text()
end
@ -80,4 +52,8 @@ class Post
def liked_count
self.likes.size
end
def liked_by?(like_id)
!! self.likes.where(id: like_id).first
end
end

View File

@ -1,8 +1,4 @@
class Subscribe
include Mongoid::Document
field :email, type: String
field :enable, type: Mongoid::Boolean, default: true
class Subscribe < ApplicationRecord
validates :email, presence: true, uniqueness: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, message: '地址无效' }
def self.subscribe_list

View File

@ -18,7 +18,7 @@
td
= mail_to comment.email
td
p.pre #{comment.content}
= simple_format(comment.content)
td
= format_time(comment.created_at)

View File

@ -2,14 +2,11 @@
= simple_form_for(@post, url: url, html: {novalidate: '' }) do |f|
.row
.large-6.columns
= f.input :title, label: t('admin.posts_attributes.title'), "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: t('admin.posts_attributes.type'), "ng-model"=>"type", input_html: { name: 'type' }
= f.input :title, label: t('admin.posts_attributes.title'), input_html: { name: 'title' }
.row
.small-12.large-6.columns
= label_tag :labels, t('admin.posts_attributes.labels')
= text_field_tag :labels, @post.labels_content(true), "ng-model"=>"labels", "ng-initial" => ''
= text_field_tag :labels, @post.labels_content(true)
.row
.small-12.columns
@ -17,22 +14,23 @@
| #{t('admin.posts_attributes.already_labels')}
span
- Label.all.each do |label|
a.tag href="#" ng-click="addTag($event)" #{label.name}
a.tag href="#" #{label.name}
/ tabs and upload file field
dl.tabs
dd ng-class="{ active: body_active }"
a href="" ng-click="changeToBody()" #{t('admin.posts_attributes.content')}
dd ng-class="{ active: !body_active }"
a href="#" ng-click="changeToPreview()" #{t('admin.posts_attributes.preview')}
ul.tabs#tabs data-tabs=''
li.tabs-title.is-active
a href="#content" #{t('admin.posts_attributes.content')}
li.tabs-title
a href="#preview" #{t('admin.posts_attributes.preview')}
= link_to t('admin.posts_attributes.upload_photo'), "#", :id=>'upload_photo'
input[type="file" style="display: none;"]
.content-field ng-show="body_active" ng-model= 'content'
= f.input :content, :as=> :text, :label => false, input_html: { name: 'content', "ng-model"=>'content', 'ng-initial'=>'' }
.tabs-content data-tabs-content='tabs'
.tabs-panel.content-field.is-active#content
= f.input :content, :as=> :text, :label => false, input_html: { name: 'content', id: 'content-input' }
.preview.markdown ng-hide="body_active" ng-bind-html=" trustAsPreviewHTML() "
.tabs-panel.preview.markdown#preview
.row
.small-12.large-6.columns.posts-button
button #{t('admin.posts_attributes.submit')}
button.button type='submit' #{t('admin.posts_attributes.submit')}

View File

@ -15,8 +15,6 @@
td.admin-post-summary-field
i.fi-calendar
span #{format_time(post.created_at)}
i.fi-list
span #{ post.type_en }
i.fi-pricetag-multiple
span #{ post.labels_content }
i.fi-torsos
@ -27,3 +25,4 @@
= link_to t('comment'), admin_post_comments_path(post.id), class: 'edit-post-link'
= link_to t('edit'), edit_admin_post_path(post), class: 'edit-post-link'
= link_to t('destroy'), admin_post_path(post), method: 'DELETE', 'data-confirm' => '确认删除?'
== paginate @posts

View File

@ -1,16 +1,12 @@
.row ng-controller="AdminSessionsController"
.small-12.large-8.columns
h3.blog-title #{t('admin.session.title')}
form ng-submit="login()"
= form_tag admin_sessions_path
.row
.small-12.large-8.columns
= label_tag 'username', t('admin.session.username')
= text_field_tag 'username', nil, placeholder: t('admin.session.username_placeholder'), "ng-model"=>"username"
= text_field_tag 'username', params[:username], placeholder: t('admin.session.username_placeholder')
= label_tag 'username', t('admin.session.password')
= password_field_tag 'password', nil, placeholder: t('admin.session.password_placeholder'), "ng-model"=>"password"
= password_field_tag 'password', params[:password], placeholder: t('admin.session.password_placeholder')
p
.alert-box.warning ng-show=" error_msg "
|{{ error_msg }}
button #{t('admin.session.login_button')}
button.button type='submit' #{t('admin.session.login_button')}

View File

@ -1,30 +1,22 @@
- content_for(:title) do
| #{@type ? @type : t('title.timeline')}
.row ng-controller="ArchivesController" ng-cloak=""
| #{t('title.timeline')}
.row
.small-12.large-9.large-centered.columns
ul.archives-field ng-model="type" ng-init=" type= '#{@type}' "
li ng-repeat=" post in posts "
a.blog-title ng-href="{{ visit(post.id) }}"
|{{ post.title }}
p.tags-field
i.fi-calendar
span
|{{ post.created_at }}
i.fi-list-thumbnails
span
|{{ post.type }}
i.fi-pricetag-multiple
span
|{{ post.labels }}
i.fi-torsos
span
|{{ post.visited_count }}
i.fi-heart
span
|{{ post.liked_count }}
.no-more-field
p ng-show="no_more_flag" #{t('nocontent')}
.load-more
button.small ng-click="load()" ng-show="!loading_flag" Load More
button.small ng-show="loading_flag" Loading
ul.archives-field
- @posts.each do |post|
li
= link_to post.title, blog_path(post), class: 'blog-title'
p.tags-field
i.fi-calendar
span
= format_date(post.created_at)
i.fi-pricetag-multiple
span
= post.labels_content
i.fi-torsos
span
= post.visited_count
i.fi-heart
span
= post.liked_count
= paginate @posts

View File

@ -1,21 +1,16 @@
.row ng-controller="CommentsController" ng-cloak=""
.row
.small-12.large-9.large-centered.columns
form novalidate='' name='form'
= form_for Comment.new, url: blog_comments_path(@post), remote: true do |f|
.row
.small-12.large-12.columns
= text_area_tag(:content, nil, placeholder: t('comment_placeholder.content'), 'ng-model'=> 'content', 'ng-required'=> true)
= f.text_area :content, placeholder: t('comment_placeholder.content')
.row
.small-12.large-6.columns
= text_field_tag(:name, nil, placeholder: t('comment_placeholder.name'), 'ng-model'=> 'name', 'ng-required'=> 'true')
= text_field_tag(:email, nil, placeholder: t('comment_placeholder.email'), 'ng-model'=> 'email', 'ng-pattern'=>"/^.+@.+$/", 'ng-required'=>"true")
button.comment-submit ng-click="submit()" ng-disabled="form.$invalid || submitting" {{ submitting && "#{t('comment_placeholder.submitting')}" || "#{t('comment_placeholder.submit')}" }}
p.comment-success ng-show="publish_success" #{t('comment_placeholder.publish_success')}
p.comment-fail ng-show="publish_success == false" #{t('comment_placeholder.publish_fail')}: {{ publish_fail_msg }}
.comment-diag
.comment-wrapper ng-repeat=" comment in comments "
p.name
|{{ comment.name + " • " }}
span.created-at
|{{ comment.created_at }}
/ ignore "white-space: pre" 's effect
<p class=comment-content>{{ comment.content }}</p>
= f.text_field :name, value: cookies[:name], placeholder: t('comment_placeholder.name')
= f.text_field :email, value: cookies[:email], placeholder: t('comment_placeholder.email')
button.button.comment-submit type='submit' data-disable-with=t('comment_placeholder.submitting') #{t('comment_placeholder.submit')}
#alert-container.alert.callout
span.text
button class="close-button" type='button'
span &times;
= render partial: 'comments/comment_content', locals: { comments: comments }

View File

@ -8,21 +8,16 @@ p.ptag.published-at
span #{format_date(post.created_at)}
= render 'common/copyright'
hr.blog-over
p ng-controller="LikesController" ng-cloak=""
button.like-button ng-show="! is_liked " ng-click="submit()"
|{{ count }}
p
button.button.like-button class="#{'liked' if post.liked_by?(cookies[:like])}" type='button' data-url=blog_likes_path(post)
span.count #{@likes_count}
span Like
button.like-button.liked ng-show=" is_liked " ng-click="cancel()"
|{{ count }}
span Like
div ng-controller = "QRCodesController"
.qrcode
a href="#" ng-model="qrcode" ng-init="qrcode=false" ng-click="show()"
i.fi-link
| #{t('qr_code')}
.qrcode
a#qrcode-link href="#"
i.fi-link
| #{t('qr_code')}
.social-share ng-show='qrcode'
.qrcode-wrapper
= render partial: "qrcode", locals: { str: blog_url(post) }
.social-share
.qrcode-wrapper
= render partial: "qrcode", locals: { str: blog_url(post) }

View File

@ -1,9 +1,6 @@
/ require: locals: { post : post }
h2.blog-title #{post.title}
p.ptag
span
i.fi-list-thumbnails
span #{post.type_en}
span
i.fi-pricetag-multiple
span #{post.labels_content}

View File

@ -1,3 +1,3 @@
.qrcode-image
= image_tag( qrcodes_path(str: str) )
#image-tag data-url=str
p #{t('qrcodetips')}

View File

@ -27,20 +27,11 @@
.row.ng-cloak ng-controller='AboutController'
.small-12.medium-6.large-12.columns
ul.subscribe-ul
- if ENV['MAIL_SERVER'].present?
li
= link_to t('subscribes.email'), '', "ng-click"=>"click('email')"
.email-subscribe ng-show="type == 'email'"
= text_field_tag 'email', nil, placeholder: 'your@email.com', 'ng-model'=>'email'
button.small ng-click="subscribe()" ng-disabled="! email_validate()" #{t('subscribes.submit')}
span.subscribe-success ng-show="subscribe_success" #{t('subscribes.submit_success')}
span.subscribe-fail ng-show="subscribe_success == false" {{subscribe_fail_msg}}
li
= link_to t('subscribes.wechat'), '', "ng-click"=>"click('weixin')"
.weixin-subscribe ng-show="type == 'weixin'"
= render partial: "qrcode", locals: { str: root_url }
a data-toggle="qrcode-home" #{t('subscribes.wechat') }
#qrcode-home.weixin-subscribe.hide data-toggler='hide' data-url=root_url
li
= link_to t('subscribes.rss'), '', "ng-click"=>"click('rss')"
.rss-subscribe ng-show="type == 'rss'"
a data-toggle="rss-home" #{t('subscribes.rss') }
#rss-home.rss-subscribe.hide data-toggler='hide'
= link_to rss_blogs_path do
- image_tag('rss.png')

View File

@ -1,14 +1,15 @@
- content_for(:meta) do
meta name="description" content="#{@post.meta_content}"
meta name="description" content=@post.meta_content
meta name="keywords" content=@post.labels_content
- content_for(:title) do
| #{@post.title}
.row.blog-wrapper
.small-12.large-9.large-centered.columns
= render partial: "post", :locals=> { :post=> @post }
= render partial: "post", :locals=> { post: @post }
.comment-field
= render partial: 'comment', locals: { comments: @comments, post: @post }
p
.row
.small-12.large-9.large-centered.columns
- if @prev

View File

@ -0,0 +1,9 @@
.comment-diag
- comments.each do |comment|
.comment-wrapper
p.name
| #{comment.name}
| #{" • "}
span.created-at
| #{format_time(comment.created_at) }
= render partial: 'comments/comment_pre', locals: { comment: comment }

View File

@ -0,0 +1,2 @@
.comment-content
= simple_format(comment.content)

View File

@ -0,0 +1,2 @@
$('#alert-container .text').text(' <%= @comment.errors.full_messages.join(' ') %>' );
$('#alert-container').removeClass('success').addClass('alert').show();

View File

@ -0,0 +1,4 @@
$('#comment_content').val('');
$('#alert-container .text').text(' <%= t('comment_placeholder.publish_success') %>' );
$('#alert-container').removeClass('alert').addClass('success').show();
$('.comment-diag').replaceWith('<%= j render partial: 'comment_content', locals: { comments: @comments }%>');

View File

@ -1,5 +1,5 @@
- if (1.days.from_now).strftime('%-m-%-d') =~ /^1-[123]$/
.new-year
.alert-box data-alert=''
.callout.primary data-closable=''
| 我的朋友, 祝你元旦快乐
a href='#' class='close' &times;
button.close-button type='button' data-close='' &times;

View File

@ -1,25 +1,25 @@
- content_for(:title) do
| #{t('title.about')}
- content_for(:main) do
.about-page ng-app='app' ng-controller='AboutScrollController'
.top-bar-wrapper.contain-to-grid.fixed ng-class="{ active: ! is_top() }"
.about-page
.top-bar-wrapper.contain-to-grid.fixed
.row
.small-12.columns
nav.top-bar data-topbar='' role='navigation'
ul.title-area
li.name
h1
a href='/' 回到博客
section.top-bar-section
ul.right
nav#about-top-bar.top-bar
.top-bar-left
ul.menu
li
a href='#about' du-smooth-scroll='' du-scrollspy='' 关于
a.name href='/' 回到博客
.top-bar-right
ul.menu
li
a href='#skill' du-smooth-scroll='' du-scrollspy='' 技能
a href='#about' 关于
li
a href='#work' du-smooth-scroll='' du-scrollspy='' 作品
a href='#skill' 技能
li
a href='#contact' du-smooth-scroll='' du-scrollspy='' 联系
a href='#work' 作品
li
a href='#contact' 联系
#intro
header.intro
.intro-heading
@ -32,7 +32,7 @@
| 懒, 是人类进步的动力
br
| 所谓技术, 就是让你的生活越来越懒
a.circle href='#about' du-smooth-scroll=''
a.circle href='#about'
i.fa.fa-angle-double-down
section#about
.row

View File

@ -0,0 +1,7 @@
/ Non-link tag that stands for skipped pages...
- available local variables
current_page : a page object for the currently displayed page
total_pages : total number of pages
per_page : number of items to fetch per page
remote : data-remote
li.ellipsis

View File

@ -0,0 +1,10 @@
/ Link to the "Last" page
- available local variables
url : url to the last page
current_page : a page object for the currently displayed page
total_pages : total number of pages
per_page : number of items to fetch per page
remote : data-remote
span.last
== link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, :remote => remote
'

View File

@ -0,0 +1,10 @@
/ Link to the "Next" page
- available local variables
url : url to the next page
current_page : a page object for the currently displayed page
total_pages : total number of pages
per_page : number of items to fetch per page
remote : data-remote
li.pagination-next class="#{'disabled' if current_page.last?}"
== link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, :rel => 'next', :remote => remote
'

View File

@ -0,0 +1,11 @@
/ Link showing page number
- available local variables
page : a page object for "this" page
url : url to this page
current_page : a page object for the currently displayed page
total_pages : total number of pages
per_page : number of items to fetch per page
remote : data-remote
li class="page#{' current' if page.current?}"
== link_to_unless page.current?, page, url, {:remote => remote, :rel => page.rel}
'

View File

@ -0,0 +1,17 @@
/ The container tag
- available local variables
current_page : a page object for the currently displayed page
total_pages : total number of pages
per_page : number of items to fetch per page
remote : data-remote
paginator : the paginator that renders the pagination tags inside
== paginator.render do
ul.pagination
== prev_page_tag
- each_page do |page|
- if page.left_outer? || page.right_outer? || page.inside_window?
== page_tag page
- elsif !page.was_truncated?
== gap_tag
== next_page_tag

View File

@ -0,0 +1,10 @@
/ Link to the "Previous" page
- available local variables
url : url to the previous page
current_page : a page object for the currently displayed page
total_pages : total number of pages
per_page : number of items to fetch per page
remote : data-remote
li.pagination-previous class="#{'disabled' if current_page.first?}"
== link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, :rel => 'prev', :remote => remote
'

View File

@ -3,7 +3,7 @@
.footer
div
span.link yafeilee.me
span.time © 2012 - 2015
span.time © 2012 - 2016
.license
| Designed by
span

View File

@ -4,24 +4,24 @@ html
meta name="viewport" content="width=device-width, initial-scale=1.0"
title Admin Page for #{ENV['SITE_NAME']}
= 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
.title-bar data-responsive-toggle="example-menu" data-hide-for="medium"
button class="menu-icon" type="button" data-toggle=''
.title-bar-title Menu
.top-bar#dashboard-topbar
.top-bar-left
ul.menu
li
= link_to "Dashboard For #{ENV['SITE_NAME']}", admin_root_path
li.toggle-topbar.menu-icon
a href="#" Menu
section.top-bar-section
ul.left
li
= link_to t('admin.new_post'), new_admin_post_path
li
= link_to t('admin.posts'), admin_posts_path
ul.right
.top-bar-right
ul.menu
li
= link_to t('admin.back'), root_path
- if admin_username
@ -29,9 +29,9 @@ html
= link_to admin_username + ' [ ' + t('admin.logout') + ' ]', admin_session_path(1), method: 'DELETE'
- flash.each do |name, msg|
- if msg.is_a?(String)
div class=("alert-box #{name.to_sym == :notice ? "success" : "alert"}") data-alert=""
div class=("callout #{name.to_sym == :notice ? "success" : "alert"}") data-closable=""
= content_tag :div, msg
a.close href="#" &times;
button.close-button type='button' data-close='' &times;
.admin-main-field ng-app="app"
= yield
= render "layouts/footer"

View File

@ -6,43 +6,24 @@ html
= yield(:meta)
title
= content_for?(:title) ? yield(:title) + " | #{ENV['SITE_NAME']}" : ENV['SITE_NAME']
= stylesheet_link_tag "application", media: 'all'
= stylesheet_link_tag "application", 'data-turbolinks-track' => "reload"
= favicon_link_tag 'favicon.png', type: 'image/png'
= javascript_include_tag "vendor/modernizr"
= javascript_include_tag "application"
= javascript_include_tag "application", 'data-turbolinks-track' => "reload"
= csrf_meta_tags
body
body data-whatinput="mouse"
- if content_for?(:main)
= yield(:main)
- else
.off-canvas-wrap data-offcanvas=''
.inner-wrap
nav.tab-bar
section.left-small
a.left-off-canvas-toggle.menu-icon href="#"
span
h1.title.middle-text
= link_to ENV['SITE_NAME'], root_path
aslide.left-off-canvas-menu
ul.off-canvas-list
.off-canvas-wrapper
.off-canvas-wrapper-inner data-off-canvas-wrapper=''
.off-canvas.position-left id="offCanvas" data-off-canvas=''
ul.vertical.menu
li
label Menu
li
= link_to root_path do
i.fi-home
| #{t('head.home')}
li
= link_to '/tech' do
i.fi-social-evernote
| #{t('head.tech')}
li
= link_to '/life' do
i.fi-torsos-male-female
| #{t('head.life')}
li
= link_to '/creator' do
i.fi-lightbulb
| #{t('head.creator')}
li
label Archive
li
@ -54,15 +35,32 @@ html
= link_to about_path do
i.fi-torso
| #{t('head.about')}
section.main-section ng-app="app"
.off-canvas-content data-off-canvas-content=''
.title-bar.hide-for-medium.my-topbar
.title-bar-left
button.menu-icon type='button' data-open='offCanvas'
span.title-bar-title
= link_to ENV['SITE_NAME'], root_path
.top-bar.show-for-medium.my-topbar
.top-bar-left
ul.dropdown.menu data-dropdown-menu=''
li
= link_to ENV['SITE_NAME'], root_path, class: 'title'
.top-bar-right
ul.dropdown.menu data-dropdown-menu=''
li
= link_to t('head.home'), root_path
li
= link_to t('head.timeline'), archives_path
li
= link_to t('head.about'), about_path
- flash.each do |name, msg|
- if msg.is_a?(String)
div class=("alert-box #{name == :notice ? "success" : "alert"}") data-alert=""
div class=("callout #{name.to_sym == :notice ? "success" : "alert"}") data-closable=""
= content_tag :div, msg
a.close href="#" &times;
button.close-button type='button' data-close='' &times;
= render 'common/welcome_new_year'
= yield
= render "layouts/footer"
a.exit-off-canvas
= render 'layouts/google_analytics'

View File

@ -1,18 +1,12 @@
require File.expand_path('../boot', __FILE__)
# Pick the frameworks you want:
require "active_model/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "sprockets/railtie"
require "rails/test_unit/railtie"
require 'rails/all'
Bundler.require(:default, Rails.env)
module WBlog
class Application < Rails::Application
# Configure sensitive parameters which will be filtered from the log file.
config.i18n.available_locales = [:en, :'zh-CN']
#I18n.config.enforce_available_locales = true
config.i18n.default_locale = (ENV['LOCALE'] || 'zh-CN').to_sym

20
config/database.yml Normal file
View File

@ -0,0 +1,20 @@
development:
adapter: postgresql
host: localhost
encoding: unicode
database: wblog_development
pool: 5
test:
adapter: postgresql
host: localhost
encoding: unicode
database: wblog_test
pool: 5
production:
adapter: postgresql
host: localhost
encoding: unicode
database: wblog_production
pool: 5

View File

@ -0,0 +1,20 @@
development:
adapter: postgresql
host: localhost
encoding: unicode
database: wblog_development
pool: 5
test:
adapter: postgresql
host: localhost
encoding: unicode
database: wblog_test
pool: 5
production:
adapter: postgresql
host: localhost
encoding: unicode
database: wblog_production
pool: 5

View File

@ -5,17 +5,19 @@ require 'mina/multistage'
require 'mina/bundler'
require 'mina/rails'
require 'mina/git'
require 'mina/rvm' # for rvm support. (http://rvm.io)
require 'mina/unicorn'
require 'mina/rvm'
require 'mina/puma'
require 'mina_sidekiq/tasks'
# Manually create these paths in shared/ (eg: shared/config/database.yml) in your server.
# They will be linked in the 'deploy:link_shared_paths' step.
set :shared_paths, ['config/mongoid.yml', 'config/application.yml', 'log', 'tmp', 'public/uploads', 'public/personal' ]
set :shared_paths, ['config/database.yml', 'config/application.yml', 'log', 'tmp', 'public/uploads', 'public/personal' ]
# rvm path
set :rvm_path, '/usr/local/rvm/scripts/rvm'
task :environment do
queue! %[source /usr/local/rvm/scripts/rvm]
queue! %[rvm use 2.0.0]
invoke :'rvm:use[2.2.3]'
end
task :setup => :environment do
@ -24,7 +26,7 @@ task :setup => :environment do
queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/#{dir}"]
end
['config/mongoid.yml', 'config/application.yml'].each do |file|
['config/database.yml', 'config/application.yml'].each do |file|
queue! %[touch "#{deploy_to}/shared/#{file}"]
queue %[echo "-----> Be sure to edit 'shared/#{file}'."]
end
@ -47,8 +49,12 @@ task :deploy => :environment do
invoke :'rails:assets_precompile'
to :launch do
invoke :'unicorn:restart'
# Insure puma is start when restart it
invoke :'puma:start'
invoke :'puma:phased_restart'
invoke :'sidekiq:restart'
end
invoke :'deploy:cleanup'
end
end

View File

@ -1,6 +1,5 @@
set :domain, 'yafeilee.me'
set :deploy_to, '/home/ruby/wblog_en'
set :repository, 'git@github.com:windy/wblog.git'
set :branch, 'master'
set :branch, 'new_design'
set :user, 'ruby'
set :unicorn_config, -> { "#{deploy_to}/#{current_path}/config/unicorn/en.rb" }

View File

@ -1,6 +1,5 @@
set :domain, 'yafeilee.me'
set :deploy_to, '/home/ruby/wblog'
set :repository, 'git@github.com:windy/wblog.git'
set :branch, 'master'
set :branch, 'new_design'
set :user, 'ruby'
set :unicorn_config, -> { "#{deploy_to}/#{current_path}/config/unicorn/zh.rb" }

View File

@ -20,7 +20,7 @@ WBlog::Application.configure do
# config.action_dispatch.rack_cache = true
# Disable Rails's static asset server (Apache or nginx will already do this).
config.serve_static_files = false
config.public_file_server.enabled = false
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier

View File

@ -0,0 +1,10 @@
Kaminari.configure do |config|
config.default_per_page = 10
# config.max_per_page = nil
config.window = 2
# config.outer_window = 0
config.left = 0
config.right = 0
# config.page_method_name = :page
# config.param_name = :page
end

View File

@ -44,7 +44,7 @@ zh-CN:
comment_placeholder:
content: '发表你的评论...'
name: '名字'
email: '邮箱'
email: '邮箱, your@example.com'
submit: '提交'
submitting: '提交中'
publish_success: '发表成功'

View File

@ -1,29 +0,0 @@
development:
clients:
default:
database: w_blog_development
hosts:
- localhost:27017
options:
production:
clients:
default:
database: w_blog_production
hosts:
- localhost:27017
options:
test:
clients:
default:
database: w_blog_test
hosts:
- localhost:27017
options:
read:
mode: :primary
# In the test environment we lower the retries and retry interval to
# low amounts for fast failures.
max_retries: 1
retry_interval: 0

View File

@ -1,5 +1,4 @@
WBlog::Application.routes.draw do
root :to => 'blogs#index'
resources :blogs, :only=>[:index, :show, :edit] do
collection do
@ -24,19 +23,20 @@ WBlog::Application.routes.draw do
# photos
resources :photos, only: [:create]
get '/qrcodes' => 'qrcodes#show'
namespace :admin do
resources :posts do
resources :posts, except: [:show] do
collection do
post :preview
end
resources :comments
end
resources :sessions, :only=>[:new, :create, :destroy]
root to: 'dashboard#index'
root 'dashboard#index'
end
get '/about' => 'home#index'
get '/mobile' => 'home#mobile'
get '/:type' => 'archives#index', constraints: { type: /tech|life|creator/ }
root 'blogs#index'
end

View File

@ -0,0 +1,11 @@
class CreatePosts < ActiveRecord::Migration[5.0]
def change
create_table :posts do |t|
t.string :title
t.text :content
t.integer :visited_count
t.timestamps
end
end
end

View File

@ -0,0 +1,12 @@
class CreateComments < ActiveRecord::Migration[5.0]
def change
create_table :comments do |t|
t.string :name
t.string :email
t.text :conent
t.integer :post_id
t.timestamps
end
end
end

View File

@ -0,0 +1,9 @@
class CreateLabels < ActiveRecord::Migration[5.0]
def change
create_table :labels do |t|
t.string :name
t.timestamps
end
end
end

View File

@ -0,0 +1,9 @@
class CreateLikes < ActiveRecord::Migration[5.0]
def change
create_table :likes do |t|
t.integer :post_id
t.timestamps
end
end
end

View File

@ -0,0 +1,9 @@
class CreatePhotos < ActiveRecord::Migration[5.0]
def change
create_table :photos do |t|
t.string :image
t.timestamps
end
end
end

View File

@ -0,0 +1,10 @@
class CreateSubscribes < ActiveRecord::Migration[5.0]
def change
create_table :subscribes do |t|
t.string :email
t.boolean :enable
t.timestamps
end
end
end

View File

@ -0,0 +1,8 @@
class CreateLabelsPosts < ActiveRecord::Migration[5.0]
def change
create_table :labels_posts, id: false do |t|
t.integer :label_id
t.integer :post_id
end
end
end

View File

@ -0,0 +1,5 @@
class ChangeColumnDefaultVisitedCountToPosts < ActiveRecord::Migration[5.0]
def change
change_column_default :posts, :visited_count, 0
end
end

View File

@ -0,0 +1,5 @@
class RenameColumnContentToComments < ActiveRecord::Migration[5.0]
def change
rename_column :comments, :conent, :content
end
end

66
db/schema.rb Normal file
View File

@ -0,0 +1,66 @@
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160421062614) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "comments", force: :cascade do |t|
t.string "name"
t.string "email"
t.text "content"
t.integer "post_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "labels", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "labels_posts", id: false, force: :cascade do |t|
t.integer "label_id"
t.integer "post_id"
end
create_table "likes", force: :cascade do |t|
t.integer "post_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "photos", force: :cascade do |t|
t.string "image"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", force: :cascade do |t|
t.string "title"
t.text "content"
t.integer "visited_count", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "subscribes", force: :cascade do |t|
t.string "email"
t.boolean "enable"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Comment, :type => :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Label, :type => :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Photo, :type => :model do
pending "add some examples to (or delete) #{__FILE__}"
end