1- import { makeAutoObservable , runInAction , toJS } from "mobx" ;
1+ import { makeAutoObservable , toJS } from "mobx" ;
22import { CommentsPresenter as PresenterAbstraction } from "./abstractions.js" ;
33import type { ICreateThreadParams } from "./abstractions.js" ;
44import { CollaborationApi } from "~/admin/features/api/abstractions.js" ;
@@ -134,9 +134,7 @@ class CommentsPresenterImpl implements PresenterAbstraction.Interface {
134134 if ( ! this . composerBody . trim ( ) || this . composerSubmitting ) {
135135 return ;
136136 }
137- runInAction ( ( ) => {
138- this . composerSubmitting = true ;
139- } ) ;
137+ this . setComposerSubmitting ( true ) ;
140138 try {
141139 // Empty locator => entry-level (unanchored) comment.
142140 await this . createThread ( {
@@ -146,20 +144,15 @@ class CommentsPresenterImpl implements PresenterAbstraction.Interface {
146144 } ) ;
147145 this . resetComposer ( ) ;
148146 } finally {
149- runInAction ( ( ) => {
150- this . composerSubmitting = false ;
151- } ) ;
147+ this . setComposerSubmitting ( false ) ;
152148 }
153149 }
154150
155151 async init ( contentId : string ) {
156152 if ( this . loadedContentId === contentId ) {
157153 return ;
158154 }
159- runInAction ( ( ) => {
160- this . contentId = contentId ;
161- this . loadedContentId = contentId ;
162- } ) ;
155+ this . setContentId ( contentId ) ;
163156 void this . loadUsers ( ) ;
164157 await this . reload ( ) ;
165158 }
@@ -170,9 +163,7 @@ class CommentsPresenterImpl implements PresenterAbstraction.Interface {
170163 }
171164 try {
172165 const users = await this . api . listMentionableUsers ( ) ;
173- runInAction ( ( ) => {
174- this . users = users ;
175- } ) ;
166+ this . setUsers ( users ) ;
176167 } catch {
177168 // Best-effort — mentions simply won't autocomplete.
178169 }
@@ -182,25 +173,16 @@ class CommentsPresenterImpl implements PresenterAbstraction.Interface {
182173 if ( ! this . contentId ) {
183174 return ;
184175 }
185- runInAction ( ( ) => {
186- this . loading = true ;
187- this . error = null ;
188- } ) ;
176+ this . startLoading ( ) ;
189177 try {
190178 const result = await this . api . listThreads ( {
191179 contentType : CONTENT_TYPE_CMS_ENTRY ,
192180 contentId : this . contentId
193181 } ) ;
194- runInAction ( ( ) => {
195- this . threads = result . items ;
196- this . loading = false ;
197- } ) ;
182+ this . finishLoading ( result . items ) ;
198183 } catch ( err ) {
199184 console . error ( "[collaboration] failed to load comment threads" , err ) ;
200- runInAction ( ( ) => {
201- this . error = readableError ( err ) ;
202- this . loading = false ;
203- } ) ;
185+ this . failLoading ( readableError ( err ) ) ;
204186 }
205187 }
206188
@@ -216,9 +198,7 @@ class CommentsPresenterImpl implements PresenterAbstraction.Interface {
216198 body : params . body ,
217199 mentions : params . mentions
218200 } ) ;
219- runInAction ( ( ) => {
220- this . threads = [ thread , ...this . threads ] ;
221- } ) ;
201+ this . prependThread ( thread ) ;
222202 }
223203
224204 async reply ( threadId : string , body : string , mentions ?: string [ ] ) {
@@ -249,9 +229,7 @@ class CommentsPresenterImpl implements PresenterAbstraction.Interface {
249229
250230 async remove ( threadId : string ) {
251231 await this . api . deleteThread ( threadId ) ;
252- runInAction ( ( ) => {
253- this . threads = this . threads . filter ( thread => thread . id !== threadId ) ;
254- } ) ;
232+ this . removeThread ( threadId ) ;
255233 }
256234
257235 async editMessage ( threadId : string , messageId : string , body : string ) {
@@ -275,12 +253,49 @@ class CommentsPresenterImpl implements PresenterAbstraction.Interface {
275253 await this . reload ( ) ;
276254 }
277255
256+ /*
257+ * State changes that happen after an `await` go through these methods. makeAutoObservable makes
258+ * each of them an action, so the change runs inside one even though the caller has already
259+ * left its own action.
260+ */
261+ private setComposerSubmitting ( submitting : boolean ) {
262+ this . composerSubmitting = submitting ;
263+ }
264+
265+ private setContentId ( contentId : string ) {
266+ this . contentId = contentId ;
267+ this . loadedContentId = contentId ;
268+ }
269+
270+ private setUsers ( users : CollabUser [ ] ) {
271+ this . users = users ;
272+ }
273+
274+ private startLoading ( ) {
275+ this . loading = true ;
276+ this . error = null ;
277+ }
278+
279+ private finishLoading ( threads : CollabThread [ ] ) {
280+ this . threads = threads ;
281+ this . loading = false ;
282+ }
283+
284+ private failLoading ( error : string ) {
285+ this . error = error ;
286+ this . loading = false ;
287+ }
288+
289+ private prependThread ( thread : CollabThread ) {
290+ this . threads = [ thread , ...this . threads ] ;
291+ }
292+
293+ private removeThread ( threadId : string ) {
294+ this . threads = this . threads . filter ( thread => thread . id !== threadId ) ;
295+ }
296+
278297 private replaceThread ( updated : CollabThread ) {
279- runInAction ( ( ) => {
280- this . threads = this . threads . map ( thread =>
281- thread . id === updated . id ? updated : thread
282- ) ;
283- } ) ;
298+ this . threads = this . threads . map ( thread => ( thread . id === updated . id ? updated : thread ) ) ;
284299 }
285300}
286301
0 commit comments