mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
init: the first public commit for AFFiNE
This commit is contained in:
10
libs/utils/src/constants.ts
Normal file
10
libs/utils/src/constants.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const AFFINE_MAIN_SERVER = '/api/v1';
|
||||
// export const AFFINE_MAIN_SERVER = 'http://localhost:3001/api/v1';
|
||||
|
||||
export const AUTHING_APP_ID_CN = '61961a7b8b90af37e7c422e0';
|
||||
export const AUTHING_APP_ID_US = '626941ad32dea590043903d8';
|
||||
export const AUTHING_APP_HOST_US = 'https://affine.us.authing.co';
|
||||
|
||||
export const USER_AUTH_LOCAL_KEY = '_authing_token';
|
||||
export const LOGOUT_LOCAL_STORAGE = [USER_AUTH_LOCAL_KEY, '_authing_user'];
|
||||
export const LOGOUT_COOKIES = ['authing_session'];
|
||||
3
libs/utils/src/copy-to-clipboard.ts
Normal file
3
libs/utils/src/copy-to-clipboard.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import copy from 'copy-to-clipboard';
|
||||
|
||||
export const copyToClipboard = copy;
|
||||
14
libs/utils/src/date.ts
Normal file
14
libs/utils/src/date.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Get the current time in ISO format, the return value example is 2021-11-17T15:45:56, the return value can be directly used to create a Date object
|
||||
*/
|
||||
export function getDateIsoStringWithTimezone(dateMilliseconds?: number) {
|
||||
if (dateMilliseconds === undefined || dateMilliseconds === null) {
|
||||
dateMilliseconds = Date.now();
|
||||
}
|
||||
//offset in milliseconds
|
||||
const timezoneOffset = new Date().getTimezoneOffset() * 60000;
|
||||
const date_iso_string = new Date(
|
||||
dateMilliseconds - timezoneOffset
|
||||
).toISOString();
|
||||
return date_iso_string.slice(0, -5);
|
||||
}
|
||||
124
libs/utils/src/di/di.ts
Normal file
124
libs/utils/src/di/di.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
type Token = any;
|
||||
|
||||
interface DependencyConfig {
|
||||
token: Token;
|
||||
lazy?: boolean;
|
||||
}
|
||||
|
||||
export interface Value<T> {
|
||||
value: T;
|
||||
}
|
||||
|
||||
export interface DependencyCallOrConstructProps {
|
||||
getDependency: <R, T = any>(
|
||||
token: T | (new (...p: any[]) => T)
|
||||
) => R extends Value<infer A> ? A : T;
|
||||
}
|
||||
|
||||
interface DependencyCallOrConstruct {
|
||||
new (props: DependencyCallOrConstructProps): any;
|
||||
(props: DependencyCallOrConstructProps): any;
|
||||
}
|
||||
|
||||
export interface RegisterDependencyConfig {
|
||||
type: 'value' | 'function' | 'class';
|
||||
value: DependencyCallOrConstruct | any;
|
||||
dependencies?: DependencyConfig[];
|
||||
/**
|
||||
* The unique identifier of the injection, if not passed, the value is equal to value
|
||||
*/
|
||||
token: Token;
|
||||
}
|
||||
|
||||
export class DiContainer {
|
||||
private dependency_config = new Map<Token, RegisterDependencyConfig>();
|
||||
private dependency_map = new Map<Token, any>();
|
||||
private dependency_status = new Map<Token, 'loading' | 'loaded'>();
|
||||
|
||||
private get_create_params(
|
||||
config: RegisterDependencyConfig
|
||||
): DependencyCallOrConstructProps {
|
||||
const get_dependency: DependencyCallOrConstructProps['getDependency'] =
|
||||
token => {
|
||||
const found = config.dependencies?.find(
|
||||
dep => dep.token === token
|
||||
);
|
||||
if (!found) {
|
||||
return null;
|
||||
}
|
||||
if (found.lazy) {
|
||||
this.load(found.token);
|
||||
}
|
||||
return this.dependency_map.get(token);
|
||||
};
|
||||
|
||||
return {
|
||||
getDependency: get_dependency,
|
||||
};
|
||||
}
|
||||
|
||||
private load(token: Token) {
|
||||
if (this.dependency_map.has(token)) {
|
||||
return;
|
||||
}
|
||||
if (this.dependency_status.get(token) === 'loading') {
|
||||
throw new Error('Circular dependency', token);
|
||||
}
|
||||
this.dependency_status.set(token, 'loading');
|
||||
const config = this.dependency_config.get(token);
|
||||
if (!config) {
|
||||
throw new Error('No dependencies found', token);
|
||||
}
|
||||
switch (config.type) {
|
||||
case 'value': {
|
||||
this.dependency_map.set(token, config.value);
|
||||
break;
|
||||
}
|
||||
case 'class': {
|
||||
const deps = config.dependencies || [];
|
||||
deps.forEach(dep => {
|
||||
if (!dep.lazy && !this.dependency_map.has(dep.token)) {
|
||||
this.load(dep.token);
|
||||
}
|
||||
});
|
||||
this.dependency_map.set(
|
||||
token,
|
||||
new config.value(this.get_create_params(config))
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'function': {
|
||||
const deps = config.dependencies || [];
|
||||
deps.forEach(dep => {
|
||||
if (!dep.lazy && !this.dependency_map.has(dep.token)) {
|
||||
this.load(dep.token);
|
||||
}
|
||||
});
|
||||
this.dependency_map.set(
|
||||
token,
|
||||
config.value(this.get_create_params(config))
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.error(
|
||||
'Unknown type of dependency',
|
||||
config.token,
|
||||
config.type
|
||||
);
|
||||
}
|
||||
}
|
||||
this.dependency_status.set(token, 'loaded');
|
||||
}
|
||||
|
||||
register(dependencyConfigs: RegisterDependencyConfig[]) {
|
||||
dependencyConfigs.forEach(config => {
|
||||
this.dependency_config.set(config.token, config);
|
||||
});
|
||||
}
|
||||
|
||||
getDependency: DependencyCallOrConstructProps['getDependency'] = token => {
|
||||
this.load(token);
|
||||
return this.dependency_map.get(token);
|
||||
};
|
||||
}
|
||||
6
libs/utils/src/di/index.ts
Normal file
6
libs/utils/src/di/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export { DiContainer } from './di';
|
||||
export type {
|
||||
Value,
|
||||
DependencyCallOrConstructProps,
|
||||
RegisterDependencyConfig,
|
||||
} from './di';
|
||||
112
libs/utils/src/dom.ts
Normal file
112
libs/utils/src/dom.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Get the position information of the dom element based on ClientRect
|
||||
* @param {HTMLElement} dom
|
||||
* @param {HTMLElement=} parent
|
||||
* @returns
|
||||
*/
|
||||
export function getBoundingOffsetRect(
|
||||
dom: HTMLElement | Element,
|
||||
parent?: HTMLElement
|
||||
): DOMRect {
|
||||
const dom_rect = dom.getBoundingClientRect();
|
||||
if (!parent) {
|
||||
return dom_rect;
|
||||
}
|
||||
const parent_rect = parent.getBoundingClientRect();
|
||||
const x = dom_rect.x - parent_rect.x;
|
||||
const y = dom_rect.y - parent_rect.y;
|
||||
const rect = {
|
||||
width: dom_rect.width,
|
||||
height: dom_rect.height,
|
||||
x,
|
||||
y,
|
||||
left: x,
|
||||
top: y,
|
||||
right: x + dom_rect.width,
|
||||
bottom: y + dom_rect.height,
|
||||
};
|
||||
return {
|
||||
...rect,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
toJSON() {
|
||||
return rect;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* get block`s id from element or node
|
||||
* @export
|
||||
* @param {(HTMLElement | Node)} node
|
||||
* @return {*}
|
||||
*/
|
||||
export function getBlockIdByDom(node: HTMLElement | Node): string | undefined {
|
||||
let element;
|
||||
if (node instanceof HTMLElement) {
|
||||
element = node;
|
||||
}
|
||||
if (node instanceof Node) {
|
||||
element = node.parentElement;
|
||||
}
|
||||
return element
|
||||
?.closest('[data-block-id]')
|
||||
?.attributes.getNamedItem('data-block-id')?.value;
|
||||
}
|
||||
|
||||
export function removeDomPaddingFromDomRect(
|
||||
dom: HTMLElement | Element,
|
||||
rect: DOMRect
|
||||
): DOMRect {
|
||||
const client_rect = {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
right: rect.right,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
};
|
||||
const computed_style = window.getComputedStyle(dom);
|
||||
const padding_left = parseInt(computed_style.paddingLeft);
|
||||
const padding_right = parseInt(computed_style.paddingRight);
|
||||
const padding_top = parseInt(computed_style.paddingTop);
|
||||
const padding_bottom = parseInt(computed_style.paddingBottom);
|
||||
|
||||
if (client_rect) {
|
||||
client_rect.left += padding_left;
|
||||
client_rect.right -= padding_right;
|
||||
client_rect.top += padding_top;
|
||||
client_rect.bottom -= padding_bottom;
|
||||
client_rect.x = client_rect.left;
|
||||
client_rect.y = client_rect.top;
|
||||
client_rect.width = client_rect.right - client_rect.left;
|
||||
client_rect.height = client_rect.bottom - client_rect.top;
|
||||
}
|
||||
return {
|
||||
...client_rect,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
toJSON() {
|
||||
return client_rect;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const withUnit = (text: string | number, unit = 'px') => {
|
||||
return `${text}${unit}`;
|
||||
};
|
||||
|
||||
export const withPx = (text: string | number) => withUnit(text, 'px');
|
||||
|
||||
export const ownerDocument = (node: Node | null | undefined): Document => {
|
||||
return (node && node.ownerDocument) || document;
|
||||
};
|
||||
|
||||
export const getBlockIdByNode = (node: Node) => {
|
||||
return (
|
||||
node.parentElement
|
||||
?.closest('[data-block-id]')
|
||||
?.attributes.getNamedItem('data-block-id')?.value ?? undefined
|
||||
);
|
||||
};
|
||||
1
libs/utils/src/env.ts
Normal file
1
libs/utils/src/env.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const isDev = process.env['NODE_ENV'] === 'development';
|
||||
177
libs/utils/src/error-boundary.tsx
Normal file
177
libs/utils/src/error-boundary.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const changedArray = (a: Array<unknown> = [], b: Array<unknown> = []) =>
|
||||
a.length !== b.length ||
|
||||
a.some((item, index) => !Object.is(item, b[index]));
|
||||
|
||||
interface FallbackProps {
|
||||
error: Error;
|
||||
resetErrorBoundary: (...args: Array<unknown>) => void;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryPropsWithComponent {
|
||||
onResetKeysChange?: (
|
||||
prevResetKeys: Array<unknown> | undefined,
|
||||
resetKeys: Array<unknown> | undefined
|
||||
) => void;
|
||||
onReset?: (...args: Array<unknown>) => void;
|
||||
onError?: (error: Error, info: { componentStack: string }) => void;
|
||||
resetKeys?: Array<unknown>;
|
||||
fallback?: never;
|
||||
FallbackComponent: React.ComponentType<FallbackProps>;
|
||||
fallbackRender?: never;
|
||||
}
|
||||
|
||||
declare function FallbackRender(
|
||||
props: FallbackProps
|
||||
): React.ReactElement<
|
||||
unknown,
|
||||
string | React.FunctionComponent | typeof React.Component
|
||||
> | null;
|
||||
|
||||
interface ErrorBoundaryPropsWithRender {
|
||||
onResetKeysChange?: (
|
||||
prevResetKeys: Array<unknown> | undefined,
|
||||
resetKeys: Array<unknown> | undefined
|
||||
) => void;
|
||||
onReset?: (...args: Array<unknown>) => void;
|
||||
onError?: (error: Error, info: { componentStack: string }) => void;
|
||||
resetKeys?: Array<unknown>;
|
||||
fallback?: never;
|
||||
FallbackComponent?: never;
|
||||
fallbackRender: typeof FallbackRender;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryPropsWithFallback {
|
||||
onResetKeysChange?: (
|
||||
prevResetKeys: Array<unknown> | undefined,
|
||||
resetKeys: Array<unknown> | undefined
|
||||
) => void;
|
||||
onReset?: (...args: Array<unknown>) => void;
|
||||
onError?: (error: Error, info: { componentStack: string }) => void;
|
||||
resetKeys?: Array<unknown>;
|
||||
fallback: React.ReactElement<
|
||||
unknown,
|
||||
string | React.FunctionComponent | typeof React.Component
|
||||
> | null;
|
||||
FallbackComponent?: never;
|
||||
fallbackRender?: never;
|
||||
}
|
||||
|
||||
type ErrorBoundaryProps =
|
||||
| ErrorBoundaryPropsWithFallback
|
||||
| ErrorBoundaryPropsWithComponent
|
||||
| ErrorBoundaryPropsWithRender;
|
||||
|
||||
type ErrorBoundaryState = { error: Error | null };
|
||||
|
||||
const initialState: ErrorBoundaryState = { error: null };
|
||||
|
||||
class ErrorBoundary extends React.Component<
|
||||
React.PropsWithRef<React.PropsWithChildren<ErrorBoundaryProps>>,
|
||||
ErrorBoundaryState
|
||||
> {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
return { error };
|
||||
}
|
||||
|
||||
override state = initialState;
|
||||
resetErrorBoundary = (...args: Array<unknown>) => {
|
||||
this.props.onReset?.(...args);
|
||||
this.reset();
|
||||
};
|
||||
|
||||
reset() {
|
||||
this.setState(initialState);
|
||||
}
|
||||
|
||||
override componentDidCatch(error: Error, info: React.ErrorInfo) {
|
||||
this.props.onError?.(error, info);
|
||||
}
|
||||
|
||||
override componentDidUpdate(
|
||||
prevProps: ErrorBoundaryProps,
|
||||
prevState: ErrorBoundaryState
|
||||
) {
|
||||
const { error } = this.state;
|
||||
const { resetKeys } = this.props;
|
||||
|
||||
// There's an edge case where if the thing that triggered the error
|
||||
// happens to *also* be in the resetKeys array, we'd end up resetting
|
||||
// the error boundary immediately. This would likely trigger a second
|
||||
// error to be thrown.
|
||||
// So we make sure that we don't check the resetKeys on the first call
|
||||
// of cDU after the error is set
|
||||
|
||||
if (
|
||||
error !== null &&
|
||||
prevState.error !== null &&
|
||||
changedArray(prevProps.resetKeys, resetKeys)
|
||||
) {
|
||||
this.props.onResetKeysChange?.(prevProps.resetKeys, resetKeys);
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
override render() {
|
||||
const { error } = this.state;
|
||||
|
||||
const { fallbackRender, FallbackComponent, fallback } = this.props;
|
||||
|
||||
if (error !== null) {
|
||||
const props = {
|
||||
error,
|
||||
resetErrorBoundary: this.resetErrorBoundary,
|
||||
};
|
||||
if (React.isValidElement(fallback)) {
|
||||
return fallback;
|
||||
} else if (typeof fallbackRender === 'function') {
|
||||
return fallbackRender(props);
|
||||
} else if (FallbackComponent) {
|
||||
return <FallbackComponent {...props} />;
|
||||
} else {
|
||||
throw new Error(
|
||||
'react-error-boundary requires either a fallback, fallbackRender, or FallbackComponent prop'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
function withErrorBoundary<P>(
|
||||
Component: React.ComponentType<P>,
|
||||
errorBoundaryProps: ErrorBoundaryProps
|
||||
): React.ComponentType<P> {
|
||||
const Wrapped: React.ComponentType<P> = props => {
|
||||
return (
|
||||
<ErrorBoundary {...errorBoundaryProps}>
|
||||
<Component {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
// Format for display in DevTools
|
||||
const name = Component.displayName || Component.name || 'Unknown';
|
||||
Wrapped.displayName = `withErrorBoundary(${name})`;
|
||||
|
||||
return Wrapped;
|
||||
}
|
||||
|
||||
function useErrorHandler(givenError?: unknown): (error: unknown) => void {
|
||||
const [error, setError] = React.useState<unknown>(null);
|
||||
if (givenError != null) throw givenError;
|
||||
if (error != null) throw error;
|
||||
return setError;
|
||||
}
|
||||
|
||||
export { ErrorBoundary, withErrorBoundary, useErrorHandler };
|
||||
export type {
|
||||
FallbackProps,
|
||||
ErrorBoundaryPropsWithComponent,
|
||||
ErrorBoundaryPropsWithRender,
|
||||
ErrorBoundaryPropsWithFallback,
|
||||
ErrorBoundaryProps,
|
||||
};
|
||||
17
libs/utils/src/function.ts
Normal file
17
libs/utils/src/function.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { log } from './logger';
|
||||
|
||||
interface CreateNoopWithMessageProps {
|
||||
module?: string;
|
||||
message: string;
|
||||
level?: 'info' | 'warn' | 'error';
|
||||
}
|
||||
|
||||
export function createNoopWithMessage(
|
||||
props: CreateNoopWithMessageProps
|
||||
): (...p: any[]) => any {
|
||||
const { message, level = 'info', module } = props;
|
||||
const module_log = module ? log.get(module) : log;
|
||||
return () => {
|
||||
module_log[level](message);
|
||||
};
|
||||
}
|
||||
35
libs/utils/src/index.ts
Normal file
35
libs/utils/src/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export * from './types';
|
||||
|
||||
export * from './constants';
|
||||
|
||||
export * from './error-boundary';
|
||||
|
||||
export * from './date';
|
||||
|
||||
export * from './utils';
|
||||
|
||||
export * from './dom';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
export * from './lodash';
|
||||
|
||||
export * from './rect';
|
||||
|
||||
export * from './keyboard';
|
||||
|
||||
export * from './useragent';
|
||||
|
||||
export { isDev } from './env';
|
||||
|
||||
export { log } from './logger';
|
||||
|
||||
export { createNoopWithMessage } from './function';
|
||||
|
||||
export { DiContainer } from './di';
|
||||
export type {
|
||||
Value as DiValue,
|
||||
DependencyCallOrConstructProps,
|
||||
RegisterDependencyConfig,
|
||||
} from './di';
|
||||
|
||||
export { copyToClipboard } from './copy-to-clipboard';
|
||||
20
libs/utils/src/keyboard.ts
Normal file
20
libs/utils/src/keyboard.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { KeyboardEvent } from 'react';
|
||||
|
||||
class KeyboardHelper {
|
||||
public eventHelper: EventHelper;
|
||||
|
||||
constructor() {
|
||||
this.eventHelper = new EventHelper();
|
||||
}
|
||||
}
|
||||
|
||||
class EventHelper {
|
||||
isSelectAll(event: KeyboardEvent) {
|
||||
if ((event.ctrlKey || event.metaKey) && event.code === 'KeyA') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const keyboardHelper = new KeyboardHelper();
|
||||
24
libs/utils/src/lodash.ts
Normal file
24
libs/utils/src/lodash.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
export {
|
||||
debounce,
|
||||
throttle,
|
||||
cloneDeep,
|
||||
isEqual,
|
||||
sum,
|
||||
has,
|
||||
forEach,
|
||||
upperFirst,
|
||||
curry,
|
||||
isFunction,
|
||||
isString,
|
||||
isPlainObject,
|
||||
noop,
|
||||
lowerFirst,
|
||||
last,
|
||||
first,
|
||||
without,
|
||||
difference,
|
||||
escape,
|
||||
countBy,
|
||||
maxBy,
|
||||
} from 'lodash-es';
|
||||
4
libs/utils/src/logger.ts
Normal file
4
libs/utils/src/logger.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import Logger from 'js-logger';
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
Logger.useDefaults();
|
||||
export const log = Logger;
|
||||
172
libs/utils/src/rect.ts
Normal file
172
libs/utils/src/rect.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
export class Point {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public equals({ x, y }: Point) {
|
||||
return this.x === x && this.y === y;
|
||||
}
|
||||
|
||||
public xDistance({ x }: Point) {
|
||||
return this.x - x;
|
||||
}
|
||||
|
||||
public yDistance({ y }: Point) {
|
||||
return this.y - y;
|
||||
}
|
||||
|
||||
public xDistanceAbsolute(point: Point) {
|
||||
return Math.abs(this.xDistance(point));
|
||||
}
|
||||
|
||||
public yDistanceAbsolute(point: Point) {
|
||||
return Math.abs(this.yDistance(point));
|
||||
}
|
||||
|
||||
public distance(point: Point) {
|
||||
return Math.sqrt(
|
||||
Math.pow(this.xDistance(point), 2) +
|
||||
Math.pow(this.yDistance(point), 2)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Rect {
|
||||
private _left: number;
|
||||
private _top: number;
|
||||
private _right: number;
|
||||
private _bottom: number;
|
||||
|
||||
constructor(left: number, top: number, right: number, bottom: number) {
|
||||
const [physicTop, physicBottom] =
|
||||
top <= bottom ? [top, bottom] : [bottom, top];
|
||||
const [physicLeft, physicRight] =
|
||||
left <= right ? [left, right] : [right, left];
|
||||
this._top = physicTop;
|
||||
this._right = physicRight;
|
||||
this._left = physicLeft;
|
||||
this._bottom = physicBottom;
|
||||
}
|
||||
|
||||
get width() {
|
||||
return Math.abs(this._left - this._right);
|
||||
}
|
||||
|
||||
get height() {
|
||||
return Math.abs(this._bottom - this._top);
|
||||
}
|
||||
|
||||
get top() {
|
||||
return this._top;
|
||||
}
|
||||
|
||||
get left() {
|
||||
return this._left;
|
||||
}
|
||||
|
||||
get bottom() {
|
||||
return this._bottom;
|
||||
}
|
||||
|
||||
get right() {
|
||||
return this._right;
|
||||
}
|
||||
|
||||
equals({ top, left, bottom, right }: Rect) {
|
||||
return (
|
||||
top === this._top &&
|
||||
bottom === this._bottom &&
|
||||
left === this._left &&
|
||||
right === this._right
|
||||
);
|
||||
}
|
||||
|
||||
isContainPoint({ x, y }: Point) {
|
||||
return (
|
||||
y >= this._top &&
|
||||
y <= this._bottom &&
|
||||
x >= this.left &&
|
||||
x <= this._right
|
||||
);
|
||||
}
|
||||
|
||||
isContain({ top, left, bottom, right }: Rect) {
|
||||
return (
|
||||
top >= this._top &&
|
||||
top <= this._bottom &&
|
||||
bottom >= this._top &&
|
||||
bottom <= this._bottom &&
|
||||
left >= this._left &&
|
||||
left <= this.right &&
|
||||
right >= this.left &&
|
||||
right <= this._right
|
||||
);
|
||||
}
|
||||
|
||||
isIntersect(rect: Rect) {
|
||||
const { left: x1, top: y1, width: w1, height: h1 } = rect;
|
||||
const { left: x2, top: y2, width: w2, height: h2 } = this;
|
||||
const maxX = x1 + w1 >= x2 + w2 ? x1 + w1 : x2 + w2;
|
||||
const maxY = y1 + h1 >= y2 + h2 ? y1 + h1 : y2 + h2;
|
||||
const minX = x1 <= x2 ? x1 : x2;
|
||||
const minY = y1 <= y2 ? y1 : y2;
|
||||
if (maxX - minX <= w1 + w2 && maxY - minY <= h1 + h2) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
isPointDown({ y }: Point) {
|
||||
return this.bottom < y;
|
||||
}
|
||||
|
||||
isPointUp({ y }: Point) {
|
||||
return y < this.top;
|
||||
}
|
||||
|
||||
isPointLeft({ x }: Point) {
|
||||
return x < this.left;
|
||||
}
|
||||
|
||||
isPointRight({ x }: Point) {
|
||||
return x > this.right;
|
||||
}
|
||||
|
||||
fromNewLeft(left: number) {
|
||||
this._left = left;
|
||||
return new Rect(left, this.top, this.right, this.bottom);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
static fromLTRB(left: number, top: number, right: number, bottom: number) {
|
||||
return new Rect(left, top, right, bottom);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
static fromLWTH(left: number, width: number, top: number, height: number) {
|
||||
return new Rect(left, top, left + width, top + height);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
static fromPoints(startPoint: Point, endPoint: Point) {
|
||||
const { y: top, x: left } = startPoint;
|
||||
const { y: bottom, x: right } = endPoint;
|
||||
return Rect.fromLTRB(left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
export const pointsToRect = (startPoint: Point, endPoint: Point) => {
|
||||
const { y: top, x: left } = startPoint;
|
||||
const { y: bottom, x: right } = endPoint;
|
||||
return Rect.fromLTRB(left, top, right, bottom);
|
||||
};
|
||||
|
||||
export const domToRect = (dom: HTMLElement) => {
|
||||
const { top, width, left, height } = dom.getBoundingClientRect();
|
||||
return Rect.fromLWTH(left, width, top, height);
|
||||
};
|
||||
2
libs/utils/src/types/index.ts
Normal file
2
libs/utils/src/types/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './user-workspace';
|
||||
export * from './tools';
|
||||
32
libs/utils/src/types/tools.ts
Normal file
32
libs/utils/src/types/tools.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* get all value`s type of a object
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // except: type Values = 1 | '2'
|
||||
* type Values = ValueOf<{a: 1, b: '2'}>
|
||||
* ```
|
||||
*/
|
||||
export type ValueOf<X extends Record<string, unknown>> = X[keyof X];
|
||||
|
||||
/**
|
||||
* get type of a array by index
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // except: type FirstType = string;
|
||||
* type FirstType = First<[string], 0>
|
||||
* ```
|
||||
*/
|
||||
export type IndexOf<X extends unknown[], I extends number> = X[I];
|
||||
|
||||
/**
|
||||
* get first type of a array
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // except: type FirstType = string;
|
||||
* type FirstType = First<[string]>
|
||||
* ```
|
||||
*/
|
||||
export type First<X extends unknown[]> = IndexOf<X, 0>;
|
||||
8
libs/utils/src/types/user-workspace.ts
Normal file
8
libs/utils/src/types/user-workspace.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/** user model for Affine */
|
||||
export type UserInfo = {
|
||||
id: string;
|
||||
nickname: string;
|
||||
username: string;
|
||||
email: string;
|
||||
photo: string;
|
||||
};
|
||||
74
libs/utils/src/useragent.ts
Normal file
74
libs/utils/src/useragent.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
const get_ua = () => {
|
||||
const ua = navigator.userAgent;
|
||||
const uas = ua.toLowerCase();
|
||||
const mobile =
|
||||
/mobile|tablet|ip(ad|hone|od)|android/i.test(ua) ||
|
||||
(uas.indexOf('safari') > -1 &&
|
||||
/Mac OS/i.test(ua) &&
|
||||
/Macintosh/i.test(ua));
|
||||
const android =
|
||||
(mobile &&
|
||||
(uas.indexOf('android') > -1 || uas.indexOf('linux') > -1)) ||
|
||||
uas.indexOf('adr') > -1;
|
||||
const ios = mobile && !android && /Mac OS/i.test(ua);
|
||||
const iphone = ios && uas.indexOf('iphone') > -1;
|
||||
const ipad = ios && !iphone;
|
||||
const wx = /MicroMessenger/i.test(ua);
|
||||
const chrome = /CriOS/i.test(ua) || /Chrome/i.test(ua);
|
||||
const tiktok = mobile && /aweme/i.test(ua);
|
||||
const weibo = mobile && /Weibo/i.test(ua);
|
||||
const safari =
|
||||
ios &&
|
||||
!chrome &&
|
||||
!wx &&
|
||||
!weibo &&
|
||||
!tiktok &&
|
||||
/Safari|Macintosh/i.test(ua);
|
||||
const firefox = /Firefox/.test(ua);
|
||||
const win = /windows|win32|win64|wow32|wow64/.test(uas);
|
||||
const linux = /linux/.test(uas);
|
||||
return {
|
||||
ua,
|
||||
mobile,
|
||||
android,
|
||||
ios,
|
||||
wx,
|
||||
chrome,
|
||||
iphone,
|
||||
ipad,
|
||||
safari,
|
||||
tiktok,
|
||||
weibo,
|
||||
win,
|
||||
linux,
|
||||
firefox,
|
||||
};
|
||||
};
|
||||
|
||||
class UaHelper {
|
||||
private ua_map;
|
||||
public isLinux = false;
|
||||
public isMacOs = false;
|
||||
public isSafari = false;
|
||||
public isWindows = false;
|
||||
public isFireFox = false;
|
||||
|
||||
constructor() {
|
||||
this.ua_map = get_ua();
|
||||
this.init_ua_flags();
|
||||
}
|
||||
|
||||
public checkUseragent(isUseragent: keyof ReturnType<typeof get_ua>) {
|
||||
return Boolean(this.ua_map[isUseragent]);
|
||||
}
|
||||
|
||||
private init_ua_flags() {
|
||||
this.isLinux = this.checkUseragent('linux');
|
||||
this.isMacOs = this.checkUseragent('ios');
|
||||
this.isSafari = this.checkUseragent('safari');
|
||||
this.isWindows = this.checkUseragent('win');
|
||||
this.isFireFox = this.checkUseragent('firefox');
|
||||
}
|
||||
}
|
||||
|
||||
export const uaHelper = new UaHelper();
|
||||
78
libs/utils/src/utils.ts
Normal file
78
libs/utils/src/utils.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { UserInfo } from './types';
|
||||
|
||||
export function getUserDisplayName(user?: UserInfo) {
|
||||
const { nickname, username, email } = user || {};
|
||||
return nickname || username || email || 'Unknown User';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workspace_id from URL
|
||||
* @returns workspace_id
|
||||
*/
|
||||
export function getWorkspaceId() {
|
||||
const path = window.location.pathname.match(/\/(\w+)\//);
|
||||
return path ? path[1] : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page_id from URL
|
||||
* @returns page_id
|
||||
*/
|
||||
export function getPageId() {
|
||||
const path = window.location.pathname.match(/\/(\w+)\/(\w+)/);
|
||||
return path ? path[2] : undefined;
|
||||
}
|
||||
|
||||
export async function sleep(delay?: number) {
|
||||
return new Promise(res => {
|
||||
window.setTimeout(() => {
|
||||
res(true);
|
||||
}, delay);
|
||||
});
|
||||
}
|
||||
|
||||
export function isInternalPageUrl(url: string) {
|
||||
if (!url) return false;
|
||||
return (
|
||||
url.startsWith(window.location.origin) &&
|
||||
/^https?:\/\/(localhost:4200|(nightly|app)\.affine\.pro|.*?\.ligo-virgo\.pages\.dev)/.test(
|
||||
url
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function getRelativeUrlForInternalPageUrl(url: string) {
|
||||
if (url?.startsWith(window.location.origin)) {
|
||||
return url.slice(window.location.origin.length);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a fragile object that will throw error at any operation.
|
||||
*/
|
||||
export const genErrorObj = (
|
||||
...args: ConstructorParameters<ErrorConstructor>
|
||||
): unknown =>
|
||||
new Proxy(
|
||||
{},
|
||||
{
|
||||
get(target, prop, receiver) {
|
||||
console.error('You are trying to access a property', prop);
|
||||
throw new Error(...args);
|
||||
},
|
||||
set(target, prop, val, receiver) {
|
||||
console.error('You are trying to set a property', prop, val);
|
||||
throw new Error(...args);
|
||||
},
|
||||
apply(target, thisArg, argumentsList) {
|
||||
console.error(
|
||||
'You are trying to call a function',
|
||||
thisArg,
|
||||
argumentsList
|
||||
);
|
||||
throw new Error(...args);
|
||||
},
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user