Skip to content

Commit

Permalink
Auto load pages on content search (#6901)
Browse files Browse the repository at this point in the history
* Auto load pages on content search

* Fix default page size for tests

* Cache content searching by portion of 10 pages

* Refactor search methods

* Cache only Content ID on searching
  • Loading branch information
yurabakhtin committed Apr 2, 2024
1 parent c1015fa commit 317d598
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 70 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-DEV.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,4 @@ HumHub Changelog
- Enh #6892: Implement new method `getCommentUrl` for comment permanent URL
- Enh #6904: Content Search: Add Tests regarding `state`
- Fix #6908: Fix default mentioning URL
- Enh #6901: Auto load pages on content search
35 changes: 30 additions & 5 deletions protected/humhub/modules/content/controllers/SearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

use humhub\components\Controller;
use humhub\modules\content\Module;
use humhub\modules\content\search\ResultSet;
use humhub\modules\content\search\SearchRequest;
use humhub\modules\content\widgets\stream\StreamEntryWidget;
use humhub\modules\content\widgets\stream\WallStreamEntryOptions;
use Yii;

/**
Expand Down Expand Up @@ -37,14 +40,36 @@ public function actionResults()
{
$resultSet = null;

$this->searchRequest = new SearchRequest();
$this->searchRequest = new SearchRequest(['pageSize' => 3]);
if ($this->searchRequest->load(Yii::$app->request->get(), '') && $this->searchRequest->validate()) {
$resultSet = $this->module->getSearchDriver()->search($this->searchRequest);
$resultSet = $this->module->getSearchDriver()->searchCached($this->searchRequest, 10);
}

return $this->renderAjax('results', [
'searchRequest' => $this->searchRequest,
'resultSet' => $resultSet,
$page = $resultSet ? $resultSet->pagination->getPage() + 1 : 1;
$totalCount = $resultSet ? $resultSet->pagination->totalCount : 0;
$results = $this->renderResults($resultSet);

return $this->asJson([
'content' => $page > 1
? $results
: $this->renderAjax('results', ['results' => $results, 'totalCount' => $totalCount]),
'page' => $page,
'isLast' => $results === '' || !$resultSet || $page === $resultSet->pagination->getPageCount()
]);
}

private function renderResults($resultSet): ?string
{
if (!($resultSet instanceof ResultSet)) {
return null;
}

$results = '';
$options = (new WallStreamEntryOptions())->viewContext(WallStreamEntryOptions::VIEW_CONTEXT_SEARCH);
foreach ($resultSet->results as $result) {
$results .= StreamEntryWidget::renderStreamEntry($result->getModel(), $options);
}

return $results;
}
}
16 changes: 16 additions & 0 deletions protected/humhub/modules/content/search/ResultSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,20 @@ class ResultSet
* @var Pagination
*/
public $pagination;

public function __serialize(): array
{
return [
'results' => array_map(function (Content $result) {return $result->id;}, $this->results),
'pagination' => $this->pagination
];
}

public function __unserialize(array $data): void
{
$this->pagination = $data['pagination'];
$this->results = empty($data['results'])
? []
: Content::find()->where(['IN', 'id', $data['results']])->all();
}
}
2 changes: 1 addition & 1 deletion protected/humhub/modules/content/search/SearchRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class SearchRequest extends Model

public $page = 1;

public $pageSize = 25;
public int $pageSize = 25;

public $contentType = '';

Expand Down
58 changes: 54 additions & 4 deletions protected/humhub/modules/content/search/driver/AbstractDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
use humhub\modules\content\models\Content;
use humhub\modules\content\search\ResultSet;
use humhub\modules\content\search\SearchRequest;
use Yii;
use yii\base\Component;
use yii\base\Model;

abstract class AbstractDriver extends Component
{
Expand All @@ -16,18 +18,66 @@ abstract public function update(Content $content): void;
abstract public function delete(Content $content): void;

/**
* Search
* Run search process
*
* // Add private content, which is in Space content containers where the user is member of
* // Add private content, of User content containers where the user is friend or self
*
* // Add all public content
* @param $query
*
* @param SearchRequest $request
* @return mixed
* @return ResultSet
*/
abstract public function search(SearchRequest $request): ResultSet;

/**
* Run search process and cache results
*
* @param SearchRequest $request
* @param int Number of pages that should be cached, 0 - don't cache
* @return ResultSet
*/
public function searchCached(SearchRequest $request, int $cachePageNumber = 0): ResultSet
{
if ($cachePageNumber < 1) {
// Search results without caching
return $this->search($request);
}

// Store original pagination
$origPage = $request->page - 1;
$origPageSize = $request->pageSize;

// Set pagination to load results from DB or Cache with bigger portion than original page size
$cachePageSize = $origPageSize * $cachePageNumber;
$cachePage = (int) ceil(($request->page * $origPageSize) / $cachePageSize);
$request->page = $cachePage;
$request->pageSize = $cachePageSize;

/* @var ResultSet $resultSet */
// Load results from cache or Search & Cache
$resultSet = Yii::$app->cache->getOrSet($this->getSearchCacheKey($request), function () use ($request) {
return $this->search($request);
});

// Extract part of results only for the current(original requested) page
$slicePageStart = ($origPage - ($cachePageSize * ($cachePage - 1)) / $origPageSize) * $origPageSize;
$resultSet->results = array_slice($resultSet->results, $slicePageStart, $origPageSize);

// Revert original pagination for correct working with AJAX response
$resultSet->pagination->setPage($origPage);
$resultSet->pagination->setPageSize($origPageSize);

return $resultSet;
}

protected function getSearchCacheKey(SearchRequest $request): string
{
$requestFilters = array_filter($request->getAttributes(), function ($value) {
return is_scalar($value) || is_array($value);
});

return static::class . Yii::$app->user->id . sha1(json_encode($requestFilters));
}

public static function rebuild($showDots = false)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ public function delete(Content $content): void
ContentFulltext::deleteAll(['content_id' => $content->id]);
}

/**
* @inheritdoc
*/
public function search(SearchRequest $request): ResultSet
{
$query = Content::find();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ public function delete(Content $content): void
$this->commit();
}

/**
* @inheritdoc
*/
public function search(SearchRequest $request): ResultSet
{
$query = $this->buildSearchQuery($request);
Expand Down
48 changes: 7 additions & 41 deletions protected/humhub/modules/content/views/search/results.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,10 @@
* @license https://www.humhub.com/licences
*/

use humhub\libs\Html;
use humhub\modules\content\search\ResultSet;
use humhub\modules\content\search\SearchRequest;
use humhub\modules\content\widgets\stream\StreamEntryWidget;
use humhub\modules\content\widgets\stream\WallStreamEntryOptions;
use humhub\widgets\AjaxLinkPager;

/* @var $resultSet ResultSet|null */
/* @var $searchRequest SearchRequest */

$hasResults = $resultSet !== null && count($resultSet->results);
/* @var $results string|null */
/* @var $totalCount int */
?>
<?php if (!$hasResults && $resultSet !== null): ?>
<?php if ($results === '') : ?>
<div class="row cards">
<div class="col-md-12">
<div class="panel panel-default">
Expand All @@ -28,36 +19,11 @@
</div>
</div>
</div>
<?php endif; ?>

<?php if ($hasResults): ?>
<?php elseif ($results !== null) : ?>
<div class="search-results-header">
<?= Yii::t('ContentModule.search', 'Results ({count})', ['count' => $resultSet->pagination->totalCount]) ?>
<?= Yii::t('ContentModule.search', 'Results ({count})', ['count' => $totalCount]) ?>
</div>
<div class="search-results">
<?php foreach ($resultSet->results as $result): ?>
<?= StreamEntryWidget::renderStreamEntry($result->getModel(),
(new WallStreamEntryOptions())->viewContext(WallStreamEntryOptions::VIEW_CONTEXT_SEARCH)) ?>
<?php endforeach; ?>
<div class="search-results" data-stream-action-form-results>
<?= $results ?>
</div>
<div class="pagination-container">
<?= AjaxLinkPager::widget([
'pagination' => $resultSet->pagination,
'linkOptions' => ['data' => ['action-click' => 'switchPage']]
]) ?>
</div>
<?php endif; ?>

<?php if ($hasResults && $searchRequest->keyword !== '') : ?>
<script <?= Html::nonce() ?>>
$('.search-results [data-ui-widget]').on('afterInit', function() {
if ($(this).data('isHighlighted')) {
return;
}
$(this).data('isHighlighted', true);
<?php foreach (explode(' ', $searchRequest->keyword) as $keyword) : ?>
$(this).highlight('<?= Html::encode($keyword) ?>');
<?php endforeach; ?>
});
</script>
<?php endif; ?>
2 changes: 1 addition & 1 deletion protected/humhub/modules/content/widgets/SearchFilters.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected function initDefaultFilters()
'title' => Yii::t('ContentModule.search', 'Find Content based on keywords'),
'placeholder' => Yii::t('ContentModule.search', 'Search...'),
'type' => 'input',
'inputOptions' => ['autocomplete' => 'off'],
'inputOptions' => ['autocomplete' => 'off', 'data-highlight' => '.search-results'],
'wrapperClass' => 'col-md-6 form-search-filter-keyword',
'afterInput' => Html::submitButton('<span class="fa fa-search"></span>', ['class' => 'form-button-search']),
'sortOrder' => 100,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,63 @@ humhub.module('stream.SimpleStream', function (module, require, $) {
return;
}

const submit = function (form) {
const params = form.serialize().replace(/(^|&)r=.*?(&|$)/, '$1');
that.actionForm = form;
that.highlightInput = form.find('input[data-highlight]');

that.actionForm.setContent = function (content) {
const resultsContent = that.getActionContent().find('[data-stream-action-form-results]');
if (resultsContent.length) {
resultsContent.append(content);
} else {
that.getActionContent().html(content);
}
const widgets = that.getActionContent().find(that.highlightInput.data('highlight') + ' [data-ui-widget]');

if (that.highlightInput.length && that.highlightInput.val() !== '') {
widgets.on('afterInit', function() {
if (!$(this).data('isHighlighted')) {
$(this).data('isHighlighted', true);
that.highlightInput.val().split(' ').forEach((keyword) => $(this).highlight(keyword));
}
});
}

widgets.each(function () {
Widget.instance($(this));
});
}

const filters = function () {
return form.serialize().replace(/(^|&)r=.*?(&|$)/, '$1');
}

const setStreamUrl = function () {
// Set stream URL from the form action URL and current filters
that.options.stream = form.data('action-url')
+ (form.data('action-url').indexOf('?') === -1 ? '?' : '&')
+ filters();
}

form.on('submit', function (e) {
e.preventDefault();
const params = filters();
const content = that.getActionContent();
loader.set(content);
that.refreshAddressBar(params);
setStreamUrl();

client.get(form.data('action-url'), {data: params}).then(function (response) {
content.html(response.response).find('[data-ui-widget]').each(function () {
Widget.instance($(this));
});
that.handleResponseActionForm(response);
}).catch(function (err) {
module.log.error(err, true);
});
}
});

form.on('submit', function (e) {
e.preventDefault();
submit($(this));
}).first().submit();
setStreamUrl();

// Prevent auto loading of stream content when the action form is used instead
that.options.autoLoad = false;
// Activate auto load next pages by scroll down action
that.options.scrollSupport = true;
that.options.scrollOptions = {root: null, rootMargin: '100px'};
};

SimpleStream.prototype.onEmptyStream = function () {
Expand Down Expand Up @@ -157,5 +192,30 @@ humhub.module('stream.SimpleStream', function (module, require, $) {
});
};

SimpleStream.prototype.handleResponse = function (request) {
return typeof this.actionForm !== 'undefined'
? this.handleResponseActionForm(request.response)
: Stream.prototype.handleResponse.call(this, request);
}

SimpleStream.prototype.handleResponseActionForm = function (response) {
this.actionForm.setContent(response.content);
this.state.lastEntryLoaded = response.isLast;

// Update stream URL for next page
this.options.stream = this.options.stream.replace(/(\?|&)page=\d+/i, '$1')
+ '&page=' + (parseInt(response.page) + 1);
this.options.stream = this.options.stream.replace('?&', '?').replace('&&', '&');

// Load next page when the stream end indicator is visible on screen
const streamEnd = this.$content.find('.stream-end:first');
if (streamEnd.length && !response.isLast &&
streamEnd.offset().top - 100 < $(window).scrollTop() + $(window).innerHeight()) {
this.load();
}

return Promise.resolve();
}

module.export = SimpleStream;
});
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ humhub.module('stream.Stream', function (module, require, $) {
contentSelector: "[data-stream-content]",
streamEntryClass: StreamEntry,
loadCount: STREAM_LOAD_COUNT,
initLoadCount: STREAM_INIT_COUNT,
autoLoad: true
initLoadCount: STREAM_INIT_COUNT
};
};

Expand All @@ -184,10 +183,6 @@ humhub.module('stream.Stream', function (module, require, $) {
this.initWidget();
}

if (!this.options.autoLoad) {
return Promise.resolve();
}

return this.clear()
.show()
.loadInit()
Expand Down

0 comments on commit 317d598

Please sign in to comment.