Improve fulltext search API and interface

This commit is contained in:
Toby Zerner 2015-07-07 20:35:18 +09:30
parent 38c2ff0306
commit 42f1fa1272
12 changed files with 62 additions and 81 deletions

View File

@ -23,7 +23,10 @@ export default class DiscussionList extends Component {
}
params.sort = this.sortMap()[params.sort];
if (params.q) {
params.filter = params.filter || {};
params.filter.q = params.q;
params.include.push('relevantPosts', 'relevantPosts.discussion', 'relevantPosts.user');
delete params.q;
}
return params;
}

View File

@ -9,7 +9,7 @@ export default class DiscussionsSearchResults {
search(string) {
this.results[string] = [];
return app.store.find('discussions', {filter: {q: string}, page: {limit: 3}, include: 'relevantPosts,relevantPosts.discussion'}).then(results => {
return app.store.find('discussions', {filter: {q: string}, page: {limit: 3}, include: 'relevantPosts,relevantPosts.discussion,relevantPosts.user'}).then(results => {
this.results[string] = results;
});
}

View File

@ -5,7 +5,7 @@ export default function(string, phrase, length) {
return string;
}
const regexp = regexp instanceof RegExp ? phrase : new RegExp(phrase, 'gi');
const regexp = phrase instanceof RegExp ? phrase : new RegExp(phrase, 'gi');
let highlightedString = string;
let start = 0;

View File

@ -113,7 +113,8 @@
font-size: 14px;
}
& .relevant-posts {
display: none;
margin-left: -52px;
margin-right: -65px;
}
& .count {
margin-top: 11px;
@ -242,7 +243,7 @@
}
}
& .relevant-posts {
margin-bottom: 20px;
padding-bottom: 15px;
@media @phone {
margin-left: -45px;
@ -254,6 +255,12 @@
display: block;
padding: 10px 15px;
border-bottom: 2px dotted @fl-body-bg;
color: @fl-body-muted-color;
transition: border-color 0.2s;
.discussion-list-item:hover & {
border-color: lighten(@fl-body-secondary-color, 3%);
}
& .avatar, & time {
display: none;

View File

@ -45,7 +45,7 @@ class DiscussionsServiceProvider extends ServiceProvider
public function register()
{
$this->app->bind(
'Flarum\Core\Discussions\Search\Fulltext\DriverInterface',
'Flarum\Core\Discussions\Search\Fulltext\Driver',
'Flarum\Core\Discussions\Search\Fulltext\MySqlFulltextDriver'
);

View File

@ -30,26 +30,13 @@ class DiscussionSearch extends Search
}
/**
* Set the relevant post IDs for a result.
* Set the relevant post IDs for the results.
*
* @param int $discussionId
* @param int[] $postIds
* @param array $relevantPostIds
* @return void
*/
public function setRelevantPostIds($discussionId, array $postIds)
public function setRelevantPostIds(array $relevantPostIds)
{
$this->relevantPostIds[$discussionId] = $postIds;
}
/**
* Add a relevant post ID for a discussion result.
*
* @param int $discussionId
* @param int $postId
* @return void
*/
public function addRelevantPostId($discussionId, $postId)
{
$this->relevantPostIds[$discussionId][] = $postId;
$this->relevantPostIds = $relevantPostIds;
}
}

View File

@ -0,0 +1,13 @@
<?php namespace Flarum\Core\Discussions\Search\Fulltext;
interface Driver
{
/**
* Return an array of arrays of post IDs, grouped by discussion ID, which
* match the given string.
*
* @param string $string
* @return array
*/
public function match($string);
}

View File

@ -1,6 +0,0 @@
<?php namespace Flarum\Core\Discussions\Search\Fulltext;
interface DriverInterface
{
public function match($string);
}

View File

@ -2,12 +2,24 @@
use Flarum\Core\Posts\Post;
class MySqlFulltextDriver implements DriverInterface
class MySqlFulltextDriver implements Driver
{
/**
* {@inheritdoc}
*/
public function match($string)
{
return Post::whereRaw('MATCH (`content`) AGAINST (? IN BOOLEAN MODE)', [$string])
$discussionIds = Post::where('type', 'comment')
->whereRaw('MATCH (`content`) AGAINST (? IN BOOLEAN MODE)', [$string])
->orderByRaw('MATCH (`content`) AGAINST (?) DESC', [$string])
->lists('id');
->lists('discussion_id', 'id');
$relevantPostIds = [];
foreach ($discussionIds as $postId => $discussionId) {
$relevantPostIds[$discussionId][] = $postId;
}
return $relevantPostIds;
}
}

View File

@ -1,7 +1,7 @@
<?php namespace Flarum\Core\Discussions\Search\Gambits;
use Flarum\Core\Discussions\Search\DiscussionSearch;
use Flarum\Core\Posts\PostRepository;
use Flarum\Core\Discussions\Search\Fulltext\Driver;
use Flarum\Core\Search\Search;
use Flarum\Core\Search\Gambit;
use LogicException;
@ -9,16 +9,16 @@ use LogicException;
class FulltextGambit implements Gambit
{
/**
* @var PostRepository
* @var Driver
*/
protected $posts;
protected $fulltext;
/**
* @param PostRepository $posts
* @param Driver $fulltext
*/
public function __construct(PostRepository $posts)
public function __construct(Driver $fulltext)
{
$this->posts = $posts;
$this->fulltext = $fulltext;
}
/**
@ -30,18 +30,14 @@ class FulltextGambit implements Gambit
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
}
$posts = $this->posts->findByContent($bit, $search->getActor());
$relevantPostIds = $this->fulltext->match($bit);
$discussions = [];
foreach ($posts as $post) {
$discussions[] = $id = $post->discussion_id;
$search->addRelevantPostId($id, $post->id);
}
$discussions = array_unique($discussions);
$discussionIds = array_keys($relevantPostIds);
// TODO: implement negate (match for - at start of string)
$search->getQuery()->whereIn('id', $discussions);
$search->setRelevantPostIds($relevantPostIds);
$search->setDefaultSort(['id' => $discussions]);
$search->getQuery()->whereIn('id', $discussionIds);
$search->setDefaultSort(['id' => $discussionIds]);
}
}

View File

@ -4,7 +4,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Flarum\Core\Users\User;
use Flarum\Core\Discussions\Discussion;
use Flarum\Core\Discussions\Search\Fulltext\DriverInterface;
use Flarum\Core\Discussions\Search\Fulltext\Driver;
// TODO: In some cases, the use of a post repository incurs extra query expense,
// because for every post retrieved we need to check if the discussion it's in
@ -28,13 +28,6 @@ use Flarum\Core\Discussions\Search\Fulltext\DriverInterface;
class PostRepository
{
protected $fulltext;
public function __construct(DriverInterface $fulltext)
{
$this->fulltext = $fulltext;
}
/**
* Find a post by ID, optionally making sure it is visible to a certain
* user, or throw an exception.
@ -99,31 +92,6 @@ class PostRepository
return $this->filterVisibleTo($posts, $actor);
}
/**
* Find posts by matching a string of words against their content,
* optionally making sure they are visible to a certain user.
*
* @param string $string
* @param \Flarum\Core\Users\User|null $actor
* @return \Illuminate\Database\Eloquent\Collection
*/
public function findByContent($string, User $actor = null)
{
$ids = $this->fulltext->match($string);
$ids = $this->filterDiscussionVisibleTo($ids, $actor);
$query = Post::select('id', 'discussion_id')->whereIn('id', $ids);
foreach ($ids as $id) {
$query->orderByRaw('id != ?', [$id]);
}
$posts = $query->get();
return $this->filterVisibleTo($posts, $actor);
}
/**
* Get the position within a discussion where a post with a certain number
* is. If the post with that number does not exist, the index of the

View File

@ -30,7 +30,8 @@ class IndexAction extends ClientAction
$params = [
'sort' => $sort ? $this->sortMap[$sort] : '',
'q' => $q
'filter' => ['q' => $q],
'include' => $q ? 'startUser,lastUser,relevantPosts,relevantPosts.discussion,relevantPosts.user' : ''
];
// FIXME: make sure this is extensible. Support pagination.