Commit 479b3c5f by HoMeTown

feat: 登录的弹窗

parent 4e3355cd
......@@ -6,5 +6,6 @@ export default antfu({
rules: {
'n/prefer-global/process': ['off'], // 关闭process报错
'react-hooks/exhaustive-deps': ['off'],
'ban-ts-comment': ['off'],
},
})
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>蒙版</title>
<defs>
<rect id="path-1" x="0" y="0" width="24" height="24"></rect>
</defs>
<g id="晓得---PC端页面-草稿" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="晓得-PC端---登录" transform="translate(-32.000000, -831.000000)">
<g id="左侧导航栏备份" transform="translate(12.000000, 290.000000)">
<g id="用户" transform="translate(12.000000, 533.000000)">
<g id="设置" transform="translate(8.000000, 8.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="蒙版"></g>
<path d="M18,14 C20.7614237,14 23,16.2385763 23,19 C23,21.7614237 20.7614237,24 18,24 L6,24 C3.23857625,24 1,21.7614237 1,19 C1,16.2385763 3.23857625,14 6,14 L18,14 Z M18,19 L14,19 C13.4477153,19 13,19.4477153 13,20 C13,20.5522847 13.4477153,21 14,21 L14,21 L18,21 C18.5522847,21 19,20.5522847 19,20 C19,19.4477153 18.5522847,19 18,19 L18,19 Z M12.0004016,0 C15.3086139,0 18,2.69156627 18,6 C18,9.30843373 15.3086139,12 12.0004016,12 C8.69138612,12 6,9.30843373 6,6 C6,2.69156627 8.69138612,0 12.0004016,0 Z" id="形状结合" fill="#82969B" fill-rule="nonzero" mask="url(#mask-2)"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
\ No newline at end of file
import React from 'react'
import { Button, Link, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@nextui-org/react'
interface LoginModalProps {
isOpen: boolean
onClose: () => void
}
export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose }) => {
return (
<Modal backdrop="blur" isOpen={isOpen} onClose={onClose}>
<ModalContent>
{onClose => (
<>
<ModalHeader className="flex flex-col gap-1">欢迎您使用晓得AI助手</ModalHeader>
<ModalBody>
<p>
为帮助您更好了解晓得AI 助手服务内容,保障您的合法权益。
</p>
<p>
请您认真阅读
<Link showAnchorIcon underline="hover">《服务协议》</Link>
<Link showAnchorIcon underline="hover">《隐私政策》</Link>
,特别是其中有关使用限制、免责声明、个人信息保护等内容。
</p>
<p>
您需在仔细阅读并确认同意相关协议后方可使用本服务。
</p>
</ModalBody>
<ModalFooter>
<Button onPress={onClose}>
再想想
</Button>
<Button color="primary" onPress={onClose}>
同意
</Button>
</ModalFooter>
</>
)}
</ModalContent>
</Modal>
)
}
import type React from 'react'
import { motion } from 'framer-motion'
import { Button, Tooltip } from '@nextui-org/react'
import { useLocalStorageState, useToggle } from 'ahooks'
import { useEffect, useRef } from 'react'
import styles from './Navbar.module.less'
import { NavBarItem } from './components/NavBarItem'
import Logo from '@/assets/svg/logo.svg?react'
......@@ -7,6 +10,8 @@ import AddNewChat from '@/assets/svg/addNewChat.svg?react'
import HistoryChat from '@/assets/svg/historyChat.svg?react'
import Collect from '@/assets/svg/collect.svg?react'
import Tools from '@/assets/svg/tools.svg?react'
import UserIcon from '@/assets/svg/user.svg?react'
import { LoginModal } from '@/components/LoginModal'
interface NavbarProps {
isCollapsed: boolean
......@@ -21,23 +26,76 @@ const NAV_BAR_ITEMS = [
{ icon: Collect, label: '收藏', key: 'collect' },
{ icon: '', label: '', key: 'line2' },
{ icon: Tools, label: '工具', key: 'tools' },
{ icon: '', label: '', key: 'line3' },
]
// onToggle isCollapsed
export const Navbar: React.FC<NavbarProps> = ({ onToggle }) => {
const handleClick = (type: string) => {
const [token] = useLocalStorageState<string | undefined>('__TOKEN__', {
defaultValue: '',
listenStorageChange: true,
})
const [isOpenLogTip, isOpenLogTipActions] = useToggle()
const [isOpenLoginModal, isOpenLoginModalActions] = useToggle()
const intervalRef = useRef<NodeJS.Timeout | null>(null)
const handleShowLoginTip = () => {
intervalRef.current = setTimeout(() => {
if (!token) {
isOpenLogTipActions.setRight()
}
if (intervalRef.current) {
clearInterval(intervalRef.current)
intervalRef.current = null
}
}, 1000)
}
const handleLogin = () => {
if (!token) {
isOpenLogTipActions.setLeft()
isOpenLoginModalActions.setRight()
return false
}
return true
}
const handleCloseLoginModal = () => {
isOpenLoginModalActions.setLeft()
handleShowLoginTip()
}
const handleClick = (type: string | undefined) => {
if (!handleLogin())
return
if (type === 'history') {
onToggle()
}
}
useEffect(() => {
handleShowLoginTip()
}, [])
return (
<>
<motion.nav className="h-full flex-shrink-0 flex flex-col items-center justify-center">
<motion.div className={`${styles.layoutNavBarAgent} sm:flex hidden w-[64px] bg-white gap-[26px]`}>
<motion.div className={`${styles.layoutNavBarAgent} sm:flex hidden w-[64px] bg-white gap-[24px]`}>
{NAV_BAR_ITEMS.map((item) => {
return (
<NavBarItem onClick={handleClick} icon={item.icon} label={item.label} key={item.key} type={item.key} />
)
})}
<Tooltip isOpen={isOpenLogTip} color="foreground" content="登录体验更多功能" placement="right">
<Button onClick={handleLogin} variant="light" isIconOnly aria-label="Like">
<UserIcon />
</Button>
</Tooltip>
</motion.div>
</motion.nav>
<LoginModal onClose={handleCloseLoginModal} isOpen={isOpenLoginModal} />
</>
)
}
import { clamp } from './math'
import type { Arrayable, Nullable } from './types'
/**
* Convert `Arrayable<T>` to `Array<T>`
*
* @category Array
*/
export function toArray<T>(array?: Nullable<Arrayable<T>>): Array<T> {
array = array ?? []
return Array.isArray(array) ? array : [array]
}
/**
* Convert `Arrayable<T>` to `Array<T>` and flatten it
*
* @category Array
*/
export function flattenArrayable<T>(array?: Nullable<Arrayable<T | Array<T>>>): Array<T> {
return toArray(array).flat(1) as Array<T>
}
/**
* Use rest arguments to merge arrays
*
* @category Array
*/
export function mergeArrayable<T>(...args: Nullable<Arrayable<T>>[]): Array<T> {
return args.flatMap(i => toArray(i))
}
export type PartitionFilter<T> = (i: T, idx: number, arr: readonly T[]) => any
/**
* Divide an array into two parts by a filter function
*
* @category Array
* @example const [odd, even] = partition([1, 2, 3, 4], i => i % 2 != 0)
*/
export function partition<T>(array: readonly T[], f1: PartitionFilter<T>): [T[], T[]]
export function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>): [T[], T[], T[]]
export function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>): [T[], T[], T[], T[]]
export function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>, f4: PartitionFilter<T>): [T[], T[], T[], T[], T[]]
export function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>, f4: PartitionFilter<T>, f5: PartitionFilter<T>): [T[], T[], T[], T[], T[], T[]]
export function partition<T>(array: readonly T[], f1: PartitionFilter<T>, f2: PartitionFilter<T>, f3: PartitionFilter<T>, f4: PartitionFilter<T>, f5: PartitionFilter<T>, f6: PartitionFilter<T>): [T[], T[], T[], T[], T[], T[], T[]]
export function partition<T>(array: readonly T[], ...filters: PartitionFilter<T>[]): any {
const result: T[][] = Array.from({ length: filters.length + 1 }).fill(null).map(() => [])
array.forEach((e, idx, arr) => {
let i = 0
for (const filter of filters) {
if (filter(e, idx, arr)) {
result[i].push(e)
return
}
i += 1
}
result[i].push(e)
})
return result
}
/**
* Unique an Array
*
* @category Array
*/
export function uniq<T>(array: readonly T[]): T[] {
return Array.from(new Set(array))
}
/**
* Unique an Array by a custom equality function
*
* @category Array
*/
export function uniqueBy<T>(array: readonly T[], equalFn: (a: any, b: any) => boolean): T[] {
return array.reduce((acc: T[], cur: any) => {
const index = acc.findIndex((item: any) => equalFn(cur, item))
if (index === -1)
acc.push(cur)
return acc
}, [])
}
/**
* Get last item
*
* @category Array
*/
export function last(array: readonly []): undefined
export function last<T>(array: readonly T[]): T
export function last<T>(array: readonly T[]): T | undefined {
return at(array, -1)
}
/**
* Remove an item from Array
*
* @category Array
*/
export function remove<T>(array: T[], value: T) {
if (!array)
return false
const index = array.indexOf(value)
if (index >= 0) {
array.splice(index, 1)
return true
}
return false
}
/**
* Get nth item of Array. Negative for backward
*
* @category Array
*/
export function at(array: readonly [], index: number): undefined
export function at<T>(array: readonly T[], index: number): T
export function at<T>(array: readonly T[] | [], index: number): T | undefined {
const len = array.length
if (!len)
return undefined
if (index < 0)
index += len
return array[index]
}
/**
* Genrate a range array of numbers. The `stop` is exclusive.
*
* @category Array
*/
export function range(stop: number): number[]
export function range(start: number, stop: number, step?: number): number[]
export function range(...args: any): number[] {
let start: number, stop: number, step: number
if (args.length === 1) {
start = 0
step = 1;
([stop] = args)
}
else {
([start, stop, step = 1] = args)
}
const arr: number[] = []
let current = start
while (current < stop) {
arr.push(current)
current += step || 1
}
return arr
}
/**
* Move element in an Array
*
* @category Array
* @param arr
* @param from
* @param to
*/
export function move<T>(arr: T[], from: number, to: number) {
arr.splice(to, 0, arr.splice(from, 1)[0])
return arr
}
/**
* Clamp a number to the index range of an array
*
* @category Array
*/
export function clampArrayRange(n: number, arr: readonly unknown[]) {
return clamp(n, 0, arr.length - 1)
}
/**
* Get random item(s) from an array
*
* @param arr
* @param quantity - quantity of random items which will be returned
*/
export function sample<T>(arr: T[], quantity: number) {
return Array.from({ length: quantity }, _ => arr[Math.round(Math.random() * (arr.length - 1))])
}
/**
* Shuffle an array. This function mutates the array.
*
* @category Array
*/
export function shuffle<T>(array: T[]): T[] {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]]
}
return array
}
export function assert(condition: boolean, message: string): asserts condition {
if (!condition)
throw new Error(message)
}
export const toString = (v: any) => Object.prototype.toString.call(v)
export function getTypeName(v: any) {
if (v === null)
return 'null'
const type = toString(v).slice(8, -1).toLowerCase()
return (typeof v === 'object' || typeof v === 'function') ? type : typeof v
}
export function noop() {}
import { getTypeName } from './base'
export function isDeepEqual(value1: any, value2: any): boolean {
const type1 = getTypeName(value1)
const type2 = getTypeName(value2)
if (type1 !== type2)
return false
if (type1 === 'array') {
if (value1.length !== value2.length)
return false
return value1.every((item: any, i: number) => {
return isDeepEqual(item, value2[i])
})
}
if (type1 === 'object') {
const keyArr = Object.keys(value1)
if (keyArr.length !== Object.keys(value2).length)
return false
return keyArr.every((key: string) => {
return isDeepEqual(value1[key], value2[key])
})
}
return Object.is(value1, value2)
}
import type { Fn, Nullable } from './types'
/**
* Call every function in an array
*/
export function batchInvoke(functions: Nullable<Fn>[]) {
functions.forEach(fn => fn && fn())
}
/**
* Call the function
*/
export function invoke(fn: Fn) {
return fn()
}
/**
* Pass the value through the callback, and return the value
*
* @example
* ```
* function createUser(name: string): User {
* return tap(new User, user => {
* user.name = name
* })
* }
* ```
*/
export function tap<T>(value: T, callback: (value: T) => void): T {
callback(value)
return value
}
/**
* Type guard to filter out null-ish values
*
* @category Guards
* @example array.filter(notNullish)
*/
export function notNullish<T>(v: T | null | undefined): v is NonNullable<T> {
return v != null
}
/**
* Type guard to filter out null values
*
* @category Guards
* @example array.filter(noNull)
*/
export function noNull<T>(v: T | null): v is Exclude<T, null> {
return v !== null
}
/**
* Type guard to filter out null-ish values
*
* @category Guards
* @example array.filter(notUndefined)
*/
export function notUndefined<T>(v: T): v is Exclude<T, undefined> {
return v !== undefined
}
/**
* Type guard to filter out falsy values
*
* @category Guards
* @example array.filter(isTruthy)
*/
export function isTruthy<T>(v: T): v is NonNullable<T> {
return Boolean(v)
}
export * from './array'
export * from './base'
export * from './equal'
export * from './guards'
export * from './is'
export * from './math'
export * from './string'
export * from './time'
export * from './types'
export * from './function'
export * from './object'
export * from './promise'
import { toString } from './base'
export const isDef = <T = any>(val?: T): val is T => typeof val !== 'undefined'
export const isBoolean = (val: any): val is boolean => typeof val === 'boolean'
// eslint-disable-next-line ts/no-unsafe-function-type
export const isFunction = <T extends Function> (val: any): val is T => typeof val === 'function'
export const isNumber = (val: any): val is number => typeof val === 'number'
export const isString = (val: unknown): val is string => typeof val === 'string'
export const isObject = (val: any): val is object => toString(val) === '[object Object]'
export const isUndefined = (val: any): val is undefined => toString(val) === '[object Undefined]'
export const isNull = (val: any): val is null => toString(val) === '[object Null]'
export const isRegExp = (val: any): val is RegExp => toString(val) === '[object RegExp]'
export const isDate = (val: any): val is Date => toString(val) === '[object Date]'
export const isWindow = (val: any): boolean => typeof window !== 'undefined' && toString(val) === '[object Window]'
export const isBrowser = typeof window !== 'undefined'
import { flattenArrayable } from './array'
export function clamp(n: number, min: number, max: number) {
return Math.min(max, Math.max(min, n))
}
export function sum(...args: number[] | number[][]) {
return flattenArrayable(args).reduce((a, b) => a + b, 0)
}
/**
* Linearly interpolates between `min` and `max` based on `t`
*
* @category Math
* @param min The minimum value
* @param max The maximum value
* @param t The interpolation value clamped between 0 and 1
* @example
* ```
* const value = lerp(0, 2, 0.5) // value will be 1
* ```
*/
export function lerp(min: number, max: number, t: number) {
const interpolation = clamp(t, 0.0, 1.0)
return min + (max - min) * interpolation
}
/**
* Linearly remaps a clamped value from its source range [`inMin`, `inMax`] to the destination range [`outMin`, `outMax`]
*
* @category Math
* @example
* ```
* const value = remap(0.5, 0, 1, 200, 400) // value will be 300
* ```
*/
export function remap(n: number, inMin: number, inMax: number, outMin: number, outMax: number) {
const interpolation = (n - inMin) / (inMax - inMin)
return lerp(outMin, outMax, interpolation)
}
/* eslint-disable ts/ban-ts-comment */
import { notNullish } from './guards'
import { isObject } from './is'
import type { DeepMerge } from './types'
/**
* Map key/value pairs for an object, and construct a new one
*
*
* @category Object
*
* Transform:
* @example
* ```
* objectMap({ a: 1, b: 2 }, (k, v) => [k.toString().toUpperCase(), v.toString()])
* // { A: '1', B: '2' }
* ```
*
* Swap key/value:
* @example
* ```
* objectMap({ a: 1, b: 2 }, (k, v) => [v, k])
* // { 1: 'a', 2: 'b' }
* ```
*
* Filter keys:
* @example
* ```
* objectMap({ a: 1, b: 2 }, (k, v) => k === 'a' ? undefined : [k, v])
* // { b: 2 }
* ```
*/
export function objectMap<K extends string, V, NK extends string | number | symbol = K, NV = V>(obj: Record<K, V>, fn: (key: K, value: V) => [NK, NV] | undefined): Record<NK, NV> {
return Object.fromEntries(
Object.entries(obj)
.map(([k, v]) => fn(k as K, v as V))
.filter(notNullish),
) as Record<NK, NV>
}
/**
* Type guard for any key, `k`.
* Marks `k` as a key of `T` if `k` is in `obj`.
*
* @category Object
* @param obj object to query for key `k`
* @param k key to check existence in `obj`
*/
export function isKeyOf<T extends object>(obj: T, k: keyof any): k is keyof T {
return k in obj
}
/**
* Strict typed `Object.keys`
*
* @category Object
*/
export function objectKeys<T extends object>(obj: T) {
return Object.keys(obj) as Array<`${keyof T & (string | number | boolean | null | undefined)}`>
}
/**
* Strict typed `Object.entries`
*
* @category Object
*/
export function objectEntries<T extends object>(obj: T) {
return Object.entries(obj) as Array<[keyof T, T[keyof T]]>
}
/**
* Deep merge
*
* The first argument is the target object, the rest are the sources.
* The target object will be mutated and returned.
*
* @category Object
*/
export function deepMerge<T extends object = object, S extends object = T>(target: T, ...sources: S[]): DeepMerge<T, S> {
if (!sources.length)
return target as any
const source = sources.shift()
if (source === undefined)
return target as any
if (isMergableObject(target) && isMergableObject(source)) {
objectKeys(source).forEach((key) => {
if (key === '__proto__' || key === 'constructor' || key === 'prototype')
return
// @ts-expect-error
if (isMergableObject(source[key])) {
// @ts-expect-error
if (!target[key])
// @ts-expect-error
target[key] = {}
// @ts-expect-error
if (isMergableObject(target[key])) {
// @ts-expect-error
deepMerge(target[key], source[key])
}
else {
// @ts-expect-error
target[key] = source[key]
}
}
else {
// @ts-expect-error
target[key] = source[key]
}
})
}
return deepMerge(target, ...sources)
}
/**
* Deep merge
*
* Differs from `deepMerge` in that it merges arrays instead of overriding them.
*
* The first argument is the target object, the rest are the sources.
* The target object will be mutated and returned.
*
* @category Object
*/
export function deepMergeWithArray<T extends object = object, S extends object = T>(target: T, ...sources: S[]): DeepMerge<T, S> {
if (!sources.length)
return target as any
const source = sources.shift()
if (source === undefined)
return target as any
if (Array.isArray(target) && Array.isArray(source))
target.push(...source)
if (isMergableObject(target) && isMergableObject(source)) {
objectKeys(source).forEach((key) => {
if (key === '__proto__' || key === 'constructor' || key === 'prototype')
return
// @ts-expect-error
if (Array.isArray(source[key])) {
// @ts-expect-error
if (!target[key])
// @ts-expect-error
target[key] = []
// @ts-expect-error
deepMergeWithArray(target[key], source[key])
}
// @ts-expect-error
else if (isMergableObject(source[key])) {
// @ts-expect-error
if (!target[key])
// @ts-expect-error
target[key] = {}
// @ts-expect-error
deepMergeWithArray(target[key], source[key])
}
else {
// @ts-expect-error
target[key] = source[key]
}
})
}
return deepMergeWithArray(target, ...sources)
}
function isMergableObject(item: any): item is object {
return isObject(item) && !Array.isArray(item)
}
/**
* Create a new subset object by giving keys
*
* @category Object
*/
export function objectPick<O extends object, T extends keyof O>(obj: O, keys: T[], omitUndefined = false) {
return keys.reduce((n, k) => {
if (k in obj) {
if (!omitUndefined || obj[k] !== undefined)
n[k] = obj[k]
}
return n
}, {} as Pick<O, T>)
}
/**
* Clear undefined fields from an object. It mutates the object
*
* @category Object
*/
export function clearUndefined<T extends object>(obj: T): T {
// @ts-expect-error
Object.keys(obj).forEach((key: string) => (obj[key] === undefined ? delete obj[key] : {}))
return obj
}
/**
* Determines whether an object has a property with the specified name
*
* @see https://eslint.org/docs/rules/no-prototype-builtins
* @category Object
*/
export function hasOwnProperty<T>(obj: T, v: PropertyKey) {
if (obj == null)
return false
return Object.prototype.hasOwnProperty.call(obj, v)
}
import { remove } from './array'
import type { Fn } from './types'
export interface SingletonPromiseReturn<T> {
(): Promise<T>
/**
* Reset current staled promise.
* Await it to have proper shutdown.
*/
reset: () => Promise<void>
}
/**
* Create singleton promise function
*
* @category Promise
*/
export function createSingletonPromise<T>(fn: () => Promise<T>): SingletonPromiseReturn<T> {
let _promise: Promise<T> | undefined
function wrapper() {
if (!_promise)
_promise = fn()
return _promise
}
wrapper.reset = async () => {
const _prev = _promise
_promise = undefined
if (_prev)
await _prev
}
return wrapper
}
/**
* Promised `setTimeout`
*
* @category Promise
*/
export function sleep(ms: number, callback?: Fn<any>) {
return new Promise<void>(resolve =>
setTimeout(async () => {
await callback?.()
resolve()
}, ms),
)
}
/**
* Create a promise lock
*
* @category Promise
* @example
* ```
* const lock = createPromiseLock()
*
* lock.run(async () => {
* await doSomething()
* })
*
* // in anther context:
* await lock.wait() // it will wait all tasking finished
* ```
*/
export function createPromiseLock() {
const locks: Promise<any>[] = []
return {
async run<T = void>(fn: () => Promise<T>): Promise<T> {
const p = fn()
locks.push(p)
try {
return await p
}
finally {
remove(locks, p)
}
},
async wait(): Promise<void> {
await Promise.allSettled(locks)
},
isWaiting() {
return Boolean(locks.length)
},
clear() {
locks.length = 0
},
}
}
/**
* Promise with `resolve` and `reject` methods of itself
*/
export interface ControlledPromise<T = void> extends Promise<T> {
resolve: (value: T | PromiseLike<T>) => void
reject: (reason?: any) => void
}
/**
* Return a Promise with `resolve` and `reject` methods
*
* @category Promise
* @example
* ```
* const promise = createControlledPromise()
*
* await promise
*
* // in anther context:
* promise.resolve(data)
* ```
*/
export function createControlledPromise<T>(): ControlledPromise<T> {
let resolve: any, reject: any
const promise = new Promise<T>((_resolve, _reject) => {
resolve = _resolve
reject = _reject
}) as ControlledPromise<T>
promise.resolve = resolve
promise.reject = reject
return promise
}
import { isObject } from './is'
/**
* Replace backslash to slash
*
* @category String
*/
export function slash(str: string) {
return str.replace(/\\/g, '/')
}
/**
* Ensure prefix of a string
*
* @category String
*/
export function ensurePrefix(prefix: string, str: string) {
if (!str.startsWith(prefix))
return prefix + str
return str
}
/**
* Ensure suffix of a string
*
* @category String
*/
export function ensureSuffix(suffix: string, str: string) {
if (!str.endsWith(suffix))
return str + suffix
return str
}
/**
* Dead simple template engine, just like Python's `.format()`
* Support passing variables as either in index based or object/name based approach
* While using object/name based approach, you can pass a fallback value as the third argument
*
* @category String
* @example
* ```
* const result = template(
* 'Hello {0}! My name is {1}.',
* 'Inès',
* 'Anthony'
* ) // Hello Inès! My name is Anthony.
* ```
*
* ```
* const result = namedTemplate(
* '{greet}! My name is {name}.',
* { greet: 'Hello', name: 'Anthony' }
* ) // Hello! My name is Anthony.
* ```
*
* const result = namedTemplate(
* '{greet}! My name is {name}.',
* { greet: 'Hello' }, // name isn't passed hence fallback will be used for name
* 'placeholder'
* ) // Hello! My name is placeholder.
* ```
*/
export function template(str: string, object: Record<string | number, any>, fallback?: string | ((key: string) => string)): string
export function template(str: string, ...args: (string | number | bigint | undefined | null)[]): string
export function template(str: string, ...args: any[]): string {
const [firstArg, fallback] = args
if (isObject(firstArg)) {
const vars = firstArg as Record<string, any>
return str.replace(/\{(\w+)\}/g, (_, key) => vars[key] || ((typeof fallback === 'function' ? fallback(key) : fallback) ?? key))
}
else {
return str.replace(/\{(\d+)\}/g, (_, key) => {
const index = Number(key)
if (Number.isNaN(index))
return key
return args[index]
})
}
}
// port from nanoid
// https://github.com/ai/nanoid
const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
/**
* Generate a random string
* @category String
*/
export function randomStr(size = 16, dict = urlAlphabet) {
let id = ''
let i = size
const len = dict.length
while (i--)
id += dict[(Math.random() * len) | 0]
return id
}
/**
* First letter uppercase, other lowercase
* @category string
* @example
* ```
* capitalize('hello') => 'Hello'
* ```
*/
export function capitalize(str: string): string {
return str[0].toUpperCase() + str.slice(1).toLowerCase()
}
const _reFullWs = /^\s*$/
/**
* Remove common leading whitespace from a template string.
* Will also remove empty lines at the beginning and end.
* @category string
* @example
* ```ts
* const str = unindent`
* if (a) {
* b()
* }
* `
*/
export function unindent(str: TemplateStringsArray | string) {
const lines = (typeof str === 'string' ? str : str[0]).split('\n')
const whitespaceLines = lines.map(line => _reFullWs.test(line))
const commonIndent = lines
.reduce((min, line, idx) => {
if (whitespaceLines[idx])
return min
const indent = line.match(/^\s*/)?.[0].length
return indent === undefined ? min : Math.min(min, indent)
}, Number.POSITIVE_INFINITY)
let emptyLinesHead = 0
while (emptyLinesHead < lines.length && whitespaceLines[emptyLinesHead])
emptyLinesHead++
let emptyLinesTail = 0
while (emptyLinesTail < lines.length && whitespaceLines[lines.length - emptyLinesTail - 1])
emptyLinesTail++
return lines
.slice(emptyLinesHead, lines.length - emptyLinesTail)
.map(line => line.slice(commonIndent))
.join('\n')
}
export const timestamp = () => +Date.now()
/**
* Promise, or maybe not
*/
export type Awaitable<T> = T | PromiseLike<T>
/**
* Null or whatever
*/
export type Nullable<T> = T | null | undefined
/**
* Array, or not yet
*/
export type Arrayable<T> = T | Array<T>
/**
* Function
*/
export type Fn<T = void> = () => T
/**
* Constructor
*/
export type Constructor<T = void> = new (...args: any[]) => T
/**
* Infers the element type of an array
*/
export type ElementOf<T> = T extends (infer E)[] ? E : never
/**
* Defines an intersection type of all union items.
*
* @param U Union of any types that will be intersected.
* @returns U items intersected
* @see https://stackoverflow.com/a/50375286/9259330
*/
export type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
/**
* Infers the arguments type of a function
*/
export type ArgumentsType<T> = T extends ((...args: infer A) => any) ? A : never
export type MergeInsertions<T> =
T extends object
? { [K in keyof T]: MergeInsertions<T[K]> }
: T
export type DeepMerge<F, S> = MergeInsertions<{
[K in keyof F | keyof S]: K extends keyof S & keyof F
? DeepMerge<F[K], S[K]>
: K extends keyof S
? S[K]
: K extends keyof F
? F[K]
: never;
}>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment