diff --git a/js/forum/dist/app.js b/js/forum/dist/app.js
index f3bccdee24..295c71f49b 100644
--- a/js/forum/dist/app.js
+++ b/js/forum/dist/app.js
@@ -19740,10 +19740,10 @@ System.register('flarum/components/CommentPost', ['flarum/components/Post', 'fla
});;
'use strict';
-System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils/ItemList', 'flarum/components/ComposerButton', 'flarum/helpers/listItems', 'flarum/utils/classList', 'flarum/utils/computed'], function (_export, _context) {
+System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils/ItemList', 'flarum/components/ComposerButton', 'flarum/helpers/listItems', 'flarum/utils/classList'], function (_export, _context) {
"use strict";
- var Component, ItemList, ComposerButton, listItems, classList, computed, Composer;
+ var Component, ItemList, ComposerButton, listItems, classList, Composer;
return {
setters: [function (_flarumComponent) {
Component = _flarumComponent.default;
@@ -19755,8 +19755,6 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
listItems = _flarumHelpersListItems.default;
}, function (_flarumUtilsClassList) {
classList = _flarumUtilsClassList.default;
- }, function (_flarumUtilsComputed) {
- computed = _flarumUtilsComputed.default;
}],
execute: function () {
Composer = function (_Component) {
@@ -19791,28 +19789,6 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
* @type {Boolean}
*/
this.active = false;
-
- /**
- * Computed the composer's current height, based on the intended height, and
- * the composer's current state. This will be applied to the composer's
- * content's DOM element.
- *
- * @return {Integer}
- */
- this.computedHeight = computed('height', 'position', function (height, position) {
- // If the composer is minimized, then we don't want to set a height; we'll
- // let the CSS decide how high it is. If it's fullscreen, then we need to
- // make it as high as the window.
- if (position === Composer.PositionEnum.MINIMIZED) {
- return '';
- } else if (position === Composer.PositionEnum.FULLSCREEN) {
- return $(window).height();
- }
-
- // Otherwise, if it's normal or hidden, then we use the intended height.
- // We don't let the composer get too small or too big, though.
- return Math.max(200, Math.min(height, $(window).height() - $('#header').outerHeight()));
- });
}
}, {
key: 'view',
@@ -19853,12 +19829,6 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
value: function config(isInitialized, context) {
var _this2 = this;
- var defaultHeight = void 0;
-
- if (!isInitialized) {
- defaultHeight = this.$().height();
- }
-
// Set the height of the Composer element and its contents on each redraw,
// so that they do not lose it if their DOM elements are recreated.
this.updateHeight();
@@ -19869,11 +19839,8 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
// routes, we will flag the DOM to be retained across route changes.
context.retain = true;
- // Initialize the composer's intended height based on what the user has set
- // it at previously, or otherwise the composer's default height. After that,
- // we'll hide the composer.
- this.height = localStorage.getItem('composerHeight') || defaultHeight;
- this.$().hide().css('bottom', -this.height);
+ this.initializeHeight();
+ this.$().hide().css('bottom', -this.computedHeight());
// Whenever any of the inputs inside the composer are have focus, we want to
// add a class to the composer to draw attention to it.
@@ -19932,8 +19899,7 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
// height so that it fills the height of the composer, and update the
// body's padding.
var deltaPixels = this.mouseStart - e.clientY;
- this.height = this.heightStart + deltaPixels;
- this.updateHeight();
+ this.changeHeight(this.heightStart + deltaPixels);
// Update the body's padding-bottom so that no content on the page will ever
// get permanently hidden behind the composer. If the user is already
@@ -19942,8 +19908,6 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
var scrollTop = $(window).scrollTop();
var anchorToBottom = scrollTop > 0 && scrollTop + $(window).height() >= $(document).height();
this.updateBodyPadding(anchorToBottom);
-
- localStorage.setItem('composerHeight', this.height);
}
}, {
key: 'onmouseup',
@@ -20172,6 +20136,54 @@ System.register('flarum/components/Composer', ['flarum/Component', 'flarum/utils
return items;
}
+ }, {
+ key: 'initializeHeight',
+ value: function initializeHeight() {
+ this.height = localStorage.getItem('composerHeight');
+
+ if (!this.height) {
+ this.height = this.defaultHeight();
+ }
+ }
+ }, {
+ key: 'defaultHeight',
+ value: function defaultHeight() {
+ return this.$().height();
+ }
+ }, {
+ key: 'minimumHeight',
+ value: function minimumHeight() {
+ return 200;
+ }
+ }, {
+ key: 'maximumHeight',
+ value: function maximumHeight() {
+ return $(window).height() - $('#header').outerHeight();
+ }
+ }, {
+ key: 'computedHeight',
+ value: function computedHeight() {
+ // If the composer is minimized, then we don't want to set a height; we'll
+ // let the CSS decide how high it is. If it's fullscreen, then we need to
+ // make it as high as the window.
+ if (this.position === Composer.PositionEnum.MINIMIZED) {
+ return '';
+ } else if (this.position === Composer.PositionEnum.FULLSCREEN) {
+ return $(window).height();
+ }
+
+ // Otherwise, if it's normal or hidden, then we use the intended height.
+ // We don't let the composer get too small or too big, though.
+ return Math.max(this.minimumHeight(), Math.min(this.height, this.maximumHeight()));
+ }
+ }, {
+ key: 'changeHeight',
+ value: function changeHeight(height) {
+ this.height = height;
+ this.updateHeight();
+
+ localStorage.setItem('composerHeight', this.height);
+ }
}]);
return Composer;
}(Component);
@@ -20605,7 +20617,7 @@ System.register('flarum/components/DiscussionList', ['flarum/Component', 'flarum
return m(
'div',
- { className: 'DiscussionList' },
+ { className: 'DiscussionList' + (this.props.params.q ? ' DiscussionList--searchResults' : '') },
m(
'ul',
{ className: 'DiscussionList-discussions' },
@@ -20634,7 +20646,7 @@ System.register('flarum/components/DiscussionList', ['flarum/Component', 'flarum
if (this.props.params.q) {
params.filter.q = this.props.params.q;
- params.include.push('relevantPosts', 'relevantPosts.discussion', 'relevantPosts.user');
+ params.include.push('mostRelevantPost', 'mostRelevantPost.user');
}
return params;
@@ -20809,8 +20821,6 @@ System.register('flarum/components/DiscussionListItem', ['flarum/Component', 'fl
}, {
key: 'view',
value: function view() {
- var _this3 = this;
-
var retain = this.subtree.retain();
if (retain) return retain;
@@ -20820,11 +20830,22 @@ System.register('flarum/components/DiscussionListItem', ['flarum/Component', 'fl
var isUnread = discussion.isUnread();
var isRead = discussion.isRead();
var showUnread = !this.showRepliesCount() && isUnread;
- var jumpTo = Math.min(discussion.lastPostNumber(), (discussion.readNumber() || 0) + 1);
- var relevantPosts = this.props.params.q ? discussion.relevantPosts() : [];
+ var jumpTo = 0;
var controls = DiscussionControls.controls(discussion, this).toArray();
var attrs = this.attrs();
+ if (this.props.params.q) {
+ var post = discussion.mostRelevantPost();
+ if (post) {
+ jumpTo = post.number();
+ }
+
+ var phrase = this.props.params.q;
+ this.highlightRegExp = new RegExp(phrase + '|' + phrase.trim().replace(/\s+/g, '|'), 'gi');
+ } else {
+ jumpTo = Math.min(discussion.lastPostNumber(), (discussion.readNumber() || 0) + 1);
+ }
+
return m(
'div',
attrs,
@@ -20867,7 +20888,7 @@ System.register('flarum/components/DiscussionListItem', ['flarum/Component', 'fl
m(
'h3',
{ className: 'DiscussionListItem-title' },
- highlight(discussion.title(), this.props.params.q)
+ highlight(discussion.title(), this.highlightRegExp)
),
m(
'ul',
@@ -20881,14 +20902,7 @@ System.register('flarum/components/DiscussionListItem', ['flarum/Component', 'fl
onclick: this.markAsRead.bind(this),
title: showUnread ? app.translator.trans('core.forum.discussion_list.mark_as_read_tooltip') : '' },
abbreviateNumber(discussion[showUnread ? 'unreadCount' : 'repliesCount']())
- ),
- relevantPosts && relevantPosts.length ? m(
- 'div',
- { className: 'DiscussionListItem-relevantPosts' },
- relevantPosts.map(function (post) {
- return PostPreview.component({ post: post, highlight: _this3.props.params.q });
- })
- ) : ''
+ )
)
);
}
@@ -20940,10 +20954,19 @@ System.register('flarum/components/DiscussionListItem', ['flarum/Component', 'fl
value: function infoItems() {
var items = new ItemList();
- items.add('terminalPost', TerminalPost.component({
- discussion: this.props.discussion,
- lastPost: !this.showStartPost()
- }));
+ if (this.props.params.q) {
+ var post = this.props.discussion.mostRelevantPost() || this.props.discussion.startPost();
+
+ if (post && post.contentType() === 'comment') {
+ var excerpt = highlight(post.contentPlain(), this.highlightRegExp, 175);
+ items.add('excerpt', excerpt, -100);
+ }
+ } else {
+ items.add('terminalPost', TerminalPost.component({
+ discussion: this.props.discussion,
+ lastPost: !this.showStartPost()
+ }));
+ }
return items;
}
@@ -21387,7 +21410,7 @@ System.register('flarum/components/DiscussionsSearchSource', ['flarum/helpers/hi
var params = {
filter: { q: query },
page: { limit: 3 },
- include: 'relevantPosts,relevantPosts.discussion,relevantPosts.user'
+ include: 'mostRelevantPost'
};
return app.store.find('discussions', params).then(function (results) {
@@ -21414,24 +21437,23 @@ System.register('flarum/components/DiscussionsSearchSource', ['flarum/helpers/hi
href: app.route('index', { q: query })
})
), results.map(function (discussion) {
- var relevantPosts = discussion.relevantPosts();
- var post = relevantPosts && relevantPosts[0];
+ var mostRelevantPost = discussion.mostRelevantPost();
return m(
'li',
{ className: 'DiscussionSearchResult', 'data-index': 'discussions' + discussion.id() },
m(
'a',
- { href: app.route.discussion(discussion, post && post.number()), config: m.route },
+ { href: app.route.discussion(discussion, mostRelevantPost && mostRelevantPost.number()), config: m.route },
m(
'div',
{ className: 'DiscussionSearchResult-title' },
highlight(discussion.title(), query)
),
- post ? m(
+ mostRelevantPost ? m(
'div',
{ className: 'DiscussionSearchResult-excerpt' },
- highlight(post.contentPlain(), query, 100)
+ highlight(mostRelevantPost.contentPlain(), query, 100)
) : ''
)
);
@@ -27313,6 +27335,11 @@ System.register('flarum/components/SignUpModal', ['flarum/components/Modal', 'fl
this.footer()
)];
}
+ }, {
+ key: 'isProvided',
+ value: function isProvided(field) {
+ return this.props.identificationFields && this.props.identificationFields.indexOf(field) !== -1;
+ }
}, {
key: 'body',
value: function body() {
@@ -27325,7 +27352,7 @@ System.register('flarum/components/SignUpModal', ['flarum/components/Modal', 'fl
m('input', { className: 'FormControl', name: 'username', type: 'text', placeholder: extractText(app.translator.trans('core.forum.sign_up.username_placeholder')),
value: this.username(),
onchange: m.withAttr('value', this.username),
- disabled: this.loading })
+ disabled: this.loading || this.isProvided('username') })
),
m(
'div',
@@ -27333,7 +27360,7 @@ System.register('flarum/components/SignUpModal', ['flarum/components/Modal', 'fl
m('input', { className: 'FormControl', name: 'email', type: 'email', placeholder: extractText(app.translator.trans('core.forum.sign_up.email_placeholder')),
value: this.email(),
onchange: m.withAttr('value', this.email),
- disabled: this.loading || this.props.token && this.props.email })
+ disabled: this.loading || this.isProvided('email') })
),
this.props.token ? '' : m(
'div',
@@ -29395,7 +29422,7 @@ System.register('flarum/models/Discussion', ['flarum/Model', 'flarum/utils/compu
return Math.max(0, commentsCount - 1);
}),
posts: Model.hasMany('posts'),
- relevantPosts: Model.hasMany('relevantPosts'),
+ mostRelevantPost: Model.hasOne('mostRelevantPost'),
readTime: Model.attribute('readTime', Model.transformDate),
readNumber: Model.attribute('readNumber'),
diff --git a/js/forum/src/components/DiscussionList.js b/js/forum/src/components/DiscussionList.js
index 35c5d2968b..978fbc05b7 100644
--- a/js/forum/src/components/DiscussionList.js
+++ b/js/forum/src/components/DiscussionList.js
@@ -62,7 +62,7 @@ export default class DiscussionList extends Component {
}
return (
-
+
{this.discussions.map(discussion => {
return (
@@ -94,7 +94,7 @@ export default class DiscussionList extends Component {
if (this.props.params.q) {
params.filter.q = this.props.params.q;
- params.include.push('relevantPosts', 'relevantPosts.discussion', 'relevantPosts.user');
+ params.include.push('mostRelevantPost', 'mostRelevantPost.user');
}
return params;
diff --git a/js/forum/src/components/DiscussionListItem.js b/js/forum/src/components/DiscussionListItem.js
index 935aa0af74..ee10ece30d 100644
--- a/js/forum/src/components/DiscussionListItem.js
+++ b/js/forum/src/components/DiscussionListItem.js
@@ -62,14 +62,24 @@ export default class DiscussionListItem extends Component {
const isUnread = discussion.isUnread();
const isRead = discussion.isRead();
const showUnread = !this.showRepliesCount() && isUnread;
- const jumpTo = Math.min(discussion.lastPostNumber(), (discussion.readNumber() || 0) + 1);
- const relevantPosts = this.props.params.q ? discussion.relevantPosts() : [];
+ let jumpTo = 0;
const controls = DiscussionControls.controls(discussion, this).toArray();
const attrs = this.attrs();
+ if (this.props.params.q) {
+ const post = discussion.mostRelevantPost();
+ if (post) {
+ jumpTo = post.number();
+ }
+
+ const phrase = this.props.params.q;
+ this.highlightRegExp = new RegExp(phrase+'|'+phrase.trim().replace(/\s+/g, '|'), 'gi');
+ } else {
+ jumpTo = Math.min(discussion.lastPostNumber(), (discussion.readNumber() || 0) + 1);
+ }
+
return (
);
@@ -188,12 +191,21 @@ export default class DiscussionListItem extends Component {
infoItems() {
const items = new ItemList();
- items.add('terminalPost',
- TerminalPost.component({
- discussion: this.props.discussion,
- lastPost: !this.showStartPost()
- })
- );
+ if (this.props.params.q) {
+ const post = this.props.discussion.mostRelevantPost() || this.props.discussion.startPost();
+
+ if (post && post.contentType() === 'comment') {
+ const excerpt = highlight(post.contentPlain(), this.highlightRegExp, 175);
+ items.add('excerpt', excerpt, -100);
+ }
+ } else {
+ items.add('terminalPost',
+ TerminalPost.component({
+ discussion: this.props.discussion,
+ lastPost: !this.showStartPost()
+ })
+ );
+ }
return items;
}
diff --git a/js/forum/src/components/DiscussionsSearchSource.js b/js/forum/src/components/DiscussionsSearchSource.js
index e1d1e1c4f1..a4b435e840 100644
--- a/js/forum/src/components/DiscussionsSearchSource.js
+++ b/js/forum/src/components/DiscussionsSearchSource.js
@@ -20,7 +20,7 @@ export default class DiscussionsSearchSource {
const params = {
filter: {q: query},
page: {limit: 3},
- include: 'relevantPosts,relevantPosts.discussion,relevantPosts.user'
+ include: 'mostRelevantPost'
};
return app.store.find('discussions', params).then(results => this.results[query] = results);
@@ -41,14 +41,13 @@ export default class DiscussionsSearchSource {
})}
,
results.map(discussion => {
- const relevantPosts = discussion.relevantPosts();
- const post = relevantPosts && relevantPosts[0];
+ const mostRelevantPost = discussion.mostRelevantPost();
return (
-
+
{highlight(discussion.title(), query)}
- {post ? {highlight(post.contentPlain(), query, 100)}
: ''}
+ {mostRelevantPost ? {highlight(mostRelevantPost.contentPlain(), query, 100)}
: ''}
);
diff --git a/js/lib/models/Discussion.js b/js/lib/models/Discussion.js
index f5a6d27119..4c4072ba9d 100644
--- a/js/lib/models/Discussion.js
+++ b/js/lib/models/Discussion.js
@@ -21,7 +21,7 @@ Object.assign(Discussion.prototype, {
commentsCount: Model.attribute('commentsCount'),
repliesCount: computed('commentsCount', commentsCount => Math.max(0, commentsCount - 1)),
posts: Model.hasMany('posts'),
- relevantPosts: Model.hasMany('relevantPosts'),
+ mostRelevantPost: Model.hasOne('mostRelevantPost'),
readTime: Model.attribute('readTime', Model.transformDate),
readNumber: Model.attribute('readNumber'),
diff --git a/less/forum/DiscussionListItem.less b/less/forum/DiscussionListItem.less
index 5bd7ecc8db..a5c3d5182a 100644
--- a/less/forum/DiscussionListItem.less
+++ b/less/forum/DiscussionListItem.less
@@ -50,27 +50,29 @@
overflow: hidden;
text-overflow: ellipsis;
- .read & {
+ .DiscussionList:not(.DiscussionList--searchResults) .read {
color: mix(@heading-color, @body-bg, 55%);
}
- .unread & {
+ .DiscussionList:not(.DiscussionList--searchResults) .unread & {
font-weight: 600;
}
+
+ mark {
+ background: none;
+ box-shadow: none;
+ font-weight: bold;
+ color: @text-color;
+ }
}
.DiscussionListItem-info {
list-style-type: none;
padding: 0;
margin: 0;
- font-size: 12px;
+ font-size: 11px;
+ color: @muted-more-color;
> li {
display: inline;
- opacity: 0.7;
- .transition(opacity 0.2s);
-
- .DiscussionListItem:hover &, .DiscussionListItem.active & {
- opacity: 1;
- }
}
.username {
font-weight: bold;
@@ -79,6 +81,24 @@
font-size: 11px;
margin-right: -1px;
}
+ .item-excerpt {
+ margin-top: 4px;
+ margin-right: 170px;
+ white-space: normal;
+ font-size: 12px;
+ line-height: 1.5em;
+ display: block;
+
+ .DiscussionPage-list & {
+ margin-right: 0;
+ }
+ mark {
+ background: none;
+ box-shadow: none;
+ font-weight: bold;
+ color: inherit;
+ }
+ }
}
.DiscussionListItem-count {
float: right;
@@ -89,41 +109,6 @@
cursor: pointer;
}
}
-.DiscussionListItem-relevantPosts {
- padding-bottom: 15px;
-
- @media @phone {
- margin-left: -45px;
- margin-right: -35px;
- }
-
- .PostPreview {
- background: @control-bg;
- display: block;
- padding: 10px 15px;
- border-bottom: 2px dotted @body-bg;
- color: @muted-color;
- transition: border-color 0.2s;
-
- .DiscussionListItem:hover & {
- border-color: lighten(@control-bg, 3%);
- }
-
- .Avatar, time {
- display: none;
- }
- .PostPreview-content {
- padding-left: 0;
- }
- &:first-child {
- border-radius: @border-radius @border-radius 0 0;
- }
- &:hover {
- background: darken(@control-bg, 3%);
- text-decoration: none;
- }
- }
-}
@media @phone {
@@ -212,7 +197,7 @@
.DiscussionListItem-controls {
position: absolute;
right: 5px;
- top: 15px;
+ top: 5px;
z-index: 1;
opacity: 0;
transition: opacity 0.2s;
@@ -244,10 +229,10 @@
margin-right: -65px;
}
.DiscussionListItem-title {
- font-size: 15px;
+ font-size: 16px;
}
.DiscussionListItem-count {
- margin-top: 21px;
+ margin-top: 12px;
margin-right: -70px;
width: 55px;
color: @muted-color;
diff --git a/migrations/2015_02_24_000000_create_posts_table.php b/migrations/2015_02_24_000000_create_posts_table.php
index e6d026298f..9f8d87f41b 100644
--- a/migrations/2015_02_24_000000_create_posts_table.php
+++ b/migrations/2015_02_24_000000_create_posts_table.php
@@ -36,8 +36,9 @@
$table->engine = 'MyISAM';
});
- $prefix = $schema->getConnection()->getTablePrefix();
- $schema->getConnection()->statement('ALTER TABLE '.$prefix.'posts ADD FULLTEXT content (content)');
+ $connection = $schema->getConnection();
+ $prefix = $connection->getTablePrefix();
+ $connection->statement('ALTER TABLE '.$prefix.'posts ADD FULLTEXT content (content)');
},
'down' => function (Builder $schema) {
diff --git a/migrations/2018_01_11_120604_change_posts_table_to_innodb.php b/migrations/2018_01_11_120604_change_posts_table_to_innodb.php
new file mode 100644
index 0000000000..54aac3c4c2
--- /dev/null
+++ b/migrations/2018_01_11_120604_change_posts_table_to_innodb.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Illuminate\Database\Schema\Builder;
+
+return [
+ 'up' => function (Builder $schema) {
+ $connection = $schema->getConnection();
+ $prefix = $connection->getTablePrefix();
+ $connection->statement('ALTER TABLE '.$prefix.'posts ENGINE = InnoDB');
+ },
+
+ 'down' => function (Builder $schema) {
+ $connection = $schema->getConnection();
+ $prefix = $connection->getTablePrefix();
+ $connection->statement('ALTER TABLE '.$prefix.'posts ENGINE = MyISAM');
+ }
+];
diff --git a/migrations/2018_01_30_112238_add_fulltext_index_to_discussions_title.php b/migrations/2018_01_30_112238_add_fulltext_index_to_discussions_title.php
new file mode 100644
index 0000000000..c63138c5a0
--- /dev/null
+++ b/migrations/2018_01_30_112238_add_fulltext_index_to_discussions_title.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Illuminate\Database\Schema\Builder;
+
+return [
+ 'up' => function (Builder $schema) {
+ $connection = $schema->getConnection();
+ $prefix = $connection->getTablePrefix();
+ $connection->statement('ALTER TABLE '.$prefix.'discussions ADD FULLTEXT title (title)');
+ },
+
+ 'down' => function (Builder $schema) {
+ $connection = $schema->getConnection();
+ $prefix = $connection->getTablePrefix();
+ $connection->statement('ALTER TABLE '.$prefix.'discussions DROP INDEX title');
+ }
+];
diff --git a/src/Api/Controller/ListDiscussionsController.php b/src/Api/Controller/ListDiscussionsController.php
index 55cc4a4e9f..c5b98a2538 100644
--- a/src/Api/Controller/ListDiscussionsController.php
+++ b/src/Api/Controller/ListDiscussionsController.php
@@ -31,9 +31,8 @@ class ListDiscussionsController extends AbstractListController
public $include = [
'startUser',
'lastUser',
- 'relevantPosts',
- 'relevantPosts.discussion',
- 'relevantPosts.user'
+ 'mostRelevantPost',
+ 'mostRelevantPost.user'
];
/**
@@ -84,7 +83,7 @@ protected function data(ServerRequestInterface $request, Document $document)
$offset = $this->extractOffset($request);
$load = array_merge($this->extractInclude($request), ['state']);
- $results = $this->searcher->search($criteria, $limit, $offset, $load);
+ $results = $this->searcher->search($criteria, $limit, $offset);
$document->addPaginationLinks(
$this->url->to('api')->route('discussions.index'),
@@ -94,7 +93,7 @@ protected function data(ServerRequestInterface $request, Document $document)
$results->areMoreResults() ? null : 0
);
- $results = $results->getResults();
+ $results = $results->getResults()->load($load);
if ($relations = array_intersect($load, ['startPost', 'lastPost'])) {
foreach ($results as $discussion) {
diff --git a/src/Api/Serializer/BasicDiscussionSerializer.php b/src/Api/Serializer/BasicDiscussionSerializer.php
index b03d6d0026..026082a23a 100644
--- a/src/Api/Serializer/BasicDiscussionSerializer.php
+++ b/src/Api/Serializer/BasicDiscussionSerializer.php
@@ -84,9 +84,9 @@ protected function posts($discussion)
/**
* @return \Tobscure\JsonApi\Relationship
*/
- protected function relevantPosts($discussion)
+ protected function mostRelevantPost($discussion)
{
- return $this->hasMany($discussion, BasicPostSerializer::class);
+ return $this->hasOne($discussion, PostSerializer::class);
}
/**
diff --git a/src/Discussion/Discussion.php b/src/Discussion/Discussion.php
index a693940b58..116f1484ce 100644
--- a/src/Discussion/Discussion.php
+++ b/src/Discussion/Discussion.php
@@ -382,6 +382,16 @@ public function lastUser()
return $this->belongsTo(User::class, 'last_user_id');
}
+ /**
+ * Define the relationship with the discussion's most relevant post.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function mostRelevantPost()
+ {
+ return $this->belongsTo(Post::class, 'most_relevant_post_id');
+ }
+
/**
* Define the relationship with the discussion's readers.
*
diff --git a/src/Discussion/Search/DiscussionSearcher.php b/src/Discussion/Search/DiscussionSearcher.php
index c3b5fdb59c..d342acfcbe 100644
--- a/src/Discussion/Search/DiscussionSearcher.php
+++ b/src/Discussion/Search/DiscussionSearcher.php
@@ -11,26 +11,20 @@
namespace Flarum\Discussion\Search;
-use Flarum\Discussion\Discussion;
use Flarum\Discussion\DiscussionRepository;
use Flarum\Discussion\Event\Searching;
-use Flarum\Post\PostRepository;
use Flarum\Search\ApplySearchParametersTrait;
use Flarum\Search\GambitManager;
use Flarum\Search\SearchCriteria;
use Flarum\Search\SearchResults;
-use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Contracts\Events\Dispatcher;
-/**
- * Takes a DiscussionSearchCriteria object, performs a search using gambits,
- * and spits out a DiscussionSearchResults object.
- */
class DiscussionSearcher
{
use ApplySearchParametersTrait;
/**
- * @var \Flarum\Search\GambitManager
+ * @var GambitManager
*/
protected $gambits;
@@ -40,37 +34,34 @@ class DiscussionSearcher
protected $discussions;
/**
- * @var PostRepository
+ * @var Dispatcher
*/
- protected $posts;
+ protected $events;
/**
- * @param \Flarum\Search\GambitManager $gambits
+ * @param GambitManager $gambits
* @param DiscussionRepository $discussions
- * @param PostRepository $posts
+ * @param Dispatcher $events
*/
- public function __construct(
- GambitManager $gambits,
- DiscussionRepository $discussions,
- PostRepository $posts
- ) {
+ public function __construct(GambitManager $gambits, DiscussionRepository $discussions, Dispatcher $events)
+ {
$this->gambits = $gambits;
$this->discussions = $discussions;
- $this->posts = $posts;
+ $this->events = $events;
}
/**
* @param SearchCriteria $criteria
* @param int|null $limit
* @param int $offset
- * @param array $load An array of relationships to load on the results.
+ *
* @return SearchResults
*/
- public function search(SearchCriteria $criteria, $limit = null, $offset = 0, array $load = [])
+ public function search(SearchCriteria $criteria, $limit = null, $offset = 0)
{
$actor = $criteria->actor;
- $query = $this->discussions->query()->whereVisibleTo($actor);
+ $query = $this->discussions->query()->select('discussions.*')->whereVisibleTo($actor);
// Construct an object which represents this search for discussions.
// Apply gambits to it, sort, and paging criteria. Also give extensions
@@ -82,8 +73,7 @@ public function search(SearchCriteria $criteria, $limit = null, $offset = 0, arr
$this->applyOffset($search, $offset);
$this->applyLimit($search, $limit + 1);
- // TODO: inject dispatcher
- event(new Searching($search, $criteria));
+ $this->events->dispatch(new Searching($search, $criteria));
// Execute the search query and retrieve the results. We get one more
// results than the user asked for, so that we can say if there are more
@@ -96,42 +86,6 @@ public function search(SearchCriteria $criteria, $limit = null, $offset = 0, arr
$discussions->pop();
}
- // The relevant posts relationship isn't a typical Eloquent
- // relationship; rather, we need to extract that information from our
- // search object. We will delegate that task and prevent Eloquent
- // from trying to load it.
- if (in_array('relevantPosts', $load)) {
- $this->loadRelevantPosts($discussions, $search);
-
- $load = array_diff($load, ['relevantPosts', 'relevantPosts.discussion', 'relevantPosts.user']);
- }
-
- Discussion::setStateUser($actor);
- $discussions->load($load);
-
return new SearchResults($discussions, $areMoreResults);
}
-
- /**
- * Load relevant posts onto each discussion using information from the
- * search.
- *
- * @param Collection $discussions
- * @param DiscussionSearch $search
- */
- protected function loadRelevantPosts(Collection $discussions, DiscussionSearch $search)
- {
- $postIds = [];
- foreach ($search->getRelevantPostIds() as $relevantPostIds) {
- $postIds = array_merge($postIds, array_slice($relevantPostIds, 0, 2));
- }
-
- $posts = $postIds ? $this->posts->findByIds($postIds, $search->getActor())->load('user')->all() : [];
-
- foreach ($discussions as $discussion) {
- $discussion->relevantPosts = array_filter($posts, function ($post) use ($discussion) {
- return $post->discussion_id == $discussion->id;
- });
- }
- }
}
diff --git a/src/Discussion/Search/Fulltext/DriverInterface.php b/src/Discussion/Search/Fulltext/DriverInterface.php
deleted file mode 100644
index bfcd370939..0000000000
--- a/src/Discussion/Search/Fulltext/DriverInterface.php
+++ /dev/null
@@ -1,24 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Flarum\Discussion\Search\Fulltext;
-
-interface DriverInterface
-{
- /**
- * 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);
-}
diff --git a/src/Discussion/Search/Fulltext/MySqlFulltextDriver.php b/src/Discussion/Search/Fulltext/MySqlFulltextDriver.php
deleted file mode 100644
index f3e1a0be21..0000000000
--- a/src/Discussion/Search/Fulltext/MySqlFulltextDriver.php
+++ /dev/null
@@ -1,36 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Flarum\Discussion\Search\Fulltext;
-
-use Flarum\Post\Post;
-
-class MySqlFulltextDriver implements DriverInterface
-{
- /**
- * {@inheritdoc}
- */
- public function match($string)
- {
- $discussionIds = Post::where('type', 'comment')
- ->whereRaw('MATCH (`content`) AGAINST (? IN BOOLEAN MODE)', [$string])
- ->orderByRaw('MATCH (`content`) AGAINST (?) DESC', [$string])
- ->pluck('discussion_id', 'id');
-
- $relevantPostIds = [];
-
- foreach ($discussionIds as $postId => $discussionId) {
- $relevantPostIds[$discussionId][] = $postId;
- }
-
- return $relevantPostIds;
- }
-}
diff --git a/src/Discussion/Search/Gambit/FulltextGambit.php b/src/Discussion/Search/Gambit/FulltextGambit.php
index 3019f9f0f4..52307d60d1 100644
--- a/src/Discussion/Search/Gambit/FulltextGambit.php
+++ b/src/Discussion/Search/Gambit/FulltextGambit.php
@@ -12,26 +12,14 @@
namespace Flarum\Discussion\Search\Gambit;
use Flarum\Discussion\Search\DiscussionSearch;
-use Flarum\Discussion\Search\Fulltext\DriverInterface;
+use Flarum\Event\ScopeModelVisibility;
+use Flarum\Post\Post;
use Flarum\Search\AbstractSearch;
use Flarum\Search\GambitInterface;
use LogicException;
class FulltextGambit implements GambitInterface
{
- /**
- * @var \Flarum\Discussion\Search\Fulltext\DriverInterface
- */
- protected $fulltext;
-
- /**
- * @param \Flarum\Discussion\Search\Fulltext\DriverInterface $fulltext
- */
- public function __construct(DriverInterface $fulltext)
- {
- $this->fulltext = $fulltext;
- }
-
/**
* {@inheritdoc}
*/
@@ -41,14 +29,22 @@ public function apply(AbstractSearch $search, $bit)
throw new LogicException('This gambit can only be applied on a DiscussionSearch');
}
- $relevantPostIds = $this->fulltext->match($bit);
-
- $discussionIds = array_keys($relevantPostIds);
-
- $search->setRelevantPostIds($relevantPostIds);
-
- $search->getQuery()->whereIn('id', $discussionIds);
-
- $search->setDefaultSort(['id' => $discussionIds]);
+ $search->getQuery()
+ ->selectRaw('SUBSTRING_INDEX(GROUP_CONCAT(posts.id ORDER BY MATCH(posts.content) AGAINST (?) DESC), \',\', 1) as most_relevant_post_id', [$bit])
+ ->leftJoin('posts', 'posts.discussion_id', '=', 'discussions.id')
+ ->where('posts.type', 'comment')
+ ->where(function ($query) use ($search) {
+ event(new ScopeModelVisibility(Post::query()->setQuery($query), $search->getActor(), 'view'));
+ })
+ ->where(function ($query) use ($bit) {
+ $query->whereRaw('MATCH(discussions.title) AGAINST (? IN BOOLEAN MODE)', [$bit])
+ ->orWhereRaw('MATCH(posts.content) AGAINST (? IN BOOLEAN MODE)', [$bit]);
+ })
+ ->groupBy('posts.discussion_id');
+
+ $search->setDefaultSort(function ($query) use ($bit) {
+ $query->orderByRaw('MATCH(discussions.title) AGAINST (?) desc', [$bit]);
+ $query->orderByRaw('MATCH(posts.content) AGAINST (?) desc', [$bit]);
+ });
}
}
diff --git a/src/Search/AbstractSearch.php b/src/Search/AbstractSearch.php
index 23e7c4b781..3c1583ba7d 100644
--- a/src/Search/AbstractSearch.php
+++ b/src/Search/AbstractSearch.php
@@ -85,12 +85,12 @@ public function getDefaultSort()
* Set the default sort order for the search. This will only be applied if
* a sort order has not been specified in the search criteria.
*
- * @param array $defaultSort An array of sort-order pairs, where the column
+ * @param mixed $defaultSort An array of sort-order pairs, where the column
* is the key, and the order is the value. The order may be 'asc',
* 'desc', or an array of IDs to order by.
* @return mixed
*/
- public function setDefaultSort(array $defaultSort)
+ public function setDefaultSort($defaultSort)
{
$this->defaultSort = $defaultSort;
}
diff --git a/src/Search/ApplySearchParametersTrait.php b/src/Search/ApplySearchParametersTrait.php
index 58691946c3..058c1b5dbb 100644
--- a/src/Search/ApplySearchParametersTrait.php
+++ b/src/Search/ApplySearchParametersTrait.php
@@ -23,13 +23,17 @@ protected function applySort(AbstractSearch $search, array $sort = null)
{
$sort = $sort ?: $search->getDefaultSort();
- foreach ($sort as $field => $order) {
- if (is_array($order)) {
- foreach ($order as $value) {
- $search->getQuery()->orderByRaw(snake_case($field).' != ?', [$value]);
+ if (is_callable($sort)) {
+ $sort($search->getQuery());
+ } else {
+ foreach ($sort as $field => $order) {
+ if (is_array($order)) {
+ foreach ($order as $value) {
+ $search->getQuery()->orderByRaw(snake_case($field).' != ?', [$value]);
+ }
+ } else {
+ $search->getQuery()->orderBy(snake_case($field), $order);
}
- } else {
- $search->getQuery()->orderBy(snake_case($field), $order);
}
}
}