mirror of
https://github.com/flarum/framework.git
synced 2024-11-25 17:57:04 +08:00
Fix listItems isSeparator function, add m() children to attrs, work on posts, subtree retainer
This commit is contained in:
parent
49d2539aef
commit
0de0c83353
12
js/dist/admin.js
vendored
12
js/dist/admin.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/admin.js.map
vendored
2
js/dist/admin.js.map
vendored
File diff suppressed because one or more lines are too long
12
js/dist/forum.js
vendored
12
js/dist/forum.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js.map
vendored
2
js/dist/forum.js.map
vendored
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
|||
import Mithril from "mithril";
|
||||
import Mithril from 'mithril';
|
||||
|
||||
import Bus from './Bus';
|
||||
import Translator from './Translator';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import MicroModal from 'micromodal';
|
||||
|
||||
import Component from "../Component";
|
||||
import Component from '../Component';
|
||||
import Modal from './Modal';
|
||||
|
||||
/**
|
||||
|
@ -106,7 +106,7 @@ export default class ModalManager extends Component {
|
|||
* @protected
|
||||
*/
|
||||
onready() {
|
||||
if (this.component && this.component.onready) {
|
||||
if (this.component?.onready) {
|
||||
this.component.onready();
|
||||
}
|
||||
}
|
||||
|
|
19
js/src/common/components/Placeholder.tsx
Normal file
19
js/src/common/components/Placeholder.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Component, {ComponentProps} from '../Component';
|
||||
|
||||
export interface PlaceholderProps extends ComponentProps {
|
||||
text: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The `Placeholder` component displays a muted text with some call to action,
|
||||
* usually used as an empty state.
|
||||
*/
|
||||
export default class Placeholder extends Component<PlaceholderProps> {
|
||||
view() {
|
||||
return (
|
||||
<div className="Placeholder">
|
||||
<p>{this.props.text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import Separator from '../components/Separator';
|
||||
|
||||
export function isSeparator(item) {
|
||||
return item && item.component === Separator;
|
||||
return item?.tag === Separator;
|
||||
}
|
||||
|
||||
export function withoutUnnecessarySeparators(items) {
|
||||
|
|
14
js/src/common/helpers/userOnline.tsx
Normal file
14
js/src/common/helpers/userOnline.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import icon from './icon';
|
||||
import User from '../models/User';
|
||||
|
||||
/**
|
||||
* The `useronline` helper displays a green circle if the user is online
|
||||
*
|
||||
* @param {User} user
|
||||
* @return {Object}
|
||||
*/
|
||||
export default function userOnline(user: User) {
|
||||
if (user.lastSeenAt() && user.isOnline()) {
|
||||
return <span className="UserOnline">{icon('fas fa-circle')}</span>;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ import stringToColor from '../utils/stringToColor';
|
|||
import ItemList from '../utils/ItemList';
|
||||
import computed from '../utils/computed';
|
||||
import GroupBadge from '../components/GroupBadge';
|
||||
import Group from "./Group";
|
||||
import Group from './Group';
|
||||
|
||||
export default class User extends Model {
|
||||
username = Model.attribute('username') as () => string;
|
||||
|
|
|
@ -39,7 +39,7 @@ export default class ItemList<T = any> {
|
|||
* Get the content of an item.
|
||||
*/
|
||||
get(key: any): T {
|
||||
return this.items[key].content;
|
||||
return this.items[key]?.content;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,6 +66,7 @@ export default class ItemList<T = any> {
|
|||
if (this.items[i] !== null && this.items[i] instanceof Item) {
|
||||
this.items[i].content = Object(this.items[i].content);
|
||||
|
||||
// @ts-ignore
|
||||
this.items[i].content.itemName = i;
|
||||
items.push(this.items[i]);
|
||||
this.items[i].key = items.length;
|
||||
|
|
30
js/src/common/utils/SubtreeRetainer.ts
Normal file
30
js/src/common/utils/SubtreeRetainer.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
export default class SubtreeRetainer {
|
||||
callbacks: Function[];
|
||||
data = {};
|
||||
|
||||
constructor(...callbacks: Function[]) {
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
check(...callbacks: Function[]) {
|
||||
this.callbacks.concat(...callbacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the component should redraw.
|
||||
*/
|
||||
update(): boolean {
|
||||
let update = false;
|
||||
|
||||
this.callbacks.forEach((callback, i) => {
|
||||
const result = callback();
|
||||
|
||||
if (result !== this.data[i]) {
|
||||
this.data[i] = result;
|
||||
update = true;
|
||||
}
|
||||
});
|
||||
|
||||
return update;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,29 @@
|
|||
import m from 'mithril';
|
||||
import prop from 'mithril/stream';
|
||||
|
||||
import Component from '../Component';
|
||||
|
||||
export default () => {
|
||||
const mo = window['m'];
|
||||
|
||||
const _m = function (comp, ...args) {
|
||||
if (!arguments[1]) arguments[1] = {};
|
||||
|
||||
if (comp.prototype && comp.prototype instanceof Component) {
|
||||
let children = args.slice(1);
|
||||
if (children.length === 1 && Array.isArray(children[0])) {
|
||||
children = children[0];
|
||||
}
|
||||
|
||||
if (children) {
|
||||
Object.defineProperty(arguments[1], 'children', {
|
||||
writable: true,
|
||||
});
|
||||
|
||||
arguments[1].children = (arguments[1].children || []).concat(children);
|
||||
}
|
||||
}
|
||||
|
||||
const node = mo.apply(this, arguments);
|
||||
|
||||
if (!node.attrs) node.attrs = {};
|
||||
|
|
151
js/src/forum/components/CommentPost.tsx
Normal file
151
js/src/forum/components/CommentPost.tsx
Normal file
|
@ -0,0 +1,151 @@
|
|||
import Post from './Post';
|
||||
import PostUser from './PostUser';
|
||||
// import PostMeta from './PostMeta';
|
||||
// import PostEdited from './PostEdited';
|
||||
// import EditPostComposer from './EditPostComposer';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import listItems from '../../common/helpers/listItems';
|
||||
import Button from '../../common/components/Button';
|
||||
|
||||
/**
|
||||
* The `CommentPost` component displays a standard `comment`-typed post. This
|
||||
* includes a number of item lists (controls, header, and footer) surrounding
|
||||
* the post's HTML content.
|
||||
*/
|
||||
export default class CommentPost extends Post {
|
||||
/**
|
||||
* If the post has been hidden, then this flag determines whether or not its
|
||||
* content has been expanded.
|
||||
*/
|
||||
revealContent: boolean = false;
|
||||
|
||||
postUser: PostUser;
|
||||
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
|
||||
// Create an instance of the component that displays the post's author so
|
||||
// that we can force the post to rerender when the user card is shown.
|
||||
this.postUser = PostUser.component({post: this.props.post});
|
||||
|
||||
this.subtree.check(
|
||||
() => this.postUser.cardVisible,
|
||||
() => this.isEditing()
|
||||
);
|
||||
}
|
||||
|
||||
content() {
|
||||
// Note: we avoid using JSX for the <ul> below because it results in some
|
||||
// weirdness in Mithril.js 0.1.x (see flarum/core#975). This workaround can
|
||||
// be reverted when we upgrade to Mithril 1.0.
|
||||
return super.content().concat([
|
||||
<header className="Post-header">{m('ul', listItems(this.headerItems().toArray()))}</header>,
|
||||
<div className="Post-body">
|
||||
{this.isEditing()
|
||||
? <div className="Post-preview" config={this.configPreview.bind(this)}/>
|
||||
: m.trust(this.props.post.contentHtml())}
|
||||
</div>
|
||||
]);
|
||||
}
|
||||
|
||||
onupdate(vnode) {
|
||||
super.onupdate(vnode);
|
||||
|
||||
const contentHtml = this.isEditing() ? '' : this.props.post.contentHtml();
|
||||
|
||||
// TODO: idk what this is supposed to be
|
||||
|
||||
// If the post content has changed since the last render, we'll run through
|
||||
// all of the <script> tags in the content and evaluate them. This is
|
||||
// necessary because TextFormatter outputs them for e.g. syntax highlighting.
|
||||
if (vnode.contentHtml !== contentHtml) {
|
||||
this.$('.Post-body script').each(function() {
|
||||
eval.call(window, $(this).text());
|
||||
});
|
||||
}
|
||||
|
||||
vnode.contentHtml = contentHtml;
|
||||
}
|
||||
|
||||
isEditing() {
|
||||
return false; // TODO
|
||||
// return app.composer?.component instanceof EditPostComposer &&
|
||||
// app.composer.component.props.post === this.props.post;
|
||||
}
|
||||
|
||||
attrs() {
|
||||
const post = this.props.post;
|
||||
const attrs = super.attrs();
|
||||
|
||||
attrs.className = (attrs.className || '') + ' ' + classNames({
|
||||
'CommentPost': true,
|
||||
'Post--hidden': post.isHidden(),
|
||||
'Post--edited': post.isEdited(),
|
||||
'revealContent': this.revealContent,
|
||||
'editing': this.isEditing()
|
||||
});
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
// TODO change so it works
|
||||
configPreview(element, isInitialized, context) {
|
||||
if (isInitialized) return;
|
||||
|
||||
// Every 50ms, if the composer content has changed, then update the post's
|
||||
// body with a preview.
|
||||
let preview;
|
||||
const updatePreview = () => {
|
||||
const content = app.composer.component.content();
|
||||
|
||||
if (preview === content) return;
|
||||
|
||||
preview = content;
|
||||
|
||||
s9e.TextFormatter.preview(preview || '', element);
|
||||
};
|
||||
updatePreview();
|
||||
|
||||
const updateInterval = setInterval(updatePreview, 50);
|
||||
context.onunload = () => clearInterval(updateInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the visibility of a hidden post's content.
|
||||
*/
|
||||
toggleContent() {
|
||||
this.revealContent = !this.revealContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an item list for the post's header.
|
||||
*
|
||||
* @return {ItemList}
|
||||
*/
|
||||
headerItems() {
|
||||
const items = new ItemList();
|
||||
const post = this.props.post;
|
||||
const props = {post};
|
||||
|
||||
items.add('user', this.postUser, 100);
|
||||
// items.add('meta', PostMeta.component(props));
|
||||
|
||||
if (post.isEdited() && !post.isHidden()) {
|
||||
items.add('edited', PostEdited.component(props));
|
||||
}
|
||||
|
||||
// If the post is hidden, add a button that allows toggling the visibility
|
||||
// of the post's content.
|
||||
if (post.isHidden()) {
|
||||
items.add('toggle', (
|
||||
Button.component({
|
||||
className: 'Button Button--default Button--more',
|
||||
icon: 'fas fa-ellipsis-h',
|
||||
onclick: this.toggleContent.bind(this)
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
118
js/src/forum/components/Post.tsx
Normal file
118
js/src/forum/components/Post.tsx
Normal file
|
@ -0,0 +1,118 @@
|
|||
import Component, {ComponentProps} from '../../common/Component';
|
||||
import Dropdown from '../../common/components/Dropdown';
|
||||
import PostControls from '../utils/PostControls';
|
||||
import listItems from '../../common/helpers/listItems';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import SubtreeRetainer from "../../common/utils/SubtreeRetainer";
|
||||
import PostModel from '../../common/models/Post';
|
||||
|
||||
export interface PostProps extends ComponentProps {
|
||||
post: PostModel
|
||||
}
|
||||
|
||||
/**
|
||||
* The `Post` component displays a single post. The basic post template just
|
||||
* includes a controls dropdown; subclasses must implement `content` and `attrs`
|
||||
* methods.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
export default class Post<T extends PostProps = PostProps> extends Component<PostProps> {
|
||||
loading = false;
|
||||
controlsOpen = false;
|
||||
|
||||
subtree: SubtreeRetainer;
|
||||
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
|
||||
/**
|
||||
* Set up a subtree retainer so that the post will not be redrawn
|
||||
* unless new data comes in.
|
||||
*/
|
||||
this.subtree = new SubtreeRetainer(
|
||||
() => this.props.post.freshness,
|
||||
() => {
|
||||
const user = this.props.post.user();
|
||||
return user?.freshness;
|
||||
},
|
||||
() => this.controlsOpen
|
||||
);
|
||||
}
|
||||
|
||||
view() {
|
||||
const controls = PostControls.controls(this.props.post, this).toArray();
|
||||
const attrs = this.attrs();
|
||||
|
||||
attrs.className = classNames('Post', this.loading && 'Post--loading', attrs.className);
|
||||
|
||||
return (
|
||||
<article {...attrs}>
|
||||
<div>
|
||||
{this.content()}
|
||||
<aside className="Post-actions">
|
||||
<ul>
|
||||
{listItems(this.actionItems().toArray())}
|
||||
{controls.length ? <li>
|
||||
<Dropdown
|
||||
className="Post-controls"
|
||||
buttonClassName="Button Button--icon Button--flat"
|
||||
menuClassName="Dropdown-menu--right"
|
||||
icon="fas fa-ellipsis-h"
|
||||
onshow={() => this.$('.Post-actions').addClass('open')}
|
||||
onhide={() => this.$('.Post-actions').removeClass('open')}>
|
||||
{controls}
|
||||
</Dropdown>
|
||||
</li> : ''}
|
||||
</ul>
|
||||
</aside>
|
||||
<footer className="Post-footer"><ul>{listItems(this.footerItems().toArray())}</ul></footer>
|
||||
</div>
|
||||
);
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
onbeforeupdate(vnode) {
|
||||
super.onbeforeupdate(vnode);
|
||||
|
||||
return this.subtree.update();
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
super.oncreate(vnode);
|
||||
|
||||
const $actions = this.$('.Post-actions');
|
||||
const $controls = this.$('.Post-controls');
|
||||
|
||||
$actions.toggleClass('open', $controls.hasClass('open'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attributes for the post element.
|
||||
*/
|
||||
attrs(): ComponentProps {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post's content.
|
||||
*/
|
||||
content() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an item list for the post's actions.
|
||||
*/
|
||||
actionItems() {
|
||||
return new ItemList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an item list for the post's footer.
|
||||
*/
|
||||
footerItems() {
|
||||
return new ItemList();
|
||||
}
|
||||
}
|
94
js/src/forum/components/PostUser.tsx
Normal file
94
js/src/forum/components/PostUser.tsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
import Component from '../../common/Component';
|
||||
import UserCard from './UserCard';
|
||||
import avatar from '../../common/helpers/avatar';
|
||||
import username from '../../common/helpers/username';
|
||||
import userOnline from '../../common/helpers/userOnline';
|
||||
import listItems from '../../common/helpers/listItems';
|
||||
import {PostProps} from "./Post";
|
||||
|
||||
/**
|
||||
* The `PostUser` component shows the avatar and username of a post's author.
|
||||
*/
|
||||
export default class PostUser extends Component<PostProps> {
|
||||
/**
|
||||
* Whether or not the user hover card is visible.
|
||||
*/
|
||||
cardVisible = false;
|
||||
|
||||
view() {
|
||||
const post = this.props.post;
|
||||
const user = post.user();
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="PostUser">
|
||||
<h3>{avatar(user, {className: 'PostUser-avatar'})} {username(user)}</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let card = '';
|
||||
|
||||
if (!post.isHidden() && this.cardVisible) {
|
||||
card = UserCard.component({
|
||||
user,
|
||||
className: 'UserCard--popover',
|
||||
controlsButtonClassName: 'Button Button--icon Button--flat'
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="PostUser">
|
||||
<h3>
|
||||
<m.route.Link href={app.route.user(user)}>
|
||||
{avatar(user, {className: 'PostUser-avatar'})}
|
||||
{userOnline(user)}
|
||||
{username(user)}
|
||||
</m.route.Link>
|
||||
</h3>
|
||||
<ul className="PostUser-badges badges">
|
||||
{listItems(user.badges().toArray())}
|
||||
</ul>
|
||||
{card}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
super.oncreate(vnode);
|
||||
|
||||
let timeout;
|
||||
|
||||
this.$()
|
||||
.on('mouseover', 'h3 a, .UserCard', () => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(this.showCard.bind(this), 500);
|
||||
})
|
||||
.on('mouseout', 'h3 a, .UserCard', () => {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(this.hideCard.bind(this), 250);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the user card.
|
||||
*/
|
||||
showCard() {
|
||||
this.cardVisible = true;
|
||||
|
||||
m.redraw();
|
||||
|
||||
setTimeout(() => this.$('.UserCard').addClass('in'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the user card.
|
||||
*/
|
||||
hideCard() {
|
||||
this.$('.UserCard').removeClass('in')
|
||||
.one('transitionend webkitTransitionEnd oTransitionEnd', () => {
|
||||
this.cardVisible = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import UserPage from './UserPage';
|
||||
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
||||
import Button from '../../common/components/Button';
|
||||
// import Placeholder from '../../common/components/Placeholder';
|
||||
// import CommentPost from './CommentPost';
|
||||
import Post from "../../common/models/Post";
|
||||
import Placeholder from '../../common/components/Placeholder';
|
||||
import CommentPost from './CommentPost';
|
||||
import Post from '../../common/models/Post';
|
||||
|
||||
/**
|
||||
* The `PostsUserPage` component shows a user's activity feed inside of their
|
||||
|
@ -43,8 +43,6 @@ export default class PostsUserPage extends UserPage {
|
|||
}
|
||||
|
||||
content() {
|
||||
return <p>test</p>;
|
||||
|
||||
if (this.posts.length === 0 && ! this.loading) {
|
||||
return (
|
||||
<div className="PostsUserPage">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import highlight from '../../common/helpers/highlight';
|
||||
import avatar from '../../common/helpers/avatar';
|
||||
import username from '../../common/helpers/username';
|
||||
import SearchSource from "./SearchSource";
|
||||
import SearchSource from './SearchSource';
|
||||
import User from '../../common/models/User';
|
||||
|
||||
/**
|
||||
|
|
175
js/src/forum/utils/PostControls.tsx
Normal file
175
js/src/forum/utils/PostControls.tsx
Normal file
|
@ -0,0 +1,175 @@
|
|||
import {Vnode} from "mithril";
|
||||
|
||||
// import EditPostComposer from '../components/EditPostComposer';
|
||||
import Button from '../../common/components/Button';
|
||||
import Separator from '../../common/components/Separator';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import Post from "../../common/models/Post";
|
||||
import PostComponent from "../../forum/components/Post";
|
||||
|
||||
/**
|
||||
* The `PostControls` utility constructs a list of buttons for a post which
|
||||
* perform actions on it.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Get a list of controls for a post.
|
||||
*
|
||||
* @param {Post} post
|
||||
* @param {*} context The parent component under which the controls menu will
|
||||
* be displayed.
|
||||
* @public
|
||||
*/
|
||||
controls(post: Post, context) {
|
||||
const items = new ItemList();
|
||||
|
||||
['user', 'moderation', 'destructive'].forEach(section => {
|
||||
const controls = this[section + 'Controls'](post, context).toArray();
|
||||
|
||||
if (controls.length) {
|
||||
controls.forEach(item => items.add(item.itemName, item));
|
||||
items.add(section + 'Separator', Separator.component());
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get controls for a post pertaining to the current user (e.g. report).
|
||||
*
|
||||
* @param {Post} post
|
||||
* @param {*} context The parent component under which the controls menu will
|
||||
* be displayed.
|
||||
* @protected
|
||||
*/
|
||||
userControls(post: Post, context) {
|
||||
return new ItemList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get controls for a post pertaining to moderation (e.g. edit).
|
||||
*
|
||||
* @param {Post} post
|
||||
* @param {*} context The parent component under which the controls menu will
|
||||
* be displayed.
|
||||
* @protected
|
||||
*/
|
||||
moderationControls(post: Post, context) {
|
||||
const items = new ItemList();
|
||||
|
||||
if (post.contentType() === 'comment' && post.canEdit()) {
|
||||
if (!post.isHidden()) {
|
||||
items.add('edit', Button.component({
|
||||
icon: 'fas fa-pencil-alt',
|
||||
onclick: this.editAction.bind(post)
|
||||
}, app.translator.trans('core.forum.post_controls.edit_button')));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get controls for a post that are destructive (e.g. delete).
|
||||
*
|
||||
* @param {Post} post
|
||||
* @param {*} context The parent component under which the controls menu will
|
||||
* be displayed.
|
||||
* @protected
|
||||
*/
|
||||
destructiveControls(post: Post, context) {
|
||||
const items = new ItemList();
|
||||
|
||||
if (post.contentType() === 'comment' && !post.isHidden()) {
|
||||
if (post.canHide()) {
|
||||
items.add('hide', Button.component({
|
||||
icon: 'far fa-trash-alt',
|
||||
children: app.translator.trans('core.forum.post_controls.delete_button'),
|
||||
onclick: this.hideAction.bind(post)
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
if (post.contentType() === 'comment' && post.canHide()) {
|
||||
items.add('restore', Button.component({
|
||||
icon: 'fas fa-reply',
|
||||
children: app.translator.trans('core.forum.post_controls.restore_button'),
|
||||
onclick: this.restoreAction.bind(post)
|
||||
}));
|
||||
}
|
||||
if (post.canDelete()) {
|
||||
items.add('delete', Button.component({
|
||||
icon: 'fas fa-times',
|
||||
children: app.translator.trans('core.forum.post_controls.delete_forever_button'),
|
||||
onclick: this.deleteAction.bind(post, context)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the composer to edit a post.
|
||||
*/
|
||||
editAction(this: Post) {
|
||||
return new Promise<EditPostComposer>(resolve => {
|
||||
const component = new EditPostComposer({ post: this });
|
||||
|
||||
app.composer.load(component);
|
||||
app.composer.show();
|
||||
|
||||
resolve(component);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide a post.
|
||||
*/
|
||||
hideAction(this: Post) {
|
||||
this.pushAttributes({ hiddenAt: new Date(), hiddenUser: app.session.user });
|
||||
|
||||
return this.save({ isHidden: true }).then(() => m.redraw());
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore a post.
|
||||
*/
|
||||
restoreAction(this: Post) {
|
||||
this.pushAttributes({ hiddenAt: null, hiddenUser: null });
|
||||
|
||||
return this.save({ isHidden: false }).then(() => m.redraw());
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a post.
|
||||
*/
|
||||
deleteAction(this: Post, context: PostComponent) {
|
||||
if (context) context.loading = true;
|
||||
|
||||
return this.delete()
|
||||
.then(() => {
|
||||
const discussion = this.discussion();
|
||||
|
||||
discussion.removePost(this.id());
|
||||
|
||||
// If this was the last post in the discussion, then we will assume that
|
||||
// the whole discussion was deleted too.
|
||||
if (!discussion.postIds().length) {
|
||||
// If there is a discussion list in the cache, remove this discussion.
|
||||
if (app.cache.discussionList) {
|
||||
app.cache.discussionList.removeDiscussion(discussion);
|
||||
}
|
||||
|
||||
if (app.viewingDiscussion(discussion)) {
|
||||
app.history.back();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
if (context) context.loading = false;
|
||||
m.redraw();
|
||||
});
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user