Merge branch 'dk/search-improvements' into main
This commit is contained in:
commit
846cc348ff
|
|
@ -27,7 +27,12 @@ return [
|
||||||
(new Flarum\Routes('api'))
|
(new Flarum\Routes('api'))
|
||||||
->get('/blomstra/search/{type}', 'blomstra.search', Api\Controllers\SearchController::class)
|
->get('/blomstra/search/{type}', 'blomstra.search', Api\Controllers\SearchController::class)
|
||||||
->put('/blomstra/search/index', 'blomstra.search.index', Api\Controllers\IndexController::class),
|
->put('/blomstra/search/index', 'blomstra.search.index', Api\Controllers\IndexController::class),
|
||||||
|
(new Flarum\Console)
|
||||||
(new Flarum\Console())
|
|
||||||
->command(Commands\BuildCommand::class),
|
->command(Commands\BuildCommand::class),
|
||||||
|
|
||||||
|
(new Flarum\Settings)
|
||||||
|
->default('blomstra-search.search-discussion-subjects', true)
|
||||||
|
->default('blomstra-search.search-post-bodies', true)
|
||||||
|
->default('blomstra-search.match-sentences', true)
|
||||||
|
->default('blomstra-search.match-words', true)
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
(()=>{var a={n:e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return a.d(t,{a:t}),t},d:(e,t)=>{for(var r in t)a.o(t,r)&&!a.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},o:(a,e)=>Object.prototype.hasOwnProperty.call(a,e),r:a=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(a,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(a,"__esModule",{value:!0})}},e={};(()=>{"use strict";a.r(e);const t=flarum.core.compat["admin/app"];var r=a.n(t);r().initializers.add("blomstra-search",(function(){var a=new Map;["arabic","armenian","basque","bengali","brazilian","bulgarian","catalan","cjk","czech","danish","dutch","english","estonian","finnish","french","galician","german","greek","hindi","hungarian","indonesian","irish","italian","latvian","lithuanian","norwegian","persian","portuguese","romanian","russian","sorani","spanish","swedish","turkish","thai"].forEach((function(e){a.set(e,e)})),r().extensionData.for("blomstra-search").registerSetting({setting:"blomstra-search.elastic-endpoint",label:r().translator.trans("blomstra-search.admin.elastic-endpoint"),type:"input"}).registerSetting({setting:"blomstra-search.elastic-username",label:r().translator.trans("blomstra-search.admin.elastic-username"),type:"input"}).registerSetting({setting:"blomstra-search.elastic-password",label:r().translator.trans("blomstra-search.admin.elastic-password"),type:"password"}).registerSetting({setting:"blomstra-search.elastic-index",label:r().translator.trans("blomstra-search.admin.elastic-index"),default:"flarum",type:"input"}).registerSetting({setting:"blomstra-search.analyzer-language",label:r().translator.trans("blomstra-search.admin.analyzer.label"),help:r().translator.trans("blomstra-search.admin.analyzer.help"),type:"select",options:Object.fromEntries(a.entries()),default:"english"}).registerSetting({setting:"blomstra-search.elastic-index",label:r().translator.trans("blomstra-search.admin.elastic-index"),default:"flarum",type:"input"})}))})(),module.exports=e})();
|
(()=>{var a={n:t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return a.d(e,{a:e}),e},d:(t,e)=>{for(var s in e)a.o(e,s)&&!a.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:e[s]})},o:(a,t)=>Object.prototype.hasOwnProperty.call(a,t),r:a=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(a,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(a,"__esModule",{value:!0})}},t={};(()=>{"use strict";a.r(t);const e=flarum.core.compat["admin/app"];var s=a.n(e);s().initializers.add("blomstra-search",(function(){var a=new Map;["arabic","armenian","basque","bengali","brazilian","bulgarian","catalan","cjk","czech","danish","dutch","english","estonian","finnish","french","galician","german","greek","hindi","hungarian","indonesian","irish","italian","latvian","lithuanian","norwegian","persian","portuguese","romanian","russian","sorani","spanish","swedish","turkish","thai"].forEach((function(t){a.set(t,t)})),s().extensionData.for("blomstra-search").registerSetting({setting:"blomstra-search.elastic-endpoint",label:s().translator.trans("blomstra-search.admin.elastic-endpoint"),type:"input"}).registerSetting({setting:"blomstra-search.elastic-username",label:s().translator.trans("blomstra-search.admin.elastic-username"),type:"input"}).registerSetting({setting:"blomstra-search.elastic-password",label:s().translator.trans("blomstra-search.admin.elastic-password"),type:"password"}).registerSetting({setting:"blomstra-search.elastic-index",label:s().translator.trans("blomstra-search.admin.elastic-index"),default:"flarum",type:"input"}).registerSetting({setting:"blomstra-search.analyzer-language",label:s().translator.trans("blomstra-search.admin.analyzer.label"),help:s().translator.trans("blomstra-search.admin.analyzer.help"),type:"select",options:Object.fromEntries(a.entries()),default:"english"}).registerSetting({setting:"blomstra-search.elastic-index",label:s().translator.trans("blomstra-search.admin.elastic-index"),default:"flarum",type:"input"}).registerSetting({setting:"blomstra-search.search-discussion-subjects",label:s().translator.trans("blomstra-search.admin.search-discussion-subjects"),type:"switch"}).registerSetting({setting:"blomstra-search.search-post-bodies",label:s().translator.trans("blomstra-search.admin.search-post-bodies"),type:"switch"}).registerSetting({setting:"blomstra-search.match-sentences",label:s().translator.trans("blomstra-search.admin.match-sentences"),type:"switch"}).registerSetting({setting:"blomstra-search.match-words",label:s().translator.trans("blomstra-search.admin.match-words"),type:"switch"})}))})(),module.exports=t})();
|
||||||
//# sourceMappingURL=admin.js.map
|
//# sourceMappingURL=admin.js.map
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -78,5 +78,25 @@ app.initializers.add('blomstra-search', () => {
|
||||||
label: app.translator.trans('blomstra-search.admin.elastic-index'),
|
label: app.translator.trans('blomstra-search.admin.elastic-index'),
|
||||||
default: 'flarum',
|
default: 'flarum',
|
||||||
type: 'input',
|
type: 'input',
|
||||||
|
})
|
||||||
|
.registerSetting({
|
||||||
|
setting: 'blomstra-search.search-discussion-subjects',
|
||||||
|
label: app.translator.trans('blomstra-search.admin.search-discussion-subjects'),
|
||||||
|
type: 'switch',
|
||||||
|
})
|
||||||
|
.registerSetting({
|
||||||
|
setting: 'blomstra-search.search-post-bodies',
|
||||||
|
label: app.translator.trans('blomstra-search.admin.search-post-bodies'),
|
||||||
|
type: 'switch',
|
||||||
|
})
|
||||||
|
.registerSetting({
|
||||||
|
setting: 'blomstra-search.match-sentences',
|
||||||
|
label: app.translator.trans('blomstra-search.admin.match-sentences'),
|
||||||
|
type: 'switch',
|
||||||
|
})
|
||||||
|
.registerSetting({
|
||||||
|
setting: 'blomstra-search.match-words',
|
||||||
|
label: app.translator.trans('blomstra-search.admin.match-words'),
|
||||||
|
type: 'switch',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,7 @@ blomstra-search:
|
||||||
help: |
|
help: |
|
||||||
The analyzer makes search understand stop words and undertakes language
|
The analyzer makes search understand stop words and undertakes language
|
||||||
specific improvements for indexing.
|
specific improvements for indexing.
|
||||||
|
search-discussion-subjects: Search inside discussion titles
|
||||||
|
search-post-bodies: Search inside comments
|
||||||
|
match-sentences: Match search term against full sentence
|
||||||
|
match-words: Match search term against full words
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ use Blomstra\Search\Elasticsearch\MatchPhraseQuery;
|
||||||
use Blomstra\Search\Elasticsearch\MatchQuery;
|
use Blomstra\Search\Elasticsearch\MatchQuery;
|
||||||
use Blomstra\Search\Elasticsearch\TermsQuery;
|
use Blomstra\Search\Elasticsearch\TermsQuery;
|
||||||
use Blomstra\Search\Save\Document as ElasticDocument;
|
use Blomstra\Search\Save\Document as ElasticDocument;
|
||||||
|
use Blomstra\Search\Searchers\Searcher;
|
||||||
use Elasticsearch\Client;
|
use Elasticsearch\Client;
|
||||||
use Flarum\Api\Controller\ListDiscussionsController;
|
use Flarum\Api\Controller\ListDiscussionsController;
|
||||||
use Flarum\Api\Serializer\DiscussionSerializer;
|
use Flarum\Api\Serializer\DiscussionSerializer;
|
||||||
|
|
@ -24,6 +25,7 @@ use Flarum\Extension\ExtensionManager;
|
||||||
use Flarum\Group\Group;
|
use Flarum\Group\Group;
|
||||||
use Flarum\Http\RequestUtil;
|
use Flarum\Http\RequestUtil;
|
||||||
use Flarum\Http\UrlGenerator;
|
use Flarum\Http\UrlGenerator;
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
use Flarum\User\User;
|
use Flarum\User\User;
|
||||||
use Illuminate\Contracts\Container\Container;
|
use Illuminate\Contracts\Container\Container;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
|
@ -47,8 +49,23 @@ class SearchController extends ListDiscussionsController
|
||||||
'commentCount' => 'comment_count',
|
'commentCount' => 'comment_count',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(protected Client $elastic, protected UrlGenerator $uri)
|
protected Collection $searchers;
|
||||||
|
protected bool $matchSentences;
|
||||||
|
protected bool $matchWords;
|
||||||
|
|
||||||
|
public function __construct(protected Client $elastic, protected UrlGenerator $uri, Container $container, SettingsRepositoryInterface $settings)
|
||||||
{
|
{
|
||||||
|
$this->searchers = $this->gatherSearchers($container->tagged('blomstra.search.searchers'));
|
||||||
|
|
||||||
|
$this->matchSentences = (bool) $settings->get('blomstra-search.match-sentences', true);
|
||||||
|
$this->matchWords = (bool) $settings->get('blomstra-search.match-words', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function gatherSearchers(iterable $searchers)
|
||||||
|
{
|
||||||
|
return collect($searchers)
|
||||||
|
->map(fn ($searcher) => new $searcher)
|
||||||
|
->filter(fn (Searcher $searcher) => $searcher->enabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function data(ServerRequestInterface $request, Document $document)
|
protected function data(ServerRequestInterface $request, Document $document)
|
||||||
|
|
@ -69,14 +86,11 @@ class SearchController extends ListDiscussionsController
|
||||||
|
|
||||||
$filterQuery = BoolQuery::create();
|
$filterQuery = BoolQuery::create();
|
||||||
|
|
||||||
if (!empty($search)) {
|
|
||||||
$filterQuery
|
if (! empty($search)) {
|
||||||
// @todo commented out to use only partial matching for now
|
if ($this->matchSentences) $filterQuery->add($this->sentenceMatch($search));
|
||||||
->add($this->sentenceMatch($search))
|
if ($this->matchWords) $filterQuery->add($this->wordMatch($search, 'and'));
|
||||||
->add($this->wordMatch($search, 'and'))
|
if ($this->matchWords) $filterQuery->add($this->wordMatch($search, 'or'));
|
||||||
->add($this->wordMatch($search, 'or'))
|
|
||||||
// ->add($this->partialMatch($search))
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$builder = (new Builder($this->elastic))
|
$builder = (new Builder($this->elastic))
|
||||||
|
|
@ -232,70 +246,40 @@ class SearchController extends ListDiscussionsController
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function boolQuery(Query $parent, float $boost = 1): Query
|
||||||
|
{
|
||||||
|
$bool = new BoolQuery;
|
||||||
|
|
||||||
|
/** @var Searcher $searcher */
|
||||||
|
foreach ($this->searchers as $searcher) {
|
||||||
|
$searcher = new $searcher;
|
||||||
|
|
||||||
|
$bool->add(
|
||||||
|
BoolQuery::create()
|
||||||
|
->add(TermQuery::create('type', $searcher->type()), 'filter')
|
||||||
|
->add(clone $parent->boost($boost * $searcher->boost())),
|
||||||
|
'should'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bool;
|
||||||
|
}
|
||||||
|
|
||||||
protected function sentenceMatch(string $q): Query
|
protected function sentenceMatch(string $q): Query
|
||||||
{
|
{
|
||||||
$query = (new MatchPhraseQuery('content', $q));
|
$query = (new MatchPhraseQuery('content', $q));
|
||||||
|
|
||||||
return BoolQuery::create()
|
return $this->boolQuery($query, 2);
|
||||||
// Discussion titles
|
|
||||||
->add(
|
|
||||||
BoolQuery::create()
|
|
||||||
->add(TermQuery::create('type', 'discussions'), 'filter')
|
|
||||||
->add($query->boost(2)),
|
|
||||||
'should'
|
|
||||||
)
|
|
||||||
// Post bodies
|
|
||||||
->add(
|
|
||||||
BoolQuery::create()
|
|
||||||
->add(TermQuery::create('type', 'posts'), 'filter')
|
|
||||||
->add($query->boost(1.9)),
|
|
||||||
'should'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function wordMatch(string $q, string $operator = 'or')
|
protected function wordMatch(string $q, string $operator = 'or'): Query
|
||||||
{
|
{
|
||||||
$query = (new MatchQuery('content', $q))
|
$query = (new MatchQuery('content', $q))
|
||||||
->operator($operator);
|
->operator($operator);
|
||||||
|
|
||||||
$boost = $operator === 'and' ? 1 : .8;
|
$boost = $operator === 'and' ? 1.8 : .8;
|
||||||
|
|
||||||
return BoolQuery::create()
|
return $this->boolQuery($query, $boost);
|
||||||
// Discussion titles
|
|
||||||
->add(
|
|
||||||
BoolQuery::create()
|
|
||||||
->add(TermQuery::create('type', 'discussions'), 'filter')
|
|
||||||
->add($query->boost($boost * 1.8)),
|
|
||||||
'should'
|
|
||||||
)
|
|
||||||
// Post bodies
|
|
||||||
->add(
|
|
||||||
BoolQuery::create()
|
|
||||||
->add(TermQuery::create('type', 'posts'), 'filter')
|
|
||||||
->add($query->boost($boost * 1.8)),
|
|
||||||
'should'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function partialMatch(string $q)
|
|
||||||
{
|
|
||||||
$query = (new MatchQuery('content', $q));
|
|
||||||
|
|
||||||
return BoolQuery::create()
|
|
||||||
// Discussion titles
|
|
||||||
->add(
|
|
||||||
BoolQuery::create()
|
|
||||||
->add(TermQuery::create('type', 'discussions'), 'filter')
|
|
||||||
->add(clone $query->boost(1.6)),
|
|
||||||
'should'
|
|
||||||
)
|
|
||||||
// Post bodies
|
|
||||||
->add(
|
|
||||||
BoolQuery::create()
|
|
||||||
->add(TermQuery::create('type', 'posts'), 'filter')
|
|
||||||
->add(clone $query->boost(1.3)),
|
|
||||||
'should'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getGroups(User $actor): Collection
|
protected function getGroups(User $actor): Collection
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ namespace Blomstra\Search;
|
||||||
use Blomstra\Search\Jobs\DeletingJob;
|
use Blomstra\Search\Jobs\DeletingJob;
|
||||||
use Blomstra\Search\Jobs\Job;
|
use Blomstra\Search\Jobs\Job;
|
||||||
use Blomstra\Search\Jobs\SavingJob;
|
use Blomstra\Search\Jobs\SavingJob;
|
||||||
|
use Blomstra\Search\Searchers;
|
||||||
|
use Blomstra\Search\Seeders;
|
||||||
use Elasticsearch\Client as Elastic;
|
use Elasticsearch\Client as Elastic;
|
||||||
use Elasticsearch\ClientBuilder;
|
use Elasticsearch\ClientBuilder;
|
||||||
use Flarum\Api\Client;
|
use Flarum\Api\Client;
|
||||||
|
|
@ -87,6 +89,11 @@ class Provider extends AbstractServiceProvider
|
||||||
return new Api\Client($pipe);
|
return new Api\Client($pipe);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->container->tag([
|
||||||
|
Searchers\DiscussionSearcher::class,
|
||||||
|
Searchers\CommentPostSearcher::class,
|
||||||
|
], 'blomstra.search.searchers');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function boot()
|
public function boot()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Blomstra\Search\Searchers;
|
||||||
|
|
||||||
|
use Blomstra\Search\Seeders\CommentSeeder;
|
||||||
|
|
||||||
|
class CommentPostSearcher extends Searcher
|
||||||
|
{
|
||||||
|
protected string|null $seeder = CommentSeeder::class;
|
||||||
|
|
||||||
|
public function enabled(): bool
|
||||||
|
{
|
||||||
|
$enabled = $this->setting('blomstra-search.search-post-bodies', true);
|
||||||
|
|
||||||
|
return boolval($enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Blomstra\Search\Searchers;
|
||||||
|
|
||||||
|
use Blomstra\Search\Seeders\DiscussionSeeder;
|
||||||
|
|
||||||
|
class DiscussionSearcher extends Searcher
|
||||||
|
{
|
||||||
|
protected string|null $seeder = DiscussionSeeder::class;
|
||||||
|
|
||||||
|
public function enabled(): bool
|
||||||
|
{
|
||||||
|
$enabled = $this->setting('blomstra-search.search-discussion-subjects', true);
|
||||||
|
|
||||||
|
return boolval($enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boost(): float
|
||||||
|
{
|
||||||
|
return 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Blomstra\Search\Searchers;
|
||||||
|
|
||||||
|
use Blomstra\Search\Seeders\Seeder;
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
|
||||||
|
abstract class Searcher
|
||||||
|
{
|
||||||
|
protected string|null $seeder = null;
|
||||||
|
|
||||||
|
public function type(): string
|
||||||
|
{
|
||||||
|
/** @var Seeder $seeder */
|
||||||
|
$seeder = $this->seeder;
|
||||||
|
|
||||||
|
if (empty($seeder)) throw new \InvalidArgumentException("Implement type or add \$seeder");
|
||||||
|
|
||||||
|
return (new $seeder)->type();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function enabled(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boost(): float
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setting(string $key, $default = null)
|
||||||
|
{
|
||||||
|
return resolve(SettingsRepositoryInterface::class)->get($key, $default);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue