mirror of
https://github.com/flarum/framework.git
synced 2025-02-05 22:40:45 +08:00
Clean up app.current, app.previous in JS (#2156)
- Encapsulate app.current, app.previous in PageState objects - Reorganize Page classes to use one central base class in common Co-authored-by: Franz Liedke <franz@develophp.org>
This commit is contained in:
parent
17eac0fa4a
commit
1c1a3b363e
|
@ -6,7 +6,6 @@ import EditCustomFooterModal from './components/EditCustomFooterModal';
|
|||
import SessionDropdown from './components/SessionDropdown';
|
||||
import HeaderPrimary from './components/HeaderPrimary';
|
||||
import AppearancePage from './components/AppearancePage';
|
||||
import Page from './components/Page';
|
||||
import StatusWidget from './components/StatusWidget';
|
||||
import HeaderSecondary from './components/HeaderSecondary';
|
||||
import SettingsModal from './components/SettingsModal';
|
||||
|
@ -36,7 +35,6 @@ export default Object.assign(compat, {
|
|||
'components/SessionDropdown': SessionDropdown,
|
||||
'components/HeaderPrimary': HeaderPrimary,
|
||||
'components/AppearancePage': AppearancePage,
|
||||
'components/Page': Page,
|
||||
'components/StatusWidget': StatusWidget,
|
||||
'components/HeaderSecondary': HeaderSecondary,
|
||||
'components/SettingsModal': SettingsModal,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Page from './Page';
|
||||
import Page from '../../common/components/Page';
|
||||
import Button from '../../common/components/Button';
|
||||
import Switch from '../../common/components/Switch';
|
||||
import EditCustomCssModal from './EditCustomCssModal';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Page from './Page';
|
||||
import Page from '../../common/components/Page';
|
||||
import FieldSet from '../../common/components/FieldSet';
|
||||
import Select from '../../common/components/Select';
|
||||
import Button from '../../common/components/Button';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Page from './Page';
|
||||
import Page from '../../common/components/Page';
|
||||
import StatusWidget from './StatusWidget';
|
||||
|
||||
export default class DashboardPage extends Page {
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import Page from './Page';
|
||||
import LinkButton from '../../common/components/LinkButton';
|
||||
import Page from '../../common/components/Page';
|
||||
import Button from '../../common/components/Button';
|
||||
import Dropdown from '../../common/components/Dropdown';
|
||||
import Separator from '../../common/components/Separator';
|
||||
import AddExtensionModal from './AddExtensionModal';
|
||||
import LoadingModal from './LoadingModal';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import icon from '../../common/helpers/icon';
|
||||
import listItems from '../../common/helpers/listItems';
|
||||
|
||||
export default class ExtensionsPage extends Page {
|
||||
view() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Page from './Page';
|
||||
import Page from '../../common/components/Page';
|
||||
import FieldSet from '../../common/components/FieldSet';
|
||||
import Button from '../../common/components/Button';
|
||||
import Alert from '../../common/components/Alert';
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
import Component from '../../common/Component';
|
||||
|
||||
/**
|
||||
* The `Page` component
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
export default class Page extends Component {
|
||||
init() {
|
||||
app.previous = app.current;
|
||||
app.current = this;
|
||||
|
||||
app.modal.close();
|
||||
|
||||
/**
|
||||
* A class name to apply to the body while the route is active.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
this.bodyClass = '';
|
||||
}
|
||||
|
||||
config(isInitialized, context) {
|
||||
if (isInitialized) return;
|
||||
|
||||
if (this.bodyClass) {
|
||||
$('#app').addClass(this.bodyClass);
|
||||
|
||||
context.onunload = () => $('#app').removeClass(this.bodyClass);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import Page from './Page';
|
||||
import Page from '../../common/components/Page';
|
||||
import GroupBadge from '../../common/components/GroupBadge';
|
||||
import EditGroupModal from './EditGroupModal';
|
||||
import Group from '../../common/models/Group';
|
||||
|
|
|
@ -21,6 +21,7 @@ import Post from './models/Post';
|
|||
import Group from './models/Group';
|
||||
import Notification from './models/Notification';
|
||||
import { flattenDeep } from 'lodash-es';
|
||||
import PageState from './states/PageState';
|
||||
|
||||
/**
|
||||
* The `App` class provides a container for an application, as well as various
|
||||
|
@ -115,6 +116,28 @@ export default class Application {
|
|||
*/
|
||||
requestError = null;
|
||||
|
||||
/**
|
||||
* The page the app is currently on.
|
||||
*
|
||||
* This object holds information about the type of page we are currently
|
||||
* visiting, and sometimes additional arbitrary page state that may be
|
||||
* relevant to lower-level components.
|
||||
*
|
||||
* @type {PageState}
|
||||
*/
|
||||
current = new PageState(null);
|
||||
|
||||
/**
|
||||
* The page the app was on before the current page.
|
||||
*
|
||||
* Once the application navigates to another page, the object previously
|
||||
* assigned to this.current will be moved to this.previous, while this.current
|
||||
* is re-initialized.
|
||||
*
|
||||
* @type {PageState}
|
||||
*/
|
||||
previous = new PageState(null);
|
||||
|
||||
data;
|
||||
|
||||
title = '';
|
||||
|
|
|
@ -30,6 +30,7 @@ import Forum from './models/Forum';
|
|||
import Component from './Component';
|
||||
import Translator from './Translator';
|
||||
import AlertManager from './components/AlertManager';
|
||||
import Page from './components/Page';
|
||||
import Switch from './components/Switch';
|
||||
import Badge from './components/Badge';
|
||||
import LoadingIndicator from './components/LoadingIndicator';
|
||||
|
@ -94,6 +95,7 @@ export default {
|
|||
Component: Component,
|
||||
Translator: Translator,
|
||||
'components/AlertManager': AlertManager,
|
||||
'components/Page': Page,
|
||||
'components/Switch': Switch,
|
||||
'components/Badge': Badge,
|
||||
'components/LoadingIndicator': LoadingIndicator,
|
||||
|
|
|
@ -43,8 +43,6 @@ export default class ModalManager extends Component {
|
|||
this.showing = true;
|
||||
this.component = component;
|
||||
|
||||
if (app.current) app.current.retain = true;
|
||||
|
||||
m.redraw(true);
|
||||
|
||||
const dismissible = !!this.component.isDismissible();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Component from '../../common/Component';
|
||||
import Component from '../Component';
|
||||
import PageState from '../states/PageState';
|
||||
|
||||
/**
|
||||
* The `Page` component
|
||||
|
@ -8,7 +9,7 @@ import Component from '../../common/Component';
|
|||
export default class Page extends Component {
|
||||
init() {
|
||||
app.previous = app.current;
|
||||
app.current = this;
|
||||
app.current = new PageState(this.constructor);
|
||||
|
||||
app.drawer.hide();
|
||||
app.modal.close();
|
33
framework/core/js/src/common/states/PageState.js
Normal file
33
framework/core/js/src/common/states/PageState.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import subclassOf from '../../common/utils/subclassOf';
|
||||
|
||||
export default class PageState {
|
||||
constructor(type, data = {}) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the page matches the given class and data.
|
||||
*
|
||||
* @param {object} type The page class to check against. Subclasses are
|
||||
* accepted as well.
|
||||
* @param {object} data
|
||||
* @return {boolean}
|
||||
*/
|
||||
matches(type, data = {}) {
|
||||
// Fail early when the page is of a different type
|
||||
if (!subclassOf(this.type, type)) return false;
|
||||
|
||||
// Now that the type is known to be correct, we loop through the provided
|
||||
// data to see whether it matches the data in our state.
|
||||
return Object.keys(data).every((key) => this.data[key] === data[key]);
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this.data[key];
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
this.data[key] = value;
|
||||
}
|
||||
}
|
6
framework/core/js/src/common/utils/subclassOf.js
Normal file
6
framework/core/js/src/common/utils/subclassOf.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Check if class A is the same as or a subclass of class B.
|
||||
*/
|
||||
export default function subclassOf(A, B) {
|
||||
return A && (A === B || A.prototype instanceof B);
|
||||
}
|
|
@ -160,7 +160,7 @@ export default class ForumApplication extends Application {
|
|||
* @return {Boolean}
|
||||
*/
|
||||
viewingDiscussion(discussion) {
|
||||
return this.current instanceof DiscussionPage && this.current.discussion === discussion;
|
||||
return this.current.matches(DiscussionPage, { discussion });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,7 +23,6 @@ import PostEdited from './components/PostEdited';
|
|||
import PostStream from './components/PostStream';
|
||||
import ChangePasswordModal from './components/ChangePasswordModal';
|
||||
import IndexPage from './components/IndexPage';
|
||||
import Page from './components/Page';
|
||||
import DiscussionRenamedNotification from './components/DiscussionRenamedNotification';
|
||||
import DiscussionsSearchSource from './components/DiscussionsSearchSource';
|
||||
import HeaderSecondary from './components/HeaderSecondary';
|
||||
|
@ -92,7 +91,6 @@ export default Object.assign(compat, {
|
|||
'components/PostStream': PostStream,
|
||||
'components/ChangePasswordModal': ChangePasswordModal,
|
||||
'components/IndexPage': IndexPage,
|
||||
'components/Page': Page,
|
||||
'components/DiscussionRenamedNotification': DiscussionRenamedNotification,
|
||||
'components/DiscussionsSearchSource': DiscussionsSearchSource,
|
||||
'components/HeaderSecondary': HeaderSecondary,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Page from './Page';
|
||||
import Page from '../../common/components/Page';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import DiscussionHero from './DiscussionHero';
|
||||
import PostStream from './PostStream';
|
||||
|
@ -42,7 +42,7 @@ export default class DiscussionPage extends Page {
|
|||
app.pane.enable();
|
||||
app.pane.hide();
|
||||
|
||||
if (app.previous instanceof DiscussionPage) {
|
||||
if (app.previous.matches(DiscussionPage)) {
|
||||
m.redraw.strategy('diff');
|
||||
}
|
||||
}
|
||||
|
@ -200,6 +200,9 @@ export default class DiscussionPage extends Page {
|
|||
this.stream = new PostStream({ discussion, includedPosts });
|
||||
this.stream.on('positionChanged', this.positionChanged.bind(this));
|
||||
this.stream.goToNumber(m.route.param('near') || (includedPosts[0] && includedPosts[0].number()), true);
|
||||
|
||||
app.current.set('discussion', discussion);
|
||||
app.current.set('stream', this.stream);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { extend } from '../../common/extend';
|
||||
import Page from './Page';
|
||||
import Page from '../../common/components/Page';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import listItems from '../../common/helpers/listItems';
|
||||
import DiscussionList from './DiscussionList';
|
||||
|
@ -25,15 +25,15 @@ export default class IndexPage extends Page {
|
|||
// If the user is returning from a discussion page, then take note of which
|
||||
// discussion they have just visited. After the view is rendered, we will
|
||||
// scroll down so that this discussion is in view.
|
||||
if (app.previous instanceof DiscussionPage) {
|
||||
this.lastDiscussion = app.previous.discussion;
|
||||
if (app.previous.matches(DiscussionPage)) {
|
||||
this.lastDiscussion = app.previous.get('discussion');
|
||||
}
|
||||
|
||||
// If the user is coming from the discussion list, then they have either
|
||||
// just switched one of the parameters (filter, sort, search) or they
|
||||
// probably want to refresh the results. We will clear the discussion list
|
||||
// cache so that results are reloaded.
|
||||
if (app.previous instanceof IndexPage) {
|
||||
if (app.previous.matches(IndexPage)) {
|
||||
app.discussions.clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Page from './Page';
|
||||
import Page from '../../common/components/Page';
|
||||
import NotificationList from './NotificationList';
|
||||
|
||||
/**
|
||||
|
|
|
@ -116,6 +116,7 @@ export default class Post extends Component {
|
|||
let classes = (existing || '').split(' ').concat(['Post']);
|
||||
|
||||
const user = this.props.post.user();
|
||||
const discussion = this.props.post.discussion();
|
||||
|
||||
if (this.loading) {
|
||||
classes.push('Post--loading');
|
||||
|
@ -125,7 +126,7 @@ export default class Post extends Component {
|
|||
classes.push('Post--by-actor');
|
||||
}
|
||||
|
||||
if (user && app.current.discussion && app.current.discussion.attribute('startUserId') == user.id()) {
|
||||
if (user && user === discussion.user()) {
|
||||
classes.push('Post--by-start-user');
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ export default class RenameDiscussionModal extends Modal {
|
|||
.save({ title })
|
||||
.then(() => {
|
||||
if (app.viewingDiscussion(this.discussion)) {
|
||||
app.current.stream.update();
|
||||
app.current.get('stream').update();
|
||||
}
|
||||
m.redraw();
|
||||
this.hide();
|
||||
|
|
|
@ -89,7 +89,8 @@ export default class ReplyComposer extends ComposerBody {
|
|||
// If we're currently viewing the discussion which this reply was made
|
||||
// in, then we can update the post stream and scroll to the post.
|
||||
if (app.viewingDiscussion(discussion)) {
|
||||
app.current.stream.update().then(() => app.current.stream.goToNumber(post.number()));
|
||||
const stream = app.current.get('stream');
|
||||
stream.update().then(() => stream.goToNumber(post.number()));
|
||||
} else {
|
||||
// Otherwise, we'll create an alert message to inform the user that
|
||||
// their reply has been posted, containing a button which will
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Page from './Page';
|
||||
import Page from '../../common/components/Page';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import affixSidebar from '../utils/affixSidebar';
|
||||
import UserCard from './UserCard';
|
||||
|
@ -71,6 +71,8 @@ export default class UserPage extends Page {
|
|||
show(user) {
|
||||
this.user = user;
|
||||
|
||||
app.current.set('user', user);
|
||||
|
||||
app.setTitle(user.displayName());
|
||||
|
||||
m.redraw();
|
||||
|
|
|
@ -77,7 +77,7 @@ export default class GlobalSearchState extends SearchState {
|
|||
* @return {String}
|
||||
*/
|
||||
getInitialSearch() {
|
||||
return app.current.constructor.providesInitialSearch && this.params().q;
|
||||
return app.current.type.providesInitialSearch && this.params().q;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -178,7 +178,7 @@ export default {
|
|||
app.composer.show();
|
||||
|
||||
if (goToLast && app.viewingDiscussion(this) && !app.composer.isFullScreen()) {
|
||||
app.current.stream.goToNumber('reply');
|
||||
app.current.get('stream').goToNumber('reply');
|
||||
}
|
||||
|
||||
deferred.resolve(component);
|
||||
|
|
|
@ -112,7 +112,7 @@ export default {
|
|||
.delete()
|
||||
.then(() => {
|
||||
this.showDeletionAlert(user, 'success');
|
||||
if (app.current instanceof UserPage && app.current.user === user) {
|
||||
if (app.current.matches(UserPage, { user })) {
|
||||
app.history.back();
|
||||
} else {
|
||||
window.location.reload();
|
||||
|
|
Loading…
Reference in New Issue
Block a user