@wepublish/editor
Version:
We.publish CMS Editor
230 lines (192 loc) • 5.96 kB
text/typescript
import {ApolloClient, ApolloLink, createHttpLink, InMemoryCache} from '@apollo/client'
import {PaymentPeriodicity, SortOrder} from '@wepublish/editor/api'
import {DocumentNode, OperationDefinitionNode} from 'graphql'
import nanoid from 'nanoid'
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {ElementID} from '../shared/elementID'
import {ClientSettings} from '../shared/types'
export enum LocalStorageKey {
SessionToken = 'sessionToken'
}
export const addOrUpdateOneInArray = (
array: Record<string | 'id', any>[] | null | undefined,
entry: Record<string | 'id', any>
) => {
let isNew = true
if (!array) {
return [entry]
}
const updated = array.map(item => {
if (item.id !== entry.id) {
// This isn't the item we care about - keep it as-is
return item
}
isNew = false
// Otherwise, this is the one we want - return an updated value
return {
...item,
...entry
}
})
if (isNew) {
return [...updated, entry]
}
return updated
}
export function generateID(): string {
return nanoid()
}
export function useScript(src: string, checkIfLoaded: () => boolean, crossOrigin = false) {
const scriptRef = useRef<HTMLScriptElement | null>(null)
const [isLoading, setLoading] = useState(false)
const [isLoaded, setLoaded] = useState(() => checkIfLoaded())
useEffect(() => {
if (isLoading && !isLoaded && !scriptRef.current) {
const script = document.createElement('script')
script.src = src
script.async = true
script.defer = true
script.onload = () => setLoaded(true)
script.crossOrigin = crossOrigin ? 'anonymous' : null
document.head.appendChild(script)
scriptRef.current = script
}
}, [isLoading, crossOrigin, src, isLoaded])
const load = useCallback(() => {
setLoading(true)
}, [])
const result = useMemo(
() => ({
isLoading,
isLoaded,
load
}),
[isLoading, isLoaded, load]
)
if (typeof window !== 'object') {
return {
isLoaded: false,
isLoading: false,
load: () => {
/* do nothing */
}
}
}
return result
}
export function getOperationNameFromDocument(node: DocumentNode) {
const firstOperation = node.definitions.find(
node => node.kind === 'OperationDefinition'
) as OperationDefinitionNode
if (!firstOperation?.name?.value) throw new Error("Coulnd't find operation name.")
return firstOperation.name.value
}
export function transformCssStringToObject(styleCustom: string): Record<string, unknown> {
const styleRules = styleCustom.split(';')
if (styleRules.length === 0) return {}
return styleRules.reduce((previousValue: Record<string, unknown>, currentValue: string) => {
const [key, value] = currentValue.split(':')
if (key && value) {
return Object.assign(previousValue, {[key.trim()]: value.trim()})
}
return previousValue
}, {})
}
export type SortType = 'asc' | 'desc' | null
export function mapTableSortTypeToGraphQLSortOrder(sortType: SortType): SortOrder | null {
switch (sortType) {
case 'desc':
return SortOrder.Descending
case 'asc':
return SortOrder.Ascending
default:
return null
}
}
export const DEFAULT_TABLE_PAGE_SIZES = [10, 20, 50, 100]
export const DEFAULT_TABLE_IMAGE_PAGE_SIZES = [5, 10, 15]
export const DEFAULT_MAX_TABLE_PAGES = 5
export const ALL_PAYMENT_PERIODICITIES: PaymentPeriodicity[] = [
PaymentPeriodicity.Monthly,
PaymentPeriodicity.Quarterly,
PaymentPeriodicity.Biannual,
PaymentPeriodicity.Yearly
]
export enum StateColor {
pending = '#f8def2',
published = '#e1f8de',
draft = '#f8efde',
none = 'white'
}
export function validateURL(url: string) {
if (url) {
const pattern = new RegExp(
'^(https?:\\/\\/)?' + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
'(\\#[-a-z\\d_]*)?$',
'i'
)
return pattern.test(url)
}
return false
}
export function flattenDOMTokenList(list: DOMTokenList) {
let string = ''
for (const element of Array.from(list)) {
string = `${string} ${element}`
}
return string.substring(1)
}
export function getSettings(): ClientSettings {
const defaultSettings = {
apiURL: 'http://localhost:4000',
peerByDefault: false,
imgMinSizeToCompress: 10
}
const settingsJson = document.getElementById(ElementID.Settings)
return settingsJson
? JSON.parse(document.getElementById(ElementID.Settings)!.textContent!)
: defaultSettings
}
const authLink = new ApolloLink((operation, forward) => {
const token = localStorage.getItem(LocalStorageKey.SessionToken)
const context = operation.getContext()
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : '',
...context.headers
},
credentials: 'include',
...context
})
return forward(operation)
})
export function getApiClientV2() {
const {apiURL} = getSettings()
return new ApolloClient({
link: authLink.concat(createHttpLink({uri: `${apiURL}/v2`, fetch})),
cache: new InMemoryCache()
})
}
/**
* Helper function to read env variable IMG_MIN_SIZE_TO_COMPRESS
*/
export function getImgMinSizeToCompress(): number {
const {imgMinSizeToCompress} = getSettings()
return imgMinSizeToCompress
}
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never
export type ValueConstructor<T> = T | (() => T)
export function isValueConstructor<T>(value: T | (() => T)): value is () => T {
return typeof value === 'function'
}
export function isFunctionalUpdate<T>(value: React.SetStateAction<T>): value is (value: T) => T {
return typeof value === 'function'
}