mirror of
https://github.com/flarum/framework.git
synced 2025-06-06 07:54:32 +08:00
Clean up model nullability
This commit is contained in:
@ -310,6 +310,8 @@ export default abstract class Model {
|
|||||||
* relationship exists; undefined if the relationship exists but the model
|
* relationship exists; undefined if the relationship exists but the model
|
||||||
* has not been loaded; or the model if it has been loaded.
|
* has not been loaded; or the model if it has been loaded.
|
||||||
*/
|
*/
|
||||||
|
static hasOne<M extends Model>(name: string): () => M | false;
|
||||||
|
static hasOne<M extends Model | null>(name: string): () => M | null | false;
|
||||||
static hasOne<M extends Model>(name: string): () => M | false {
|
static hasOne<M extends Model>(name: string): () => M | false {
|
||||||
return function (this: Model) {
|
return function (this: Model) {
|
||||||
if (this.data.relationships) {
|
if (this.data.relationships) {
|
||||||
@ -358,8 +360,12 @@ export default abstract class Model {
|
|||||||
/**
|
/**
|
||||||
* Transform the given value into a Date object.
|
* Transform the given value into a Date object.
|
||||||
*/
|
*/
|
||||||
static transformDate(value: string | null): Date | null {
|
static transformDate(value: string): Date;
|
||||||
return value ? new Date(value) : null;
|
static transformDate(value: string | null): Date | null;
|
||||||
|
static transformDate(value: string | undefined): Date | undefined;
|
||||||
|
static transformDate(value: string | null | undefined): Date | null | undefined;
|
||||||
|
static transformDate(value: string | null | undefined): Date | null | undefined {
|
||||||
|
return value != null ? new Date(value) : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,8 +24,8 @@ export default function avatar(user: User, attrs: ComponentAttrs = {}): Mithril.
|
|||||||
// uploaded image, or the first letter of their username if they haven't
|
// uploaded image, or the first letter of their username if they haven't
|
||||||
// uploaded one.
|
// uploaded one.
|
||||||
if (user) {
|
if (user) {
|
||||||
const username: string = user.displayName() || '?';
|
const username = user.displayName() || '?';
|
||||||
const avatarUrl: string = user.avatarUrl();
|
const avatarUrl = user.avatarUrl();
|
||||||
|
|
||||||
if (hasTitle) attrs.title = attrs.title || username;
|
if (hasTitle) attrs.title = attrs.title || username;
|
||||||
|
|
||||||
|
@ -16,46 +16,46 @@ export default class Discussion extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createdAt() {
|
createdAt() {
|
||||||
return Model.attribute<Date | null, string | null>('createdAt', Model.transformDate).call(this);
|
return Model.attribute<Date | undefined, string | undefined>('createdAt', Model.transformDate).call(this);
|
||||||
}
|
}
|
||||||
user() {
|
user() {
|
||||||
return Model.hasOne<User>('user').call(this);
|
return Model.hasOne<User | null>('user').call(this);
|
||||||
}
|
}
|
||||||
firstPost() {
|
firstPost() {
|
||||||
return Model.hasOne<Post>('firstPost').call(this);
|
return Model.hasOne<Post | null>('firstPost').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastPostedAt() {
|
lastPostedAt() {
|
||||||
return Model.attribute<Date | null, string | null>('lastPostedAt', Model.transformDate).call(this);
|
return Model.attribute('lastPostedAt', Model.transformDate).call(this);
|
||||||
}
|
}
|
||||||
lastPostedUser() {
|
lastPostedUser() {
|
||||||
return Model.hasOne<User>('lastPostedUser').call(this);
|
return Model.hasOne<User | null>('lastPostedUser').call(this);
|
||||||
}
|
}
|
||||||
lastPost() {
|
lastPost() {
|
||||||
return Model.hasOne<Post>('lastPost').call(this);
|
return Model.hasOne<Post | null>('lastPost').call(this);
|
||||||
}
|
}
|
||||||
lastPostNumber() {
|
lastPostNumber() {
|
||||||
return Model.attribute<number | null>('lastPostNumber').call(this);
|
return Model.attribute<number | null | undefined>('lastPostNumber').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
commentCount() {
|
commentCount() {
|
||||||
return Model.attribute<number | null>('commentCount').call(this);
|
return Model.attribute<number | undefined>('commentCount').call(this);
|
||||||
}
|
}
|
||||||
replyCount() {
|
replyCount() {
|
||||||
return computed<number, this>('commentCount', (commentCount) => Math.max(0, (commentCount as number) - 1)).call(this);
|
return computed<Number, this>('commentCount', (commentCount) => Math.max(0, (commentCount as number ?? 0) - 1)).call(this);
|
||||||
}
|
}
|
||||||
posts() {
|
posts() {
|
||||||
return Model.hasMany<Post>('posts').call(this);
|
return Model.hasMany<Post>('posts').call(this);
|
||||||
}
|
}
|
||||||
mostRelevantPost() {
|
mostRelevantPost() {
|
||||||
return Model.hasOne<Post>('mostRelevantPost').call(this);
|
return Model.hasOne<Post | null>('mostRelevantPost').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastReadAt() {
|
lastReadAt() {
|
||||||
return Model.attribute<Date | null, string | null>('lastReadAt', Model.transformDate).call(this);
|
return Model.attribute('lastReadAt', Model.transformDate).call(this);
|
||||||
}
|
}
|
||||||
lastReadPostNumber() {
|
lastReadPostNumber() {
|
||||||
return Model.attribute<number | null>('lastReadPostNumber').call(this);
|
return Model.attribute<number | null | undefined>('lastReadPostNumber').call(this);
|
||||||
}
|
}
|
||||||
isUnread() {
|
isUnread() {
|
||||||
return computed<boolean, this>('unreadCount', (unreadCount) => !!unreadCount).call(this);
|
return computed<boolean, this>('unreadCount', (unreadCount) => !!unreadCount).call(this);
|
||||||
@ -65,26 +65,26 @@ export default class Discussion extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hiddenAt() {
|
hiddenAt() {
|
||||||
return Model.attribute<Date | null, string | null>('hiddenAt', Model.transformDate).call(this);
|
return Model.attribute('hiddenAt', Model.transformDate).call(this);
|
||||||
}
|
}
|
||||||
hiddenUser() {
|
hiddenUser() {
|
||||||
return Model.hasOne<User>('hiddenUser').call(this);
|
return Model.hasOne<User | null>('hiddenUser').call(this);
|
||||||
}
|
}
|
||||||
isHidden() {
|
isHidden() {
|
||||||
return computed<boolean, this>('hiddenAt', (hiddenAt) => !!hiddenAt).call(this);
|
return computed<boolean, this>('hiddenAt', (hiddenAt) => !!hiddenAt).call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
canReply() {
|
canReply() {
|
||||||
return Model.attribute<boolean | null>('canReply').call(this);
|
return Model.attribute<boolean | undefined>('canReply').call(this);
|
||||||
}
|
}
|
||||||
canRename() {
|
canRename() {
|
||||||
return Model.attribute<boolean | null>('canRename').call(this);
|
return Model.attribute<boolean | undefined>('canRename').call(this);
|
||||||
}
|
}
|
||||||
canHide() {
|
canHide() {
|
||||||
return Model.attribute<boolean | null>('canHide').call(this);
|
return Model.attribute<boolean | undefined>('canHide').call(this);
|
||||||
}
|
}
|
||||||
canDelete() {
|
canDelete() {
|
||||||
return Model.attribute<boolean | null>('canDelete').call(this);
|
return Model.attribute<boolean | undefined>('canDelete').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,10 +13,10 @@ export default class Group extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
color() {
|
color() {
|
||||||
return Model.attribute<string>('color').call(this);
|
return Model.attribute<string | null>('color').call(this);
|
||||||
}
|
}
|
||||||
icon() {
|
icon() {
|
||||||
return Model.attribute<string>('icon').call(this);
|
return Model.attribute<string | null>('icon').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
isHidden() {
|
isHidden() {
|
||||||
|
@ -9,7 +9,7 @@ export default class Notification extends Model {
|
|||||||
return Model.attribute<string>('content').call(this);
|
return Model.attribute<string>('content').call(this);
|
||||||
}
|
}
|
||||||
createdAt() {
|
createdAt() {
|
||||||
return Model.attribute<Date | null, string | null>('createdAt', Model.transformDate).call(this);
|
return Model.attribute<Date, string>('createdAt', Model.transformDate).call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
isRead() {
|
isRead() {
|
||||||
@ -20,9 +20,9 @@ export default class Notification extends Model {
|
|||||||
return Model.hasOne<User>('user').call(this);
|
return Model.hasOne<User>('user').call(this);
|
||||||
}
|
}
|
||||||
fromUser() {
|
fromUser() {
|
||||||
return Model.hasOne<User>('fromUser').call(this);
|
return Model.hasOne<User | null>('fromUser').call(this);
|
||||||
}
|
}
|
||||||
subject() {
|
subject() {
|
||||||
return Model.hasOne('subject').call(this);
|
return Model.hasOne<Model | null>('subject').call(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,55 +13,61 @@ export default class Post extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createdAt() {
|
createdAt() {
|
||||||
return Model.attribute<Date | null, string>('createdAt', Model.transformDate).call(this);
|
return Model.attribute<Date, string>('createdAt', Model.transformDate).call(this);
|
||||||
}
|
}
|
||||||
user() {
|
user() {
|
||||||
return Model.hasOne<User>('user').call(this);
|
return Model.hasOne<User>('user').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
contentType() {
|
contentType() {
|
||||||
return Model.attribute<string>('contentType').call(this);
|
return Model.attribute<string | null>('contentType').call(this);
|
||||||
}
|
}
|
||||||
content() {
|
content() {
|
||||||
return Model.attribute<string>('content').call(this);
|
return Model.attribute<string | null | undefined>('content').call(this);
|
||||||
}
|
}
|
||||||
contentHtml() {
|
contentHtml() {
|
||||||
return Model.attribute<string>('contentHtml').call(this);
|
return Model.attribute<string | null | undefined>('contentHtml').call(this);
|
||||||
}
|
}
|
||||||
renderFailed() {
|
renderFailed() {
|
||||||
return Model.attribute<boolean>('renderFailed').call(this);
|
return Model.attribute<boolean | undefined>('renderFailed').call(this);
|
||||||
}
|
}
|
||||||
contentPlain() {
|
contentPlain() {
|
||||||
return computed<string>('contentHtml', getPlainContent as (content: unknown) => string).call(this);
|
return computed<string | null | undefined>('contentHtml', (content) => {
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
return getPlainContent(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content as (null | undefined);
|
||||||
|
}).call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
editedAt() {
|
editedAt() {
|
||||||
return Model.attribute<Date | null, string>('editedAt', Model.transformDate).call(this);
|
return Model.attribute('editedAt', Model.transformDate).call(this);
|
||||||
}
|
}
|
||||||
editedUser() {
|
editedUser() {
|
||||||
return Model.hasOne<User>('editedUser').call(this);
|
return Model.hasOne<User | null>('editedUser').call(this);
|
||||||
}
|
}
|
||||||
isEdited() {
|
isEdited() {
|
||||||
return computed<boolean>('editedAt', (editedAt) => !!editedAt).call(this);
|
return computed<boolean>('editedAt', (editedAt) => !!editedAt).call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
hiddenAt() {
|
hiddenAt() {
|
||||||
return Model.attribute<Date | null, string>('hiddenAt', Model.transformDate).call(this);
|
return Model.attribute('hiddenAt', Model.transformDate).call(this);
|
||||||
}
|
}
|
||||||
hiddenUser() {
|
hiddenUser() {
|
||||||
return Model.hasOne<User>('hiddenUser').call(this);
|
return Model.hasOne<User | null>('hiddenUser').call(this);
|
||||||
}
|
}
|
||||||
isHidden() {
|
isHidden() {
|
||||||
return computed<boolean>('hiddenAt', (hiddenAt) => !!hiddenAt).call(this);
|
return computed<boolean>('hiddenAt', (hiddenAt) => !!hiddenAt).call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
canEdit() {
|
canEdit() {
|
||||||
return Model.attribute<boolean>('canEdit').call(this);
|
return Model.attribute<boolean | undefined>('canEdit').call(this);
|
||||||
}
|
}
|
||||||
canHide() {
|
canHide() {
|
||||||
return Model.attribute<boolean>('canHide').call(this);
|
return Model.attribute<boolean | undefined>('canHide').call(this);
|
||||||
}
|
}
|
||||||
canDelete() {
|
canDelete() {
|
||||||
return Model.attribute<boolean>('canDelete').call(this);
|
return Model.attribute<boolean | undefined>('canDelete').call(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import ItemList from '../utils/ItemList';
|
|||||||
import computed from '../utils/computed';
|
import computed from '../utils/computed';
|
||||||
import GroupBadge from '../components/GroupBadge';
|
import GroupBadge from '../components/GroupBadge';
|
||||||
import Mithril from 'mithril';
|
import Mithril from 'mithril';
|
||||||
|
import Group from './Group';
|
||||||
|
|
||||||
export default class User extends Model {
|
export default class User extends Model {
|
||||||
username() {
|
username() {
|
||||||
@ -19,65 +20,65 @@ export default class User extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
email() {
|
email() {
|
||||||
return Model.attribute<string | null>('email').call(this);
|
return Model.attribute<string | undefined>('email').call(this);
|
||||||
}
|
}
|
||||||
isEmailConfirmed() {
|
isEmailConfirmed() {
|
||||||
return Model.attribute<boolean | null>('isEmailConfirmed').call(this);
|
return Model.attribute<boolean | undefined>('isEmailConfirmed').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
password() {
|
password() {
|
||||||
return Model.attribute<string | null>('password').call(this);
|
return Model.attribute<string | undefined>('password').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
avatarUrl() {
|
avatarUrl() {
|
||||||
return Model.attribute<string>('avatarUrl').call(this);
|
return Model.attribute<string | null>('avatarUrl').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences() {
|
preferences() {
|
||||||
return Model.attribute<Record<string, any> | null>('preferences').call(this);
|
return Model.attribute<Record<string, any> | null | undefined>('preferences').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
groups() {
|
groups() {
|
||||||
return Model.hasMany('groups').call(this);
|
return Model.hasMany<Group>('groups').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
joinTime() {
|
joinTime() {
|
||||||
return Model.attribute<Date | null, string | null>('joinTime', Model.transformDate).call(this);
|
return Model.attribute('joinTime', Model.transformDate).call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastSeenAt() {
|
lastSeenAt() {
|
||||||
return Model.attribute<Date | null, string | null>('lastSeenAt', Model.transformDate).call(this);
|
return Model.attribute('lastSeenAt', Model.transformDate).call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
markedAllAsReadAt() {
|
markedAllAsReadAt() {
|
||||||
return Model.attribute<Date | null, string | null>('markedAllAsReadAt', Model.transformDate).call(this);
|
return Model.attribute('markedAllAsReadAt', Model.transformDate).call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
unreadNotificationCount() {
|
unreadNotificationCount() {
|
||||||
return Model.attribute<number | null>('unreadNotificationCount').call(this);
|
return Model.attribute<number | undefined>('unreadNotificationCount').call(this);
|
||||||
}
|
}
|
||||||
newNotificationCount() {
|
newNotificationCount() {
|
||||||
return Model.attribute<number | null>('newNotificationCount').call(this);
|
return Model.attribute<number | undefined>('newNotificationCount').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
discussionCount() {
|
discussionCount() {
|
||||||
return Model.attribute<number | null>('discussionCount').call(this);
|
return Model.attribute<number | undefined>('discussionCount').call(this);
|
||||||
}
|
}
|
||||||
commentCount() {
|
commentCount() {
|
||||||
return Model.attribute<number | null>('commentCount').call(this);
|
return Model.attribute<number | undefined>('commentCount').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
canEdit() {
|
canEdit() {
|
||||||
return Model.attribute<boolean | null>('canEdit').call(this);
|
return Model.attribute<boolean | undefined>('canEdit').call(this);
|
||||||
}
|
}
|
||||||
canEditCredentials() {
|
canEditCredentials() {
|
||||||
return Model.attribute<boolean | null>('canEditCredentials').call(this);
|
return Model.attribute<boolean | undefined>('canEditCredentials').call(this);
|
||||||
}
|
}
|
||||||
canEditGroups() {
|
canEditGroups() {
|
||||||
return Model.attribute<boolean | null>('canEditGroups').call(this);
|
return Model.attribute<boolean | undefined>('canEditGroups').call(this);
|
||||||
}
|
}
|
||||||
canDelete() {
|
canDelete() {
|
||||||
return Model.attribute<boolean | null>('canDelete').call(this);
|
return Model.attribute<boolean | undefined>('canDelete').call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
color() {
|
color() {
|
||||||
@ -148,7 +149,7 @@ export default class User extends Model {
|
|||||||
m.redraw();
|
m.redraw();
|
||||||
};
|
};
|
||||||
image.crossOrigin = 'anonymous';
|
image.crossOrigin = 'anonymous';
|
||||||
image.src = this.avatarUrl();
|
image.src = this.avatarUrl() ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,7 +40,7 @@ export default class DiscussionsSearchSource implements SearchSource {
|
|||||||
<li className="DiscussionSearchResult" data-index={'discussions' + discussion.id()}>
|
<li className="DiscussionSearchResult" data-index={'discussions' + discussion.id()}>
|
||||||
<Link href={app.route.discussion(discussion, mostRelevantPost && mostRelevantPost.number())}>
|
<Link href={app.route.discussion(discussion, mostRelevantPost && mostRelevantPost.number())}>
|
||||||
<div className="DiscussionSearchResult-title">{highlight(discussion.title(), query)}</div>
|
<div className="DiscussionSearchResult-title">{highlight(discussion.title(), query)}</div>
|
||||||
{mostRelevantPost ? <div className="DiscussionSearchResult-excerpt">{highlight(mostRelevantPost.contentPlain(), query, 100)}</div> : ''}
|
{mostRelevantPost ? <div className="DiscussionSearchResult-excerpt">{highlight(mostRelevantPost.contentPlain() ?? '', query, 100)}</div> : ''}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user