mirror of
https://github.com/flarum/framework.git
synced 2025-02-06 04:29:46 +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 SessionDropdown from './components/SessionDropdown';
|
||||||
import HeaderPrimary from './components/HeaderPrimary';
|
import HeaderPrimary from './components/HeaderPrimary';
|
||||||
import AppearancePage from './components/AppearancePage';
|
import AppearancePage from './components/AppearancePage';
|
||||||
import Page from './components/Page';
|
|
||||||
import StatusWidget from './components/StatusWidget';
|
import StatusWidget from './components/StatusWidget';
|
||||||
import HeaderSecondary from './components/HeaderSecondary';
|
import HeaderSecondary from './components/HeaderSecondary';
|
||||||
import SettingsModal from './components/SettingsModal';
|
import SettingsModal from './components/SettingsModal';
|
||||||
|
@ -36,7 +35,6 @@ export default Object.assign(compat, {
|
||||||
'components/SessionDropdown': SessionDropdown,
|
'components/SessionDropdown': SessionDropdown,
|
||||||
'components/HeaderPrimary': HeaderPrimary,
|
'components/HeaderPrimary': HeaderPrimary,
|
||||||
'components/AppearancePage': AppearancePage,
|
'components/AppearancePage': AppearancePage,
|
||||||
'components/Page': Page,
|
|
||||||
'components/StatusWidget': StatusWidget,
|
'components/StatusWidget': StatusWidget,
|
||||||
'components/HeaderSecondary': HeaderSecondary,
|
'components/HeaderSecondary': HeaderSecondary,
|
||||||
'components/SettingsModal': SettingsModal,
|
'components/SettingsModal': SettingsModal,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Page from './Page';
|
import Page from '../../common/components/Page';
|
||||||
import Button from '../../common/components/Button';
|
import Button from '../../common/components/Button';
|
||||||
import Switch from '../../common/components/Switch';
|
import Switch from '../../common/components/Switch';
|
||||||
import EditCustomCssModal from './EditCustomCssModal';
|
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 FieldSet from '../../common/components/FieldSet';
|
||||||
import Select from '../../common/components/Select';
|
import Select from '../../common/components/Select';
|
||||||
import Button from '../../common/components/Button';
|
import Button from '../../common/components/Button';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Page from './Page';
|
import Page from '../../common/components/Page';
|
||||||
import StatusWidget from './StatusWidget';
|
import StatusWidget from './StatusWidget';
|
||||||
|
|
||||||
export default class DashboardPage extends Page {
|
export default class DashboardPage extends Page {
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import Page from './Page';
|
import Page from '../../common/components/Page';
|
||||||
import LinkButton from '../../common/components/LinkButton';
|
|
||||||
import Button from '../../common/components/Button';
|
import Button from '../../common/components/Button';
|
||||||
import Dropdown from '../../common/components/Dropdown';
|
import Dropdown from '../../common/components/Dropdown';
|
||||||
import Separator from '../../common/components/Separator';
|
|
||||||
import AddExtensionModal from './AddExtensionModal';
|
import AddExtensionModal from './AddExtensionModal';
|
||||||
import LoadingModal from './LoadingModal';
|
import LoadingModal from './LoadingModal';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from '../../common/utils/ItemList';
|
||||||
import icon from '../../common/helpers/icon';
|
import icon from '../../common/helpers/icon';
|
||||||
import listItems from '../../common/helpers/listItems';
|
|
||||||
|
|
||||||
export default class ExtensionsPage extends Page {
|
export default class ExtensionsPage extends Page {
|
||||||
view() {
|
view() {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Page from './Page';
|
import Page from '../../common/components/Page';
|
||||||
import FieldSet from '../../common/components/FieldSet';
|
import FieldSet from '../../common/components/FieldSet';
|
||||||
import Button from '../../common/components/Button';
|
import Button from '../../common/components/Button';
|
||||||
import Alert from '../../common/components/Alert';
|
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 GroupBadge from '../../common/components/GroupBadge';
|
||||||
import EditGroupModal from './EditGroupModal';
|
import EditGroupModal from './EditGroupModal';
|
||||||
import Group from '../../common/models/Group';
|
import Group from '../../common/models/Group';
|
||||||
|
|
|
@ -21,6 +21,7 @@ import Post from './models/Post';
|
||||||
import Group from './models/Group';
|
import Group from './models/Group';
|
||||||
import Notification from './models/Notification';
|
import Notification from './models/Notification';
|
||||||
import { flattenDeep } from 'lodash-es';
|
import { flattenDeep } from 'lodash-es';
|
||||||
|
import PageState from './states/PageState';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `App` class provides a container for an application, as well as various
|
* The `App` class provides a container for an application, as well as various
|
||||||
|
@ -115,6 +116,28 @@ export default class Application {
|
||||||
*/
|
*/
|
||||||
requestError = null;
|
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;
|
data;
|
||||||
|
|
||||||
title = '';
|
title = '';
|
||||||
|
|
|
@ -30,6 +30,7 @@ import Forum from './models/Forum';
|
||||||
import Component from './Component';
|
import Component from './Component';
|
||||||
import Translator from './Translator';
|
import Translator from './Translator';
|
||||||
import AlertManager from './components/AlertManager';
|
import AlertManager from './components/AlertManager';
|
||||||
|
import Page from './components/Page';
|
||||||
import Switch from './components/Switch';
|
import Switch from './components/Switch';
|
||||||
import Badge from './components/Badge';
|
import Badge from './components/Badge';
|
||||||
import LoadingIndicator from './components/LoadingIndicator';
|
import LoadingIndicator from './components/LoadingIndicator';
|
||||||
|
@ -94,6 +95,7 @@ export default {
|
||||||
Component: Component,
|
Component: Component,
|
||||||
Translator: Translator,
|
Translator: Translator,
|
||||||
'components/AlertManager': AlertManager,
|
'components/AlertManager': AlertManager,
|
||||||
|
'components/Page': Page,
|
||||||
'components/Switch': Switch,
|
'components/Switch': Switch,
|
||||||
'components/Badge': Badge,
|
'components/Badge': Badge,
|
||||||
'components/LoadingIndicator': LoadingIndicator,
|
'components/LoadingIndicator': LoadingIndicator,
|
||||||
|
|
|
@ -43,8 +43,6 @@ export default class ModalManager extends Component {
|
||||||
this.showing = true;
|
this.showing = true;
|
||||||
this.component = component;
|
this.component = component;
|
||||||
|
|
||||||
if (app.current) app.current.retain = true;
|
|
||||||
|
|
||||||
m.redraw(true);
|
m.redraw(true);
|
||||||
|
|
||||||
const dismissible = !!this.component.isDismissible();
|
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
|
* The `Page` component
|
||||||
|
@ -8,7 +9,7 @@ import Component from '../../common/Component';
|
||||||
export default class Page extends Component {
|
export default class Page extends Component {
|
||||||
init() {
|
init() {
|
||||||
app.previous = app.current;
|
app.previous = app.current;
|
||||||
app.current = this;
|
app.current = new PageState(this.constructor);
|
||||||
|
|
||||||
app.drawer.hide();
|
app.drawer.hide();
|
||||||
app.modal.close();
|
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}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
viewingDiscussion(discussion) {
|
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 PostStream from './components/PostStream';
|
||||||
import ChangePasswordModal from './components/ChangePasswordModal';
|
import ChangePasswordModal from './components/ChangePasswordModal';
|
||||||
import IndexPage from './components/IndexPage';
|
import IndexPage from './components/IndexPage';
|
||||||
import Page from './components/Page';
|
|
||||||
import DiscussionRenamedNotification from './components/DiscussionRenamedNotification';
|
import DiscussionRenamedNotification from './components/DiscussionRenamedNotification';
|
||||||
import DiscussionsSearchSource from './components/DiscussionsSearchSource';
|
import DiscussionsSearchSource from './components/DiscussionsSearchSource';
|
||||||
import HeaderSecondary from './components/HeaderSecondary';
|
import HeaderSecondary from './components/HeaderSecondary';
|
||||||
|
@ -92,7 +91,6 @@ export default Object.assign(compat, {
|
||||||
'components/PostStream': PostStream,
|
'components/PostStream': PostStream,
|
||||||
'components/ChangePasswordModal': ChangePasswordModal,
|
'components/ChangePasswordModal': ChangePasswordModal,
|
||||||
'components/IndexPage': IndexPage,
|
'components/IndexPage': IndexPage,
|
||||||
'components/Page': Page,
|
|
||||||
'components/DiscussionRenamedNotification': DiscussionRenamedNotification,
|
'components/DiscussionRenamedNotification': DiscussionRenamedNotification,
|
||||||
'components/DiscussionsSearchSource': DiscussionsSearchSource,
|
'components/DiscussionsSearchSource': DiscussionsSearchSource,
|
||||||
'components/HeaderSecondary': HeaderSecondary,
|
'components/HeaderSecondary': HeaderSecondary,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Page from './Page';
|
import Page from '../../common/components/Page';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from '../../common/utils/ItemList';
|
||||||
import DiscussionHero from './DiscussionHero';
|
import DiscussionHero from './DiscussionHero';
|
||||||
import PostStream from './PostStream';
|
import PostStream from './PostStream';
|
||||||
|
@ -42,7 +42,7 @@ export default class DiscussionPage extends Page {
|
||||||
app.pane.enable();
|
app.pane.enable();
|
||||||
app.pane.hide();
|
app.pane.hide();
|
||||||
|
|
||||||
if (app.previous instanceof DiscussionPage) {
|
if (app.previous.matches(DiscussionPage)) {
|
||||||
m.redraw.strategy('diff');
|
m.redraw.strategy('diff');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,6 +200,9 @@ export default class DiscussionPage extends Page {
|
||||||
this.stream = new PostStream({ discussion, includedPosts });
|
this.stream = new PostStream({ discussion, includedPosts });
|
||||||
this.stream.on('positionChanged', this.positionChanged.bind(this));
|
this.stream.on('positionChanged', this.positionChanged.bind(this));
|
||||||
this.stream.goToNumber(m.route.param('near') || (includedPosts[0] && includedPosts[0].number()), true);
|
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 { extend } from '../../common/extend';
|
||||||
import Page from './Page';
|
import Page from '../../common/components/Page';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from '../../common/utils/ItemList';
|
||||||
import listItems from '../../common/helpers/listItems';
|
import listItems from '../../common/helpers/listItems';
|
||||||
import DiscussionList from './DiscussionList';
|
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
|
// 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
|
// discussion they have just visited. After the view is rendered, we will
|
||||||
// scroll down so that this discussion is in view.
|
// scroll down so that this discussion is in view.
|
||||||
if (app.previous instanceof DiscussionPage) {
|
if (app.previous.matches(DiscussionPage)) {
|
||||||
this.lastDiscussion = app.previous.discussion;
|
this.lastDiscussion = app.previous.get('discussion');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user is coming from the discussion list, then they have either
|
// If the user is coming from the discussion list, then they have either
|
||||||
// just switched one of the parameters (filter, sort, search) or they
|
// just switched one of the parameters (filter, sort, search) or they
|
||||||
// probably want to refresh the results. We will clear the discussion list
|
// probably want to refresh the results. We will clear the discussion list
|
||||||
// cache so that results are reloaded.
|
// cache so that results are reloaded.
|
||||||
if (app.previous instanceof IndexPage) {
|
if (app.previous.matches(IndexPage)) {
|
||||||
app.discussions.clear();
|
app.discussions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import Page from './Page';
|
import Page from '../../common/components/Page';
|
||||||
import NotificationList from './NotificationList';
|
import NotificationList from './NotificationList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -116,6 +116,7 @@ export default class Post extends Component {
|
||||||
let classes = (existing || '').split(' ').concat(['Post']);
|
let classes = (existing || '').split(' ').concat(['Post']);
|
||||||
|
|
||||||
const user = this.props.post.user();
|
const user = this.props.post.user();
|
||||||
|
const discussion = this.props.post.discussion();
|
||||||
|
|
||||||
if (this.loading) {
|
if (this.loading) {
|
||||||
classes.push('Post--loading');
|
classes.push('Post--loading');
|
||||||
|
@ -125,7 +126,7 @@ export default class Post extends Component {
|
||||||
classes.push('Post--by-actor');
|
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');
|
classes.push('Post--by-start-user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default class RenameDiscussionModal extends Modal {
|
||||||
.save({ title })
|
.save({ title })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (app.viewingDiscussion(this.discussion)) {
|
if (app.viewingDiscussion(this.discussion)) {
|
||||||
app.current.stream.update();
|
app.current.get('stream').update();
|
||||||
}
|
}
|
||||||
m.redraw();
|
m.redraw();
|
||||||
this.hide();
|
this.hide();
|
||||||
|
|
|
@ -89,7 +89,8 @@ export default class ReplyComposer extends ComposerBody {
|
||||||
// If we're currently viewing the discussion which this reply was made
|
// 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.
|
// in, then we can update the post stream and scroll to the post.
|
||||||
if (app.viewingDiscussion(discussion)) {
|
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 {
|
} else {
|
||||||
// Otherwise, we'll create an alert message to inform the user that
|
// Otherwise, we'll create an alert message to inform the user that
|
||||||
// their reply has been posted, containing a button which will
|
// 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 ItemList from '../../common/utils/ItemList';
|
||||||
import affixSidebar from '../utils/affixSidebar';
|
import affixSidebar from '../utils/affixSidebar';
|
||||||
import UserCard from './UserCard';
|
import UserCard from './UserCard';
|
||||||
|
@ -71,6 +71,8 @@ export default class UserPage extends Page {
|
||||||
show(user) {
|
show(user) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
|
||||||
|
app.current.set('user', user);
|
||||||
|
|
||||||
app.setTitle(user.displayName());
|
app.setTitle(user.displayName());
|
||||||
|
|
||||||
m.redraw();
|
m.redraw();
|
||||||
|
|
|
@ -77,7 +77,7 @@ export default class GlobalSearchState extends SearchState {
|
||||||
* @return {String}
|
* @return {String}
|
||||||
*/
|
*/
|
||||||
getInitialSearch() {
|
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();
|
app.composer.show();
|
||||||
|
|
||||||
if (goToLast && app.viewingDiscussion(this) && !app.composer.isFullScreen()) {
|
if (goToLast && app.viewingDiscussion(this) && !app.composer.isFullScreen()) {
|
||||||
app.current.stream.goToNumber('reply');
|
app.current.get('stream').goToNumber('reply');
|
||||||
}
|
}
|
||||||
|
|
||||||
deferred.resolve(component);
|
deferred.resolve(component);
|
||||||
|
|
|
@ -112,7 +112,7 @@ export default {
|
||||||
.delete()
|
.delete()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.showDeletionAlert(user, 'success');
|
this.showDeletionAlert(user, 'success');
|
||||||
if (app.current instanceof UserPage && app.current.user === user) {
|
if (app.current.matches(UserPage, { user })) {
|
||||||
app.history.back();
|
app.history.back();
|
||||||
} else {
|
} else {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user