mirror of
https://github.com/flarum/core.git
synced 2025-08-06 16:36:47 +02:00
feat: theming and extensibility improvements (#3876)
* feat: make page structure customizable across different pages (#3867) * feat: create `PageStructure` component * feat: apply to `DiscussionPage` * feat: apply to `UserPage` * feat: apply to `TagsPage` * fix: adapt subscriptions ext * chore: cleanup * chore: use grid & flexbox for the discussion list item (#3868) * chore: rename `DiscussionPage-list` to `DiscussionListPane` * chore: itemlistify `DiscussionListItem` * chore: use flex and grid for `DiscussionListItem` * chore: use flexbox for `App-header` (#3869) * chore: use flex and grid for `App-header` * chore: drop search floats * fix: adapt admin styles * chore: use flexbox in dropdowns and SplitDropdown for subscriptions (#3874) * chore: flexbox dropdown menu items * chore: normalize subscriptions menu (use slit dropdown) * chore: cleanup * chore: misc flexbox/grid changes (#3875) * chore: `TagsPage` to tsx * chore: `TagsPage` flexbox/grid * chore: `IndexPage-toolbar` flexbox * chore: `UserCard` flexbox & itemlists * fix: `Post` improve spacing logic * chore: `Post` grid and proper spacing * fix: avatar editor hover layer layout * chore: `Button` flex * chore: normalize form semantics (#3877) * chore: normalize fieldsets * fix: `LinkButton` spacing * chore: consistent form semantics * fix: styling regressions (#3878) * fix: post spacing goes off in other pages * fix: regression * feat: extract reusable components from `NotificationsDropdown` (#3879) * feat: extensible global notices (#3880) * fix: js error on null item list * feat: extensible global notices * chore: housekeeping (#3881) * chore: use CSS variables where still not using * chore: cleanup suspension modal * chore: cleanup post flag * fix: badge vertical align * chore: use CSS variables for custom coloring * chore: `icon` helper to `Icon` component * chore: `avatar` helper to `Avatar` component * fix: chunk loading fails on admin frontend * chore: format * feat: reusable `UploadImageButton` component (#3882) * chore: convert `UploadImageButton` to tsx * feat: reusable `UploadImageButton` component * feat: add `image-upload` setting type * feat: extensible default footer component (#3883) * chore: yarn format
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { extend } from 'flarum/common/extend';
|
||||
import { extend, override } from 'flarum/common/extend';
|
||||
import app from 'flarum/forum/app';
|
||||
import Post from 'flarum/forum/components/Post';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
@@ -75,7 +75,7 @@ export default function () {
|
||||
return items;
|
||||
};
|
||||
|
||||
extend(Post.prototype, 'content', function (vdom) {
|
||||
override(Post.prototype, 'header', function (vdom) {
|
||||
const post = this.attrs.post;
|
||||
const flags = post.flags();
|
||||
|
||||
@@ -83,7 +83,7 @@ export default function () {
|
||||
|
||||
if (post.isHidden()) this.revealContent = true;
|
||||
|
||||
vdom.unshift(
|
||||
return (
|
||||
<div className="Post-flagged">
|
||||
<div className="Post-flagged-flags">
|
||||
{flags.map((flag) => (
|
||||
|
@@ -1,65 +0,0 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import Component from 'flarum/common/Component';
|
||||
import Link from 'flarum/common/components/Link';
|
||||
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
||||
import avatar from 'flarum/common/helpers/avatar';
|
||||
import username from 'flarum/common/helpers/username';
|
||||
import icon from 'flarum/common/helpers/icon';
|
||||
import humanTime from 'flarum/common/helpers/humanTime';
|
||||
|
||||
export default class FlagList extends Component {
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
this.state = this.attrs.state;
|
||||
}
|
||||
|
||||
view() {
|
||||
const flags = this.state.cache || [];
|
||||
|
||||
return (
|
||||
<div className="NotificationList FlagList">
|
||||
<div className="NotificationList-header">
|
||||
<h4 className="App-titleControl App-titleControl--text">{app.translator.trans('flarum-flags.forum.flagged_posts.title')}</h4>
|
||||
</div>
|
||||
<div className="NotificationList-content">
|
||||
<ul className="NotificationGroup-content">
|
||||
{flags.length ? (
|
||||
flags.map((flag) => {
|
||||
const post = flag.post();
|
||||
|
||||
return (
|
||||
<li>
|
||||
<Link
|
||||
href={app.route.post(post)}
|
||||
className="Notification Flag"
|
||||
onclick={(e) => {
|
||||
app.flags.index = post;
|
||||
e.redraw = false;
|
||||
}}
|
||||
>
|
||||
{avatar(post.user())}
|
||||
{icon('fas fa-flag', { className: 'Notification-icon' })}
|
||||
<span className="Notification-content">
|
||||
{app.translator.trans('flarum-flags.forum.flagged_posts.item_text', {
|
||||
username: username(post.user()),
|
||||
em: <em />,
|
||||
discussion: post.discussion().title(),
|
||||
})}
|
||||
</span>
|
||||
{humanTime(flag.createdAt())}
|
||||
<div className="Notification-excerpt">{post.contentPlain()}</div>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
) : !this.state.loading ? (
|
||||
<div className="NotificationList-empty">{app.translator.trans('flarum-flags.forum.flagged_posts.empty_text')}</div>
|
||||
) : (
|
||||
<LoadingIndicator className="LoadingIndicator--block" />
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
64
extensions/flags/js/src/forum/components/FlagList.tsx
Normal file
64
extensions/flags/js/src/forum/components/FlagList.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import Component from 'flarum/common/Component';
|
||||
import type { ComponentAttrs } from 'flarum/common/Component';
|
||||
import Avatar from 'flarum/common/components/Avatar';
|
||||
import username from 'flarum/common/helpers/username';
|
||||
import HeaderList from 'flarum/forum/components/HeaderList';
|
||||
import HeaderListItem from 'flarum/forum/components/HeaderListItem';
|
||||
import type Mithril from 'mithril';
|
||||
import type Post from 'flarum/common/models/Post';
|
||||
import type FlagListState from '../states/FlagListState';
|
||||
|
||||
export interface IFlagListAttrs extends ComponentAttrs {
|
||||
state: FlagListState;
|
||||
}
|
||||
|
||||
export default class FlagList<CustomAttrs extends IFlagListAttrs = IFlagListAttrs> extends Component<CustomAttrs, FlagListState> {
|
||||
oninit(vnode: Mithril.Vnode<CustomAttrs, this>) {
|
||||
super.oninit(vnode);
|
||||
this.state = this.attrs.state;
|
||||
}
|
||||
|
||||
view() {
|
||||
const flags = this.state.cache || [];
|
||||
|
||||
return (
|
||||
<HeaderList
|
||||
className="FlagList"
|
||||
title={app.translator.trans('flarum-flags.forum.flagged_posts.title')}
|
||||
hasItems={flags.length}
|
||||
loading={this.state.loading}
|
||||
emptyText={app.translator.trans('flarum-flags.forum.flagged_posts.empty_text')}
|
||||
>
|
||||
<ul className="HeaderListGroup-content">
|
||||
{!this.state.loading &&
|
||||
flags.map((flag) => {
|
||||
const post = flag.post() as Post;
|
||||
|
||||
return (
|
||||
<li>
|
||||
<HeaderListItem
|
||||
className="Flag"
|
||||
avatar={<Avatar user={post.user() || null} />}
|
||||
icon="fas fa-flag"
|
||||
content={app.translator.trans('flarum-flags.forum.flagged_posts.item_text', {
|
||||
username: username(post.user()),
|
||||
em: <em />,
|
||||
discussion: post.discussion().title(),
|
||||
})}
|
||||
excerpt={post.contentPlain()}
|
||||
datetime={flag.createdAt()}
|
||||
href={app.route.post(post)}
|
||||
onclick={(e: MouseEvent) => {
|
||||
app.flags.index = post;
|
||||
e.redraw = false;
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</HeaderList>
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import Modal from 'flarum/common/components/Modal';
|
||||
import Form from 'flarum/common/components/Form';
|
||||
import Button from 'flarum/common/components/Button';
|
||||
|
||||
import Stream from 'flarum/common/utils/Stream';
|
||||
@@ -28,31 +29,31 @@ export default class FlagPostModal extends Modal {
|
||||
if (this.success) {
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form Form--centered">
|
||||
<Form className="Form--centered">
|
||||
<p className="helpText">{app.translator.trans('flarum-flags.forum.flag_post.confirmation_message')}</p>
|
||||
<div className="Form-group">
|
||||
<div className="Form-group Form-controls">
|
||||
<Button className="Button Button--primary Button--block" onclick={this.hide.bind(this)}>
|
||||
{app.translator.trans('flarum-flags.forum.flag_post.dismiss_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div className="Form Form--centered">
|
||||
<Form className="Form--centered">
|
||||
<div className="Form-group">
|
||||
<div>{this.flagReasons().toArray()}</div>
|
||||
</div>
|
||||
|
||||
<div className="Form-group">
|
||||
<div className="Form-group Form-controls">
|
||||
<Button className="Button Button--primary Button--block" type="submit" loading={this.loading} disabled={!this.reason()}>
|
||||
{app.translator.trans('flarum-flags.forum.flag_post.submit_button')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -1,33 +0,0 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import NotificationsDropdown from 'flarum/forum/components/NotificationsDropdown';
|
||||
|
||||
import FlagList from './FlagList';
|
||||
|
||||
export default class FlagsDropdown extends NotificationsDropdown {
|
||||
static initAttrs(attrs) {
|
||||
attrs.label = attrs.label || app.translator.trans('flarum-flags.forum.flagged_posts.tooltip');
|
||||
attrs.icon = attrs.icon || 'fas fa-flag';
|
||||
|
||||
super.initAttrs(attrs);
|
||||
}
|
||||
|
||||
getMenu() {
|
||||
return (
|
||||
<div className={'Dropdown-menu ' + this.attrs.menuClassName} onclick={this.menuClick.bind(this)}>
|
||||
{this.showing && <FlagList state={this.attrs.state} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
goToRoute() {
|
||||
m.route.set(app.route('flags'));
|
||||
}
|
||||
|
||||
getUnreadCount() {
|
||||
return app.flags.cache ? app.flags.cache.length : app.forum.attribute('flagCount');
|
||||
}
|
||||
|
||||
getNewCount() {
|
||||
return app.session.user.attribute('newFlagCount');
|
||||
}
|
||||
}
|
34
extensions/flags/js/src/forum/components/FlagsDropdown.tsx
Normal file
34
extensions/flags/js/src/forum/components/FlagsDropdown.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import HeaderDropdown from 'flarum/forum/components/HeaderDropdown';
|
||||
import type { IHeaderDropdownAttrs } from 'flarum/forum/components/HeaderDropdown';
|
||||
import classList from 'flarum/common/utils/classList';
|
||||
|
||||
import FlagList from './FlagList';
|
||||
|
||||
export interface IFlagsDropdownAttrs extends IHeaderDropdownAttrs {}
|
||||
|
||||
export default class FlagsDropdown<CustomAttrs extends IFlagsDropdownAttrs = IFlagsDropdownAttrs> extends HeaderDropdown<CustomAttrs> {
|
||||
static initAttrs(attrs: IFlagsDropdownAttrs) {
|
||||
attrs.className = classList('FlagsDropdown', attrs.className);
|
||||
attrs.label = attrs.label || app.translator.trans('flarum-flags.forum.flagged_posts.tooltip');
|
||||
attrs.icon = attrs.icon || 'fas fa-flag';
|
||||
|
||||
super.initAttrs(attrs);
|
||||
}
|
||||
|
||||
getContent() {
|
||||
return <FlagList state={this.attrs.state} />;
|
||||
}
|
||||
|
||||
goToRoute() {
|
||||
m.route.set(app.route('flags'));
|
||||
}
|
||||
|
||||
getUnreadCount() {
|
||||
return app.flags.cache ? app.flags.cache.length : app.forum.attribute<number>('flagCount');
|
||||
}
|
||||
|
||||
getNewCount() {
|
||||
return app.session.user!.attribute<number>('newFlagCount');
|
||||
}
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
export default class FlagListState {
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
|
||||
/**
|
||||
* Whether or not the flags are loading.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load flags into the application's cache if they haven't already
|
||||
* been loaded.
|
||||
*/
|
||||
load() {
|
||||
if (this.cache && !this.app.session.user.attribute('newFlagCount')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
m.redraw();
|
||||
|
||||
this.app.store
|
||||
.find('flags')
|
||||
.then((flags) => {
|
||||
this.app.session.user.pushAttributes({ newFlagCount: 0 });
|
||||
this.cache = flags.sort((a, b) => b.createdAt() - a.createdAt());
|
||||
})
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}
|
39
extensions/flags/js/src/forum/states/FlagListState.tsx
Normal file
39
extensions/flags/js/src/forum/states/FlagListState.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import type ForumApplication from 'flarum/forum/ForumApplication';
|
||||
import type Flag from '../models/Flag';
|
||||
import type Post from 'flarum/common/models/Post';
|
||||
|
||||
export default class FlagListState {
|
||||
public app: ForumApplication;
|
||||
public loading = false;
|
||||
public cache: Flag[] | null = null;
|
||||
public index: Post | false | null = null;
|
||||
|
||||
constructor(app: ForumApplication) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load flags into the application's cache if they haven't already
|
||||
* been loaded.
|
||||
*/
|
||||
load() {
|
||||
if (this.cache && !this.app.session.user!.attribute<number>('newFlagCount')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
m.redraw();
|
||||
|
||||
this.app.store
|
||||
.find<Flag[]>('flags')
|
||||
.then((flags) => {
|
||||
this.app.session.user!.pushAttributes({ newFlagCount: 0 });
|
||||
this.cache = flags.sort((a, b) => b.createdAt()!.getTime() - a.createdAt()!.getTime());
|
||||
})
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,6 +1,9 @@
|
||||
.Post--flagged {
|
||||
--border-width: 2px;
|
||||
padding-top: 0 !important;
|
||||
border: 2px solid @primary-color;
|
||||
padding-left: var(--post-padding);
|
||||
margin-left: calc(~"0px - var(--post-padding)");
|
||||
border: var(--border-width) solid var(--primary-color);
|
||||
}
|
||||
|
||||
.Post-header .item-flagged {
|
||||
@@ -8,29 +11,22 @@
|
||||
margin: 0;
|
||||
}
|
||||
.Post-flagged {
|
||||
background: @primary-color;
|
||||
margin-top: -2px;
|
||||
margin-bottom: 20px;
|
||||
margin-left: -22px;
|
||||
margin-right: -22px;
|
||||
background: var(--primary-color);
|
||||
margin: calc(~"0px - var(--border-width)") calc(~"0px - var(--border-width) - var(--post-padding)") var(--post-padding);
|
||||
padding: 10px;
|
||||
border-radius: @border-radius @border-radius 0 0;
|
||||
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||
overflow: hidden;
|
||||
.light-contents(@color: @body-bg; @control-color: @body-bg);
|
||||
|
||||
@media @tablet-up {
|
||||
margin-left: -22px - 85px;
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&, a {
|
||||
color: @body-bg !important;
|
||||
color: var(--body-bg) !important;
|
||||
}
|
||||
}
|
||||
.Post-flagged-flags {
|
||||
@media @tablet-up {
|
||||
float: left;
|
||||
}
|
||||
|
||||
font-size: 14px;
|
||||
margin: 7px 10px;
|
||||
text-align: left;
|
||||
@@ -42,19 +38,10 @@
|
||||
font-weight: normal;
|
||||
}
|
||||
.Post-flagged-actions {
|
||||
@media @tablet-up {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.Post-flagged-actions .Button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.FlagsDropdown .Dropdown-toggle {
|
||||
.Button-label,
|
||||
.Button-caret {
|
||||
display: none;
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.FlagPostModal {
|
||||
@@ -66,7 +53,16 @@
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
color: @text-color;
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Flag .HeaderListItem-title {
|
||||
justify-content: space-between;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.Flag .HeaderListItem-time {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user