diff --git a/app/models/search.rb b/app/models/search.rb
index 3c92c55b..0f10a75d 100644
--- a/app/models/search.rb
+++ b/app/models/search.rb
@@ -124,7 +124,7 @@ class Search
parse_tree.each do |node|
type, value = node.first
case type
- when :commenter
+ when :commenter, :user
n_commenters += 1
return invalid("A comment only has one commenter") if n_commenters > 1
query.joins!(:user).where!(users: {username: value.to_s})
@@ -240,7 +240,7 @@ class Search
n_domains += 1
return invalid("A story can't be from multiple domains at once") if n_domains > 1
query.joins!(:domain).where!(domains: {domain: value.to_s})
- when :submitter
+ when :submitter, :user
n_submitters += 1
return invalid("A story only has one submitter") if n_submitters > 1
query.joins!(:user).where!(user: {username: value.to_s})
diff --git a/app/models/search_parser.rb b/app/models/search_parser.rb
index aaffc78b..46bac2fc 100644
--- a/app/models/search_parser.rb
+++ b/app/models/search_parser.rb
@@ -25,6 +25,8 @@ class SearchParser < Parslet::Parser
match("[A-Za-z0-9\\-_.:@/()%~?&=#]").repeat(1)
).as(:url) >> space?
}
+ # User::VALID_USERNAME
+ rule(:user) { match("[@~]") >> match("[A-Za-z0-9_\\-]").repeat(1, 24).as(:user) >> space? }
rule(:negated) { str("-") >> (domain | tag | quoted | term).as(:negated) >> space? }
# catchall consumes ill-structured input
@@ -36,9 +38,9 @@ class SearchParser < Parslet::Parser
domain |
submitter |
tag |
- # title before quoted so that doesn't consume the quotes
- title |
+ title | # title before quoted so that doesn't consume the quotes
url |
+ user | # user must come after commenter and submitter
# term and quoted after operators they would fail to consume
term |
quoted |
diff --git a/app/views/search/index.html.erb b/app/views/search/index.html.erb
index 50055538..356e8aff 100644
--- a/app/views/search/index.html.erb
+++ b/app/views/search/index.html.erb
@@ -58,8 +58,10 @@
type, value = node.first
case type
- when :domain
- %>
+ when :commenter %>
+
commenter:<%= value %>
+ Comment by: <%= link_to value, user_path(value) %>
+ <% when :domain %>
domain:<%= value %>
Domain: <%= link_to value, domain_path(value) %>
<% when :submitter %>
@@ -71,6 +73,9 @@
<% when :title %>
title:<%= @search.flatten_title(value) %>
Title: <%= @search.flatten_title(value) %>
+ <% when :user %>
+ @<%= value %>
+ <%= @search.searched_model.name %> by: <%= link_to value, user_path(value) %>
<% when :negated %>
<% when :quoted %>
"<%= value %>"
diff --git a/spec/models/search_parser_spec.rb b/spec/models/search_parser_spec.rb
index a546ae88..6bbe0d3b 100644
--- a/spec/models/search_parser_spec.rb
+++ b/spec/models/search_parser_spec.rb
@@ -65,6 +65,13 @@ describe SearchParser do
it("parses multiple words") { expect(sp.quoted).to parse('"research words"') }
end
+ describe "commenter rule" do
+ it("parses username") { expect(sp.commenter).to parse("commenter:alice") }
+ it("parses with @") { expect(sp.commenter).to parse("commenter:@bob") }
+ it("parses with ~") { expect(sp.commenter).to parse("commenter:~carol") }
+ it("doesn't parse blank") { expect(sp.commenter).to_not parse("commenter:") }
+ end
+
describe "domain rule" do
it("parses single") { expect(sp.domain).to parse("domain:example.com") }
it("parses dash") { expect(sp.domain).to parse("domain:foo-bar.com") }
@@ -90,6 +97,14 @@ describe SearchParser do
it("parses punctuation stripped from terms") { expect(sp.url).to parse("https://example.com/foo-bar&a=b") }
end
+ describe "user rule" do
+ it("parses with @") { expect(sp.user).to parse("@bob") }
+ it("parses with ~") { expect(sp.user).to parse("~carol") }
+ it("doesn't parse blank @") { expect(sp.user).to_not parse("@") }
+ it("doesn't parse emails") { expect(sp.user).to_not parse("user@example.com") }
+ it("doesn't parse blank ~") { expect(sp.user).to_not parse("~") }
+ end
+
describe "title rule" do
it("parses single") { expect(sp.title).to parse("title:seven") }
it("does parse single word quote") { expect(sp.title).to parse('title:"tips"') }