mirror of
https://github.com/flarum/core.git
synced 2025-08-15 04:44:08 +02:00
Compare commits
3 Commits
dw/sidenav
...
dw/consist
Author | SHA1 | Date | |
---|---|---|---|
|
3718673882 | ||
|
837657831f | ||
|
0334b6c972 |
@@ -9,6 +9,7 @@
|
||||
<a href="https://github.styleci.io/repos/28257573"><img src="https://github.styleci.io/repos/28257573/shield?style=flat" alt="StyleCI"></a>
|
||||
</p>
|
||||
|
||||
|
||||
## About Flarum
|
||||
|
||||
**[Flarum](https://flarum.org/) is a delightfully simple discussion platform for your website.** It's fast and easy to use, with all the features you need to run a successful community. It is designed to be:
|
||||
@@ -19,15 +20,13 @@
|
||||
|
||||
* **Powerful and extensible.** Customize, extend, and integrate Flarum to suit your community. Flarum’s architecture is amazingly flexible, with a powerful Extension API.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
This repository contains Flarum's core code. If you want to set up a forum, visit the [Flarum skeleton repository](https://github.com/flarum/flarum). For support, refer to the [documentation](https://docs.flarum.org/), and ask questions on [Flarum Discuss](https://discuss.flarum.org/) (our community forum) or [Discord server](https://flarum.org/discord/).
|
||||
This repository contains Flarum's core code. If you want to set up a forum, visit the [Flarum skeleton repository](https://github.com/flarum/flarum).
|
||||
|
||||
## Contributing
|
||||
|
||||
Thank you for considering contributing to Flarum! Please read the **[Contributing guide](https://docs.flarum.org/contributing)** to learn how you can help.
|
||||
Thank you for considering contributing to Flarum! Please read the **[Contributing guide](https://flarum.org/docs/contributing.html)** to learn how you can help.
|
||||
|
||||
## Security Vulnerabilities
|
||||
|
||||
|
@@ -10,17 +10,3 @@
|
||||
* can fix.
|
||||
*/
|
||||
export default function fireDebugWarning(...args: Parameters<typeof console.warn>): void;
|
||||
/**
|
||||
* Fire a Flarum deprecation warning which is shown in the JS console.
|
||||
*
|
||||
* These warnings are only shown when the forum is in debug mode, and the function exists to
|
||||
* reduce bundle size caused by multiple warnings across our JavaScript.
|
||||
*
|
||||
* @param message The message to display. (Short, but sweet, please!)
|
||||
* @param githubId The PR or Issue ID with more info in relation to this change.
|
||||
* @param [removedFrom] The version in which this feature will be completely removed. (default: 2.0)
|
||||
* @param [repo] The repo which the issue or PR is located in. (default: flarum/core)
|
||||
*
|
||||
* @see {@link fireDebugWarning}
|
||||
*/
|
||||
export declare function fireDeprecationWarning(message: string, githubId: string, removedFrom?: string, repo?: string): void;
|
||||
|
12
js/dist-typings/forum/components/Search.d.ts
vendored
12
js/dist-typings/forum/components/Search.d.ts
vendored
@@ -42,22 +42,12 @@ export interface SearchAttrs extends ComponentAttrs {
|
||||
*
|
||||
* - state: SearchState instance.
|
||||
*/
|
||||
export default class Search<T extends SearchAttrs = SearchAttrs> extends Component<T, SearchState> {
|
||||
export default class Search<T extends SearchAttrs = SearchAttrs> extends Component<T> {
|
||||
/**
|
||||
* The minimum query length before sources are searched.
|
||||
*/
|
||||
protected static MIN_SEARCH_LEN: number;
|
||||
/**
|
||||
* The instance of `SearchState` for this component.
|
||||
*/
|
||||
protected searchState: SearchState;
|
||||
/**
|
||||
* The instance of `SearchState` for this component.
|
||||
*
|
||||
* @deprecated Replace with`this.searchState` instead.
|
||||
*/
|
||||
protected get state(): SearchState;
|
||||
protected set state(state: SearchState);
|
||||
/**
|
||||
* Whether or not the search input has focus.
|
||||
*/
|
||||
|
@@ -1,26 +1,13 @@
|
||||
import Component from '../../common/Component';
|
||||
import type Mithril from 'mithril';
|
||||
export interface IWelcomeHeroAttrs {
|
||||
}
|
||||
/**
|
||||
* The `WelcomeHero` component displays a hero that welcomes the user to the
|
||||
* forum.
|
||||
*/
|
||||
export default class WelcomeHero extends Component<IWelcomeHeroAttrs> {
|
||||
/**
|
||||
* @deprecated Extend the `isHidden` method instead.
|
||||
*/
|
||||
hidden: boolean;
|
||||
oninit(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>): void;
|
||||
view(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>): JSX.Element | null;
|
||||
export default class WelcomeHero extends Component<import("../../common/Component").ComponentAttrs, undefined> {
|
||||
constructor();
|
||||
hidden: string | boolean | null | undefined;
|
||||
/**
|
||||
* Hide the welcome hero.
|
||||
*/
|
||||
hide(): void;
|
||||
/**
|
||||
* Determines whether the welcome hero should be hidden.
|
||||
*
|
||||
* @returns if the welcome hero is hidden.
|
||||
*/
|
||||
isHidden(): boolean;
|
||||
}
|
||||
import Component from "../../common/Component";
|
||||
|
2
js/dist/admin.js
generated
vendored
2
js/dist/admin.js
generated
vendored
File diff suppressed because one or more lines are too long
2
js/dist/admin.js.map
generated
vendored
2
js/dist/admin.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js
generated
vendored
2
js/dist/forum.js
generated
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js.map
generated
vendored
2
js/dist/forum.js.map
generated
vendored
File diff suppressed because one or more lines are too long
@@ -20,6 +20,6 @@ export default class DashboardWidget extends Component {
|
||||
* @return {VirtualElement}
|
||||
*/
|
||||
content() {
|
||||
return [];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ export default class ExtensionsWidget extends DashboardWidget {
|
||||
|
||||
return (
|
||||
<div className="ExtensionsWidget-list">
|
||||
{Object.keys(categories).map((category) => (this.categorizedExtensions[category] ? this.extensionCategory(category) : ''))}
|
||||
{Object.keys(categories).map((category) => !!this.categorizedExtensions[category] && this.extensionCategory(category))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export default class ExtensionsWidget extends DashboardWidget {
|
||||
<Link href={app.route('extension', { id: extension.id })}>
|
||||
<div className="ExtensionListItem-content">
|
||||
<span className="ExtensionListItem-icon ExtensionIcon" style={extension.icon}>
|
||||
{extension.icon ? icon(extension.icon.name) : ''}
|
||||
{!!extension.icon && icon(extension.icon.name)}
|
||||
</span>
|
||||
<span className="ExtensionListItem-title">{extension.extra['flarum-extension'].title}</span>
|
||||
</div>
|
||||
|
@@ -18,7 +18,7 @@ export default class LoadingModal<ModalAttrs extends ILoadingModalAttrs = ILoadi
|
||||
}
|
||||
|
||||
content() {
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
onsubmit(e: Event): void {
|
||||
|
@@ -56,9 +56,8 @@ export default class PermissionGrid<CustomAttrs extends IPermissionGridAttrs = I
|
||||
{scopes.map((scope) => (
|
||||
<th>
|
||||
{scope.label}{' '}
|
||||
{scope.onremove
|
||||
? Button.component({ icon: 'fas fa-times', className: 'Button Button--text PermissionGrid-removeScope', onclick: scope.onremove })
|
||||
: ''}
|
||||
{!!scope.onremove &&
|
||||
Button.component({ icon: 'fas fa-times', className: 'Button Button--text PermissionGrid-removeScope', onclick: scope.onremove })}
|
||||
</th>
|
||||
))}
|
||||
<th>{this.scopeControlItems().toArray()}</th>
|
||||
|
@@ -175,7 +175,7 @@ export default class UserListPage extends AdminPage {
|
||||
'id',
|
||||
{
|
||||
name: app.translator.trans('core.admin.users.grid.columns.user_id.title'),
|
||||
content: (user: User) => user.id() ?? '',
|
||||
content: (user: User) => user.id() ?? null,
|
||||
},
|
||||
100
|
||||
);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import app from '../common/app';
|
||||
import { FlarumRequestOptions } from './Application';
|
||||
import { fireDeprecationWarning } from './helpers/fireDebugWarning';
|
||||
import fireDebugWarning from './helpers/fireDebugWarning';
|
||||
import Model, { ModelData, SavedModelData } from './Model';
|
||||
|
||||
export interface MetaInformation {
|
||||
@@ -123,7 +123,11 @@ export default class Store {
|
||||
if (!this.models[data.type]) {
|
||||
if (!allowUnregistered) {
|
||||
setTimeout(() =>
|
||||
fireDeprecationWarning(`Pushing object of type \`${data.type}\` not allowed, as type not yet registered in the store.`, '3206')
|
||||
fireDebugWarning(
|
||||
`[Flarum 2.0 Deprecation] Cannot push object of type \`${data.type}\`, as that type has not yet been registered in the store. This will throw an error in Flarum 2.0 and later.
|
||||
|
||||
For more information, see https://github.com/flarum/core/pull/3206.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -37,7 +37,7 @@ export default class Dropdown extends Component {
|
||||
}
|
||||
|
||||
view(vnode) {
|
||||
const items = vnode.children ? listItems(vnode.children) : [];
|
||||
const items = vnode.children ? listItems(vnode.children) : null;
|
||||
const renderItems = this.attrs.lazyDraw ? this.showing : true;
|
||||
|
||||
return (
|
||||
@@ -128,9 +128,9 @@ export default class Dropdown extends Component {
|
||||
*/
|
||||
getButtonContent(children) {
|
||||
return [
|
||||
this.attrs.icon ? icon(this.attrs.icon, { className: 'Button-icon' }) : '',
|
||||
!!this.attrs.icon && icon(this.attrs.icon, { className: 'Button-icon' }),
|
||||
<span className="Button-label">{this.attrs.label}</span>,
|
||||
this.attrs.caretIcon ? icon(this.attrs.caretIcon, { className: 'Button-caret' }) : '',
|
||||
!!this.attrs.caretIcon && icon(this.attrs.caretIcon, { className: 'Button-caret' }),
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -89,7 +89,7 @@ export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEd
|
||||
disabled={this.nonAdminEditingAdmin()}
|
||||
/>
|
||||
</div>
|
||||
{!this.isEmailConfirmed() && this.userIsAdmin(app.session.user) ? (
|
||||
{!this.isEmailConfirmed() && this.userIsAdmin(app.session.user) && (
|
||||
<div>
|
||||
{Button.component(
|
||||
{
|
||||
@@ -100,8 +100,6 @@ export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEd
|
||||
app.translator.trans('core.lib.edit_user.activate_button')
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>,
|
||||
30
|
||||
@@ -126,7 +124,7 @@ export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEd
|
||||
/>
|
||||
{app.translator.trans('core.lib.edit_user.set_password_label')}
|
||||
</label>
|
||||
{this.setPassword() ? (
|
||||
{this.setPassword() && (
|
||||
<input
|
||||
className="FormControl"
|
||||
type="password"
|
||||
@@ -135,8 +133,6 @@ export default class EditUserModal<CustomAttrs extends IEditUserModalAttrs = IEd
|
||||
bidi={this.password}
|
||||
disabled={this.nonAdminEditingAdmin()}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
</div>,
|
||||
|
@@ -105,7 +105,7 @@ export default abstract class Modal<ModalAttrs extends IInternalModalAttrs = IIn
|
||||
<h3 className="App-titleControl App-titleControl--text">{this.title()}</h3>
|
||||
</div>
|
||||
|
||||
{this.alertAttrs ? <div className="Modal-alert">{Alert.component(this.alertAttrs)}</div> : ''}
|
||||
{!!this.alertAttrs && <div className="Modal-alert">{Alert.component(this.alertAttrs)}</div>}
|
||||
|
||||
{this.content()}
|
||||
</form>
|
||||
|
@@ -65,7 +65,7 @@ export default class Navigation extends Component {
|
||||
getPaneButton() {
|
||||
const { pane } = app;
|
||||
|
||||
if (!pane || !pane.active) return '';
|
||||
if (!pane || !pane.active) return null;
|
||||
|
||||
return Button.component({
|
||||
className: 'Button Button--icon Navigation-pin' + (pane.pinned ? ' active' : ''),
|
||||
@@ -81,7 +81,7 @@ export default class Navigation extends Component {
|
||||
* @protected
|
||||
*/
|
||||
getDrawerButton() {
|
||||
if (!this.attrs.drawer) return '';
|
||||
if (!this.attrs.drawer) return null;
|
||||
|
||||
const { drawer } = app;
|
||||
const user = app.session.user;
|
||||
|
@@ -12,7 +12,7 @@ export default class RequestErrorModal<CustomAttrs extends IRequestErrorModalAtt
|
||||
}
|
||||
|
||||
title() {
|
||||
return this.attrs.error.xhr ? `${this.attrs.error.xhr.status} ${this.attrs.error.xhr.statusText}` : '';
|
||||
return !!this.attrs.error.xhr && `${this.attrs.error.xhr.status} ${this.attrs.error.xhr.statusText}`;
|
||||
}
|
||||
|
||||
content() {
|
||||
|
@@ -12,6 +12,6 @@ export default class Switch extends Checkbox {
|
||||
}
|
||||
|
||||
getDisplay() {
|
||||
return this.attrs.loading ? super.getDisplay() : '';
|
||||
return !!this.attrs.loading && super.getDisplay();
|
||||
}
|
||||
}
|
||||
|
@@ -16,21 +16,3 @@ export default function fireDebugWarning(...args: Parameters<typeof console.warn
|
||||
|
||||
console.warn(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a Flarum deprecation warning which is shown in the JS console.
|
||||
*
|
||||
* These warnings are only shown when the forum is in debug mode, and the function exists to
|
||||
* reduce bundle size caused by multiple warnings across our JavaScript.
|
||||
*
|
||||
* @param message The message to display. (Short, but sweet, please!)
|
||||
* @param githubId The PR or Issue ID with more info in relation to this change.
|
||||
* @param [removedFrom] The version in which this feature will be completely removed. (default: 2.0)
|
||||
* @param [repo] The repo which the issue or PR is located in. (default: flarum/core)
|
||||
*
|
||||
* @see {@link fireDebugWarning}
|
||||
*/
|
||||
export function fireDeprecationWarning(message: string, githubId: string, removedFrom: string = '2.0', repo: string = 'flarum/core'): void {
|
||||
// GitHub auto-redirects between `/pull` and `/issues` for us, so using `/pull` saves 2 bytes!
|
||||
fireDebugWarning(`[Flarum ${removedFrom} Deprecation] ${message}\n\nSee: https://github.com/${repo}/pull/${githubId}`);
|
||||
}
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import { fireDeprecationWarning } from '../helpers/fireDebugWarning';
|
||||
|
||||
const deprecatedNotice = 'The `evented` util is deprecated and no longer supported.';
|
||||
const deprecationIssueId = '2547';
|
||||
|
||||
/**
|
||||
* The `evented` mixin provides methods allowing an object to trigger events,
|
||||
* running externally registered event handlers.
|
||||
*
|
||||
* @deprecated v1.2, to be removed in v2.0
|
||||
*/
|
||||
|
||||
import fireDebugWarning from '../helpers/fireDebugWarning';
|
||||
|
||||
const deprecatedNotice =
|
||||
'The `evented` util is deprecated and will be removed in Flarum 2.0. For more info, please see https://github.com/flarum/core/issues/2547';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Arrays of registered event handlers, grouped by the event name.
|
||||
@@ -26,7 +27,7 @@ export default {
|
||||
* @protected
|
||||
*/
|
||||
getHandlers(event) {
|
||||
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
|
||||
fireDebugWarning(deprecatedNotice);
|
||||
|
||||
this.handlers = this.handlers || {};
|
||||
|
||||
@@ -43,7 +44,7 @@ export default {
|
||||
* @public
|
||||
*/
|
||||
trigger(event, ...args) {
|
||||
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
|
||||
fireDebugWarning(deprecatedNotice);
|
||||
|
||||
this.getHandlers(event).forEach((handler) => handler.apply(this, args));
|
||||
},
|
||||
@@ -55,7 +56,7 @@ export default {
|
||||
* @param {function} handler The function to handle the event.
|
||||
*/
|
||||
on(event, handler) {
|
||||
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
|
||||
fireDebugWarning(deprecatedNotice);
|
||||
|
||||
this.getHandlers(event).push(handler);
|
||||
},
|
||||
@@ -68,7 +69,7 @@ export default {
|
||||
* @param {function} handler The function to handle the event.
|
||||
*/
|
||||
one(event, handler) {
|
||||
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
|
||||
fireDebugWarning(deprecatedNotice);
|
||||
|
||||
const wrapper = function () {
|
||||
handler.apply(this, arguments);
|
||||
@@ -86,7 +87,7 @@ export default {
|
||||
* @param {function} handler The function that handles the event.
|
||||
*/
|
||||
off(event, handler) {
|
||||
fireDeprecationWarning(deprecatedNotice, deprecationIssueId);
|
||||
fireDebugWarning(deprecatedNotice);
|
||||
|
||||
const handlers = this.getHandlers(event);
|
||||
const index = handlers.indexOf(handler);
|
||||
|
@@ -51,7 +51,7 @@ export default class Composer extends Component {
|
||||
<div className="Composer-handle" oncreate={this.configHandle.bind(this)} />
|
||||
<ul className="Composer-controls">{listItems(this.controlItems().toArray())}</ul>
|
||||
<div className="Composer-content" onclick={showIfMinimized}>
|
||||
{body.componentClass ? body.componentClass.component({ ...body.attrs, composer: this.state, disabled: classes.minimized }) : ''}
|
||||
{!!body.componentClass && body.componentClass.component({ ...body.attrs, composer: this.state, disabled: classes.minimized })}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -125,17 +125,16 @@ export default class LogInModal<CustomAttrs extends ILoginModalAttrs = ILoginMod
|
||||
}
|
||||
|
||||
footer() {
|
||||
return [
|
||||
<p className="LogInModal-forgotPassword">
|
||||
<a onclick={this.forgotPassword.bind(this)}>{app.translator.trans('core.forum.log_in.forgot_password_link')}</a>
|
||||
</p>,
|
||||
|
||||
app.forum.attribute('allowSignUp') ? (
|
||||
<p className="LogInModal-signUp">{app.translator.trans('core.forum.log_in.sign_up_text', { a: <a onclick={this.signUp.bind(this)} /> })}</p>
|
||||
) : (
|
||||
''
|
||||
),
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<p className="LogInModal-forgotPassword">
|
||||
<a onclick={this.forgotPassword.bind(this)}>{app.translator.trans('core.forum.log_in.forgot_password_link')}</a>
|
||||
</p>
|
||||
{!!app.forum.attribute('allowSignUp') && (
|
||||
<p className="LogInModal-signUp">{app.translator.trans('core.forum.log_in.sign_up_text', { a: <a onclick={this.signUp.bind(this)} /> })}</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -96,7 +96,7 @@ export default class NotificationList extends Component {
|
||||
<ul className="NotificationGroup-content">
|
||||
{group.notifications.map((notification) => {
|
||||
const NotificationComponent = app.notificationComponents[notification.contentType()];
|
||||
return NotificationComponent ? <li>{NotificationComponent.component({ notification })}</li> : '';
|
||||
return !!NotificationComponent && <li>{NotificationComponent.component({ notification })}</li>;
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
|
@@ -59,7 +59,7 @@ export default class Post extends Component {
|
||||
<aside className="Post-actions">
|
||||
<ul>
|
||||
{listItems(this.actionItems().toArray())}
|
||||
{controls.length ? (
|
||||
{!!controls.length && (
|
||||
<li>
|
||||
<Dropdown
|
||||
className="Post-controls"
|
||||
@@ -73,8 +73,6 @@ export default class Post extends Component {
|
||||
{controls}
|
||||
</Dropdown>
|
||||
</li>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</ul>
|
||||
</aside>
|
||||
@@ -114,7 +112,7 @@ export default class Post extends Component {
|
||||
* @return {Array}
|
||||
*/
|
||||
content() {
|
||||
return [];
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -55,6 +55,6 @@ export default class PostMeta extends Component {
|
||||
* @returns {String}
|
||||
*/
|
||||
getPermalink(post) {
|
||||
return app.forum.attribute('baseUrl') + app.route.post(post);
|
||||
return window.location.origin + app.route.post(post);
|
||||
}
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ export default class PostPreview extends Component {
|
||||
const post = this.attrs.post;
|
||||
const user = post.user();
|
||||
const content = post.contentType() === 'comment' && post.contentPlain();
|
||||
const excerpt = content ? highlight(content, this.attrs.highlight, 300) : '';
|
||||
const excerpt = !!content && highlight(content, this.attrs.highlight, 300);
|
||||
|
||||
return (
|
||||
<Link className="PostPreview" href={app.route.post(post)} onclick={this.attrs.onclick}>
|
||||
|
@@ -29,7 +29,10 @@ export default class PostUser extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
let card = '';
|
||||
/**
|
||||
* @type {import('mithril').Children}
|
||||
*/
|
||||
let card = null;
|
||||
|
||||
if (!post.isHidden() && this.attrs.cardVisible) {
|
||||
card = UserCard.component({
|
||||
|
@@ -9,8 +9,8 @@ import icon from '../../common/helpers/icon';
|
||||
import SearchState from '../states/SearchState';
|
||||
import DiscussionsSearchSource from './DiscussionsSearchSource';
|
||||
import UsersSearchSource from './UsersSearchSource';
|
||||
import { fireDeprecationWarning } from '../../common/helpers/fireDebugWarning';
|
||||
import type Mithril from 'mithril';
|
||||
import Model from '../../common/Model';
|
||||
|
||||
/**
|
||||
* The `SearchSource` interface defines a section of search results in the
|
||||
@@ -53,33 +53,14 @@ export interface SearchAttrs extends ComponentAttrs {
|
||||
*
|
||||
* - state: SearchState instance.
|
||||
*/
|
||||
export default class Search<T extends SearchAttrs = SearchAttrs> extends Component<T, SearchState> {
|
||||
export default class Search<T extends SearchAttrs = SearchAttrs> extends Component<T> {
|
||||
/**
|
||||
* The minimum query length before sources are searched.
|
||||
*/
|
||||
protected static MIN_SEARCH_LEN = 3;
|
||||
|
||||
/**
|
||||
* The instance of `SearchState` for this component.
|
||||
*/
|
||||
protected searchState!: SearchState;
|
||||
|
||||
/**
|
||||
* The instance of `SearchState` for this component.
|
||||
*
|
||||
* @deprecated Replace with`this.searchState` instead.
|
||||
*/
|
||||
// TODO: [Flarum 2.0] Remove this.
|
||||
// @ts-expect-error This is a get accessor, while superclass defines this as a property. This is needed to prevent breaking changes, however.
|
||||
protected get state() {
|
||||
fireDeprecationWarning('`state` property of the Search component is deprecated', '3212');
|
||||
return this.searchState;
|
||||
}
|
||||
protected set state(state: SearchState) {
|
||||
fireDeprecationWarning('`state` property of the Search component is deprecated', '3212');
|
||||
this.searchState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the search input has focus.
|
||||
*/
|
||||
|
@@ -34,19 +34,18 @@ export default class UserCard extends Component {
|
||||
<div className={'UserCard ' + (this.attrs.className || '')} style={color && { '--usercard-bg': color }}>
|
||||
<div className="darkenBackground">
|
||||
<div className="container">
|
||||
{controls.length
|
||||
? Dropdown.component(
|
||||
{
|
||||
className: 'UserCard-controls App-primaryControl',
|
||||
menuClassName: 'Dropdown-menu--right',
|
||||
buttonClassName: this.attrs.controlsButtonClassName,
|
||||
label: app.translator.trans('core.forum.user_controls.button'),
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.user_controls.toggle_dropdown_accessible_label'),
|
||||
icon: 'fas fa-ellipsis-v',
|
||||
},
|
||||
controls
|
||||
)
|
||||
: ''}
|
||||
{!!controls.length &&
|
||||
Dropdown.component(
|
||||
{
|
||||
className: 'UserCard-controls App-primaryControl',
|
||||
menuClassName: 'Dropdown-menu--right',
|
||||
buttonClassName: this.attrs.controlsButtonClassName,
|
||||
label: app.translator.trans('core.forum.user_controls.button'),
|
||||
accessibleToggleLabel: app.translator.trans('core.forum.user_controls.toggle_dropdown_accessible_label'),
|
||||
icon: 'fas fa-ellipsis-v',
|
||||
},
|
||||
controls
|
||||
)}
|
||||
|
||||
<div className="UserCard-profile">
|
||||
<h2 className="UserCard-identity">
|
||||
@@ -60,7 +59,7 @@ export default class UserCard extends Component {
|
||||
)}
|
||||
</h2>
|
||||
|
||||
{badges.length ? <ul className="UserCard-badges badges">{listItems(badges)}</ul> : ''}
|
||||
{!!badges.length && <ul className="UserCard-badges badges">{listItems(badges)}</ul>}
|
||||
|
||||
<ul className="UserCard-info">{listItems(this.infoItems().toArray())}</ul>
|
||||
</div>
|
||||
|
@@ -39,7 +39,7 @@ export default class UsersSearchResults implements SearchSource {
|
||||
.filter((e, i, arr) => arr.lastIndexOf(e) === i)
|
||||
.sort((a, b) => a.displayName().localeCompare(b.displayName()));
|
||||
|
||||
if (!results.length) return [];
|
||||
if (!results.length) return null;
|
||||
|
||||
return [
|
||||
<li className="Dropdown-header">{app.translator.trans('core.forum.search.users_heading')}</li>,
|
||||
|
50
js/src/forum/components/WelcomeHero.js
Normal file
50
js/src/forum/components/WelcomeHero.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import app from '../../forum/app';
|
||||
import Component from '../../common/Component';
|
||||
import Button from '../../common/components/Button';
|
||||
|
||||
/**
|
||||
* The `WelcomeHero` component displays a hero that welcomes the user to the
|
||||
* forum.
|
||||
*/
|
||||
export default class WelcomeHero extends Component {
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.hidden = localStorage.getItem('welcomeHidden');
|
||||
}
|
||||
|
||||
view() {
|
||||
if (this.hidden) return <div />;
|
||||
|
||||
const slideUp = () => {
|
||||
this.$().slideUp(this.hide.bind(this));
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="Hero WelcomeHero">
|
||||
<div class="container">
|
||||
{Button.component({
|
||||
icon: 'fas fa-times',
|
||||
onclick: slideUp,
|
||||
className: 'Hero-close Button Button--icon Button--link',
|
||||
'aria-label': app.translator.trans('core.forum.welcome_hero.hide'),
|
||||
})}
|
||||
|
||||
<div className="containerNarrow">
|
||||
<h2 className="Hero-title">{app.forum.attribute('welcomeTitle')}</h2>
|
||||
<div className="Hero-subtitle">{m.trust(app.forum.attribute('welcomeMessage'))}</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the welcome hero.
|
||||
*/
|
||||
hide() {
|
||||
localStorage.setItem('welcomeHidden', 'true');
|
||||
|
||||
this.hidden = true;
|
||||
}
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
import app from '../app';
|
||||
import Component from '../../common/Component';
|
||||
import Button from '../../common/components/Button';
|
||||
import type Mithril from 'mithril';
|
||||
|
||||
export interface IWelcomeHeroAttrs {}
|
||||
|
||||
const LOCAL_STORAGE_KEY = 'welcomeHidden';
|
||||
|
||||
/**
|
||||
* The `WelcomeHero` component displays a hero that welcomes the user to the
|
||||
* forum.
|
||||
*/
|
||||
export default class WelcomeHero extends Component<IWelcomeHeroAttrs> {
|
||||
/**
|
||||
* @deprecated Extend the `isHidden` method instead.
|
||||
*/
|
||||
hidden: boolean = false;
|
||||
|
||||
oninit(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
}
|
||||
|
||||
view(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>) {
|
||||
if (this.isHidden()) return null;
|
||||
|
||||
const slideUp = () => {
|
||||
this.$().slideUp(this.hide.bind(this));
|
||||
};
|
||||
|
||||
return (
|
||||
<header class="Hero WelcomeHero">
|
||||
<div class="container">
|
||||
<Button
|
||||
icon="fas fa-times"
|
||||
onclick={slideUp}
|
||||
className="Hero-close Button Button--icon Button--link"
|
||||
aria-label={app.translator.trans('core.forum.welcome_hero.hide')}
|
||||
/>
|
||||
|
||||
<div class="containerNarrow">
|
||||
<h2 class="Hero-title">{app.forum.attribute('welcomeTitle')}</h2>
|
||||
<div class="Hero-subtitle">{m.trust(app.forum.attribute('welcomeMessage'))}</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the welcome hero.
|
||||
*/
|
||||
hide() {
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY, 'true');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the welcome hero should be hidden.
|
||||
*
|
||||
* @returns if the welcome hero is hidden.
|
||||
*/
|
||||
isHidden(): boolean {
|
||||
if (!app.forum.attribute<string>('welcomeTitle')?.trim()) return true;
|
||||
if (localStorage.getItem(LOCAL_STORAGE_KEY)) return true;
|
||||
if (this.hidden) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -260,7 +260,7 @@ class ComposerState {
|
||||
// let the CSS decide how high it is. If it's fullscreen, then we need to
|
||||
// make it as high as the window.
|
||||
if (this.position === ComposerState.Position.MINIMIZED) {
|
||||
return '';
|
||||
return null;
|
||||
} else if (this.position === ComposerState.Position.FULLSCREEN) {
|
||||
return $(window).height();
|
||||
}
|
||||
|
@@ -2,8 +2,8 @@
|
||||
position: relative !important;
|
||||
padding-top: var(--header-height);
|
||||
padding-bottom: 50px;
|
||||
overflow-x: hidden;
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
|
||||
@media @phone {
|
||||
padding-top: var(--header-height-phone);
|
||||
@@ -27,16 +27,13 @@
|
||||
box-shadow: 0 2px 6px var(--shadow-color);
|
||||
}
|
||||
}
|
||||
.App-primaryControl,
|
||||
.App-titleControl,
|
||||
.App-backControl {
|
||||
.App-primaryControl, .App-titleControl, .App-backControl {
|
||||
position: absolute !important;
|
||||
z-index: calc(~"var(--zindex-header) + 1");
|
||||
top: 0 !important;
|
||||
margin: 0;
|
||||
|
||||
.App.affix &,
|
||||
.Composer & {
|
||||
.App.affix &, .Composer & {
|
||||
position: fixed !important;
|
||||
}
|
||||
|
||||
@@ -62,18 +59,15 @@
|
||||
right: 0;
|
||||
|
||||
&.Dropdown {
|
||||
.Button,
|
||||
.Button-caret {
|
||||
.Button, .Button-caret {
|
||||
display: none !important;
|
||||
}
|
||||
.Dropdown-toggle,
|
||||
.Button-icon {
|
||||
.Dropdown-toggle, .Button-icon {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.App-primaryControl,
|
||||
.App-backControl {
|
||||
.App-primaryControl, .App-backControl {
|
||||
margin: 0 !important;
|
||||
|
||||
> .Button {
|
||||
@@ -96,8 +90,7 @@
|
||||
text-align: center;
|
||||
color: var(--header-color) !important;
|
||||
|
||||
&,
|
||||
> .Button {
|
||||
&, > .Button {
|
||||
font-size: 16px;
|
||||
}
|
||||
> .Button {
|
||||
@@ -150,7 +143,7 @@
|
||||
|
||||
.drawerOpen & {
|
||||
-webkit-transform: none !important;
|
||||
transform: none !important;
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
.drawer-backdrop {
|
||||
@@ -207,15 +200,12 @@
|
||||
> li {
|
||||
padding: 0 10px 0;
|
||||
}
|
||||
.FormControl,
|
||||
.ButtonGroup,
|
||||
.Button {
|
||||
.FormControl, .ButtonGroup, .Button {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
.Dropdown-menu {
|
||||
.ButtonGroup,
|
||||
.Button {
|
||||
.ButtonGroup, .Button {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
@@ -231,7 +221,7 @@
|
||||
@media @phone {
|
||||
.App-drawer {
|
||||
& when (@config-colored-header = true) {
|
||||
.light-contents(@name: "header-colored");
|
||||
.light-contents(@name: 'header-colored');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,7 +245,7 @@
|
||||
}
|
||||
|
||||
& when (@config-colored-header = true) {
|
||||
.light-contents(@name: "header-colored");
|
||||
.light-contents(@name: 'header-colored');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,8 +254,7 @@
|
||||
margin-right: 25px;
|
||||
}
|
||||
.Header-controls {
|
||||
&,
|
||||
> li {
|
||||
&, > li {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
@@ -13,7 +13,6 @@ body {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@@ -66,8 +65,6 @@ p {
|
||||
@media @desktop-hd {
|
||||
width: @screen-desktop-hd;
|
||||
}
|
||||
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.containerNarrow {
|
||||
|
@@ -15,8 +15,7 @@
|
||||
margin: 15px;
|
||||
display: inline-block;
|
||||
|
||||
&.item-controls,
|
||||
&.item-scrubber {
|
||||
&.item-controls, &.item-scrubber {
|
||||
margin: 0;
|
||||
display: block;
|
||||
}
|
||||
@@ -24,27 +23,22 @@
|
||||
}
|
||||
}
|
||||
@media @tablet-up {
|
||||
.DiscussionPage-discussion > .container {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.DiscussionPage-nav {
|
||||
min-width: 150px;
|
||||
float: right;
|
||||
|
||||
&, > ul {
|
||||
width: 150px;
|
||||
}
|
||||
> ul {
|
||||
margin-top: 24px;
|
||||
position: sticky;
|
||||
top: ~"calc(24px + var(--header-height))";
|
||||
position: fixed;
|
||||
margin-top: 30px;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
|
||||
> li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.ButtonGroup,
|
||||
.Button {
|
||||
.ButtonGroup, .Button {
|
||||
width: 100%;
|
||||
}
|
||||
.ButtonGroup:not(.itemCount1) {
|
||||
@@ -60,7 +54,7 @@
|
||||
|
||||
@media @tablet-up {
|
||||
.DiscussionPage-stream {
|
||||
margin-right: 75px;
|
||||
margin-right: 225px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,8 +132,7 @@
|
||||
}
|
||||
}
|
||||
// When the pane is pinned, move the other page content inwards
|
||||
.App-content,
|
||||
.App-footer {
|
||||
.App-content, .App-footer {
|
||||
.hasPane.panePinned & {
|
||||
margin-left: var(--pane-width);
|
||||
|
||||
|
Reference in New Issue
Block a user