wip
This commit is contained in:
commit
2bf4ec01d0
|
|
@ -0,0 +1,11 @@
|
||||||
|
root = true
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
[*.md]
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
.gitattributes export-ignore
|
||||||
|
.gitignore export-ignore
|
||||||
|
.gitmodules export-ignore
|
||||||
|
.travis.yml export-ignore
|
||||||
|
changelog.md export-ignore
|
||||||
|
tests export-ignore
|
||||||
|
phpunit.xml export-ignore
|
||||||
|
js/*/src export-ignore
|
||||||
|
js/*/Gulpfile.js
|
||||||
|
js/*/package.json
|
||||||
|
js/*/yarn.lock
|
||||||
|
|
||||||
|
js/*/dist/*.js -diff
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
name: Build JavaScript assets
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
- uses: flarum/action-build@master
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
js/node_modules
|
||||||
|
js/dist
|
||||||
|
vendor/
|
||||||
|
composer.lock
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"name": "blomstra/search",
|
||||||
|
"description": "Replaces Flarum search with a fulltext search server.",
|
||||||
|
"keywords": [
|
||||||
|
"extension",
|
||||||
|
"flarum", "blomstra",
|
||||||
|
"search", "fulltext"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"email": "helpdesk@blomstra.net",
|
||||||
|
"forum": "https://blomstra.community/t/ext-search"
|
||||||
|
},
|
||||||
|
"type": "flarum-extension",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Daniël Klabbers",
|
||||||
|
"email": "daniel@klabbers.email",
|
||||||
|
"homepage": "http://luceos.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">= 8.0",
|
||||||
|
"flarum/core": "^1.0.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"meilisearch/meilisearch-php": "Required to use the MeiliSearch engine (^0.19)."
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"flarum-extension": {
|
||||||
|
"title": "Search",
|
||||||
|
"category": "feature",
|
||||||
|
"icon": {
|
||||||
|
"image": "resources/assets/logo.svg",
|
||||||
|
"backgroundColor": "#E3E7F1",
|
||||||
|
"backgroundSize": "100%",
|
||||||
|
"backgroundRepeat": "no-repeat",
|
||||||
|
"backgroundPosition": "center"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Blomstra\\Search\\": "src/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Blomstra\Search;
|
||||||
|
|
||||||
|
use Flarum\Extend as Flarum;
|
||||||
|
|
||||||
|
return [
|
||||||
|
(new Flarum\ServiceProvider)->register(Provider::class),
|
||||||
|
|
||||||
|
(new Flarum\Frontend('forum'))
|
||||||
|
->js(__DIR__ . '/js/dist/forum.js'),
|
||||||
|
|
||||||
|
(new Flarum\Routes('api'))
|
||||||
|
->get('blomstra/search', 'blomstra.search', Api\Controllers\SearchController::class)
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './src/forum';
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "@blomstra/realtime",
|
||||||
|
"dependencies": {
|
||||||
|
"flarum-webpack-config": "^1.0.0",
|
||||||
|
"lodash-es": "^4.17.14",
|
||||||
|
"pusher-js": "^7.0.3",
|
||||||
|
"webpack": "^4.43.0",
|
||||||
|
"webpack-cli": "^3.3.11"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "webpack --mode development --watch",
|
||||||
|
"build": "webpack --mode production",
|
||||||
|
"lint": "prettier --single-quote --trailing-comma es5 --print-width 150 --tab-width 4 --write src"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^2.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import app from 'flarum/app';
|
||||||
|
import highlight from 'flarum/common/helpers/highlight';
|
||||||
|
import LinkButton from 'flarum/common/components/LinkButton';
|
||||||
|
import Link from 'flarum/common/components/Link';
|
||||||
|
import { SearchSource } from 'flarum/forum/components/Search';
|
||||||
|
import type Mithril from 'mithril';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `DiscussionsSearchSource` finds and displays discussion search results in
|
||||||
|
* the search dropdown.
|
||||||
|
*/
|
||||||
|
export default class Source implements SearchSource {
|
||||||
|
protected results = new Map<string, unknown[]>();
|
||||||
|
|
||||||
|
search(query: string) {
|
||||||
|
query = query.toLowerCase();
|
||||||
|
|
||||||
|
this.results.set(query, []);
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
filter: { q: query },
|
||||||
|
page: { limit: 3 },
|
||||||
|
include: 'mostRelevantPost',
|
||||||
|
};
|
||||||
|
|
||||||
|
return app.request(
|
||||||
|
app.forum.attribute('apiUrl') + '/blomstra/search',
|
||||||
|
params
|
||||||
|
).then((results) => this.results.set(query, results));
|
||||||
|
}
|
||||||
|
|
||||||
|
view(query: string): Array<Mithril.Vnode> {
|
||||||
|
query = query.toLowerCase();
|
||||||
|
|
||||||
|
const results = (this.results.get(query) || []).map((discussion: unknown) => {
|
||||||
|
const mostRelevantPost = discussion.mostRelevantPost();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className="DiscussionSearchResult" data-index={'discussions' + discussion.id()}>
|
||||||
|
<Link href={app.route.discussion(discussion, mostRelevantPost && mostRelevantPost.number())}>
|
||||||
|
<div className="DiscussionSearchResult-title">{highlight(discussion.title(), query)}</div>
|
||||||
|
{mostRelevantPost ? <div className="DiscussionSearchResult-excerpt">{highlight(mostRelevantPost.contentPlain(), query, 100)}</div> : ''}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}) as Array<Mithril.Vnode>;
|
||||||
|
|
||||||
|
return [
|
||||||
|
<li className="Dropdown-header">{app.translator.trans('core.forum.search.discussions_heading')}</li>,
|
||||||
|
<li>
|
||||||
|
<LinkButton icon="fas fa-search" href={app.route('index', { q: query })}>
|
||||||
|
{app.translator.trans('core.forum.search.all_discussions_button', { query })}
|
||||||
|
</LinkButton>
|
||||||
|
</li>,
|
||||||
|
...results,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import app from 'flarum/app';
|
||||||
|
import { extend } from 'flarum/common/extend';
|
||||||
|
import Search from 'flarum/forum/components/Search';
|
||||||
|
import Source from './Source';
|
||||||
|
|
||||||
|
app.initializers.add('blomstra-search', () => {
|
||||||
|
extend(Search.prototype, 'sourceItems', function (items) {
|
||||||
|
items.remove('users');
|
||||||
|
items.remove('discussions');
|
||||||
|
|
||||||
|
items.add('discussions', new Source());
|
||||||
|
|
||||||
|
console.log(items)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
const config = require('flarum-webpack-config');
|
||||||
|
|
||||||
|
module.exports = config();
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Blomstra\Search\Api\Controllers;
|
||||||
|
|
||||||
|
use Flarum\Api\Controller\AbstractListController;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Tobscure\JsonApi\Document;
|
||||||
|
|
||||||
|
class SearchController extends AbstractListController
|
||||||
|
{
|
||||||
|
protected function data(ServerRequestInterface $request, Document $document)
|
||||||
|
{
|
||||||
|
// TODO: Implement data() method.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Blomstra\Search;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
class Mapping extends Collection
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Blomstra\Search\Observe;
|
||||||
|
|
||||||
|
use Blomstra\Search\Mapping;
|
||||||
|
use Blomstra\Search\Searchables\Searchable;
|
||||||
|
use Flarum\Queue\AbstractJob;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use MeiliSearch\Client;
|
||||||
|
|
||||||
|
class Job extends AbstractJob
|
||||||
|
{
|
||||||
|
public function __construct(protected Model $model)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public function handle(Client $meili, Mapping $mapping)
|
||||||
|
{
|
||||||
|
$map = $mapping->get(get_class($this->model));
|
||||||
|
|
||||||
|
/** @var Searchable $searchable */
|
||||||
|
$searchable = new $map['searchable']($this->model);
|
||||||
|
|
||||||
|
$body = array_merge([
|
||||||
|
$searchable->fulltext()
|
||||||
|
], [
|
||||||
|
$this->model->getKeyName() => $this->model->getKey()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$meili->index($map['index'])->addDocuments(
|
||||||
|
[$body],
|
||||||
|
$this->model->getKeyName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Blomstra\Search\Observe;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Queue\Queue;
|
||||||
|
|
||||||
|
class Observer
|
||||||
|
{
|
||||||
|
|
||||||
|
public function saved($model)
|
||||||
|
{
|
||||||
|
$this->queue()->push(new Job($model));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleted($model)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function queue(): Queue
|
||||||
|
{
|
||||||
|
return resolve(Queue::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Blomstra\Search;
|
||||||
|
|
||||||
|
use Blomstra\Search\Observe\Observer;
|
||||||
|
use Flarum\Foundation\AbstractServiceProvider;
|
||||||
|
use Flarum\Post\CommentPost;
|
||||||
|
use Illuminate\Contracts\Container\Container;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use MeiliSearch\Client;
|
||||||
|
|
||||||
|
class Provider extends AbstractServiceProvider
|
||||||
|
{
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
$this->container->singleton(Mapping::class, function () {
|
||||||
|
return new Mapping([
|
||||||
|
\Flarum\Discussion\Discussion::class => [
|
||||||
|
'index' => 'discussions',
|
||||||
|
'searchable' => \Blomstra\Search\Searchables\Discussion::class
|
||||||
|
],
|
||||||
|
CommentPost::class => [
|
||||||
|
'index' => 'posts',
|
||||||
|
'searchable' => null,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->container->singleton(Client::class, function (Container $container) {
|
||||||
|
$config = $container->make('flarum.config') ?? [];
|
||||||
|
|
||||||
|
$meili = Arr::get($config, 'meili');
|
||||||
|
|
||||||
|
if (! $meili) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Client($meili);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
/** @var Mapping $searchables */
|
||||||
|
$searchables = $this->container->get(Mapping::class);
|
||||||
|
|
||||||
|
$searchables->each(function ($mapped, $model) {
|
||||||
|
forward_static_call(
|
||||||
|
[$model, 'observe'],
|
||||||
|
Observer::class
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Blomstra\Search\Searchables;
|
||||||
|
|
||||||
|
use Flarum\Discussion\Discussion as Model;
|
||||||
|
use Flarum\Tags\Tag;
|
||||||
|
|
||||||
|
class Discussion extends Searchable
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected Model $discussion
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function filters(): ?array
|
||||||
|
{
|
||||||
|
if ($this->extensionEnabled('flarum-tags')) {
|
||||||
|
return $this->discussion->tags->map(function (Tag $tag) {
|
||||||
|
return "tag:$tag->id";
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fulltext(): ?array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'title' => $this->discussion->title,
|
||||||
|
'content' => $this->discussion->firstPost->content
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Blomstra\Search\Searchables;
|
||||||
|
|
||||||
|
use Flarum\Extension\ExtensionManager;
|
||||||
|
|
||||||
|
abstract class Searchable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns an array of filterable attributes.
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
abstract public function filters(): ?array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of strings with which fulltext matching can be executed.
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
abstract public function fulltext(): ?array;
|
||||||
|
|
||||||
|
protected function extensionEnabled(string $extension): bool
|
||||||
|
{
|
||||||
|
/** @var ExtensionManager $manager */
|
||||||
|
$manager = resolve(ExtensionManager::class);
|
||||||
|
|
||||||
|
return $manager->isEnabled($extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue