mirror of
https://github.com/flarum/core.git
synced 2025-08-06 08:27:42 +02:00
forum: move DiscussionList component logic into a custom state class
Only the state has to be saved to app.cache.discussionList Cleans up the component class
This commit is contained in:
@@ -6,10 +6,11 @@ import Navigation from '../common/components/Navigation';
|
|||||||
import HeaderPrimary from './components/HeaderPrimary';
|
import HeaderPrimary from './components/HeaderPrimary';
|
||||||
import HeaderSecondary from './components/HeaderSecondary';
|
import HeaderSecondary from './components/HeaderSecondary';
|
||||||
import Page from './components/Page';
|
import Page from './components/Page';
|
||||||
import DiscussionList from './components/DiscussionList';
|
|
||||||
import CommentPost from './components/CommentPost';
|
import CommentPost from './components/CommentPost';
|
||||||
import DiscussionRenamedPost from './components/DiscussionRenamedPost';
|
import DiscussionRenamedPost from './components/DiscussionRenamedPost';
|
||||||
|
|
||||||
|
import { DiscussionListState } from './states/DiscussionListState';
|
||||||
|
|
||||||
import Notification from '../common/models/Notification';
|
import Notification from '../common/models/Notification';
|
||||||
|
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
@@ -26,7 +27,7 @@ export default class Forum extends Application {
|
|||||||
*/
|
*/
|
||||||
cache: {
|
cache: {
|
||||||
notifications?: Notification[][];
|
notifications?: Notification[][];
|
||||||
discussionList?: DiscussionList | null;
|
discussionList?: DiscussionListState | null;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
|
@@ -3,64 +3,49 @@ import DiscussionListItem from './DiscussionListItem';
|
|||||||
import Button from '../../common/components/Button';
|
import Button from '../../common/components/Button';
|
||||||
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
||||||
import Placeholder from '../../common/components/Placeholder';
|
import Placeholder from '../../common/components/Placeholder';
|
||||||
import Discussion from '../../common/models/Discussion';
|
import { DiscussionListState } from '../states/DiscussionListState';
|
||||||
|
|
||||||
export interface DiscussionListProps extends ComponentProps {
|
export interface DiscussionListProps extends ComponentProps {
|
||||||
/**
|
state: DiscussionListState;
|
||||||
* A map of parameters used to construct a refined parameter object
|
|
||||||
* to send along in the API request to get discussion results.
|
|
||||||
*/
|
|
||||||
params: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `DiscussionList` component displays a list of discussions.
|
* The `DiscussionList` component displays a list of discussions.
|
||||||
*/
|
*/
|
||||||
export default class DiscussionList<T extends DiscussionListProps = DiscussionListProps> extends Component<T> {
|
export default class DiscussionList<T extends DiscussionListProps = DiscussionListProps> extends Component<T> {
|
||||||
/**
|
state!: DiscussionListState;
|
||||||
* Whether or not discussion results are loading.
|
|
||||||
*/
|
|
||||||
loading = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not there are more results that can be loaded.
|
|
||||||
*/
|
|
||||||
moreResults = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The discussions in the discussion list.
|
|
||||||
*/
|
|
||||||
discussions: Discussion[] = [];
|
|
||||||
|
|
||||||
oninit(vnode) {
|
oninit(vnode) {
|
||||||
super.oninit(vnode);
|
super.oninit(vnode);
|
||||||
|
|
||||||
this.refresh();
|
this.state = this.props.state || new DiscussionListState();
|
||||||
}
|
}
|
||||||
|
|
||||||
view() {
|
view() {
|
||||||
const params = this.props.params;
|
const state = this.state;
|
||||||
|
|
||||||
|
const params = state.params;
|
||||||
let loading;
|
let loading;
|
||||||
|
|
||||||
if (this.loading) {
|
if (state.loading) {
|
||||||
loading = LoadingIndicator.component();
|
loading = LoadingIndicator.component();
|
||||||
} else if (this.moreResults) {
|
} else if (state.moreResults) {
|
||||||
loading = Button.component({
|
loading = Button.component({
|
||||||
children: app.translator.trans('core.forum.discussion_list.load_more_button'),
|
children: app.translator.trans('core.forum.discussion_list.load_more_button'),
|
||||||
className: 'Button',
|
className: 'Button',
|
||||||
onclick: this.loadMore.bind(this),
|
onclick: state.loadMore.bind(this),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.discussions.length === 0 && !this.loading) {
|
if (state.discussions.length === 0 && !state.loading) {
|
||||||
const text = app.translator.trans('core.forum.discussion_list.empty_text');
|
const text = app.translator.trans('core.forum.discussion_list.empty_text');
|
||||||
return <div className="DiscussionList">{Placeholder.component({ text })}</div>;
|
return <div className="DiscussionList">{Placeholder.component({ text })}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'DiscussionList' + (this.props.params.q ? ' DiscussionList--searchResults' : '')}>
|
<div className={'DiscussionList' + (state.params.q ? ' DiscussionList--searchResults' : '')}>
|
||||||
<ul className="DiscussionList-discussions">
|
<ul className="DiscussionList-discussions">
|
||||||
{this.discussions.map((discussion) => {
|
{state.discussions.map((discussion) => {
|
||||||
return (
|
return (
|
||||||
<li key={discussion.id()} data-id={discussion.id()}>
|
<li key={discussion.id()} data-id={discussion.id()}>
|
||||||
{DiscussionListItem.component({ discussion, params })}
|
{DiscussionListItem.component({ discussion, params })}
|
||||||
@@ -72,123 +57,4 @@ export default class DiscussionList<T extends DiscussionListProps = DiscussionLi
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the parameters that should be passed in the API request to get
|
|
||||||
* discussion results.
|
|
||||||
*
|
|
||||||
* @api
|
|
||||||
*/
|
|
||||||
requestParams(): any {
|
|
||||||
const params = { include: ['user', 'lastPostedUser'], filter: {} };
|
|
||||||
|
|
||||||
params.sort = this.sortMap()[this.props.params.sort];
|
|
||||||
|
|
||||||
if (this.props.params.q) {
|
|
||||||
params.filter.q = this.props.params.q;
|
|
||||||
|
|
||||||
params.include.push('mostRelevantPost', 'mostRelevantPost.user');
|
|
||||||
}
|
|
||||||
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a map of sort keys (which appear in the URL, and are used for
|
|
||||||
* translation) to the API sort value that they represent.
|
|
||||||
*/
|
|
||||||
sortMap() {
|
|
||||||
const map: any = {};
|
|
||||||
|
|
||||||
if (this.props.params.q) {
|
|
||||||
map.relevance = '';
|
|
||||||
}
|
|
||||||
map.latest = '-lastPostedAt';
|
|
||||||
map.top = '-commentCount';
|
|
||||||
map.newest = '-createdAt';
|
|
||||||
map.oldest = 'createdAt';
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear and reload the discussion list.
|
|
||||||
*/
|
|
||||||
public refresh(clear = true) {
|
|
||||||
if (clear) {
|
|
||||||
this.loading = true;
|
|
||||||
this.discussions = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.loadResults().then(
|
|
||||||
(results) => {
|
|
||||||
this.discussions = [];
|
|
||||||
this.parseResults(results);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.loading = false;
|
|
||||||
m.redraw();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a new page of discussion results.
|
|
||||||
*
|
|
||||||
* @param offset The index to start the page at.
|
|
||||||
*/
|
|
||||||
loadResults(offset?: number): Promise<Discussion[]> {
|
|
||||||
const preloadedDiscussions = app.preloadedApiDocument();
|
|
||||||
|
|
||||||
if (preloadedDiscussions) {
|
|
||||||
return Promise.resolve(preloadedDiscussions);
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = this.requestParams();
|
|
||||||
params.page = { offset };
|
|
||||||
params.include = params.include.join(',');
|
|
||||||
|
|
||||||
return app.store.find('discussions', params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the next page of discussion results.
|
|
||||||
*/
|
|
||||||
public loadMore() {
|
|
||||||
this.loading = true;
|
|
||||||
|
|
||||||
this.loadResults(this.discussions.length).then(this.parseResults.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse results and append them to the discussion list.
|
|
||||||
*/
|
|
||||||
parseResults(results: Discussion[]): Discussion[] {
|
|
||||||
[].push.apply(this.discussions, results);
|
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
this.moreResults = !!results.payload.links.next;
|
|
||||||
|
|
||||||
m.redraw();
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a discussion from the list if it is present.
|
|
||||||
*/
|
|
||||||
public removeDiscussion(discussion: Discussion) {
|
|
||||||
const index = this.discussions.indexOf(discussion);
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
this.discussions.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a discussion to the top of the list.
|
|
||||||
*/
|
|
||||||
public addDiscussion(discussion: Discussion) {
|
|
||||||
this.discussions.unshift(discussion);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ import listItems from '../../common/helpers/listItems';
|
|||||||
import DiscussionControls from '../utils/DiscussionControls';
|
import DiscussionControls from '../utils/DiscussionControls';
|
||||||
import Discussion from '../../common/models/Discussion';
|
import Discussion from '../../common/models/Discussion';
|
||||||
import Post from '../../common/models/Post';
|
import Post from '../../common/models/Post';
|
||||||
|
import DiscussionList from './DiscussionList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `DiscussionPage` component displays a whole discussion page, including
|
* The `DiscussionPage` component displays a whole discussion page, including
|
||||||
@@ -111,7 +112,7 @@ export default class DiscussionPage extends Page {
|
|||||||
<div className="DiscussionPage">
|
<div className="DiscussionPage">
|
||||||
{app.cache.discussionList ? (
|
{app.cache.discussionList ? (
|
||||||
<div className="DiscussionPage-list" oncreate={this.oncreatePane.bind(this)} onbeforeupdate={() => false}>
|
<div className="DiscussionPage-list" oncreate={this.oncreatePane.bind(this)} onbeforeupdate={() => false}>
|
||||||
{!$('.App-navigation').is(':visible') ? app.cache.discussionList.render() : ''}
|
{!$('.App-navigation').is(':visible') && <DiscussionList state={app.cache.discussionList} />}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
|
@@ -21,7 +21,7 @@ export default class DiscussionsSearchSource extends SearchSource {
|
|||||||
include: 'mostRelevantPost',
|
include: 'mostRelevantPost',
|
||||||
};
|
};
|
||||||
|
|
||||||
return app.store.find<Discussion>('discussions', params).then((results) => (this.results[query] = results));
|
return app.store.find<Discussion>('discussions', params).then((results) => (this.results[query] = results as Discussion[]));
|
||||||
}
|
}
|
||||||
|
|
||||||
view(query: string) {
|
view(query: string) {
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Params } from 'mithril';
|
import { Params } from 'mithril';
|
||||||
|
import app from '../app';
|
||||||
|
|
||||||
import { extend } from '../../common/extend';
|
import { extend } from '../../common/extend';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from '../../common/helpers/listItems';
|
||||||
@@ -10,12 +11,14 @@ import Dropdown from '../../common/components/Dropdown';
|
|||||||
import LinkButton from '../../common/components/LinkButton';
|
import LinkButton from '../../common/components/LinkButton';
|
||||||
import SelectDropdown from '../../common/components/SelectDropdown';
|
import SelectDropdown from '../../common/components/SelectDropdown';
|
||||||
// import DiscussionComposer from './DiscussionComposer';
|
// import DiscussionComposer from './DiscussionComposer';
|
||||||
import DiscussionList from './DiscussionList';
|
|
||||||
import DiscussionPage from './DiscussionPage';
|
import DiscussionPage from './DiscussionPage';
|
||||||
import LogInModal from './LogInModal';
|
import LogInModal from './LogInModal';
|
||||||
import Page from './Page';
|
import Page from './Page';
|
||||||
import WelcomeHero from './WelcomeHero';
|
import WelcomeHero from './WelcomeHero';
|
||||||
|
|
||||||
|
import { DiscussionListState } from '../states/DiscussionListState';
|
||||||
|
import DiscussionList from './DiscussionList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `IndexPage` component displays the index page, including the welcome
|
* The `IndexPage` component displays the index page, including the welcome
|
||||||
* hero, the sidebar, and the discussion list.
|
* hero, the sidebar, and the discussion list.
|
||||||
@@ -49,7 +52,7 @@ export default class IndexPage extends Page {
|
|||||||
// will clear the cache and set up a new discussion list component with
|
// will clear the cache and set up a new discussion list component with
|
||||||
// the new parameters.
|
// the new parameters.
|
||||||
Object.keys(params).some((key) => {
|
Object.keys(params).some((key) => {
|
||||||
if (app.cache.discussionList!.props.params[key] !== params[key]) {
|
if (app.cache.discussionList!.params[key] !== params[key]) {
|
||||||
app.cache.discussionList = null;
|
app.cache.discussionList = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -57,7 +60,7 @@ export default class IndexPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!app.cache.discussionList) {
|
if (!app.cache.discussionList) {
|
||||||
app.cache.discussionList = new DiscussionList({ params, oninit: (vnode) => (app.cache.discussionList = vnode.state) });
|
app.cache.discussionList = new DiscussionListState({ params });
|
||||||
}
|
}
|
||||||
|
|
||||||
app.history.push('index', app.translator.transText('core.forum.header.back_to_index_tooltip'));
|
app.history.push('index', app.translator.transText('core.forum.header.back_to_index_tooltip'));
|
||||||
@@ -76,8 +79,6 @@ export default class IndexPage extends Page {
|
|||||||
view() {
|
view() {
|
||||||
if (!app.cache.discussionList) return;
|
if (!app.cache.discussionList) return;
|
||||||
|
|
||||||
const discussionList = app.cache.discussionList.render();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="IndexPage">
|
<div className="IndexPage">
|
||||||
{this.hero()}
|
{this.hero()}
|
||||||
@@ -91,7 +92,7 @@ export default class IndexPage extends Page {
|
|||||||
<ul className="IndexPage-toolbar-view">{listItems(this.viewItems().toArray())}</ul>
|
<ul className="IndexPage-toolbar-view">{listItems(this.viewItems().toArray())}</ul>
|
||||||
<ul className="IndexPage-toolbar-action">{listItems(this.actionItems().toArray())}</ul>
|
<ul className="IndexPage-toolbar-action">{listItems(this.actionItems().toArray())}</ul>
|
||||||
</div>
|
</div>
|
||||||
{discussionList}
|
<DiscussionList state={app.cache.discussionList} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -257,7 +258,7 @@ export default class IndexPage extends Page {
|
|||||||
icon: 'fas fa-sync',
|
icon: 'fas fa-sync',
|
||||||
className: 'Button Button--icon',
|
className: 'Button Button--icon',
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
app.cache.discussionList.refresh();
|
app.cache.discussionList!.refresh();
|
||||||
if (app.session.user) {
|
if (app.session.user) {
|
||||||
app.store.find('users', app.session.user.id());
|
app.store.find('users', app.session.user.id());
|
||||||
m.redraw();
|
m.redraw();
|
||||||
|
149
js/src/forum/states/DiscussionListState.ts
Normal file
149
js/src/forum/states/DiscussionListState.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import Discussion from '../../common/models/Discussion';
|
||||||
|
|
||||||
|
export class DiscussionListState {
|
||||||
|
/**
|
||||||
|
* Whether or not discussion results are loading.
|
||||||
|
*/
|
||||||
|
loading = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not there are more results that can be loaded.
|
||||||
|
*/
|
||||||
|
moreResults = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The discussions in the discussion list.
|
||||||
|
*/
|
||||||
|
discussions: Discussion[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of parameters used to construct a refined parameter object
|
||||||
|
* to send along in the API request to get discussion results.
|
||||||
|
*/
|
||||||
|
params: any;
|
||||||
|
|
||||||
|
constructor({ params = {} } = {}) {
|
||||||
|
this.params = params;
|
||||||
|
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the parameters that should be passed in the API request to get
|
||||||
|
* discussion results.
|
||||||
|
*
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
requestParams(): any {
|
||||||
|
const params: any = { include: ['user', 'lastPostedUser'], filter: {} };
|
||||||
|
|
||||||
|
params.sort = this.sortMap()[this.params.sort];
|
||||||
|
|
||||||
|
if (this.params.q) {
|
||||||
|
params.filter.q = this.params.q;
|
||||||
|
|
||||||
|
params.include.push('mostRelevantPost', 'mostRelevantPost.user');
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a map of sort keys (which appear in the URL, and are used for
|
||||||
|
* translation) to the API sort value that they represent.
|
||||||
|
*/
|
||||||
|
sortMap() {
|
||||||
|
const map: any = {};
|
||||||
|
|
||||||
|
if (this.params.q) {
|
||||||
|
map.relevance = '';
|
||||||
|
}
|
||||||
|
map.latest = '-lastPostedAt';
|
||||||
|
map.top = '-commentCount';
|
||||||
|
map.newest = '-createdAt';
|
||||||
|
map.oldest = 'createdAt';
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear and reload the discussion list.
|
||||||
|
*/
|
||||||
|
public refresh(clear = true) {
|
||||||
|
if (clear) {
|
||||||
|
this.loading = true;
|
||||||
|
this.discussions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.loadResults().then(
|
||||||
|
(results) => {
|
||||||
|
this.discussions = [];
|
||||||
|
this.parseResults(results);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.loading = false;
|
||||||
|
m.redraw();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a new page of discussion results.
|
||||||
|
*
|
||||||
|
* @param offset The index to start the page at.
|
||||||
|
*/
|
||||||
|
loadResults(offset?: number): Promise<Discussion[]> {
|
||||||
|
const preloadedDiscussions = app.preloadedApiDocument();
|
||||||
|
|
||||||
|
if (preloadedDiscussions) {
|
||||||
|
return Promise.resolve(preloadedDiscussions as Discussion[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = this.requestParams();
|
||||||
|
params.page = { offset };
|
||||||
|
params.include = params.include.join(',');
|
||||||
|
|
||||||
|
return app.store.find('discussions', params) as Promise<Discussion[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the next page of discussion results.
|
||||||
|
*/
|
||||||
|
public loadMore() {
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
this.loadResults(this.discussions.length).then(this.parseResults.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse results and append them to the discussion list.
|
||||||
|
*/
|
||||||
|
parseResults(results: Discussion[]): Discussion[] {
|
||||||
|
this.discussions.push(...results);
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
this.moreResults = !!results.payload.links.next;
|
||||||
|
|
||||||
|
m.redraw();
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a discussion from the list if it is present.
|
||||||
|
*/
|
||||||
|
public removeDiscussion(discussion: Discussion) {
|
||||||
|
const index = this.discussions.indexOf(discussion);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
this.discussions.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a discussion to the top of the list.
|
||||||
|
*/
|
||||||
|
public addDiscussion(discussion: Discussion) {
|
||||||
|
this.discussions.unshift(discussion);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user