diff --git a/js/src/forum/Forum.ts b/js/src/forum/Forum.ts index 2b0cd9213..a560359e4 100644 --- a/js/src/forum/Forum.ts +++ b/js/src/forum/Forum.ts @@ -6,10 +6,11 @@ import Navigation from '../common/components/Navigation'; import HeaderPrimary from './components/HeaderPrimary'; import HeaderSecondary from './components/HeaderSecondary'; import Page from './components/Page'; -import DiscussionList from './components/DiscussionList'; import CommentPost from './components/CommentPost'; import DiscussionRenamedPost from './components/DiscussionRenamedPost'; +import { DiscussionListState } from './states/DiscussionListState'; + import Notification from '../common/models/Notification'; import routes from './routes'; @@ -26,7 +27,7 @@ export default class Forum extends Application { */ cache: { notifications?: Notification[][]; - discussionList?: DiscussionList | null; + discussionList?: DiscussionListState | null; [key: string]: any; } = {}; diff --git a/js/src/forum/components/DiscussionList.tsx b/js/src/forum/components/DiscussionList.tsx index 01bba8220..38578b9be 100644 --- a/js/src/forum/components/DiscussionList.tsx +++ b/js/src/forum/components/DiscussionList.tsx @@ -3,64 +3,49 @@ import DiscussionListItem from './DiscussionListItem'; import Button from '../../common/components/Button'; import LoadingIndicator from '../../common/components/LoadingIndicator'; import Placeholder from '../../common/components/Placeholder'; -import Discussion from '../../common/models/Discussion'; +import { DiscussionListState } from '../states/DiscussionListState'; export interface DiscussionListProps extends ComponentProps { - /** - * A map of parameters used to construct a refined parameter object - * to send along in the API request to get discussion results. - */ - params: any; + state: DiscussionListState; } /** * The `DiscussionList` component displays a list of discussions. */ export default class DiscussionList extends Component { - /** - * 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[] = []; + state!: DiscussionListState; oninit(vnode) { super.oninit(vnode); - this.refresh(); + this.state = this.props.state || new DiscussionListState(); } view() { - const params = this.props.params; + const state = this.state; + + const params = state.params; let loading; - if (this.loading) { + if (state.loading) { loading = LoadingIndicator.component(); - } else if (this.moreResults) { + } else if (state.moreResults) { loading = Button.component({ children: app.translator.trans('core.forum.discussion_list.load_more_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'); return
{Placeholder.component({ text })}
; } return ( -
+
    - {this.discussions.map((discussion) => { + {state.discussions.map((discussion) => { return (
  • {DiscussionListItem.component({ discussion, params })} @@ -72,123 +57,4 @@ export default class DiscussionList ); } - - /** - * 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 { - 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); - } } diff --git a/js/src/forum/components/DiscussionPage.tsx b/js/src/forum/components/DiscussionPage.tsx index e990c6e34..968e86a43 100644 --- a/js/src/forum/components/DiscussionPage.tsx +++ b/js/src/forum/components/DiscussionPage.tsx @@ -9,6 +9,7 @@ import listItems from '../../common/helpers/listItems'; import DiscussionControls from '../utils/DiscussionControls'; import Discussion from '../../common/models/Discussion'; import Post from '../../common/models/Post'; +import DiscussionList from './DiscussionList'; /** * The `DiscussionPage` component displays a whole discussion page, including @@ -111,7 +112,7 @@ export default class DiscussionPage extends Page {
    {app.cache.discussionList ? (
    false}> - {!$('.App-navigation').is(':visible') ? app.cache.discussionList.render() : ''} + {!$('.App-navigation').is(':visible') && }
    ) : ( '' diff --git a/js/src/forum/components/DiscussionsSearchSource.tsx b/js/src/forum/components/DiscussionsSearchSource.tsx index 99ed301e8..689fea00f 100644 --- a/js/src/forum/components/DiscussionsSearchSource.tsx +++ b/js/src/forum/components/DiscussionsSearchSource.tsx @@ -21,7 +21,7 @@ export default class DiscussionsSearchSource extends SearchSource { include: 'mostRelevantPost', }; - return app.store.find('discussions', params).then((results) => (this.results[query] = results)); + return app.store.find('discussions', params).then((results) => (this.results[query] = results as Discussion[])); } view(query: string) { diff --git a/js/src/forum/components/IndexPage.tsx b/js/src/forum/components/IndexPage.tsx index 6e0fc3925..4d1714c56 100644 --- a/js/src/forum/components/IndexPage.tsx +++ b/js/src/forum/components/IndexPage.tsx @@ -1,4 +1,5 @@ import { Params } from 'mithril'; +import app from '../app'; import { extend } from '../../common/extend'; import listItems from '../../common/helpers/listItems'; @@ -10,12 +11,14 @@ import Dropdown from '../../common/components/Dropdown'; import LinkButton from '../../common/components/LinkButton'; import SelectDropdown from '../../common/components/SelectDropdown'; // import DiscussionComposer from './DiscussionComposer'; -import DiscussionList from './DiscussionList'; import DiscussionPage from './DiscussionPage'; import LogInModal from './LogInModal'; import Page from './Page'; import WelcomeHero from './WelcomeHero'; +import { DiscussionListState } from '../states/DiscussionListState'; +import DiscussionList from './DiscussionList'; + /** * The `IndexPage` component displays the index page, including the welcome * 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 // the new parameters. 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; return true; } @@ -57,7 +60,7 @@ export default class IndexPage extends Page { } 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')); @@ -76,8 +79,6 @@ export default class IndexPage extends Page { view() { if (!app.cache.discussionList) return; - const discussionList = app.cache.discussionList.render(); - return (
    {this.hero()} @@ -91,7 +92,7 @@ export default class IndexPage extends Page {
      {listItems(this.viewItems().toArray())}
      {listItems(this.actionItems().toArray())}
    - {discussionList} +
@@ -257,7 +258,7 @@ export default class IndexPage extends Page { icon: 'fas fa-sync', className: 'Button Button--icon', onclick: () => { - app.cache.discussionList.refresh(); + app.cache.discussionList!.refresh(); if (app.session.user) { app.store.find('users', app.session.user.id()); m.redraw(); diff --git a/js/src/forum/states/DiscussionListState.ts b/js/src/forum/states/DiscussionListState.ts new file mode 100644 index 000000000..a0445c445 --- /dev/null +++ b/js/src/forum/states/DiscussionListState.ts @@ -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 { + 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; + } + + /** + * 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); + } +}