Skip to content

Commit

Permalink
Merge branch 'feature/blocked-users-converter' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
kitce committed May 13, 2022
2 parents f80b848 + 7d934a8 commit 3fa26cf
Show file tree
Hide file tree
Showing 35 changed files with 325 additions and 68 deletions.
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -38,6 +38,7 @@
"@fortawesome/react-fontawesome": "^0.1.18",
"@reduxjs/toolkit": "^1.5.1",
"classnames": "^2.3.1",
"crypto-js": "^4.1.1",
"date-fns": "^2.27.0",
"debug": "^4.3.3",
"events": "^3.3.0",
Expand All @@ -64,6 +65,7 @@
"devDependencies": {
"@svgr/webpack": "^6.2.1",
"@types/chance": "^1.1.3",
"@types/crypto-js": "^4.1.1",
"@types/debug": "^4.1.7",
"@types/gapi": "^0.0.41",
"@types/gapi.auth2": "^0.0.55",
Expand Down
18 changes: 15 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions src/apis/announcement.ts → src/apis/app.ts
@@ -1,16 +1,17 @@
import { publicDataURL } from '../../config/config';
import { publicDataURL as baseURL } from '../../config/config';
import type { IconName } from '../components/Icon/types';

interface IAnnouncement {
id?: string; // added in 1.0.18
/** @since 1.0.18 */
id?: string;
icon?: IconName;
body: string;
endAt?: number;
forced?: boolean;
}

export const fetchAnnouncements = async () => {
const url = `${publicDataURL}/announcements.json`;
const url = `${baseURL}/announcements.json`;
const response = await fetch(url);
const json = await response.json();
return json as IAnnouncement[];
Expand Down
71 changes: 70 additions & 1 deletion src/apis/lihkg.ts
@@ -1,10 +1,79 @@
import crypto from 'crypto-js';
import * as LIHKG from '../helpers/lihkg';
import { localStorage } from '../helpers/storage';
import { RequestMethod } from '../types/http';
import type { APIv2 } from '../types/lihkg';

enum DeviceType {
// Android = 'android',
Browser = 'browser'
}

const baseURL = 'https://lihkg.com/api_v2';

const getDigestHeader = (url: string, init: RequestInit, data: Record<string, string> | undefined, timestamp: string, token: string) => {
const { method } = init;
const searchParams = new URLSearchParams(data);
const messages = [
'jeams',
method,
method === RequestMethod.GET && data ? `${url}?${searchParams.toString()}` : url,
method === RequestMethod.POST && data ? JSON.stringify(data) : '',
token,
timestamp
];
const hash = crypto.SHA1(messages.join('$'));
return crypto.enc.Hex.stringify(hash);
};

const getLoadTimeHeader = (x: boolean) => {
const a = (('false'[0] + x) + ('false'[2])).length;
const b = 0 | 4 + Math.PI * Math.random();
const c = b + '.';
const d = a ^ b;
const e = c + d;
const f = 1000000 * Math.random();
const g = f | 0;
const h = e + g;
return h;
};

const getPlusHeader = (timestamp: string) => {
const a = 'false'[1] + [false] + undefined;
const b = (window as any).isPlusUser;
const c = a + b;
const d = c + timestamp;
const hash = crypto.SHA1(d);
return crypto.enc.Hex.stringify(hash);
};

const getRequestHeaders = (url: string, init: RequestInit, data?: Record<string, string>) => {
const store = LIHKG.getStore();
const state = store!.getState();
const device = localStorage.getItem('device');
const { currentUser } = state.app;
const headers: Record<string, string> = {
'X-LI-DEVICE-TYPE': DeviceType.Browser,
'X-LI-LOAD-TIME': getLoadTimeHeader(false)
};
if (device) {
headers['X-LI-DEVICE'] = JSON.parse(device) as string;
}
if (currentUser) {
const timestamp = Math.floor(Date.now() / 1000).toString();
headers['X-LI-DIGEST'] = getDigestHeader(url, init, data, timestamp, currentUser.token);
headers['X-LI-PLUS'] = getPlusHeader(timestamp);
headers['X-LI-REQUEST-TIME'] = timestamp;
headers['X-LI-USER'] = currentUser.user_id;
}
return headers;
};

export const fetchBlockedUser = async () => {
const url = `${baseURL}/me/blocked-user`;
const response = await fetch(url);
const init: RequestInit = { method: RequestMethod.GET };
const headers = getRequestHeaders(url, init);
const response = await fetch(url, { ...init, headers });
const json = await response.json();
return json as APIv2.IBlockedUserResponseBody | APIv2.IErrorResponseBody;
};
3 changes: 2 additions & 1 deletion src/apis/nacx.ts
@@ -1,3 +1,4 @@
import { RequestMethod } from '../types/http';
import type { IUploadResponse } from '../types/nacx';

const baseURL = 'https://api.na.cx';
Expand All @@ -6,7 +7,7 @@ export const uploadImage = async (image: Blob) => {
const url = `${baseURL}/upload`;
const body = new FormData();
body.append('image', image);
const init: RequestInit = { method: 'POST', body };
const init: RequestInit = { method: RequestMethod.POST, body };
const response = await fetch(url, init);
const json = await response.json();
return json as IUploadResponse;
Expand Down
6 changes: 3 additions & 3 deletions src/components/DataSetEditor/DataSetEditor.tsx
Expand Up @@ -7,9 +7,9 @@ import { namespace } from '../../../package.json';
import * as TEXTS from '../../constants/texts';
import { filterLabelsGroupsByKeyword, findLabelsGroupByUser, mapDataSetToLabelsGroupsGroupedByUser, mapLabelsGroupsGroupedByUserToDataSet } from '../../helpers/dataSetEditor';
import useFadeoutScroll from '../../hooks/useFadeoutScroll';
import type DataSet from '../../models/DataSet';
import type { IDataSet } from '../../models/DataSet';
import type { ILabel } from '../../models/Label';
import type Personal from '../../models/Personal';
import schema from '../../schemas/dataSet';
import ErrorMessage from '../ErrorMessage/ErrorMessage';
import styles from './DataSetEditor.module.scss';
Expand All @@ -24,10 +24,10 @@ interface IAutoScrollUserItemIndex {
export interface IProps {
dataSet: IDataSet;
onChange?: (user: string, index: number, label?: ILabel) => void;
onSubmit: (dataSet: Personal) => void;
onSubmit: (dataSet: DataSet) => void;
}

type TComponentProps = Omit<React.ComponentPropsWithoutRef<'form'>, 'onChange' | 'onSubmit'>;
type TComponentProps = TComponentPropsWithoutRef<'form', IProps>;

export type TProps = IProps & TComponentProps;

Expand Down
4 changes: 3 additions & 1 deletion src/components/DataSetEditor/Filter/Filter.tsx
Expand Up @@ -9,7 +9,9 @@ export interface IProps {
onChange: (keyword: string) => void;
}

type TProps = IProps & Omit<TTextInputProps, 'onChange'>;
type TComponentProps = Omit<TTextInputProps, keyof IProps>;

type TProps = IProps & TComponentProps;

const Filter: React.FunctionComponent<TProps> = (props) => {
const { onChange, ...otherProps } = props;
Expand Down
Expand Up @@ -42,7 +42,7 @@ export interface IProps {
onScroll: (target: HTMLLIElement) => void;
}

type TComponentProps = Omit<React.ComponentPropsWithoutRef<'div'>, 'onChange' | 'onScroll'>;
type TComponentProps = TComponentPropsWithoutRef<'div', IProps>;

type TProps = IProps & TComponentProps;

Expand Down
Expand Up @@ -4,4 +4,9 @@
.data-set-editor {
@apply flex-1;
}

}

.post-body {
@apply pr-4 pb-4 pl-4;
}
15 changes: 12 additions & 3 deletions src/components/DataSetEditorModal/DataSetEditorModal.tsx
Expand Up @@ -3,18 +3,20 @@ import type React from 'react';
import { useId } from 'react';
import * as TEXTS from '../../constants/texts';
import Button from '../Button/Button';
import DataSetEditor, { TProps as TDataSetEditorProps } from '../DataSetEditor/DataSetEditor';
import DataSetEditor, { IProps as IDataSetEditorProps, TProps as TDataSetEditorProps } from '../DataSetEditor/DataSetEditor';
import Modal, { TProps as TModalProps } from '../Modal/Modal';
import styles from './DataSetEditorModal.module.scss';

interface IProps { }

type TProps = IProps & Omit<TModalProps, 'onChange' | 'onSubmit'> & TDataSetEditorProps;
type TComponentProps = Omit<TModalProps, keyof IDataSetEditorProps>;

type TProps = IProps & TComponentProps & TDataSetEditorProps;

// const debug = debugFactory('libel:component:DataSetEditorModal');

const DataSetEditorModal: React.FunctionComponent<TProps> = (props) => {
const { id, onClose, dataSet, onChange, onSubmit, ...otherProps } = props;
const { id, onClose, dataSet, onChange, onSubmit, children, ...otherProps } = props;

const _formId = id || useId();

Expand All @@ -32,6 +34,13 @@ const DataSetEditorModal: React.FunctionComponent<TProps> = (props) => {
onSubmit={onSubmit}
/>
</Modal.Body>
{
children && (
<div className={styles.postBody}>
{children}
</div>
)
}
<Modal.Footer>
<Button form={_formId} type="submit">
{TEXTS.BUTTON_TEXT_DATA_SET_EDITOR_SAVE}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ErrorMessage/ErrorMessage.tsx
@@ -1,12 +1,12 @@
import type React from 'react';
import { IconName } from '../Icon/types';
import IconMessage, { TProps as TIconMessageProps } from '../IconMessage/IconMessage';
import IconMessage, { IProps as IIconMessageProps, TProps as TIconMessageProps } from '../IconMessage/IconMessage';

interface IProps { }

type TComponentProps<T extends React.ElementType> = TComponentPropsWithoutRefWithAs<T, IProps>;

type TProps<T extends React.ElementType> = IProps & TComponentProps<T> & Omit<TIconMessageProps<T>, 'icon'>;
type TProps<T extends React.ElementType> = IProps & TComponentProps<T> & Omit<TIconMessageProps<T>, keyof IIconMessageProps>;

/**
* @extends IconMessage
Expand Down
2 changes: 1 addition & 1 deletion src/components/IconLink/IconLink.tsx
Expand Up @@ -5,7 +5,7 @@ interface IProps { }

type TComponentProps = {};

type TProps = IProps & TComponentProps & Omit<TIconButtonProps<'a'>, 'as'>;
type TProps = IProps & TComponentProps & TIconButtonProps<'a'>;

/**
* @extends IconButton
Expand Down
2 changes: 1 addition & 1 deletion src/components/IconMessage/IconMessage.tsx
Expand Up @@ -4,7 +4,7 @@ import Icon from '../Icon/Icon';
import type { IconName } from '../Icon/types';
import styles from './IconMessage.module.scss';

interface IProps {
export interface IProps {
icon: IconName;
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/LabelForm/LabelForm.tsx
Expand Up @@ -37,7 +37,7 @@ interface IInputErrors {
[name: string]: string | undefined;
}

interface IProps {
export interface IProps {
/**
* the target user ID
*/
Expand All @@ -62,7 +62,7 @@ interface IProps {
onSubmit: (data: TFormData) => Promise<void>;
}

type TComponentProps = Omit<React.ComponentPropsWithoutRef<'form'>, 'onSubmit' | 'target'>;
type TComponentProps = TComponentPropsWithoutRef<'form', IProps>;

export type TProps = IProps & TComponentProps;

Expand Down
4 changes: 2 additions & 2 deletions src/components/LabelFormModal/LabelFormModal.tsx
Expand Up @@ -2,12 +2,12 @@ import type React from 'react';
import { useId } from 'react';
import * as TEXTS from '../../constants/texts';
import Button from '../Button/Button';
import LabelForm, { TProps as TLabelFormProps } from '../LabelForm/LabelForm';
import LabelForm, { IProps as ILabelFormProps, TProps as TLabelFormProps } from '../LabelForm/LabelForm';
import Modal, { TProps as TModalProps } from '../Modal/Modal';

interface IProps { }

type TComponentProps = Omit<TModalProps, 'onSubmit'>;
type TComponentProps = Omit<TModalProps, keyof ILabelFormProps>;

type TProps = IProps & TComponentProps & TLabelFormProps;

Expand Down
6 changes: 3 additions & 3 deletions src/components/Modal/Modal.tsx
Expand Up @@ -40,7 +40,7 @@ interface IProps {
onClose: () => void;
}

type TComponentProps = React.ComponentPropsWithoutRef<'div'>;
type TComponentProps = TComponentPropsWithoutRef<'div', IProps>;

export type TProps = IProps & TComponentProps;

Expand All @@ -50,12 +50,12 @@ const Modal: TModal = (props) => {
const {
id,
className,
children,
open = false,
backdrop = true,
fragile = true,
escape = true,
onClose
onClose,
children
} = props;

const [initialFocus, setInitialFocus] = useState<HTMLElement>();
Expand Down
4 changes: 2 additions & 2 deletions src/components/RemoveLabelButton/RemoveLabelButton.tsx
Expand Up @@ -31,8 +31,8 @@ const RemoveLabelButton: React.FunctionComponent<TProps> = (props) => {
event.preventDefault();
const _user = cache.getUser(user);
const question = render(questions.remove.label, { user: _user, label });
const confirmed = window.confirm(question);
if (confirmed) {
const yes = window.confirm(question);
if (yes) {
dispatch(personalActions.remove({ user, index }));
// analytics
gtag.event(EventAction.Remove, { event_category: EventCategory.Label, event_label: label.text });
Expand Down

0 comments on commit 3fa26cf

Please sign in to comment.