import { Slicer , SelectorCreator , Parameterizer } from '@rematch/select' ; import { message } from 'antd' ; import { Node } from 'editure' ; import * as F from 'editure-constants' ; import { Remote , Meta , Step , Article , randHex , getHeadings , isCommitEqual , flattenSteps as flatten , unflattenSteps as unflatten , } from '@tuture/core' ; import axios from 'axios' ; import { History } from 'history' ; import { FILE , DIFF_BLOCK , STEP , NOW_STEP_START , STEP_END , } from '../utils/constants' ; import { saveData } from '../utils/request' ; import { Dispatch , RootState } from '../store' ; export type CollectionState = { meta : Meta | null ; articles : Article [ ] ; nowArticleId : string | null ; editArticleId : string | null ; nowStepCommit : string | null ; fragment : Node [ ] ; remotes : Remote [ ] ; lastSaved : Date | null ; saveFailed : boolean ; outdatedNotificationClicked : boolean ; } ; export type SaveKey = 'meta' | 'articles' | 'fragment' | 'remotes' ; const initialState = { meta : null , articles : [ ] , nowArticleId : null , editArticleId : '' , nowStepCommit : null , fragment : [ { type : F . PARAGRAPH , children : [ { text : '' } ] } ] , remotes : [ ] , lastSaved : null , saveFailed : false , outdatedNotificationClicked : false , } ; export const collection = { state : initialState , reducers : { setMeta ( state , props ) { state . meta = { ... state . meta , ... props } as Meta ; return state ; } , setArticles ( state , articles ) { state . articles = articles ; return state ; } , setNowArticle ( state , articleId ) { state . nowArticleId = articleId ; if ( articleId ) { state . nowStepCommit = state . fragment . filter ( ( step ) => step . articleId === articleId ) [ 0 ] ?. commit ; } return state ; } , setArticleById ( state , payload ) { const { articleId = state . nowArticleId , props } = payload ; if ( state . articles . length > 0 ) { state . articles = state . articles . map ( ( article ) => { if ( article . id === articleId ) { return { ... article , ... props } ; } return article ; } ) ; } return state ; } , setDiffItemHiddenLines ( state , payload ) { const { file , commit , hiddenLines } = payload ; for ( const node of state . fragment ) { if ( node . type === DIFF_BLOCK && node . file === file && isCommitEqual ( node . commit , commit ) ) { node . hiddenLines = hiddenLines ; break ; } } return state ; } , setHideDiffFlag ( state , payload ) { const { file , commit , hideDiff } = payload ; for ( const node of state . fragment ) { if ( node . type === DIFF_BLOCK && node . file === file && isCommitEqual ( node . commit , commit ) ) { node . hideDiff = hideDiff ; break ; } } return state ; } , switchFile ( state , payload ) { const { removedIndex , addedIndex , commit } = payload ; const steps = unflatten ( state . fragment ) ; steps . forEach ( ( step ) => { if ( isCommitEqual ( step . commit , commit ) ) { const oldFile = step . children [ removedIndex + 2 ] ; step . children . splice ( removedIndex + 2 , 1 ) ; step . children . splice ( addedIndex + 2 , 0 , oldFile ) ; } } ) ; state . fragment = flatten ( steps ) ; return state ; } , setFragment ( state , fragment ) { state . fragment = fragment ; return state ; } , setNowStepCommit ( state , commit ) { state . nowStepCommit = commit ; return state ; } , setFileShowStatus ( state , payload ) { let steps = unflatten ( state . fragment ) ; steps = steps . map ( ( step ) => { if ( isCommitEqual ( step . commit , payload . commit ) ) { step . children . map ( ( node ) => { if ( node . type === FILE && node ?. file === payload . file ) { node . display = payload . display ; } return node ; } ) ; } return step ; } ) ; state . fragment = flatten ( steps ) ; return state ; } , setRemotes ( state , remotes ) { state . remotes = remotes ; return state ; } , setEditArticleId ( state , articleId ) { state . editArticleId = articleId ; return state ; } , editArticle ( state , payload ) { const { editArticleId } = state ; state . articles = state . articles . map ( ( article ) => article . id === editArticleId ? { ... article , ... payload } : article ) ; return state ; } , createArticle ( state , props ) { const id = randHex ( 8 ) ; const created = new Date ( ) ; state . articles . push ( { id , created , ... props } as Article ) ; return state ; } , setStepById ( state , payload ) { const { stepId , stepProps } = payload ; let steps = unflatten ( state . fragment ) ; steps = steps . map ( ( node ) => node . id === stepId ? { ... node , ... stepProps } : node ) ; state . fragment = flatten ( steps ) ; return state ; } , setLastSaved ( state , date ) { state . lastSaved = date ; return state ; } , setSaveFailed ( state , failed ) { state . saveFailed = failed ; return state ; } , setOutdatedNotificationClicked ( state , payload ) { state . outdatedNotificationClicked = payload ; return state ; } , } , effects : ( dispatch ) => ( { async fetchMeta ( ) { const response = await fetch ( '/api/meta' ) ; const data = await response . json ( ) ; dispatch . collection . setMeta ( data ) ; } , async fetchArticles ( _ ? , rootState ? ) { const response = await fetch ( '/api/articles' ) ; const data = await response . json ( ) ; dispatch . collection . setArticles ( data ) ; const { nowArticleId } = ( rootState as RootState ) . collection ; if ( ! nowArticleId ) { dispatch . collection . setNowArticle ( data [ 0 ] . id ) ; } } , async fetchFragment ( _ ? , rootState ? ) { const { nowArticleId } = ( rootState as RootState ) . collection ; let url = '/api/fragment' ; if ( nowArticleId ) { url = url + `?articleId=${ nowArticleId }` ; } const response = await fetch ( url ) ; const data = await response . json ( ) ; dispatch . collection . setFragment ( data ) ; } , async fetchRemotes ( ) { const response = await fetch ( '/api/remotes' ) ; const data = await response . json ( ) ; dispatch . collection . setRemotes ( data ) ; } , async updateSteps ( payload , rootState ) { const { updatedStepsId , articleId } = payload ; await axios . put ( '/api/collection-steps' , { updatedStepsId , articleId , } ) ; const { nowArticleId } = rootState . collection ; if ( articleId === nowArticleId ) { await dispatch . collection . fetchFragment ( ) ; } } , async save ( payload , rootState ) { const { keys = [ 'meta' , 'articles' , 'fragment' , 'remotes' ] , showMessage , } = payload || { } ; let success = false ; if ( keys . includes ( 'meta' ) ) { const { meta } = rootState . collection ; if ( meta ) { success = await saveData ( meta , '/api/meta' ) ; } } if ( keys . includes ( 'articles' ) ) { const { articles } = rootState . collection ; if ( articles ) { success = await saveData ( articles , '/api/articles' ) ; } } if ( keys . includes ( 'fragment' ) ) { const { fragment } = rootState . collection ; if ( fragment ) { success = await saveData ( fragment , '/api/fragment' ) ; } } if ( keys . includes ( 'remotes' ) ) { const { remotes } = rootState . collection ; if ( remotes ) { success = await saveData ( remotes , '/api/remotes' ) ; } } if ( showMessage ) { success ? message . success ( '保存内容成功！' ) : message . error ( '保存内容失败！' ) ; } dispatch . collection . setLastSaved ( new Date ( ) ) ; dispatch . collection . setSaveFailed ( ! success ) ; } , async deleteArticle ( articleId ) { const response = await fetch ( `/articles/${ articleId }` , { method : 'DELETE' , } ) ; if ( response . ok ) { await dispatch . collection . fetchArticles ( ) ; await dispatch . collection . fetchFragment ( ) ; } } , } ) , selectors : ( slice , createSelector , hasProps ) => ( { nowArticleMeta ( ) { return slice ( ( collectionState ) : Article | { } => { const { articles , nowArticleId } = collectionState ; if ( nowArticleId ) { return articles . filter ( ( elem ) => elem . id . toString ( ) === nowArticleId . toString ( ) ) [ 0 ] ; } return { } ; } ) ; } , nowArticleCatalogue ( ) { return slice ( ( collectionState ) => { return getHeadings ( collectionState . fragment ) ; } ) ; } , getArticleMetaById : hasProps ( ( __ , props ) => { return slice ( ( collectionState ) => { const { id } = props ; const { articles , meta } = collectionState || { } ; return id ? articles . filter ( ( elem ) => elem . id === id ) [ 0 ] : meta ; } ) ; } ) , getStepFileListAndTitle : hasProps ( ( __ , props ) => { return slice ( ( collectionState ) => { const { commit } = props ; let flag = '' ; let title = '' ; let fileList = [ ] ; collectionState . fragment . forEach ( ( node ) => { switch ( node . type ) { case STEP : { if ( isCommitEqual ( node . commit , commit ) ) { flag = NOW_STEP_START ; } break ; } case FILE : { if ( flag === NOW_STEP_START ) { fileList . push ( { file : node . file , display : node ?. display } ) ; } break ; } case F . H2 : { if ( flag === NOW_STEP_START ) { title = node . children [ 0 ] . text ; } break ; } case STEP_END : { flag = '' ; break ; } default : { break ; } } } ) ; return { fileList , title } ; } ) ; } ) , } ) , } ;