@stackend/api
Version:
JS bindings to api.stackend.com
350 lines (309 loc) • 8.12 kB
text/typescript
import { getJsonErrorText, newXcapJsonErrorResult, newXcapJsonResult, Thunk } from '../api';
import {
CLEAR_PAGE,
CLEAR_PAGES,
RECEIVE_PAGES,
PagesState,
RECEIVE_SUB_SITES,
PageAndLoadedState,
PageActions
} from './pageReducer';
import { getPages, GetPagesResult, getSubSite, GetSubSiteResult, Page, SubSiteNode, SubSite } from './index';
import { getPermalink, getTreeNodeByPermalink } from '../api/tree';
/**
* Request multiple pages
*/
export function requestPages({
pageIds,
permalinks,
communityPermalink
}: {
pageIds?: Array<number>;
permalinks?: Array<string>;
communityPermalink?: string | null;
}): Thunk<Promise<GetPagesResult>> {
return async (dispatch: any): Promise<GetPagesResult> => {
const r = await dispatch(getPages({ pageIds, permalinks, communityPermalink }));
await dispatch({
type: RECEIVE_PAGES,
json: r,
pageIds,
permalinks
});
return r;
};
}
/**
* Request the missing pages. Works as requestPages, but does only fetch if the page is not present.
* @param pageIds
* @param permalinks
* @param communityPermalink
* @param getMenuForIds
*/
export function requestMissingPages({
pageIds,
permalinks,
communityPermalink
}: {
pageIds?: Array<number>;
permalinks?: Array<string>;
communityPermalink?: string | null;
}): Thunk<Promise<GetPagesResult>> {
return async (dispatch: any, getState): Promise<GetPagesResult> => {
const fetchPageIds: Array<number> = [];
const fetchPermalinks: Array<string> = [];
const { pages } = getState();
//console.log('Pages: ', pages);
const now = new Date().getTime();
if (pageIds) {
pageIds.forEach(id => {
const p = pages.byId[id];
if (shouldFetchPage(p, now)) {
fetchPageIds.push(id);
}
});
}
if (permalinks) {
permalinks.forEach(permalink => {
const p = getPageByPermalink(pages, permalink);
if (shouldFetchPage(p, now)) {
fetchPermalinks.push(permalink);
}
});
}
if (fetchPageIds.length === 0 && fetchPermalinks.length === 0) {
return newXcapJsonResult('success', { pages: {} }) as GetPagesResult;
}
return await dispatch(
requestPages({
pageIds: fetchPageIds,
permalinks: fetchPermalinks,
communityPermalink
})
);
};
}
export function shouldFetchPage(p: Page | PageAndLoadedState | null | undefined, now: number): boolean {
if (typeof p === 'undefined') {
return true;
}
if (p === null) {
return false; // Has cached the fact that p does not exists
}
const s = p as PageAndLoadedState;
if (!s.loaded) {
return true;
}
const age = now - s.loaded;
return age > 60000;
}
/**
* Request a single page
* @param pageId
* @returns {Thunk<GetPagesResult>}
*/
export function requestPage(pageId: number): Thunk<Promise<GetPagesResult>> {
return requestPages({ pageIds: [pageId] });
}
/**
* Request a single page
* @param permalink
* @returns {Thunk<GetPagesResult>}
*/
export function requestPageByPermalink(permalink: string): Thunk<Promise<GetPagesResult>> {
return requestPages({ permalinks: [permalink] });
}
/**
* Clear all pages
* @returns {Function}
*/
export function clearPages(): Thunk<PageActions> {
return (dispatch /*, getState: any*/): PageActions => {
return dispatch({
type: CLEAR_PAGES
});
};
}
/**
* Clear a single page
* @param pageId
* @returns {Function}
*/
export function clearPage(pageId: number): Thunk<PageActions> {
return (dispatch /*, getState: any*/): PageActions => {
return dispatch({
type: CLEAR_PAGE,
id: pageId
});
};
}
export function receivePages(json: GetPagesResult): Thunk<any> {
return (dispatch /*, getState: any*/): PageActions => {
return dispatch({
type: RECEIVE_PAGES,
json
});
};
}
/**
* Request a sub site by id or permalink
* @param arg May be a
*/
export function requestSubSite(arg: number | { id?: number; permalink?: string }): Thunk<Promise<GetSubSiteResult>> {
return async (dispatch: any): Promise<GetSubSiteResult> => {
// For backwards compatibility, allow an id
let req: any = {};
if (typeof arg === 'number') {
req.id = arg as number;
} else {
req = arg;
}
const r = await dispatch(getSubSite(req));
if (r.error) {
console.error('Could not get sub sites ' + getJsonErrorText(r));
return r;
}
if (!r.error && r.tree) {
const pages: { [id: number]: Page } = {};
Object.keys(r.referencedObjects).forEach(k => {
const p = r.referencedObjects[k];
if (p) {
pages[p.id] = p;
}
});
await dispatch(
receivePages(
newXcapJsonResult('success', {
pages
})
)
);
await dispatch(receiveSubSites({ subSites: { [r.tree.id]: r.tree } }));
}
return r;
};
}
/**
* Request a missing sub site
* @param id
* @param permalink
*/
export function requestMissingSubSite({
id,
permalink
}: {
id?: number;
permalink?: string;
}): Thunk<Promise<GetSubSiteResult>> {
return async (dispatch: any, getState: any): Promise<GetSubSiteResult> => {
const pages: PagesState = getState().pages;
let subSite: SubSite | null = null;
if (id) {
subSite = pages.subSiteById[id];
} else if (permalink) {
const id = pages.subSiteIdByPermalink[permalink];
if (id) {
subSite = pages.subSiteById[id];
}
} else {
return newXcapJsonErrorResult<GetSubSiteResult>('supply_id_or_permalink');
}
if (subSite) {
return newXcapJsonResult<GetSubSiteResult>('success', {
tree: subSite,
referencedObjects: {}
});
}
return dispatch(requestSubSite({ id, permalink }));
};
}
export function receiveSubSites({ subSites }: { subSites: { [id: number]: SubSite } }): Thunk<PageActions> {
return (dispatch /*, getState: any*/): PageActions => {
return dispatch({
type: RECEIVE_SUB_SITES,
subSites
});
};
}
/**
* Get a page from the store given a permalink
* @param pages
* @param permalink
* @returns {null|Page}
*/
export function getPageByPermalink(pages: PagesState, permalink: string | null): Page | undefined | null {
if (!pages || !permalink) {
return null; // For cache
}
const id = pages.idByPermalink[permalink];
if (id === null) {
return null;
}
if (id) {
return pages.byId[id];
}
return undefined;
}
export const SITE_HASH_PREFIX = '#/site';
/**
* Get a hash permalink to a subsite page. Specify treePath or permalink
* @param treePath
* @param permalink
* @returns {string|null}
*/
export function getSubSitePageHashPermalink({
treePath,
permalink
}: {
treePath?: Array<SubSiteNode> | null;
permalink?: string | null;
}): string | null {
if (treePath) {
return SITE_HASH_PREFIX + getPermalink(treePath);
}
if (permalink) {
return SITE_HASH_PREFIX + permalink;
}
return null;
}
/**
* Given a sub site node hash permalink, extract the real permalink
* @param hashPermalink
* @returns {null}
*/
export function getSubSiteNodePermalink(hashPermalink: string | null): string | null {
if (!hashPermalink) {
return null;
}
const i = hashPermalink.indexOf('#');
if (i === -1) {
return null;
} else if (i > 0) {
hashPermalink = hashPermalink.substring(i);
}
if (!hashPermalink.startsWith(SITE_HASH_PREFIX + '/')) {
return null;
}
return hashPermalink.substr(SITE_HASH_PREFIX.length);
}
/**
* Get a subsite from storage given a permalink
* @param pages
* @param permalink
*/
export function getSubSiteByPermalink(pages: PagesState, permalink: string): SubSite | null {
if (!permalink) {
return null;
}
const id = pages.subSiteIdByPermalink[permalink];
if (!id) {
return null;
}
return pages.subSiteById[id] || null;
}
/**
* Get a node within a subsite given its permalink
* @param subSite
* @param permalink
*/
export const getSubSiteNodeByPermalink = getTreeNodeByPermalink;