Commit 80f1d343 by tady

Merge pull request #69 from tadyjp/wip/140320_user_page

[WIP] ユーザー管理画面
parents 86f64a99 1d537e20
...@@ -10,43 +10,41 @@ GIT ...@@ -10,43 +10,41 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actionmailer (4.0.3) actionmailer (4.0.4)
actionpack (= 4.0.3) actionpack (= 4.0.4)
mail (~> 2.5.4) mail (~> 2.5.4)
actionpack (4.0.3) actionpack (4.0.4)
activesupport (= 4.0.3) activesupport (= 4.0.4)
builder (~> 3.1.0) builder (~> 3.1.0)
erubis (~> 2.7.0) erubis (~> 2.7.0)
rack (~> 1.5.2) rack (~> 1.5.2)
rack-test (~> 0.6.2) rack-test (~> 0.6.2)
activemodel (4.0.3) activemodel (4.0.4)
activesupport (= 4.0.3) activesupport (= 4.0.4)
builder (~> 3.1.0) builder (~> 3.1.0)
activerecord (4.0.3) activerecord (4.0.4)
activemodel (= 4.0.3) activemodel (= 4.0.4)
activerecord-deprecated_finders (~> 1.0.2) activerecord-deprecated_finders (~> 1.0.2)
activesupport (= 4.0.3) activesupport (= 4.0.4)
arel (~> 4.0.0) arel (~> 4.0.0)
activerecord-deprecated_finders (1.0.3) activerecord-deprecated_finders (1.0.3)
activesupport (4.0.3) activesupport (4.0.4)
i18n (~> 0.6, >= 0.6.4) i18n (~> 0.6, >= 0.6.9)
minitest (~> 4.2) minitest (~> 4.2)
multi_json (~> 1.3) multi_json (~> 1.3)
thread_safe (~> 0.1) thread_safe (~> 0.1)
tzinfo (~> 0.3.37) tzinfo (~> 0.3.37)
addressable (2.3.5) addressable (2.3.6)
ancestry (2.0.0) ancestry (2.0.0)
activerecord (>= 3.0.0) activerecord (>= 3.0.0)
arel (4.0.2) arel (4.0.2)
ast (1.1.0) ast (1.1.0)
atomic (1.1.15) atomic (1.1.16)
aws-sdk (1.36.1) aws-sdk (1.38.0)
json (~> 1.4) json (~> 1.4)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
uuidtools (~> 2.1) uuidtools (~> 2.1)
bcrypt (3.1.7) bcrypt (3.1.7)
bcrypt-ruby (3.1.5)
bcrypt (>= 3.1.3)
better_errors (1.1.0) better_errors (1.1.0)
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubis (>= 2.6.6) erubis (>= 2.6.6)
...@@ -99,15 +97,15 @@ GEM ...@@ -99,15 +97,15 @@ GEM
daemons (1.1.9) daemons (1.1.9)
database_rewinder (0.0.3) database_rewinder (0.0.3)
debug_inspector (0.0.2) debug_inspector (0.0.2)
devise (3.2.3) devise (3.2.4)
bcrypt-ruby (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5) railties (>= 3.2.6, < 5)
thread_safe (~> 0.1) thread_safe (~> 0.1)
warden (~> 1.2.3) warden (~> 1.2.3)
diff-lcs (1.2.5) diff-lcs (1.2.5)
docile (1.1.3) docile (1.1.3)
domain_name (0.5.16) domain_name (0.5.18)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
draper (1.3.0) draper (1.3.0)
actionpack (>= 3.0) actionpack (>= 3.0)
...@@ -128,12 +126,12 @@ GEM ...@@ -128,12 +126,12 @@ GEM
formatador (0.2.4) formatador (0.2.4)
gherkin (2.12.2) gherkin (2.12.2)
multi_json (~> 1.3) multi_json (~> 1.3)
github-markdown (0.6.4) github-markdown (0.6.5)
gmail_xoauth (0.4.1) gmail_xoauth (0.4.1)
oauth (>= 0.3.6) oauth (>= 0.3.6)
guard (2.5.1) guard (2.6.0)
formatador (>= 0.2.4) formatador (>= 0.2.4)
listen (~> 2.6) listen (~> 2.7)
lumberjack (~> 1.0) lumberjack (~> 1.0)
pry (>= 0.9.12) pry (>= 0.9.12)
thor (>= 0.18.1) thor (>= 0.18.1)
...@@ -152,7 +150,7 @@ GEM ...@@ -152,7 +150,7 @@ GEM
i18n_generators (1.2.1) i18n_generators (1.2.1)
mechanize mechanize
rails (>= 3.0.0) rails (>= 3.0.0)
jbuilder (2.0.4) jbuilder (2.0.5)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
multi_json (>= 1.2.0) multi_json (>= 1.2.0)
jquery-rails (3.1.0) jquery-rails (3.1.0)
...@@ -164,12 +162,12 @@ GEM ...@@ -164,12 +162,12 @@ GEM
launchy (2.4.2) launchy (2.4.2)
addressable (~> 2.3) addressable (~> 2.3)
libv8 (3.16.14.3) libv8 (3.16.14.3)
listen (2.7.0) listen (2.7.1)
celluloid (>= 0.15.2) celluloid (>= 0.15.2)
celluloid-io (>= 0.15.0) celluloid-io (>= 0.15.0)
rb-fsevent (>= 0.9.3) rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9) rb-inotify (>= 0.9)
lumberjack (1.0.4) lumberjack (1.0.5)
mail (2.5.4) mail (2.5.4)
mime-types (~> 1.16) mime-types (~> 1.16)
treetop (~> 1.4.8) treetop (~> 1.4.8)
...@@ -184,9 +182,9 @@ GEM ...@@ -184,9 +182,9 @@ GEM
webrobots (>= 0.0.9, < 0.2) webrobots (>= 0.0.9, < 0.2)
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (1.25.1)
mini_portile (0.5.2) mini_portile (0.5.3)
minitest (4.7.5) minitest (4.7.5)
multi_json (1.9.0) multi_json (1.9.2)
multi_xml (0.5.5) multi_xml (0.5.5)
multipart-post (2.0.0) multipart-post (2.0.0)
mysql2 (0.3.15) mysql2 (0.3.15)
...@@ -219,7 +217,7 @@ GEM ...@@ -219,7 +217,7 @@ GEM
oauth2 (~> 0.9.3) oauth2 (~> 0.9.3)
omniauth (~> 1.2) omniauth (~> 1.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
paper_trail (3.0.0) paper_trail (3.0.1)
activerecord (>= 3.0, < 5.0) activerecord (>= 3.0, < 5.0)
activesupport (>= 3.0, < 5.0) activesupport (>= 3.0, < 5.0)
parser (2.1.7) parser (2.1.7)
...@@ -232,7 +230,7 @@ GEM ...@@ -232,7 +230,7 @@ GEM
websocket-driver (>= 0.2.0) websocket-driver (>= 0.2.0)
polyglot (0.3.4) polyglot (0.3.4)
powerpack (0.0.9) powerpack (0.0.9)
premailer (1.8.1) premailer (1.8.2)
css_parser (>= 1.3.5) css_parser (>= 1.3.5)
htmlentities (>= 4.0.0) htmlentities (>= 4.0.0)
pry (0.9.12.6) pry (0.9.12.6)
...@@ -244,21 +242,21 @@ GEM ...@@ -244,21 +242,21 @@ GEM
rack (1.5.2) rack (1.5.2)
rack-test (0.6.2) rack-test (0.6.2)
rack (>= 1.0) rack (>= 1.0)
rails (4.0.3) rails (4.0.4)
actionmailer (= 4.0.3) actionmailer (= 4.0.4)
actionpack (= 4.0.3) actionpack (= 4.0.4)
activerecord (= 4.0.3) activerecord (= 4.0.4)
activesupport (= 4.0.3) activesupport (= 4.0.4)
bundler (>= 1.3.0, < 2.0) bundler (>= 1.3.0, < 2.0)
railties (= 4.0.3) railties (= 4.0.4)
sprockets-rails (~> 2.0.0) sprockets-rails (~> 2.0.0)
railties (4.0.3) railties (4.0.4)
actionpack (= 4.0.3) actionpack (= 4.0.4)
activesupport (= 4.0.3) activesupport (= 4.0.4)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.0.0) rainbow (2.0.0)
rake (10.1.1) rake (10.2.1)
rb-fsevent (0.9.4) rb-fsevent (0.9.4)
rb-inotify (0.9.3) rb-inotify (0.9.3)
ffi (>= 0.5.0) ffi (>= 0.5.0)
...@@ -276,7 +274,7 @@ GEM ...@@ -276,7 +274,7 @@ GEM
rspec-expectations (2.14.5) rspec-expectations (2.14.5)
diff-lcs (>= 1.1.3, < 2.0) diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.6) rspec-mocks (2.14.6)
rspec-rails (2.14.1) rspec-rails (2.14.2)
actionpack (>= 3.0) actionpack (>= 3.0)
activemodel (>= 3.0) activemodel (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
...@@ -284,12 +282,14 @@ GEM ...@@ -284,12 +282,14 @@ GEM
rspec-core (~> 2.14.0) rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0) rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0) rspec-mocks (~> 2.14.0)
rubocop (0.18.1) rubocop (0.19.1)
json (>= 1.7.7, < 2) json (>= 1.7.7, < 2)
parser (~> 2.1.3) parser (~> 2.1.7)
powerpack (~> 0.0.6) powerpack (~> 0.0.6)
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
sass (3.3.2) ruby-progressbar (~> 1.4)
ruby-progressbar (1.4.2)
sass (3.3.4)
sass-rails (4.0.1) sass-rails (4.0.1)
railties (>= 4.0.0, < 5.0) railties (>= 4.0.0, < 5.0)
sass (>= 3.1.10) sass (>= 3.1.10)
...@@ -307,7 +307,7 @@ GEM ...@@ -307,7 +307,7 @@ GEM
temple (~> 0.6.6) temple (~> 0.6.6)
tilt (>= 1.3.3, < 2.1) tilt (>= 1.3.3, < 2.1)
slop (3.5.0) slop (3.5.0)
sprockets (2.11.0) sprockets (2.12.0)
hike (~> 1.2) hike (~> 1.2)
multi_json (~> 1.0) multi_json (~> 1.0)
rack (~> 1.0) rack (~> 1.0)
...@@ -330,12 +330,12 @@ GEM ...@@ -330,12 +330,12 @@ GEM
daemons (>= 1.0.9) daemons (>= 1.0.9)
eventmachine (>= 1.0.0) eventmachine (>= 1.0.0)
rack (>= 1.0.0) rack (>= 1.0.0)
thor (0.18.1) thor (0.19.1)
thread_safe (0.2.0) thread_safe (0.3.1)
atomic (>= 1.1.7, < 2) atomic (>= 1.1.7, < 2)
tilt (1.4.1) tilt (1.4.1)
timers (1.1.0) timers (1.1.0)
tins (1.0.0) tins (1.0.1)
treetop (1.4.15) treetop (1.4.15)
polyglot polyglot
polyglot (>= 0.3.1) polyglot (>= 0.3.1)
...@@ -343,7 +343,7 @@ GEM ...@@ -343,7 +343,7 @@ GEM
gherkin (>= 2.5) gherkin (>= 2.5)
rspec (>= 2.0, < 4.0) rspec (>= 2.0, < 4.0)
tzinfo (0.3.39) tzinfo (0.3.39)
uglifier (2.4.0) uglifier (2.5.0)
execjs (>= 0.3.0) execjs (>= 0.3.0)
json (>= 1.8.0) json (>= 1.8.0)
unf (0.1.3) unf (0.1.3)
......
/*! jquery-textcomplete - v0.1.3 - 2014-03-07 */!function(a){"use strict";var b=function(a){var b,d;return b=function(){d=!1},function(){var e;d||(d=!0,e=c(arguments),e.unshift(b),a.apply(this,e))}},c=function(a){var b;return b=Array.prototype.slice.call(a)},d=function(){var b;return b=a("<div></div>").css(["color"]).color,"undefined"!=typeof b?function(a,b){return a.css(b)}:function(b,c){var d;return d={},a.each(c,function(a,c){d[c]=b.css(c)}),d}}(),e=function(a){return a},f=function(a){var b={};return function(c,d){b[c]?d(b[c]):a.call(this,c,function(a){b[c]=(b[c]||[]).concat(a),d.apply(null,arguments)})}},g=function(a,b){var c,d;if(a.indexOf)return-1!=a.indexOf(b);for(c=0,d=a.length;d>c;c++)if(a[c]===b)return!0;return!1},h=function(){function c(b){var c;this.el=b.get(0),c=this.el===document.activeElement,this.$el=k(b),this.id="textComplete"+j++,this.strategies=[],c?(this.initialize(),this.$el.focus()):this.$el.one("focus.textComplete",a.proxy(this.initialize,this))}var e,f,g,h,j;e={wrapper:'<div class="textcomplete-wrapper"></div>',list:'<ul class="dropdown-menu"></ul>'},f={wrapper:{position:"relative"},list:{position:"absolute",top:0,left:0,zIndex:"100",display:"none"}},g=a(e.wrapper).css(f.wrapper),h=a(e.list).css(f.list),j=0,a.extend(c.prototype,{initialize:function(){var b,c;b=h.clone(),this.listView=new i(b,this),this.$el.before(b).on({"keyup.textComplete":a.proxy(this.onKeyup,this),"keydown.textComplete":a.proxy(this.listView.onKeydown,this.listView)}),c={},c["click."+this.id]=a.proxy(this.onClickDocument,this),c["keyup."+this.id]=a.proxy(this.onKeyupDocument,this),a(document).on(c)},register:function(a){this.strategies=this.strategies.concat(a)},renderList:function(a){this.clearAtNext&&(this.listView.clear(),this.clearAtNext=!1),a.length&&(this.listView.shown||(this.listView.setPosition(this.getCaretPosition()).clear().activate(),this.listView.strategy=this.strategy),a=a.slice(0,this.strategy.maxCount),this.listView.render(a)),!this.listView.data.length&&this.listView.shown&&this.listView.deactivate()},searchCallbackFactory:function(a){var b=this;return function(c,d){b.renderList(c),d||(a(),b.clearAtNext=!0)}},onKeyup:function(a){var b,c;if(!this.skipSearch(a))if(b=this.extractSearchQuery(this.getTextFromHeadToCaret()),b.length){if(c=b[1],this.term===c)return;this.term=c,this.search(b)}else this.term=null,this.listView.deactivate()},skipSearch:function(a){if(this.skipNextKeyup)return this.skipNextKeyup=!1,!0;switch(a.keyCode){case 40:case 38:return!0}},onSelect:function(b){var c,d,e;c=this.getTextFromHeadToCaret(),d=this.el.value.substring(this.el.selectionEnd),e=this.strategy.replace(b),a.isArray(e)&&(d=e[1]+d,e=e[0]),c=c.replace(this.strategy.match,e),this.$el.val(c+d).trigger("change").trigger("textComplete:select",b),this.el.focus(),this.el.selectionStart=this.el.selectionEnd=c.length,this.skipNextKeyup=!0},onClickDocument:function(a){a.originalEvent&&!a.originalEvent.keepTextCompleteDropdown&&this.listView.deactivate()},onKeyupDocument:function(a){this.listView.shown&&27===a.keyCode&&(this.listView.deactivate(),this.$el.focus())},destroy:function(){var b;this.$el.off(".textComplete"),a(document).off("."+this.id),this.listView&&this.listView.destroy(),b=this.$el.parent(),b.after(this.$el).remove(),this.$el.data("textComplete",void 0),this.$el=null},getCaretPosition:function(){if(0!==this.el.selectionEnd){var b,c,e,f,g,h;return h=this.$el.attr("dir")||this.$el.css("direction"),b=["border-width","font-family","font-size","font-style","font-variant","font-weight","height","letter-spacing","word-spacing","line-height","text-decoration","text-align","width","padding-top","padding-right","padding-bottom","padding-left","margin-top","margin-right","margin-bottom","margin-left"],c=a.extend({position:"absolute",overflow:"auto","white-space":"pre-wrap",top:0,left:-9999,direction:h},d(this.$el,b)),e=a("<div></div>").css(c).text(this.getTextFromHeadToCaret()),f=a("<span></span>").text(".").appendTo(e),this.$el.before(e),g=f.position(),g.top+=f.height()-this.$el.scrollTop(),"rtl"===h&&(g.left-=this.listView.$el.width()),e.remove(),g}},getTextFromHeadToCaret:function(){var a,b,c;return b=this.el.selectionEnd,"number"==typeof b?a=this.el.value.substring(0,b):document.selection&&(c=this.el.createTextRange(),c.moveStart("character",0),c.moveEnd("textedit"),a=c.text),a},extractSearchQuery:function(a){var b,c,d,e;for(b=0,c=this.strategies.length;c>b;b++)if(d=this.strategies[b],e=a.match(d.match))return[d,e[d.index]];return[]},search:b(function(a,b){var c;this.strategy=b[0],c=b[1],this.strategy.search(c,this.searchCallbackFactory(a))})});var k=function(a){return a.wrap(g.clone().css("display",a.css("display")))};return c}(),i=function(){function b(b,c){this.data=[],this.$el=b,this.index=0,this.completer=c,this.$el.on("click.textComplete","li.textcomplete-item",a.proxy(this.onClick,this))}return a.extend(b.prototype,{shown:!1,render:function(a){var b,c,d,e,f;for(b="",c=0,d=a.length;d>c&&(f=a[c],g(this.data,f)||(e=this.data.length,this.data.push(f),b+='<li class="textcomplete-item" data-index="'+e+'"><a>',b+=this.strategy.template(f),b+="</a></li>",this.data.length!==this.strategy.maxCount));c++);this.$el.append(b),this.data.length?this.activateIndexedItem():this.deactivate()},clear:function(){return this.data=[],this.$el.html(""),this.index=0,this},activateIndexedItem:function(){this.$el.find(".active").removeClass("active"),this.getActiveItem().addClass("active")},getActiveItem:function(){return a(this.$el.children().get(this.index))},activate:function(){return this.shown||(this.$el.show(),this.completer.$el.trigger("textComplete:show"),this.shown=!0),this},deactivate:function(){return this.shown&&(this.$el.hide(),this.completer.$el.trigger("textComplete:hide"),this.shown=!1,this.data=[],this.index=null),this},setPosition:function(a){return this.$el.css(a),this},select:function(a){var b=this;this.completer.onSelect(this.data[a]),setTimeout(function(){b.deactivate()},0)},onKeydown:function(a){this.shown&&(38===a.keyCode?(a.preventDefault(),0===this.index?this.index=this.data.length-1:this.index-=1,this.activateIndexedItem()):40===a.keyCode?(a.preventDefault(),this.index===this.data.length-1?this.index=0:this.index+=1,this.activateIndexedItem()):(13===a.keyCode||9===a.keyCode)&&(a.preventDefault(),this.select(parseInt(this.getActiveItem().data("index"),10))))},onClick:function(b){var c=a(b.target);b.originalEvent.keepTextCompleteDropdown=!0,c.hasClass("textcomplete-item")||(c=c.parents("li.textcomplete-item")),this.select(parseInt(c.data("index"),10))},destroy:function(){this.deactivate(),this.$el.off("click.textComplete").remove(),this.$el=null}}),b}();a.fn.textcomplete=function(b){var c,d,g,i;if(i="textComplete","destroy"===b)return this.each(function(){var b=a(this).data(i);b&&b.destroy()});for(c=0,d=b.length;d>c;c++)g=b[c],g.template||(g.template=e),null==g.index&&(g.index=2),g.cache&&(g.search=f(g.search)),g.maxCount||(g.maxCount=10);return this.each(function(){var c,d;c=a(this),d=c.data(i),d||(d=new h(c),c.data(i,d)),d.register(b)})}}(window.jQuery||window.Zepto);
/*
//@ sourceMappingURL=jquery.textcomplete.min.map
*/
\ No newline at end of file
...@@ -21,6 +21,9 @@ $.fn.extend ...@@ -21,6 +21,9 @@ $.fn.extend
keyCode = e.keyCode || e.which keyCode = e.keyCode || e.which
console.log(keyCode)
console.log($this.data('autocompleting'))
# tab key # tab key
if keyCode is 9 if keyCode is 9
e.preventDefault() e.preventDefault()
...@@ -38,6 +41,9 @@ $.fn.extend ...@@ -38,6 +41,9 @@ $.fn.extend
# enter key # enter key
else if keyCode is 13 else if keyCode is 13
return true if $this.data('autocompleting')
val = $this.val() val = $this.val()
start = $this.get(0).selectionStart start = $this.get(0).selectionStart
bl = val.lastIndexOf("\n", start-1) bl = val.lastIndexOf("\n", start-1)
......
/* Sample */
.dropdown-menu {
border: 1px solid #ddd;
background-color: white;
}
.dropdown-menu li {
border-top: 1px solid #ddd;
padding: 2px 5px;
}
.dropdown-menu li:first-child {
border-top: none;
}
.dropdown-menu li:hover,
.dropdown-menu .active {
background-color: rgb(110, 183, 219);
}
/* SHOULD not modify */
.dropdown-menu {
list-style: none;
padding: 0;
margin: 0;
}
.dropdown-menu a:hover {
cursor: pointer;
}
...@@ -32,4 +32,10 @@ class ApisController < ApplicationController ...@@ -32,4 +32,10 @@ class ApisController < ApplicationController
render json: { status: 'OK', files: s3_files } render json: { status: 'OK', files: s3_files }
end end
def user_mention
name_list = User.search(params[:q]).map{ |_user| "#{_user.nickname}[#{_user.name}]" }
render json: name_list
end
end end
...@@ -9,6 +9,6 @@ class SearchController < ApplicationController ...@@ -9,6 +9,6 @@ class SearchController < ApplicationController
end end
@count = scope.count @count = scope.count
@posts = scope.limit(10).decorate @posts = scope.limit(100).decorate
end end
end end
class UsersController < ApplicationController
before_action :set_user, only: [:edit, :update]
def edit
end
def update
respond_to do |format|
if @user.update(user_params)
format.html { redirect_to edit_user_path(@user), flash: { notice: 'Post was successfully updated.' } }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
@user = current_user
end
def user_params
params.require(:user).permit(:nickname).to_hash
end
end
...@@ -9,10 +9,10 @@ class PostsDecorator < Draper::CollectionDecorator ...@@ -9,10 +9,10 @@ class PostsDecorator < Draper::CollectionDecorator
end end
def related_authors def related_authors
_authors = self.map do |_post| self.map do |_post|
_post.author _post.author
end.flatten.uniq end.flatten.uniq.map do |_author|
_author.decorate
UserDecorator.decorate_collection(_authors) end
end end
end end
class UsersDecorator < Draper::Decorator
delegate_all
# Define presentation-specific methods here. Helpers are accessed through
# `helpers` (aka `h`). You can override attributes, for example:
#
# def created_at
# helpers.content_tag :span, class: 'time' do
# object.created_at.strftime("%a %m/%d/%y")
# end
# end
end
module UsersHelper
end
...@@ -16,7 +16,7 @@ class Post < ActiveRecord::Base ...@@ -16,7 +16,7 @@ class Post < ActiveRecord::Base
# Named scope # Named scope
###################################################################### ######################################################################
scope :search, (lambda do |query| scope :search, (lambda do |query|
_where_list = includes(:author, :tags) _where_list = includes(:author, :tags).order(updated_at: :desc)
# Convert spaces to one space. # Convert spaces to one space.
query_list = query.split(/[\s ]+/) query_list = query.split(/[\s ]+/)
...@@ -30,7 +30,7 @@ class Post < ActiveRecord::Base ...@@ -30,7 +30,7 @@ class Post < ActiveRecord::Base
when /\Abody:(.+)/ when /\Abody:(.+)/
_where_list = _where_list.where('posts.body LIKE ?', "%#{Regexp.last_match[1]}%") _where_list = _where_list.where('posts.body LIKE ?', "%#{Regexp.last_match[1]}%")
when /\A@(.+)/ when /\A@(.+)/
_where_list = _where_list.where(users: { name: Regexp.last_match[1] }) _where_list = _where_list.where(users: { nickname: Regexp.last_match[1] })
when /\A#(.+)/ when /\A#(.+)/
_where_list = _where_list.where(tags: { name: Regexp.last_match[1] }) _where_list = _where_list.where(tags: { name: Regexp.last_match[1] })
when /\Adate:(\d+)-(\d+)-(\d+)/ when /\Adate:(\d+)-(\d+)-(\d+)/
......
...@@ -7,13 +7,34 @@ class User < ActiveRecord::Base ...@@ -7,13 +7,34 @@ class User < ActiveRecord::Base
:recoverable, :rememberable, :trackable, :validatable :recoverable, :rememberable, :trackable, :validatable
devise :omniauthable, omniauth_providers: [:google_oauth2] devise :omniauthable, omniauth_providers: [:google_oauth2]
######################################################################
# association
######################################################################
has_many :posts, foreign_key: 'author_id' has_many :posts, foreign_key: 'author_id'
has_many :comments, foreign_key: 'author_id' has_many :comments, foreign_key: 'author_id'
######################################################################
# scope
######################################################################
scope :post_recently, -> { scope :post_recently, -> {
User.joins(:posts).group('id').order('posts.updated_at desc') User.joins(:posts).group('id').order('posts.updated_at desc')
} }
scope :search, (lambda do |_query|
where('name LIKE ? OR nickname LIKE ?', "%#{_query}%", "%#{_query}%")
end)
######################################################################
# validations
######################################################################
validates :name, presence: true
validates :email, presence: true
validates :email, uniqueness: true
validates :nickname, presence: true
validates :nickname, format: { with: /\A[0-9A-Za-z]+\z/i }
validates :nickname, uniqueness: true
# Device # Device
def self.find_for_google_oauth2(access_token, signed_in_resource = nil) def self.find_for_google_oauth2(access_token, signed_in_resource = nil)
info = access_token.info info = access_token.info
...@@ -36,6 +57,7 @@ class User < ActiveRecord::Base ...@@ -36,6 +57,7 @@ class User < ActiveRecord::Base
user user
end end
# check if google oauth token is expired # check if google oauth token is expired
def google_oauth_token_expired? def google_oauth_token_expired?
google_token_expires_at < Time.now google_token_expires_at < Time.now
...@@ -61,4 +83,6 @@ class User < ActiveRecord::Base ...@@ -61,4 +83,6 @@ class User < ActiveRecord::Base
) )
end end
end end
...@@ -23,6 +23,6 @@ ...@@ -23,6 +23,6 @@
h2.panel-title 最近投稿したユーザー(調整中) h2.panel-title 最近投稿したユーザー(調整中)
.panel-body.list-group .panel-body.list-group
- User.post_recently.limit(10).each_with_index do |author, i| - User.post_recently.limit(10).each_with_index do |author, i|
a.list-group-item.post-list data-author-id=author.id href="#" = author.name a.list-group-item.post-list data-author-id=author.id href=search_path(q: "@#{author.nickname}") = author.name
...@@ -33,11 +33,11 @@ nav.navbar.navbar-default.navbar-fixed-top role="navigation" ...@@ -33,11 +33,11 @@ nav.navbar.navbar-default.navbar-fixed-top role="navigation"
b.caret b.caret
ul.dropdown-menu ul.dropdown-menu
li li
a href=search_path(q: "@#{current_user.name} draft:1") a href=search_path(q: "@#{current_user.nickname} draft:1")
| 下書き | 下書き
span.badge.pull-right = current_user.decorate.draft_count span.badge.pull-right = current_user.decorate.draft_count
li li
a Settings (todo) a href=edit_user_path(current_user) マイページ
li.divider li.divider
li li
a href=destroy_user_session_path data-method="delete" rel="nofollow" SignOut a href=destroy_user_session_path data-method="delete" rel="nofollow" SignOut
......
...@@ -55,3 +55,47 @@ input#fileupload data-url="/apis/file_receiver" multiple="" name="files[]" style ...@@ -55,3 +55,47 @@ input#fileupload data-url="/apis/file_receiver" multiple="" name="files[]" style
var val = $('.mod-mdEditor-body').val(); var val = $('.mod-mdEditor-body').val();
console.log(val); console.log(val);
}); });
// mention補完
// TODO: mod-md-editorに入れる?
$('textarea').textcomplete([
{ // html
mentions: ['yuku_t'],
match: /\B@([^\B]*)$/,
search: function (term, callback) {
$.getJSON('/apis/user_mention', { q: term })
.done(function (resp) { callback(resp); })
.fail(function () { callback([]); });
},
index: 1,
replace: function (mention) {
return '@' + mention + ' ';
}
}
]).on({
'textComplete:select': function (e, value) {
console.log('textComplete:select ' + value);
},
'textComplete:show': function (e) {
console.log('textComplete:show');
$(this).data('autocompleting', true);
},
'textComplete:hide': function (e) {
console.log('textComplete:hide');
$(this).data('autocompleting', false);
}
});
// .overlay([
// {
// match: /\B@\w+/g,
// css: {
// 'background-color': '#d8dfea'
// }
// }
// ]);
css:
.textcomplete-wrapper {
width: 100%;
}
...@@ -24,6 +24,6 @@ ...@@ -24,6 +24,6 @@
h2.panel-title "#{params[:q]}"に関連するユーザー h2.panel-title "#{params[:q]}"に関連するユーザー
.panel-body.list-group .panel-body.list-group
- @posts.related_authors.each do |_author| - @posts.related_authors.each do |_author|
a.list-group-item href=search_path(q: "@#{_author.name}") = _author.name a.list-group-item href=search_path(q: "@#{_author.nickname}") = _author.name
#post-form
= form_for(@user) do |f|
- if @user.errors.any?
#error_explanation
h2
= pluralize(@user.errors.count, "error")
| prohibited this post from being saved:
ul
- @user.errors.full_messages.each do |msg|
li= msg
.row
.form-horizontal role="form"
.form-group
= f.label :name, '名前', class: 'col-sm-2 control-label'
.col-sm-10
= f.text_field :name, class: 'form-control', readonly: true
.form-group
= f.label :email, 'メールアドレス', class: 'col-sm-2 control-label'
.col-sm-10
= f.text_field :email, class: 'form-control', readonly: true
.form-group
= f.label :nickname, 'ニックネーム', class: 'col-sm-2 control-label'
.col-sm-10
= f.text_field :nickname, class: 'form-control'
.form-group
.col-sm-offset-2.col-sm-10
button.btn.btn-success type="submit" 保存
h1 編集
= render 'form'
<h1>Users#update</h1>
<p>Find me in app/views/users/update.html.erb</p>
...@@ -2,6 +2,7 @@ Rendezvous::Application.routes.draw do ...@@ -2,6 +2,7 @@ Rendezvous::Application.routes.draw do
post 'apis/markdown_preview' post 'apis/markdown_preview'
post 'apis/file_receiver' post 'apis/file_receiver'
get 'apis/user_mention'
get 'tags/:name/events' => 'tags#events', as: 'event_tag' get 'tags/:name/events' => 'tags#events', as: 'event_tag'
root 'welcome#top', as: 'root' root 'welcome#top', as: 'root'
...@@ -20,7 +21,11 @@ Rendezvous::Application.routes.draw do ...@@ -20,7 +21,11 @@ Rendezvous::Application.routes.draw do
post 'tags/:name/move_to/:move_to_name' => 'tags#move_to', as: 'move_to_tag' post 'tags/:name/move_to/:move_to_name' => 'tags#move_to', as: 'move_to_tag'
resources :tags, :param => :name resources :tags, :param => :name
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' } resources :users, :only => [:edit, :update]
devise_for :users , controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
# get 'users/edit' => 'users#edit', as: 'edit_user'
# post 'users/update' => 'users#update', as: 'update_user'
# The priority is based upon order of creation: first created -> highest priority. # The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes". # See how all your routes lay out with "rake routes".
......
class AddNicknameToUsers < ActiveRecord::Migration
def change
add_column :users, :nickname, :string, default: '', null: false
add_index "users", ["nickname"]
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20140318040809) do ActiveRecord::Schema.define(version: 20140320043116) do
create_table "comments", force: true do |t| create_table "comments", force: true do |t|
t.integer "author_id" t.integer "author_id"
...@@ -73,9 +73,11 @@ ActiveRecord::Schema.define(version: 20140318040809) do ...@@ -73,9 +73,11 @@ ActiveRecord::Schema.define(version: 20140318040809) do
t.string "google_auth_token" t.string "google_auth_token"
t.string "google_refresh_token" t.string "google_refresh_token"
t.datetime "google_token_expires_at" t.datetime "google_token_expires_at"
t.string "nickname", default: "", null: false
end end
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["nickname"], name: "index_users_on_nickname", using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
create_table "versions", force: true do |t| create_table "versions", force: true do |t|
......
namespace :migrations do
desc '001 nicknameを自動付与'
task task_001_user_nickname: :environment do
User.all.each do |_user|
next if _user.nickname.present?
new_nickname = (("a".."z").to_a + ("A".."Z").to_a + (0..9).to_a).shuffle[0..4].join
_user.update_attributes!(nickname: new_nickname)
end
end
end
...@@ -21,21 +21,21 @@ Feature: アクセス制限 ...@@ -21,21 +21,21 @@ Feature: アクセス制限
# Then response code is 200 # Then response code is 200
# Then response includes '<!--view:welcome/login-->' # Then response includes '<!--view:welcome/login-->'
Scenario: ログイン --> TOPページ # Scenario: ログイン --> TOPページ
Given login # Given login
When visit '/' # When visit '/'
Then response code is 200 # Then response code is 200
Then response includes '<!--view:flow/show-->' # Then response includes '<!--view:flow/show-->'
Scenario: ログイン --> flowページ # Scenario: ログイン --> flowページ
Given login # Given login
When visit '/flow' # When visit '/flow'
Then response code is 200 # Then response code is 200
Then response includes '<!--view:flow/show-->' # Then response includes '<!--view:flow/show-->'
Scenario: ログイン --> ログアウト # Scenario: ログイン --> ログアウト
Given login # Given login
When logout # When logout
Then response code is 200 # Then response code is 200
Then response includes '<!--view:welcome/login-->' # Then response includes '<!--view:welcome/login-->'
require 'spec_helper'
describe UsersController do
describe "GET 'edit'" do
pending
# it "returns http success" do
# get :edit
# response.should be_success
# end
end
describe "GET 'update'" do
pending
# it "returns http success" do
# put :update
# response.should be_success
# end
end
end
require 'spec_helper'
describe UsersDecorator do
end
...@@ -2,6 +2,7 @@ FactoryGirl.define do ...@@ -2,6 +2,7 @@ FactoryGirl.define do
factory :alice, class: User do factory :alice, class: User do
name 'Alice' name 'Alice'
email 'alice@mail.com' email 'alice@mail.com'
nickname 'alice'
password Devise.friendly_token[0, 20] password Devise.friendly_token[0, 20]
google_token_expires_at Time.now + 30.minutes google_token_expires_at Time.now + 30.minutes
end end
...@@ -9,6 +10,7 @@ FactoryGirl.define do ...@@ -9,6 +10,7 @@ FactoryGirl.define do
factory :bob, class: User do factory :bob, class: User do
name 'Bob' name 'Bob'
email 'bob@mail.com' email 'bob@mail.com'
nickname 'bob'
password Devise.friendly_token[0, 20] password Devise.friendly_token[0, 20]
google_token_expires_at Time.now - 1.hour google_token_expires_at Time.now - 1.hour
end end
...@@ -16,6 +18,7 @@ FactoryGirl.define do ...@@ -16,6 +18,7 @@ FactoryGirl.define do
factory :login_user_1, class: User do factory :login_user_1, class: User do
name 'Test User' name 'Test User'
email 'example@example.com' email 'example@example.com'
nickname 'testuser'
password 'changeme' password 'changeme'
password_confirmation 'changeme' password_confirmation 'changeme'
# required if the Devise Confirmable module is used # required if the Devise Confirmable module is used
......
require 'spec_helper'
# Specs in this file have access to a helper object that includes
# the UsersHelper. For example:
#
# describe UsersHelper do
# describe "string concat" do
# it "concats two strings with spaces" do
# expect(helper.concat_strings("this","that")).to eq("this that")
# end
# end
# end
describe UsersHelper do
pending "add some examples to (or delete) #{__FILE__}"
end
...@@ -25,6 +25,7 @@ describe User do ...@@ -25,6 +25,7 @@ describe User do
@attr = { @attr = {
name: 'Example User', name: 'Example User',
email: 'user@example.com', email: 'user@example.com',
nickname: 'testnick',
password: 'changeme', password: 'changeme',
password_confirmation: 'changeme' password_confirmation: 'changeme'
} }
......
require 'spec_helper'
describe "users/edit.html.erb" do
pending "add some examples to (or delete) #{__FILE__}"
end
require 'spec_helper'
describe "users/update.html.erb" do
pending "add some examples to (or delete) #{__FILE__}"
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment