123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- import React from "react"
- import Button from "misago/components/button"
- import {
- compareGlobalWeight,
- compareWeight
- } from "misago/components/threads/compare"
- import Container from "misago/components/threads/container"
- import Header from "misago/components/threads/header"
- import {
- diffThreads,
- getModerationActions,
- getPageTitle,
- getTitle
- } from "misago/components/threads/utils"
- import ThreadsList from "misago/components/threads-list"
- import ThreadsListEmpty from "misago/components/threads/list-empty"
- import WithDropdown from "misago/components/with-dropdown"
- import misago from "misago/index"
- import * as select from "misago/reducers/selection"
- import { append, deleteThread, hydrate, patch } from "misago/reducers/threads"
- import ajax from "misago/services/ajax"
- import polls from "misago/services/polls"
- import snackbar from "misago/services/snackbar"
- import store from "misago/services/store"
- import title from "misago/services/page-title"
- import * as sets from "misago/utils/sets"
- export default class extends WithDropdown {
- constructor(props) {
- super(props)
- this.state = {
- isMounted: true,
- isLoaded: false,
- isBusy: false,
- diff: {
- results: []
- },
- moderation: [],
- busyThreads: [],
- dropdown: false,
- subcategories: [],
- count: 0,
- more: 0,
- page: 1,
- pages: 1
- }
- let category = this.getCategory()
- if (misago.has("THREADS")) {
- this.initWithPreloadedData(category, misago.get("THREADS"))
- } else {
- this.initWithoutPreloadedData(category)
- }
- }
- getCategory() {
- if (!this.props.route.category.special_role) {
- return this.props.route.category.id
- } else {
- return null
- }
- }
- initWithPreloadedData(category, data) {
- this.state = Object.assign(this.state, {
- moderation: getModerationActions(data.results),
- subcategories: data.subcategories,
- count: data.count,
- more: data.more,
- page: data.page,
- pages: data.pages
- })
- this.startPolling(category)
- }
- initWithoutPreloadedData(category) {
- this.loadThreads(category)
- }
- loadThreads(category, page = 1) {
- ajax
- .get(
- this.props.options.api,
- {
- category: category,
- list: this.props.route.list.type,
- page: page || 1
- },
- "threads"
- )
- .then(
- data => {
- if (!this.state.isMounted) {
- // user changed route before loading completion
- return
- }
- if (page === 1) {
- store.dispatch(hydrate(data.results))
- } else {
- store.dispatch(append(data.results, this.getSorting()))
- }
- this.setState({
- isLoaded: true,
- isBusy: false,
- moderation: getModerationActions(store.getState().threads),
- subcategories: data.subcategories,
- count: data.count,
- more: data.more,
- page: data.page,
- pages: data.pages
- })
- this.startPolling(category)
- },
- rejection => {
- snackbar.apiError(rejection)
- }
- )
- }
- startPolling(category) {
- polls.start({
- poll: "threads",
- url: this.props.options.api,
- data: {
- category: category,
- list: this.props.route.list.type
- },
- frequency: 120 * 1000,
- update: this.pollResponse
- })
- }
- componentDidMount() {
- this.setPageTitle()
- if (misago.has("THREADS")) {
- // unlike in other components, routes are root components for threads
- // so we can't dispatch store action from constructor
- store.dispatch(hydrate(misago.pop("THREADS").results))
- this.setState({
- isLoaded: true
- })
- }
- store.dispatch(select.none())
- }
- componentWillUnmount() {
- this.state.isMounted = false
- polls.stop("threads")
- }
- getTitle() {
- if (this.props.options.title) {
- return this.props.options.title
- }
- return getTitle(this.props.route)
- }
- setPageTitle() {
- if (this.props.route.category.level || !misago.get("THREADS_ON_INDEX")) {
- title.set(getPageTitle(this.props.route))
- } else if (this.props.options.title) {
- title.set(this.props.options.title)
- } else {
- if (misago.get("SETTINGS").forum_index_title) {
- document.title = misago.get("SETTINGS").forum_index_title
- } else {
- document.title = misago.get("SETTINGS").forum_name
- }
- }
- }
- getSorting() {
- if (this.props.route.category.level) {
- return compareWeight
- } else {
- return compareGlobalWeight
- }
- }
- // AJAX
- loadMore = () => {
- this.setState({
- isBusy: true
- })
- this.loadThreads(this.getCategory(), this.state.page + 1)
- }
- pollResponse = data => {
- this.setState({
- diff: Object.assign({}, data, {
- results: diffThreads(this.props.threads, data.results)
- })
- })
- }
- addThreads = threads => {
- store.dispatch(append(threads, this.getSorting()))
- }
- applyDiff = () => {
- this.addThreads(this.state.diff.results)
- this.setState(
- Object.assign({}, this.state.diff, {
- moderation: getModerationActions(store.getState().threads),
- diff: {
- results: []
- }
- })
- )
- }
- // Thread state utils
- freezeThread = thread => {
- this.setState(function(currentState) {
- return {
- busyThreads: sets.toggle(currentState.busyThreads, thread)
- }
- })
- }
- updateThread = thread => {
- store.dispatch(patch(thread, thread, this.getSorting()))
- }
- deleteThread = thread => {
- store.dispatch(deleteThread(thread))
- }
- getMoreButton() {
- if (this.state.more) {
- return (
- <div className="pager-more">
- <Button
- className="btn btn-default btn-outline"
- loading={this.state.isBusy || this.state.busyThreads.length}
- onClick={this.loadMore}
- >
- {gettext("Show more")}
- </Button>
- </div>
- )
- } else {
- return null
- }
- }
- getClassName() {
- let className = "page page-threads"
- className += " page-threads-" + this.props.route.list.type
- if (this.props.route.category.css_class) {
- className += " page-threads-" + this.props.route.category.css_class
- }
- return className
- }
- render() {
- return (
- <div className={this.getClassName()}>
- <Header
- categories={this.props.route.categoriesMap}
- disabled={!this.state.isLoaded}
- startThread={this.props.options.startThread}
- threads={this.props.threads}
- title={this.getTitle()}
- toggleNav={this.toggleNav}
- route={this.props.route}
- user={this.props.user}
- />
- <Container
- api={this.props.options.api}
- route={this.props.route}
- subcategories={this.state.subcategories}
- user={this.props.user}
- pageLead={this.props.options.pageLead}
- threads={this.props.threads}
- threadsCount={this.state.count}
- moderation={this.state.moderation}
- selection={this.props.selection}
- busyThreads={this.state.busyThreads}
- addThreads={this.addThreads}
- freezeThread={this.freezeThread}
- deleteThread={this.deleteThread}
- updateThread={this.updateThread}
- isLoaded={this.state.isLoaded}
- isBusy={this.state.isBusy}
- >
- <ThreadsList
- category={this.props.route.category}
- categories={this.props.route.categoriesMap}
- list={this.props.route.list}
- selection={this.props.selection}
- threads={this.props.threads}
- diffSize={this.state.diff.results.length}
- applyDiff={this.applyDiff}
- showOptions={!!this.props.user.id}
- isLoaded={this.state.isLoaded}
- busyThreads={this.state.busyThreads}
- >
- <ThreadsListEmpty
- category={this.props.route.category}
- emptyMessage={this.props.options.emptyMessage}
- list={this.props.route.list}
- />
- </ThreadsList>
- {this.getMoreButton()}
- </Container>
- </div>
- )
- }
- }
|