mirror of
https://github.com/flarum/core.git
synced 2025-08-04 23:47:32 +02:00
Drop mixin-like attributes and convert to typescript
This commit is contained in:
2
js/shims.d.ts
vendored
2
js/shims.d.ts
vendored
@@ -4,6 +4,7 @@ import Mithril from 'mithril';
|
||||
// Other third-party libs
|
||||
import * as _dayjs from 'dayjs';
|
||||
import * as _$ from 'jquery';
|
||||
import * as _ColorThief from 'color-thief-browser';
|
||||
|
||||
// Globals from flarum/core
|
||||
import Application from './src/common/Application';
|
||||
@@ -22,6 +23,7 @@ declare global {
|
||||
const $: typeof _$;
|
||||
const m: Mithril.Static;
|
||||
const dayjs: typeof _dayjs;
|
||||
const ColorThief: _ColorThief;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,3 +1,17 @@
|
||||
import Store from './Store';
|
||||
import Mithril from 'mithril';
|
||||
|
||||
interface ModelData {
|
||||
type?: string;
|
||||
id?: string;
|
||||
attributes?: any;
|
||||
relationships?: any;
|
||||
}
|
||||
|
||||
interface SaveOptions extends Mithril.RequestOptions<any> {
|
||||
meta?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `Model` class represents a local data resource. It provides methods to
|
||||
* persist changes via the API.
|
||||
@@ -5,55 +19,58 @@
|
||||
* @abstract
|
||||
*/
|
||||
export default class Model {
|
||||
/**
|
||||
* The resource object from the API.
|
||||
*
|
||||
* @type {Object}
|
||||
* @public
|
||||
*/
|
||||
data: ModelData = {};
|
||||
|
||||
/**
|
||||
* The time at which the model's data was last updated. Watching the value
|
||||
* of this property is a fast way to retain/cache a subtree if data hasn't
|
||||
* changed.
|
||||
*
|
||||
* @type {Date}
|
||||
* @public
|
||||
*/
|
||||
freshness: Date = new Date();
|
||||
|
||||
/**
|
||||
* Whether or not the resource exists on the server.
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @public
|
||||
*/
|
||||
exists: boolean = false;
|
||||
|
||||
/**
|
||||
* The data store that this resource should be persisted to.
|
||||
*
|
||||
* @type {Store}
|
||||
* @protected
|
||||
*/
|
||||
store?: Store = null;
|
||||
|
||||
/**
|
||||
* @param {Object} data A resource object from the API.
|
||||
* @param {Store} store The data store that this model should be persisted to.
|
||||
* @public
|
||||
*/
|
||||
constructor(data = {}, store = null) {
|
||||
/**
|
||||
* The resource object from the API.
|
||||
*
|
||||
* @type {Object}
|
||||
* @public
|
||||
*/
|
||||
constructor(data: ModelData = {}, store = null) {
|
||||
this.data = data;
|
||||
|
||||
/**
|
||||
* The time at which the model's data was last updated. Watching the value
|
||||
* of this property is a fast way to retain/cache a subtree if data hasn't
|
||||
* changed.
|
||||
*
|
||||
* @type {Date}
|
||||
* @public
|
||||
*/
|
||||
this.freshness = new Date();
|
||||
|
||||
/**
|
||||
* Whether or not the resource exists on the server.
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @public
|
||||
*/
|
||||
this.exists = false;
|
||||
|
||||
/**
|
||||
* The data store that this resource should be persisted to.
|
||||
*
|
||||
* @type {Store}
|
||||
* @protected
|
||||
*/
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model's ID.
|
||||
*
|
||||
* @return {Integer}
|
||||
* @return {String}
|
||||
* @public
|
||||
* @final
|
||||
*/
|
||||
id() {
|
||||
id(): string | undefined {
|
||||
return this.data.id;
|
||||
}
|
||||
|
||||
@@ -121,8 +138,8 @@ export default class Model {
|
||||
* @return {Promise}
|
||||
* @public
|
||||
*/
|
||||
save(attributes, options = {}) {
|
||||
const data = {
|
||||
save(attributes, options: SaveOptions = {}) {
|
||||
const data: ModelData = {
|
||||
type: this.data.type,
|
||||
id: this.data.id,
|
||||
attributes,
|
||||
@@ -152,7 +169,7 @@ export default class Model {
|
||||
|
||||
this.pushData(data);
|
||||
|
||||
const request = { data };
|
||||
const request: any = { data };
|
||||
if (options.meta) request.meta = options.meta;
|
||||
|
||||
return app
|
||||
@@ -220,11 +237,11 @@ export default class Model {
|
||||
* @return {String}
|
||||
* @protected
|
||||
*/
|
||||
apiEndpoint() {
|
||||
apiEndpoint(): string {
|
||||
return '/' + this.data.type + (this.exists ? '/' + this.data.id : '');
|
||||
}
|
||||
|
||||
copyData() {
|
||||
copyData(): ModelData {
|
||||
return JSON.parse(JSON.stringify(this.data));
|
||||
}
|
||||
|
||||
@@ -236,8 +253,8 @@ export default class Model {
|
||||
* @return {*}
|
||||
* @public
|
||||
*/
|
||||
static attribute(name, transform) {
|
||||
return function () {
|
||||
static attribute<T>(name: string, transform?: Function) {
|
||||
return function (this: Model): T | null | undefined {
|
||||
const value = this.data.attributes && this.data.attributes[name];
|
||||
|
||||
return transform ? transform(value) : value;
|
||||
@@ -254,8 +271,8 @@ export default class Model {
|
||||
* has not been loaded; or the model if it has been loaded.
|
||||
* @public
|
||||
*/
|
||||
static hasOne(name) {
|
||||
return function () {
|
||||
static hasOne<T>(name: string) {
|
||||
return function (this: Model): T | null | false {
|
||||
if (this.data.relationships) {
|
||||
const relationship = this.data.relationships[name];
|
||||
|
||||
@@ -278,8 +295,8 @@ export default class Model {
|
||||
* loaded, and undefined for those that have not.
|
||||
* @public
|
||||
*/
|
||||
static hasMany(name) {
|
||||
return function () {
|
||||
static hasMany<T>(name: string) {
|
||||
return function (this: Model): T[] | false {
|
||||
if (this.data.relationships) {
|
||||
const relationship = this.data.relationships[name];
|
||||
|
||||
@@ -299,7 +316,7 @@ export default class Model {
|
||||
* @return {Date|null}
|
||||
* @public
|
||||
*/
|
||||
static transformDate(value) {
|
||||
static transformDate(value: string): Date | null {
|
||||
return value ? new Date(value) : null;
|
||||
}
|
||||
|
||||
@@ -310,7 +327,7 @@ export default class Model {
|
||||
* @return {Object}
|
||||
* @protected
|
||||
*/
|
||||
static getIdentifier(model) {
|
||||
static getIdentifier(model: Model) {
|
||||
return {
|
||||
type: model.data.type,
|
||||
id: model.data.id,
|
@@ -2,40 +2,40 @@ import Model from '../Model';
|
||||
import computed from '../utils/computed';
|
||||
import ItemList from '../utils/ItemList';
|
||||
import Badge from '../components/Badge';
|
||||
import User from './User';
|
||||
import Post from './Post';
|
||||
|
||||
export default class Discussion extends Model {}
|
||||
export default class Discussion extends Model {
|
||||
title = Model.attribute<string>('title');
|
||||
slug = Model.attribute<string>('slug');
|
||||
|
||||
Object.assign(Discussion.prototype, {
|
||||
title: Model.attribute('title'),
|
||||
slug: Model.attribute('slug'),
|
||||
createdAt = Model.attribute<Date>('createdAt', Model.transformDate);
|
||||
user = Model.hasOne<User>('user');
|
||||
firstPost = Model.hasOne<Post>('firstPost');
|
||||
|
||||
createdAt: Model.attribute('createdAt', Model.transformDate),
|
||||
user: Model.hasOne('user'),
|
||||
firstPost: Model.hasOne('firstPost'),
|
||||
lastPostedAt = Model.attribute<Date>('lastPostedAt', Model.transformDate);
|
||||
lastPostedUser = Model.hasOne<User>('lastPostedUser');
|
||||
lastPost = Model.hasOne<Post>('lastPost');
|
||||
lastPostNumber = Model.attribute<number>('lastPostNumber');
|
||||
|
||||
lastPostedAt: Model.attribute('lastPostedAt', Model.transformDate),
|
||||
lastPostedUser: Model.hasOne('lastPostedUser'),
|
||||
lastPost: Model.hasOne('lastPost'),
|
||||
lastPostNumber: Model.attribute('lastPostNumber'),
|
||||
commentCount = Model.attribute<number>('commentCount');
|
||||
replyCount = computed<number>('commentCount', (commentCount) => Math.max(0, commentCount - 1));
|
||||
posts = Model.hasMany<Post>('posts');
|
||||
mostRelevantPost = Model.hasOne<Post>('mostRelevantPost');
|
||||
|
||||
commentCount: Model.attribute('commentCount'),
|
||||
replyCount: computed('commentCount', (commentCount) => Math.max(0, commentCount - 1)),
|
||||
posts: Model.hasMany('posts'),
|
||||
mostRelevantPost: Model.hasOne('mostRelevantPost'),
|
||||
lastReadAt = Model.attribute<Date>('lastReadAt', Model.transformDate);
|
||||
lastReadPostNumber = Model.attribute<number>('lastReadPostNumber');
|
||||
isUnread = computed<boolean>('unreadCount', (unreadCount) => !!unreadCount);
|
||||
isRead = computed<boolean>('unreadCount', (unreadCount) => app.session.user && !unreadCount);
|
||||
|
||||
lastReadAt: Model.attribute('lastReadAt', Model.transformDate),
|
||||
lastReadPostNumber: Model.attribute('lastReadPostNumber'),
|
||||
isUnread: computed('unreadCount', (unreadCount) => !!unreadCount),
|
||||
isRead: computed('unreadCount', (unreadCount) => app.session.user && !unreadCount),
|
||||
hiddenAt = Model.attribute<Date>('hiddenAt', Model.transformDate);
|
||||
hiddenUser = Model.hasOne<User>('hiddenUser');
|
||||
isHidden = computed<boolean>('hiddenAt', (hiddenAt) => !!hiddenAt);
|
||||
|
||||
hiddenAt: Model.attribute('hiddenAt', Model.transformDate),
|
||||
hiddenUser: Model.hasOne('hiddenUser'),
|
||||
isHidden: computed('hiddenAt', (hiddenAt) => !!hiddenAt),
|
||||
|
||||
canReply: Model.attribute('canReply'),
|
||||
canRename: Model.attribute('canRename'),
|
||||
canHide: Model.attribute('canHide'),
|
||||
canDelete: Model.attribute('canDelete'),
|
||||
canReply = Model.attribute<boolean>('canReply');
|
||||
canRename = Model.attribute<boolean>('canRename');
|
||||
canHide = Model.attribute<boolean>('canHide');
|
||||
canDelete = Model.attribute<boolean>('canDelete');
|
||||
|
||||
/**
|
||||
* Remove a post from the discussion's posts relationship.
|
||||
@@ -55,7 +55,7 @@ Object.assign(Discussion.prototype, {
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the estimated number of unread posts in this discussion for the current
|
||||
@@ -64,7 +64,7 @@ Object.assign(Discussion.prototype, {
|
||||
* @return {Integer}
|
||||
* @public
|
||||
*/
|
||||
unreadCount() {
|
||||
unreadCount(): number {
|
||||
const user = app.session.user;
|
||||
|
||||
if (user && user.markedAllAsReadAt() < this.lastPostedAt()) {
|
||||
@@ -75,7 +75,7 @@ Object.assign(Discussion.prototype, {
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Badge components that apply to this discussion.
|
||||
@@ -83,7 +83,7 @@ Object.assign(Discussion.prototype, {
|
||||
* @return {ItemList}
|
||||
* @public
|
||||
*/
|
||||
badges() {
|
||||
badges(): ItemList {
|
||||
const items = new ItemList();
|
||||
|
||||
if (this.isHidden()) {
|
||||
@@ -91,7 +91,7 @@ Object.assign(Discussion.prototype, {
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all of the post IDs in this discussion.
|
||||
@@ -99,9 +99,9 @@ Object.assign(Discussion.prototype, {
|
||||
* @return {Array}
|
||||
* @public
|
||||
*/
|
||||
postIds() {
|
||||
postIds(): string[] {
|
||||
const posts = this.data.relationships.posts;
|
||||
|
||||
return posts ? posts.data.map((link) => link.id) : [];
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
import Model from '../Model';
|
||||
|
||||
class Group extends Model {}
|
||||
|
||||
Object.assign(Group.prototype, {
|
||||
nameSingular: Model.attribute('nameSingular'),
|
||||
namePlural: Model.attribute('namePlural'),
|
||||
color: Model.attribute('color'),
|
||||
icon: Model.attribute('icon'),
|
||||
isHidden: Model.attribute('isHidden'),
|
||||
});
|
||||
|
||||
Group.ADMINISTRATOR_ID = '1';
|
||||
Group.GUEST_ID = '2';
|
||||
Group.MEMBER_ID = '3';
|
||||
|
||||
export default Group;
|
13
js/src/common/models/Group.ts
Normal file
13
js/src/common/models/Group.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import Model from '../Model';
|
||||
|
||||
export default class Group extends Model {
|
||||
static ADMINISTRATOR_ID = '1';
|
||||
static GUEST_ID = '2';
|
||||
static MEMBER_ID = '3';
|
||||
|
||||
nameSingular = Model.attribute<string>('nameSingular');
|
||||
namePlural = Model.attribute<string>('namePlural');
|
||||
color = Model.attribute<string>('color');
|
||||
icon = Model.attribute<string>('icon');
|
||||
isHidden = Model.attribute<boolean>('isHidden');
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
import Model from '../Model';
|
||||
|
||||
export default class Notification extends Model {}
|
||||
|
||||
Object.assign(Notification.prototype, {
|
||||
contentType: Model.attribute('contentType'),
|
||||
content: Model.attribute('content'),
|
||||
createdAt: Model.attribute('createdAt', Model.transformDate),
|
||||
|
||||
isRead: Model.attribute('isRead'),
|
||||
|
||||
user: Model.hasOne('user'),
|
||||
fromUser: Model.hasOne('fromUser'),
|
||||
subject: Model.hasOne('subject'),
|
||||
});
|
14
js/src/common/models/Notification.ts
Normal file
14
js/src/common/models/Notification.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import Model from '../Model';
|
||||
import User from './User';
|
||||
|
||||
export default class Notification extends Model {
|
||||
contentType = Model.attribute<string>('contentType');
|
||||
content = Model.attribute<any>('content');
|
||||
createdAt = Model.attribute<Date>('createdAt', Model.transformDate);
|
||||
|
||||
isRead = Model.attribute<boolean>('isRead');
|
||||
|
||||
user = Model.hasOne<User>('user');
|
||||
fromUser = Model.hasOne<User>('fromUser');
|
||||
subject = Model.hasOne<any>('subject');
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
import Model from '../Model';
|
||||
import computed from '../utils/computed';
|
||||
import { getPlainContent } from '../utils/string';
|
||||
|
||||
export default class Post extends Model {}
|
||||
|
||||
Object.assign(Post.prototype, {
|
||||
number: Model.attribute('number'),
|
||||
discussion: Model.hasOne('discussion'),
|
||||
|
||||
createdAt: Model.attribute('createdAt', Model.transformDate),
|
||||
user: Model.hasOne('user'),
|
||||
contentType: Model.attribute('contentType'),
|
||||
content: Model.attribute('content'),
|
||||
contentHtml: Model.attribute('contentHtml'),
|
||||
contentPlain: computed('contentHtml', getPlainContent),
|
||||
|
||||
editedAt: Model.attribute('editedAt', Model.transformDate),
|
||||
editedUser: Model.hasOne('editedUser'),
|
||||
isEdited: computed('editedAt', (editedAt) => !!editedAt),
|
||||
|
||||
hiddenAt: Model.attribute('hiddenAt', Model.transformDate),
|
||||
hiddenUser: Model.hasOne('hiddenUser'),
|
||||
isHidden: computed('hiddenAt', (hiddenAt) => !!hiddenAt),
|
||||
|
||||
canEdit: Model.attribute('canEdit'),
|
||||
canHide: Model.attribute('canHide'),
|
||||
canDelete: Model.attribute('canDelete'),
|
||||
});
|
29
js/src/common/models/Post.ts
Normal file
29
js/src/common/models/Post.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import Model from '../Model';
|
||||
import computed from '../utils/computed';
|
||||
import { getPlainContent } from '../utils/string';
|
||||
import Discussion from './Discussion';
|
||||
import User from './User';
|
||||
|
||||
export default class Post extends Model {
|
||||
number = Model.attribute<number>('number');
|
||||
discussion = Model.hasOne<Discussion>('discussion');
|
||||
|
||||
createdAt = Model.attribute<Date>('createdAt', Model.transformDate);
|
||||
user = Model.hasOne<User>('user');
|
||||
contentType = Model.attribute<string>('contentType');
|
||||
content = Model.attribute<string>('content');
|
||||
contentHtml = Model.attribute<string>('contentHtml');
|
||||
contentPlain = computed<string>('contentHtml', getPlainContent);
|
||||
|
||||
editedAt = Model.attribute<Date>('editedAt', Model.transformDate);
|
||||
editedUser = Model.hasOne<User>('editedUser');
|
||||
isEdited = computed<boolean>('editedAt', (editedAt) => !!editedAt);
|
||||
|
||||
hiddenAt = Model.attribute<Date>('hiddenAt', Model.transformDate);
|
||||
hiddenUser = Model.hasOne<User>('hiddenUser');
|
||||
isHidden = computed<boolean>('hiddenAt', (hiddenAt) => !!hiddenAt);
|
||||
|
||||
canEdit = Model.attribute<boolean>('canEdit');
|
||||
canHide = Model.attribute<boolean>('canHide');
|
||||
canDelete = Model.attribute<boolean>('canDelete');
|
||||
}
|
@@ -5,34 +5,33 @@ import stringToColor from '../utils/stringToColor';
|
||||
import ItemList from '../utils/ItemList';
|
||||
import computed from '../utils/computed';
|
||||
import GroupBadge from '../components/GroupBadge';
|
||||
import Group from './Group';
|
||||
|
||||
export default class User extends Model {}
|
||||
export default class User extends Model {
|
||||
username = Model.attribute<string>('username');
|
||||
displayName = Model.attribute<string>('displayName');
|
||||
email = Model.attribute<string>('email');
|
||||
isEmailConfirmed = Model.attribute<boolean>('isEmailConfirmed');
|
||||
password = Model.attribute<string>('password');
|
||||
|
||||
Object.assign(User.prototype, {
|
||||
username: Model.attribute('username'),
|
||||
displayName: Model.attribute('displayName'),
|
||||
email: Model.attribute('email'),
|
||||
isEmailConfirmed: Model.attribute('isEmailConfirmed'),
|
||||
password: Model.attribute('password'),
|
||||
avatarUrl = Model.attribute<string>('avatarUrl');
|
||||
preferences = Model.attribute<any>('preferences');
|
||||
groups = Model.hasMany<Group>('groups');
|
||||
|
||||
avatarUrl: Model.attribute('avatarUrl'),
|
||||
preferences: Model.attribute('preferences'),
|
||||
groups: Model.hasMany('groups'),
|
||||
joinTime = Model.attribute<Date>('joinTime', Model.transformDate);
|
||||
lastSeenAt = Model.attribute<Date>('lastSeenAt', Model.transformDate);
|
||||
markedAllAsReadAt = Model.attribute<Date>('markedAllAsReadAt', Model.transformDate);
|
||||
unreadNotificationCount = Model.attribute<number>('unreadNotificationCount');
|
||||
newNotificationCount = Model.attribute<number>('newNotificationCount');
|
||||
|
||||
joinTime: Model.attribute('joinTime', Model.transformDate),
|
||||
lastSeenAt: Model.attribute('lastSeenAt', Model.transformDate),
|
||||
markedAllAsReadAt: Model.attribute('markedAllAsReadAt', Model.transformDate),
|
||||
unreadNotificationCount: Model.attribute('unreadNotificationCount'),
|
||||
newNotificationCount: Model.attribute('newNotificationCount'),
|
||||
discussionCount = Model.attribute<number>('discussionCount');
|
||||
commentCount = Model.attribute<number>('commentCount');
|
||||
|
||||
discussionCount: Model.attribute('discussionCount'),
|
||||
commentCount: Model.attribute('commentCount'),
|
||||
canEdit = Model.attribute<boolean>('canEdit');
|
||||
canDelete = Model.attribute<boolean>('canDelete');
|
||||
|
||||
canEdit: Model.attribute('canEdit'),
|
||||
canDelete: Model.attribute('canDelete'),
|
||||
|
||||
avatarColor: null,
|
||||
color: computed('username', 'avatarUrl', 'avatarColor', function (username, avatarUrl, avatarColor) {
|
||||
avatarColor = null;
|
||||
color = computed<string>('username', 'avatarUrl', 'avatarColor', (username, avatarUrl, avatarColor) => {
|
||||
// If we've already calculated and cached the dominant color of the user's
|
||||
// avatar, then we can return that in RGB format. If we haven't, we'll want
|
||||
// to calculate it. Unless the user doesn't have an avatar, in which case
|
||||
@@ -45,7 +44,7 @@ Object.assign(User.prototype, {
|
||||
}
|
||||
|
||||
return '#' + stringToColor(username);
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Check whether or not the user has been seen in the last 5 minutes.
|
||||
@@ -53,16 +52,16 @@ Object.assign(User.prototype, {
|
||||
* @return {Boolean}
|
||||
* @public
|
||||
*/
|
||||
isOnline() {
|
||||
isOnline(): boolean {
|
||||
return dayjs().subtract(5, 'minutes').isBefore(this.lastSeenAt());
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Badge components that apply to this user.
|
||||
*
|
||||
* @return {ItemList}
|
||||
*/
|
||||
badges() {
|
||||
badges(): ItemList {
|
||||
const items = new ItemList();
|
||||
const groups = this.groups();
|
||||
|
||||
@@ -73,7 +72,7 @@ Object.assign(User.prototype, {
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the dominant color of the user's avatar. The dominant color will
|
||||
@@ -93,7 +92,7 @@ Object.assign(User.prototype, {
|
||||
};
|
||||
image.crossOrigin = 'anonymous';
|
||||
image.src = this.avatarUrl();
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's preferences.
|
||||
@@ -107,5 +106,5 @@ Object.assign(User.prototype, {
|
||||
Object.assign(preferences, newPreferences);
|
||||
|
||||
return this.save({ preferences });
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,3 +1,5 @@
|
||||
import Model from '../Model';
|
||||
|
||||
/**
|
||||
* The `computed` utility creates a function that will cache its output until
|
||||
* any of the dependent values are dirty.
|
||||
@@ -7,14 +9,14 @@
|
||||
* dependent values.
|
||||
* @return {Function}
|
||||
*/
|
||||
export default function computed(...dependentKeys) {
|
||||
export default function computed<T, M = Model>(...dependentKeys: any[]) {
|
||||
const keys = dependentKeys.slice(0, -1);
|
||||
const compute = dependentKeys.slice(-1)[0];
|
||||
|
||||
const dependentValues = {};
|
||||
let computedValue;
|
||||
|
||||
return function () {
|
||||
return function (this: M): T {
|
||||
let recompute = false;
|
||||
|
||||
// Read all of the dependent values. If any of them have changed since last
|
Reference in New Issue
Block a user