remote-content.ts 3.36 KB
import { configureStore } from '@reduxjs/toolkit'
import { createSlice } from '@reduxjs/toolkit'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'

import { parseHtml } from './html.ts'

interface SiteConfig {
  name: string,
  baseUrl: string,
}

export const configSlice = createSlice({
  name: 'config',
  initialState: {
    sites: {},
  },
  reducers: {
    setSiteConfig(state, { payload: { name, baseUrl } }) {
      if (!state.sites[ name ]) state.sites[ name ] = {}
      Object.assign(state.sites[ name ], { baseUrl })
    }
  },
  selectors: {
    getSites: (state) => Object.keys(state.sites).filter(site => site !== 'default'),
    getSiteBaseUrl: (state, name) => state.sites?.[ name ]?.baseUrl,
  },
})


const baseQuery = fetchBaseQuery()

const siteBaseQuery = async (args, api, options) => {
  const { site, url } = args
  const baseUrl = getSiteBaseUrl(site) || ''
  return baseQuery({ ...args, url: `${baseUrl}${url}` }, api, options)
}

export const sitePageSlice = createApi({
  reducerPath: 'pages',
  tagTypes: ['Site', 'Page'],
  keepUnusedDataFor: 60,
  refetchOnReconnect: true,
  refetchOnMountOrArgChange: true,
  baseQuery: siteBaseQuery,
  endpoints: builder => ({
    getPage: builder.query({
      query: (args) => {
        const { site, page } = args
        return {
          site,
          url: page,
          method: 'GET',
          responseHandler: 'text',
        }
      },
      providesTags: (result, err, args) => {
        const { site, page } = args
        return [
          { type: 'Site', id: site },
          { type: 'Page', id: `${site}:${page}` },
        ]
      },
    }),
  }),
})

export const store = configureStore({
  reducer: {
    [ configSlice.reducerPath ]: configSlice.reducer,
    [ sitePageSlice.reducerPath ]: sitePageSlice.reducer,
  },
  middleware: getDefaultMiddleware => getDefaultMiddleware().concat([
    sitePageSlice.middleware,
  ]),
})

export const getUrl = async(url: string) => {
  const state = store.getState()
  const sites = configSlice.selectors.getSites(state)
  for (const site of sites) {
    const baseUrl = configSlice.selectors.getSiteBaseUrl(state, site)
    if (url.startsWith(baseUrl)) {
      return getSitePage(site, url.substring(baseUrl.length))
    }
  }
  return getSitePage('default', url)
}

export const getSitePage = async (site: string, page: string) => {
  const result = await store.dispatch(sitePageSlice.endpoints.getPage.initiate({ site, page }))
  return result
}

export const setSiteConfig = (siteConfig: SiteConfig) => {
  return store.dispatch(configSlice.actions.setSiteConfig(siteConfig))
}

export const getSiteBaseUrl = (name: string): string => {
  return configSlice.selectors.getSiteBaseUrl(store.getState(), name)
}

export const clearAll = () => {
  return store.dispatch(sitePageSlice.util.invalidateTags([ 'Page' ]))
}

export const clearSite = (site: string) => {
  return store.dispatch(sitePageSlice.util.invalidateTags([ { type: 'Site', id: site } ]))
}

export const clearPages = (site: string, ...pages: string) => {
  return store.dispatch(sitePageSlice.util.invalidateTags(pages.map(page => ({ type: 'Page', id: `${site}/${page}` }) )))
}

export const prefetch = (site: string, ...pages: string) => {
  return Promise.all(pages.map(page => {
    return store.dispatch(sitePageSlice.util.prefetch('getPage', { site, page }, { force: true }))
  }))
}

setSiteConfig({ name: 'direct' })