mirror of
https://github.com/flarum/core.git
synced 2025-05-08 16:35:26 +02:00
Improve fulltext search API and interface
This commit is contained in:
parent
38c2ff0306
commit
42f1fa1272
@ -23,7 +23,10 @@ export default class DiscussionList extends Component {
|
|||||||
}
|
}
|
||||||
params.sort = this.sortMap()[params.sort];
|
params.sort = this.sortMap()[params.sort];
|
||||||
if (params.q) {
|
if (params.q) {
|
||||||
|
params.filter = params.filter || {};
|
||||||
|
params.filter.q = params.q;
|
||||||
params.include.push('relevantPosts', 'relevantPosts.discussion', 'relevantPosts.user');
|
params.include.push('relevantPosts', 'relevantPosts.discussion', 'relevantPosts.user');
|
||||||
|
delete params.q;
|
||||||
}
|
}
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ export default class DiscussionsSearchResults {
|
|||||||
|
|
||||||
search(string) {
|
search(string) {
|
||||||
this.results[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;
|
this.results[string] = results;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ export default function(string, phrase, length) {
|
|||||||
return string;
|
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 highlightedString = string;
|
||||||
let start = 0;
|
let start = 0;
|
||||||
|
@ -113,7 +113,8 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
& .relevant-posts {
|
& .relevant-posts {
|
||||||
display: none;
|
margin-left: -52px;
|
||||||
|
margin-right: -65px;
|
||||||
}
|
}
|
||||||
& .count {
|
& .count {
|
||||||
margin-top: 11px;
|
margin-top: 11px;
|
||||||
@ -242,7 +243,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
& .relevant-posts {
|
& .relevant-posts {
|
||||||
margin-bottom: 20px;
|
padding-bottom: 15px;
|
||||||
|
|
||||||
@media @phone {
|
@media @phone {
|
||||||
margin-left: -45px;
|
margin-left: -45px;
|
||||||
@ -254,6 +255,12 @@
|
|||||||
display: block;
|
display: block;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
border-bottom: 2px dotted @fl-body-bg;
|
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 {
|
& .avatar, & time {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -45,7 +45,7 @@ class DiscussionsServiceProvider extends ServiceProvider
|
|||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
$this->app->bind(
|
$this->app->bind(
|
||||||
'Flarum\Core\Discussions\Search\Fulltext\DriverInterface',
|
'Flarum\Core\Discussions\Search\Fulltext\Driver',
|
||||||
'Flarum\Core\Discussions\Search\Fulltext\MySqlFulltextDriver'
|
'Flarum\Core\Discussions\Search\Fulltext\MySqlFulltextDriver'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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 array $relevantPostIds
|
||||||
* @param int[] $postIds
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function setRelevantPostIds($discussionId, array $postIds)
|
public function setRelevantPostIds(array $relevantPostIds)
|
||||||
{
|
{
|
||||||
$this->relevantPostIds[$discussionId] = $postIds;
|
$this->relevantPostIds = $relevantPostIds;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
src/Core/Discussions/Search/Fulltext/Driver.php
Normal file
13
src/Core/Discussions/Search/Fulltext/Driver.php
Normal 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);
|
||||||
|
}
|
@ -1,6 +0,0 @@
|
|||||||
<?php namespace Flarum\Core\Discussions\Search\Fulltext;
|
|
||||||
|
|
||||||
interface DriverInterface
|
|
||||||
{
|
|
||||||
public function match($string);
|
|
||||||
}
|
|
@ -2,12 +2,24 @@
|
|||||||
|
|
||||||
use Flarum\Core\Posts\Post;
|
use Flarum\Core\Posts\Post;
|
||||||
|
|
||||||
class MySqlFulltextDriver implements DriverInterface
|
class MySqlFulltextDriver implements Driver
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
public function match($string)
|
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])
|
->orderByRaw('MATCH (`content`) AGAINST (?) DESC', [$string])
|
||||||
->lists('id');
|
->lists('discussion_id', 'id');
|
||||||
|
|
||||||
|
$relevantPostIds = [];
|
||||||
|
|
||||||
|
foreach ($discussionIds as $postId => $discussionId) {
|
||||||
|
$relevantPostIds[$discussionId][] = $postId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $relevantPostIds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php namespace Flarum\Core\Discussions\Search\Gambits;
|
<?php namespace Flarum\Core\Discussions\Search\Gambits;
|
||||||
|
|
||||||
use Flarum\Core\Discussions\Search\DiscussionSearch;
|
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\Search;
|
||||||
use Flarum\Core\Search\Gambit;
|
use Flarum\Core\Search\Gambit;
|
||||||
use LogicException;
|
use LogicException;
|
||||||
@ -9,16 +9,16 @@ use LogicException;
|
|||||||
class FulltextGambit implements Gambit
|
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');
|
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 = [];
|
$discussionIds = array_keys($relevantPostIds);
|
||||||
foreach ($posts as $post) {
|
|
||||||
$discussions[] = $id = $post->discussion_id;
|
|
||||||
$search->addRelevantPostId($id, $post->id);
|
|
||||||
}
|
|
||||||
$discussions = array_unique($discussions);
|
|
||||||
|
|
||||||
// TODO: implement negate (match for - at start of string)
|
$search->setRelevantPostIds($relevantPostIds);
|
||||||
$search->getQuery()->whereIn('id', $discussions);
|
|
||||||
|
|
||||||
$search->setDefaultSort(['id' => $discussions]);
|
$search->getQuery()->whereIn('id', $discussionIds);
|
||||||
|
|
||||||
|
$search->setDefaultSort(['id' => $discussionIds]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use Illuminate\Database\Eloquent\Builder;
|
|||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Flarum\Core\Users\User;
|
use Flarum\Core\Users\User;
|
||||||
use Flarum\Core\Discussions\Discussion;
|
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,
|
// 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
|
// 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
|
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
|
* Find a post by ID, optionally making sure it is visible to a certain
|
||||||
* user, or throw an exception.
|
* user, or throw an exception.
|
||||||
@ -99,31 +92,6 @@ class PostRepository
|
|||||||
return $this->filterVisibleTo($posts, $actor);
|
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
|
* 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
|
* is. If the post with that number does not exist, the index of the
|
||||||
|
@ -30,7 +30,8 @@ class IndexAction extends ClientAction
|
|||||||
|
|
||||||
$params = [
|
$params = [
|
||||||
'sort' => $sort ? $this->sortMap[$sort] : '',
|
'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.
|
// FIXME: make sure this is extensible. Support pagination.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user