mirror of https://gitee.com/answerdev/answer.git
Merge remote-tracking branch 'github/feat/1.1.2/ui' into feat/1.1.2/user-center
This commit is contained in:
commit
045cd0b1e8
|
@ -1183,6 +1183,7 @@ ui:
|
||||||
plugins:
|
plugins:
|
||||||
login: Login
|
login: Login
|
||||||
qrcode_login_tip: Please use {{ agentName }} to scan the QR code and log in.
|
qrcode_login_tip: Please use {{ agentName }} to scan the QR code and log in.
|
||||||
|
login_failed_email_tip: Login failed, please allow this app to access your email information before try again.
|
||||||
oauth:
|
oauth:
|
||||||
connect: Connect with {{ auth_name }}
|
connect: Connect with {{ auth_name }}
|
||||||
remove: Remove {{ auth_name }}
|
remove: Remove {{ auth_name }}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { markdownToHtml } from '@/services';
|
import { markdownToHtml } from '@/services';
|
||||||
|
import ImgViewer from '@/components/ImgViewer';
|
||||||
|
|
||||||
import { htmlRender } from './utils';
|
import { htmlRender } from './utils';
|
||||||
|
|
||||||
|
@ -48,11 +49,13 @@ const Index = ({ value }, ref) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<ImgViewer>
|
||||||
ref={previewRef}
|
<div
|
||||||
className="preview-wrap position-relative p-3 bg-light rounded text-break text-wrap mt-2 fmt"
|
ref={previewRef}
|
||||||
dangerouslySetInnerHTML={{ __html: html }}
|
className="preview-wrap position-relative p-3 bg-light rounded text-break text-wrap mt-2 fmt"
|
||||||
/>
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
|
/>
|
||||||
|
</ImgViewer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
.img-viewer .cursor-zoom-out {
|
||||||
|
cursor: zoom-out !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-viewer img:not(a img, img.broken) {
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
|
@ -1,14 +1,13 @@
|
||||||
import { useLayoutEffect, useState, MouseEvent, useEffect } from 'react';
|
import { FC, MouseEvent, ReactNode, useEffect, useState } from 'react';
|
||||||
import { Modal } from 'react-bootstrap';
|
import { Modal } from 'react-bootstrap';
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ReactDOM from 'react-dom/client';
|
import './index.css';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
const div = document.createElement('div');
|
const Index: FC<{
|
||||||
const root = ReactDOM.createRoot(div);
|
children: ReactNode;
|
||||||
|
className?: classnames.Argument;
|
||||||
const useImgViewer = () => {
|
}> = ({ children, className }) => {
|
||||||
const location = useLocation();
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [imgSrc, setImgSrc] = useState('');
|
const [imgSrc, setImgSrc] = useState('');
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
|
@ -47,8 +46,18 @@ const useImgViewer = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useEffect(() => {
|
||||||
root.render(
|
return () => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
|
||||||
|
<div
|
||||||
|
className={classnames('img-viewer', className)}
|
||||||
|
onClick={checkClickForImgView}>
|
||||||
|
{children}
|
||||||
<Modal
|
<Modal
|
||||||
show={visible}
|
show={visible}
|
||||||
fullscreen
|
fullscreen
|
||||||
|
@ -56,23 +65,16 @@ const useImgViewer = () => {
|
||||||
scrollable
|
scrollable
|
||||||
contentClassName="bg-transparent"
|
contentClassName="bg-transparent"
|
||||||
onHide={onClose}>
|
onHide={onClose}>
|
||||||
<Modal.Body onClick={onClose} className="p-0 d-flex">
|
<Modal.Body onClick={onClose} className="img-viewer p-0 d-flex">
|
||||||
<img
|
<img
|
||||||
className="cursor-zoom-out img-fluid m-auto"
|
className="cursor-zoom-out img-fluid m-auto"
|
||||||
src={imgSrc}
|
src={imgSrc}
|
||||||
alt={imgSrc}
|
alt={imgSrc}
|
||||||
/>
|
/>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
</Modal>,
|
</Modal>
|
||||||
);
|
</div>
|
||||||
});
|
);
|
||||||
useEffect(() => {
|
|
||||||
onClose();
|
|
||||||
}, [location]);
|
|
||||||
return {
|
|
||||||
onClose,
|
|
||||||
checkClickForImgView,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useImgViewer;
|
export default Index;
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { FC, useState } from 'react';
|
import React, { FC, useState } from 'react';
|
||||||
import { Button } from 'react-bootstrap';
|
import { Button, ButtonProps } from 'react-bootstrap';
|
||||||
|
|
||||||
import { request } from '@/utils';
|
import { request } from '@/utils';
|
||||||
import type * as Type from '@/common/interface';
|
import type * as Type from '@/common/interface';
|
||||||
|
@ -11,14 +11,18 @@ interface Props {
|
||||||
action: UIAction | undefined;
|
action: UIAction | undefined;
|
||||||
formData: Type.FormDataType;
|
formData: Type.FormDataType;
|
||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
|
variant?: ButtonProps['variant'];
|
||||||
|
size?: ButtonProps['size'];
|
||||||
}
|
}
|
||||||
const Index: FC<Props> = ({
|
const Index: FC<Props> = ({
|
||||||
fieldName,
|
fieldName,
|
||||||
action,
|
action,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
formData,
|
formData,
|
||||||
readOnly = false,
|
|
||||||
text = '',
|
text = '',
|
||||||
|
readOnly = false,
|
||||||
|
variant = 'primary',
|
||||||
|
size,
|
||||||
}) => {
|
}) => {
|
||||||
const [isLoading, setLoading] = useState(false);
|
const [isLoading, setLoading] = useState(false);
|
||||||
const handleAction = async () => {
|
const handleAction = async () => {
|
||||||
|
@ -33,7 +37,12 @@ const Index: FC<Props> = ({
|
||||||
const disabled = isLoading || readOnly;
|
const disabled = isLoading || readOnly;
|
||||||
return (
|
return (
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
<Button name={fieldName} onClick={handleAction} disabled={disabled}>
|
<Button
|
||||||
|
name={fieldName}
|
||||||
|
onClick={handleAction}
|
||||||
|
disabled={disabled}
|
||||||
|
size={size}
|
||||||
|
variant={variant}>
|
||||||
{text || fieldName}
|
{text || fieldName}
|
||||||
{isLoading ? '...' : ''}
|
{isLoading ? '...' : ''}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -42,7 +42,7 @@ const Index: FC<Props> = ({
|
||||||
type={type}
|
type={type}
|
||||||
value={fieldObject?.value || ''}
|
value={fieldObject?.value || ''}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
readOnly={readOnly}
|
disabled={readOnly}
|
||||||
isInvalid={fieldObject?.isInvalid}
|
isInvalid={fieldObject?.isInvalid}
|
||||||
style={type === 'color' ? { width: '6rem' } : {}}
|
style={type === 'color' ? { width: '6rem' } : {}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -10,6 +10,7 @@ interface Props {
|
||||||
enumValues: (string | boolean | number)[];
|
enumValues: (string | boolean | number)[];
|
||||||
enumNames: string[];
|
enumNames: string[];
|
||||||
formData: Type.FormDataType;
|
formData: Type.FormDataType;
|
||||||
|
readOnly: boolean;
|
||||||
}
|
}
|
||||||
const Index: FC<Props> = ({
|
const Index: FC<Props> = ({
|
||||||
desc,
|
desc,
|
||||||
|
@ -18,6 +19,7 @@ const Index: FC<Props> = ({
|
||||||
enumValues,
|
enumValues,
|
||||||
enumNames,
|
enumNames,
|
||||||
formData,
|
formData,
|
||||||
|
readOnly = false,
|
||||||
}) => {
|
}) => {
|
||||||
const fieldObject = formData[fieldName];
|
const fieldObject = formData[fieldName];
|
||||||
const handleChange = (evt: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleChange = (evt: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
@ -40,6 +42,7 @@ const Index: FC<Props> = ({
|
||||||
name={fieldName}
|
name={fieldName}
|
||||||
value={fieldObject?.value || ''}
|
value={fieldObject?.value || ''}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
disabled={readOnly}
|
||||||
isInvalid={fieldObject?.isInvalid}>
|
isInvalid={fieldObject?.isInvalid}>
|
||||||
{enumValues?.map((item, index) => {
|
{enumValues?.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -12,6 +12,7 @@ interface Props {
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
onChange?: (fd: Type.FormDataType) => void;
|
onChange?: (fd: Type.FormDataType) => void;
|
||||||
formData: Type.FormDataType;
|
formData: Type.FormDataType;
|
||||||
|
readOnly: boolean;
|
||||||
}
|
}
|
||||||
const Index: FC<Props> = ({
|
const Index: FC<Props> = ({
|
||||||
placeholder = '',
|
placeholder = '',
|
||||||
|
@ -20,6 +21,7 @@ const Index: FC<Props> = ({
|
||||||
fieldName,
|
fieldName,
|
||||||
onChange,
|
onChange,
|
||||||
formData,
|
formData,
|
||||||
|
readOnly = false,
|
||||||
}) => {
|
}) => {
|
||||||
const fieldObject = formData[fieldName];
|
const fieldObject = formData[fieldName];
|
||||||
const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
@ -46,6 +48,7 @@ const Index: FC<Props> = ({
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
isInvalid={fieldObject?.isInvalid}
|
isInvalid={fieldObject?.isInvalid}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
|
disabled={readOnly}
|
||||||
className={classnames(className)}
|
className={classnames(className)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import React, {
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useEffect,
|
useEffect,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { Form, Button } from 'react-bootstrap';
|
import { Form, Button, ButtonProps } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
@ -100,6 +100,8 @@ export interface ButtonOptions extends BaseUIOptions {
|
||||||
text: string;
|
text: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
action?: UIAction;
|
action?: UIAction;
|
||||||
|
variant?: ButtonProps['variant'];
|
||||||
|
size?: ButtonProps['size'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UIOptions =
|
export type UIOptions =
|
||||||
|
@ -367,6 +369,7 @@ const SchemaForm: ForwardRefRenderFunction<IRef, IProps> = (
|
||||||
enumValues={enumValues}
|
enumValues={enumValues}
|
||||||
enumNames={enumNames}
|
enumNames={enumNames}
|
||||||
formData={formData}
|
formData={formData}
|
||||||
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{widget === 'radio' || widget === 'checkbox' ? (
|
{widget === 'radio' || widget === 'checkbox' ? (
|
||||||
|
@ -418,6 +421,7 @@ const SchemaForm: ForwardRefRenderFunction<IRef, IProps> = (
|
||||||
fieldName={key}
|
fieldName={key}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
formData={formData}
|
formData={formData}
|
||||||
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{widget === 'input' ? (
|
{widget === 'input' ? (
|
||||||
|
@ -439,6 +443,10 @@ const SchemaForm: ForwardRefRenderFunction<IRef, IProps> = (
|
||||||
action={uiOpt && 'action' in uiOpt ? uiOpt.action : undefined}
|
action={uiOpt && 'action' in uiOpt ? uiOpt.action : undefined}
|
||||||
formData={formData}
|
formData={formData}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
|
variant={
|
||||||
|
uiOpt && 'variant' in uiOpt ? uiOpt.variant : undefined
|
||||||
|
}
|
||||||
|
size={uiOpt && 'size' in uiOpt ? uiOpt.size : undefined}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{/* Unified handling of `Feedback` and `Text` */}
|
{/* Unified handling of `Feedback` and `Text` */}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import { uploadImage } from '@/services';
|
import { uploadImage } from '@/services';
|
||||||
import * as Type from '@/common/interface';
|
import * as Type from '@/common/interface';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
type: Type.UploadType;
|
type: Type.UploadType;
|
||||||
className?: string;
|
className?: classnames.Argument;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
acceptType?: string;
|
acceptType?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
@ -49,7 +51,8 @@ const Index: React.FC<IProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label className={`btn btn-outline-secondary uploadBtn ${className}`}>
|
<label
|
||||||
|
className={classnames('btn btn-outline-secondary uploadBtn', className)}>
|
||||||
{children || (status ? t('upload_img.loading') : t('upload_img.name'))}
|
{children || (status ? t('upload_img.loading') : t('upload_img.name'))}
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
|
|
|
@ -39,6 +39,7 @@ import QuestionList from './QuestionList';
|
||||||
import HotQuestions from './HotQuestions';
|
import HotQuestions from './HotQuestions';
|
||||||
import HttpErrorContent from './HttpErrorContent';
|
import HttpErrorContent from './HttpErrorContent';
|
||||||
import CustomSidebar from './CustomSidebar';
|
import CustomSidebar from './CustomSidebar';
|
||||||
|
import ImgViewer from './ImgViewer';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
@ -84,5 +85,6 @@ export {
|
||||||
HotQuestions,
|
HotQuestions,
|
||||||
HttpErrorContent,
|
HttpErrorContent,
|
||||||
CustomSidebar,
|
CustomSidebar,
|
||||||
|
ImgViewer,
|
||||||
};
|
};
|
||||||
export type { EditorRef, JSONSchema, UISchema };
|
export type { EditorRef, JSONSchema, UISchema };
|
||||||
|
|
|
@ -10,7 +10,6 @@ import useChangePasswordModal from './useChangePasswordModal';
|
||||||
import usePageTags from './usePageTags';
|
import usePageTags from './usePageTags';
|
||||||
import useLoginRedirect from './useLoginRedirect';
|
import useLoginRedirect from './useLoginRedirect';
|
||||||
import usePromptWithUnload from './usePrompt';
|
import usePromptWithUnload from './usePrompt';
|
||||||
import useImgViewer from './useImgViewer';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useTagModal,
|
useTagModal,
|
||||||
|
@ -25,5 +24,4 @@ export {
|
||||||
usePageTags,
|
usePageTags,
|
||||||
useLoginRedirect,
|
useLoginRedirect,
|
||||||
usePromptWithUnload,
|
usePromptWithUnload,
|
||||||
useImgViewer,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -122,14 +122,6 @@ a {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cursor-zoom-out {
|
|
||||||
cursor: zoom-out !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
img:not(a img, img.broken) {
|
|
||||||
cursor: zoom-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resize-none {
|
.resize-none {
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { FC, memo, useEffect, useState } from 'react';
|
import { FC, memo, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { JSONSchema, SchemaForm, UISchema } from '@/components';
|
import { JSONSchema, SchemaForm, UISchema, ImgViewer } from '@/components';
|
||||||
import { FormDataType } from '@/common/interface';
|
import { FormDataType } from '@/common/interface';
|
||||||
import { brandSetting, getBrandSetting } from '@/services';
|
import { brandSetting, getBrandSetting } from '@/services';
|
||||||
import { brandingStore } from '@/stores';
|
import { brandingStore } from '@/stores';
|
||||||
|
@ -142,7 +142,7 @@ const Index: FC = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<ImgViewer>
|
||||||
<h3 className="mb-4">{t('page_title')}</h3>
|
<h3 className="mb-4">{t('page_title')}</h3>
|
||||||
<SchemaForm
|
<SchemaForm
|
||||||
schema={schema}
|
schema={schema}
|
||||||
|
@ -151,7 +151,7 @@ const Index: FC = () => {
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
onChange={handleOnChange}
|
onChange={handleOnChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</ImgViewer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -146,6 +146,9 @@ const Index: FC = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getUsersSetting().then((resp) => {
|
getUsersSetting().then((resp) => {
|
||||||
|
if (!resp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const formMeta: Type.FormDataType = {};
|
const formMeta: Type.FormDataType = {};
|
||||||
Object.keys(formData).forEach((k) => {
|
Object.keys(formData).forEach((k) => {
|
||||||
let v = resp[k];
|
let v = resp[k];
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
HttpErrorContent,
|
HttpErrorContent,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { LoginToContinueModal } from '@/components/Modal';
|
import { LoginToContinueModal } from '@/components/Modal';
|
||||||
import { useImgViewer } from '@/hooks';
|
|
||||||
|
|
||||||
const Layout: FC = () => {
|
const Layout: FC = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
@ -24,7 +23,6 @@ const Layout: FC = () => {
|
||||||
toastClear();
|
toastClear();
|
||||||
};
|
};
|
||||||
const { code: httpStatusCode, reset: httpStatusReset } = errorCodeStore();
|
const { code: httpStatusCode, reset: httpStatusReset } = errorCodeStore();
|
||||||
const imgViewer = useImgViewer();
|
|
||||||
const { show: showLoginToContinueModal } = loginToContinueStore();
|
const { show: showLoginToContinueModal } = loginToContinueStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -40,9 +38,7 @@ const Layout: FC = () => {
|
||||||
}}>
|
}}>
|
||||||
<Header />
|
<Header />
|
||||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
|
||||||
<div
|
<div className="position-relative page-wrap">
|
||||||
className="position-relative page-wrap"
|
|
||||||
onClick={imgViewer.checkClickForImgView}>
|
|
||||||
{httpStatusCode ? (
|
{httpStatusCode ? (
|
||||||
<HttpErrorContent httpCode={httpStatusCode} />
|
<HttpErrorContent httpCode={httpStatusCode} />
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
FormatTime,
|
FormatTime,
|
||||||
htmlRender,
|
htmlRender,
|
||||||
Icon,
|
Icon,
|
||||||
|
ImgViewer,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { formatCount, guard } from '@/utils';
|
import { formatCount, guard } from '@/utils';
|
||||||
import { following } from '@/services';
|
import { following } from '@/services';
|
||||||
|
@ -114,11 +115,13 @@ const Index: FC<Props> = ({ data, initPage, hasAnswer, isLogged }) => {
|
||||||
return <Tag className="m-1" key={item.slug_name} data={item} />;
|
return <Tag className="m-1" key={item.slug_name} data={item} />;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<article
|
<ImgViewer>
|
||||||
ref={ref}
|
<article
|
||||||
className="fmt text-break text-wrap mt-4"
|
ref={ref}
|
||||||
dangerouslySetInnerHTML={{ __html: data?.html }}
|
className="fmt text-break text-wrap mt-4"
|
||||||
/>
|
dangerouslySetInnerHTML={{ __html: data?.html }}
|
||||||
|
/>
|
||||||
|
</ImgViewer>
|
||||||
|
|
||||||
<Actions
|
<Actions
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
|
|
|
@ -35,7 +35,7 @@ const Index: FC = () => {
|
||||||
if (!targetUrl) {
|
if (!targetUrl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QrCode.toDataURL(targetUrl, { width: 240 }, (err, url) => {
|
QrCode.toDataURL(targetUrl, { width: 240, margin: 0 }, (err, url) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -67,14 +67,21 @@ const Index: FC = () => {
|
||||||
return (
|
return (
|
||||||
<Card className="text-center">
|
<Card className="text-center">
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Card.Title as="h3">
|
<Card.Title as="h3" className="mb-3">
|
||||||
{agentName} {t('login')}
|
{ucAgent?.agent_info.display_name} {t('login')}
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
{qrcodeDataUrl ? (
|
{qrcodeDataUrl ? (
|
||||||
<>
|
<>
|
||||||
<img width={240} height={240} src={qrcodeDataUrl} alt="" />
|
<img
|
||||||
<div className="text-secondary">
|
width={240}
|
||||||
{t('qrcode_login_tip', { agentName })}
|
height={240}
|
||||||
|
src={qrcodeDataUrl}
|
||||||
|
alt={agentName}
|
||||||
|
/>
|
||||||
|
<div className="text-secondary mt-3">
|
||||||
|
{t('qrcode_login_tip', {
|
||||||
|
agentName: ucAgent?.agent_info.display_name,
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { Card, Col, Carousel } from 'react-bootstrap';
|
import { Card, Col, Carousel } from 'react-bootstrap';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { userCenterStore } from '@/stores';
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
|
@ -25,14 +28,17 @@ const data = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const Index: FC = () => {
|
const Index: FC = () => {
|
||||||
|
const { t } = useTranslation('translation', { keyPrefix: 'plugins' });
|
||||||
|
const ucAgent = userCenterStore().agent;
|
||||||
return (
|
return (
|
||||||
<Col lg={4} className="mx-auto mt-3 py-5">
|
<Col lg={4} className="mx-auto mt-3 py-5">
|
||||||
<Card>
|
<Card>
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<h3 className="text-center pt-3 mb-3">WeCom Login</h3>
|
<h3 className="text-center pt-3 mb-3">
|
||||||
|
{ucAgent?.agent_info.display_name} {t('login')}
|
||||||
|
</h3>
|
||||||
<p className="text-danger text-center">
|
<p className="text-danger text-center">
|
||||||
Login failed, please allow this app to access your email information
|
{t('login_failed_email_tip')}
|
||||||
before try again.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Carousel controls={false}>
|
<Carousel controls={false}>
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
loginSettingStore,
|
loginSettingStore,
|
||||||
userCenterStore,
|
userCenterStore,
|
||||||
} from '@/stores';
|
} from '@/stores';
|
||||||
import { guard, handleFormError } from '@/utils';
|
import { floppyNavigation, guard, handleFormError, userCenter } from '@/utils';
|
||||||
import { login, checkImgCode, UcAgent } from '@/services';
|
import { login, checkImgCode, UcAgent } from '@/services';
|
||||||
import { PicAuthCodeModal } from '@/components/Modal';
|
import { PicAuthCodeModal } from '@/components/Modal';
|
||||||
|
|
||||||
|
@ -246,7 +246,10 @@ const Index: React.FC = () => {
|
||||||
<div className="text-center mt-5">
|
<div className="text-center mt-5">
|
||||||
<Trans i18nKey="login.info_sign" ns="translation">
|
<Trans i18nKey="login.info_sign" ns="translation">
|
||||||
Don’t have an account?
|
Don’t have an account?
|
||||||
<Link to="/users/register" tabIndex={2}>
|
<Link
|
||||||
|
to={userCenter.getSignUpUrl()}
|
||||||
|
tabIndex={2}
|
||||||
|
onClick={floppyNavigation.handleRouteLinkClick}>
|
||||||
Sign up
|
Sign up
|
||||||
</Link>
|
</Link>
|
||||||
</Trans>
|
</Trans>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Trans, useTranslation } from 'react-i18next';
|
||||||
import MD5 from 'md5';
|
import MD5 from 'md5';
|
||||||
|
|
||||||
import type { FormDataType } from '@/common/interface';
|
import type { FormDataType } from '@/common/interface';
|
||||||
import { UploadImg, Avatar, Icon } from '@/components';
|
import { UploadImg, Avatar, Icon, ImgViewer } from '@/components';
|
||||||
import { loggedUserInfoStore, userCenterStore, siteInfoStore } from '@/stores';
|
import { loggedUserInfoStore, userCenterStore, siteInfoStore } from '@/stores';
|
||||||
import { useToast } from '@/hooks';
|
import { useToast } from '@/hooks';
|
||||||
import {
|
import {
|
||||||
|
@ -27,7 +27,6 @@ const Index: React.FC = () => {
|
||||||
const [mailHash, setMailHash] = useState('');
|
const [mailHash, setMailHash] = useState('');
|
||||||
const [count] = useState(0);
|
const [count] = useState(0);
|
||||||
const [profileAgent, setProfileAgent] = useState<UcSettingAgent>();
|
const [profileAgent, setProfileAgent] = useState<UcSettingAgent>();
|
||||||
|
|
||||||
const [formData, setFormData] = useState<FormDataType>({
|
const [formData, setFormData] = useState<FormDataType>({
|
||||||
display_name: {
|
display_name: {
|
||||||
value: '',
|
value: '',
|
||||||
|
@ -282,7 +281,7 @@ const Index: React.FC = () => {
|
||||||
<Form.Control
|
<Form.Control
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
readOnly={!usersSetting.allow_update_display_name}
|
disabled={!usersSetting.allow_update_display_name}
|
||||||
value={formData.display_name.value}
|
value={formData.display_name.value}
|
||||||
isInvalid={formData.display_name.isInvalid}
|
isInvalid={formData.display_name.isInvalid}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
@ -305,7 +304,7 @@ const Index: React.FC = () => {
|
||||||
<Form.Control
|
<Form.Control
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
readOnly={!usersSetting.allow_update_username}
|
disabled={!usersSetting.allow_update_username}
|
||||||
value={formData.username.value}
|
value={formData.username.value}
|
||||||
isInvalid={formData.username.isInvalid}
|
isInvalid={formData.username.isInvalid}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
@ -343,66 +342,68 @@ const Index: React.FC = () => {
|
||||||
</option>
|
</option>
|
||||||
</Form.Select>
|
</Form.Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex">
|
<ImgViewer>
|
||||||
{formData.avatar.type === 'gravatar' && (
|
<div className="d-flex">
|
||||||
<Stack>
|
{formData.avatar.type === 'gravatar' && (
|
||||||
<Avatar
|
<Stack>
|
||||||
size="160px"
|
|
||||||
avatar={formData.avatar.gravatar}
|
|
||||||
searchStr={`s=256&d=identicon${
|
|
||||||
count > 0 ? `&t=${new Date().valueOf()}` : ''
|
|
||||||
}`}
|
|
||||||
className="me-3 rounded"
|
|
||||||
/>
|
|
||||||
<Form.Text className="text-muted mt-1">
|
|
||||||
<Trans i18nKey="settings.profile.avatar.gravatar_text">
|
|
||||||
You can change image on
|
|
||||||
<a
|
|
||||||
href="https://gravatar.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer">
|
|
||||||
gravatar.com
|
|
||||||
</a>
|
|
||||||
</Trans>
|
|
||||||
</Form.Text>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{formData.avatar.type === 'custom' && (
|
|
||||||
<Stack>
|
|
||||||
<Stack direction="horizontal" className="align-items-start">
|
|
||||||
<Avatar
|
<Avatar
|
||||||
size="160px"
|
size="160px"
|
||||||
searchStr="s=256"
|
avatar={formData.avatar.gravatar}
|
||||||
avatar={formData.avatar.custom}
|
searchStr={`s=256&d=identicon${
|
||||||
className="me-2 bg-gray-300 "
|
count > 0 ? `&t=${new Date().valueOf()}` : ''
|
||||||
|
}`}
|
||||||
|
className="me-3 rounded"
|
||||||
/>
|
/>
|
||||||
<ButtonGroup vertical className="fit-content">
|
<Form.Text className="text-muted mt-1">
|
||||||
<UploadImg
|
<Trans i18nKey="settings.profile.avatar.gravatar_text">
|
||||||
type="avatar"
|
You can change image on
|
||||||
disabled={!usersSetting.allow_update_avatar}
|
<a
|
||||||
uploadCallback={avatarUpload}>
|
href="https://gravatar.com"
|
||||||
<Icon name="cloud-upload" />
|
target="_blank"
|
||||||
</UploadImg>
|
rel="noreferrer">
|
||||||
<Button
|
gravatar.com
|
||||||
variant="outline-secondary"
|
</a>
|
||||||
disabled={!usersSetting.allow_update_avatar}
|
</Trans>
|
||||||
onClick={removeCustomAvatar}>
|
</Form.Text>
|
||||||
<Icon name="trash" />
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
<Form.Text className="text-muted mt-1">
|
)}
|
||||||
<Trans i18nKey="settings.profile.avatar.text">
|
|
||||||
You can upload your image.
|
{formData.avatar.type === 'custom' && (
|
||||||
</Trans>
|
<Stack>
|
||||||
</Form.Text>
|
<Stack direction="horizontal" className="align-items-start">
|
||||||
</Stack>
|
<Avatar
|
||||||
)}
|
size="160px"
|
||||||
{formData.avatar.type === 'default' && (
|
searchStr="s=256"
|
||||||
<Avatar size="160px" avatar="" />
|
avatar={formData.avatar.custom}
|
||||||
)}
|
className="me-2 bg-gray-300 "
|
||||||
</div>
|
/>
|
||||||
|
<ButtonGroup vertical className="fit-content">
|
||||||
|
<UploadImg
|
||||||
|
type="avatar"
|
||||||
|
disabled={!usersSetting.allow_update_avatar}
|
||||||
|
uploadCallback={avatarUpload}>
|
||||||
|
<Icon name="cloud-upload" />
|
||||||
|
</UploadImg>
|
||||||
|
<Button
|
||||||
|
variant="outline-secondary"
|
||||||
|
disabled={!usersSetting.allow_update_avatar}
|
||||||
|
onClick={removeCustomAvatar}>
|
||||||
|
<Icon name="trash" />
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</Stack>
|
||||||
|
<Form.Text className="text-muted mt-1">
|
||||||
|
<Trans i18nKey="settings.profile.avatar.text">
|
||||||
|
You can upload your image.
|
||||||
|
</Trans>
|
||||||
|
</Form.Text>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
{formData.avatar.type === 'default' && (
|
||||||
|
<Avatar size="160px" avatar="" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ImgViewer>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
isInvalid={formData.avatar.isInvalid}
|
isInvalid={formData.avatar.isInvalid}
|
||||||
className="d-none"
|
className="d-none"
|
||||||
|
@ -423,7 +424,7 @@ const Index: React.FC = () => {
|
||||||
required
|
required
|
||||||
as="textarea"
|
as="textarea"
|
||||||
rows={5}
|
rows={5}
|
||||||
readOnly={!usersSetting.allow_update_bio}
|
disabled={!usersSetting.allow_update_bio}
|
||||||
value={formData.bio.value}
|
value={formData.bio.value}
|
||||||
isInvalid={formData.bio.isInvalid}
|
isInvalid={formData.bio.isInvalid}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
@ -449,7 +450,7 @@ const Index: React.FC = () => {
|
||||||
required
|
required
|
||||||
type="url"
|
type="url"
|
||||||
placeholder={t('website.placeholder')}
|
placeholder={t('website.placeholder')}
|
||||||
readOnly={!usersSetting.allow_update_website}
|
disabled={!usersSetting.allow_update_website}
|
||||||
value={formData.website.value}
|
value={formData.website.value}
|
||||||
isInvalid={formData.website.isInvalid}
|
isInvalid={formData.website.isInvalid}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
@ -475,7 +476,7 @@ const Index: React.FC = () => {
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={t('location.placeholder')}
|
placeholder={t('location.placeholder')}
|
||||||
readOnly={!usersSetting.allow_update_location}
|
disabled={!usersSetting.allow_update_location}
|
||||||
value={formData.location.value}
|
value={formData.location.value}
|
||||||
isInvalid={formData.location.isInvalid}
|
isInvalid={formData.location.isInvalid}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
|
|
@ -13,7 +13,6 @@ interface Props {
|
||||||
const Index: FC<Props> = ({ className }) => {
|
const Index: FC<Props> = ({ className }) => {
|
||||||
const { t } = useTranslation('translation', { keyPrefix: 'plugins.oauth' });
|
const { t } = useTranslation('translation', { keyPrefix: 'plugins.oauth' });
|
||||||
const ucAgent = userCenterStore().agent;
|
const ucAgent = userCenterStore().agent;
|
||||||
const agentName = ucAgent?.agent_info?.name || '';
|
|
||||||
const ucLoginRedirect =
|
const ucLoginRedirect =
|
||||||
ucAgent?.enabled && ucAgent?.agent_info?.login_redirect_url;
|
ucAgent?.enabled && ucAgent?.agent_info?.login_redirect_url;
|
||||||
|
|
||||||
|
@ -24,7 +23,9 @@ const Index: FC<Props> = ({ className }) => {
|
||||||
variant="outline-secondary"
|
variant="outline-secondary"
|
||||||
href={ucAgent?.agent_info.login_redirect_url}>
|
href={ucAgent?.agent_info.login_redirect_url}>
|
||||||
<SvgIcon base64={ucAgent?.agent_info.icon} />
|
<SvgIcon base64={ucAgent?.agent_info.icon} />
|
||||||
<span>{t('connect', { auth_name: agentName })}</span>
|
<span>
|
||||||
|
{t('connect', { auth_name: ucAgent?.agent_info.display_name })}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ export interface UcAgent {
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
display_name: string;
|
||||||
login_redirect_url: string;
|
login_redirect_url: string;
|
||||||
sign_up_redirect_url: string;
|
sign_up_redirect_url: string;
|
||||||
control_center: UcAgentControl[];
|
control_center: UcAgentControl[];
|
||||||
|
|
|
@ -13,6 +13,16 @@ interface SiteInfoType {
|
||||||
updateUsers: (users: SiteInfoType['users']) => void;
|
updateUsers: (users: SiteInfoType['users']) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultUsersConf: AdminSettingsUsers = {
|
||||||
|
allow_update_avatar: false,
|
||||||
|
allow_update_bio: false,
|
||||||
|
allow_update_display_name: false,
|
||||||
|
allow_update_location: false,
|
||||||
|
allow_update_username: false,
|
||||||
|
allow_update_website: false,
|
||||||
|
default_avatar: 'system',
|
||||||
|
};
|
||||||
|
|
||||||
const siteInfo = create<SiteInfoType>((set) => ({
|
const siteInfo = create<SiteInfoType>((set) => ({
|
||||||
siteInfo: {
|
siteInfo: {
|
||||||
name: DEFAULT_SITE_NAME,
|
name: DEFAULT_SITE_NAME,
|
||||||
|
@ -22,15 +32,7 @@ const siteInfo = create<SiteInfoType>((set) => ({
|
||||||
contact_email: '',
|
contact_email: '',
|
||||||
permalink: 1,
|
permalink: 1,
|
||||||
},
|
},
|
||||||
users: {
|
users: defaultUsersConf,
|
||||||
allow_update_avatar: false,
|
|
||||||
allow_update_bio: false,
|
|
||||||
allow_update_display_name: false,
|
|
||||||
allow_update_location: false,
|
|
||||||
allow_update_username: false,
|
|
||||||
allow_update_website: false,
|
|
||||||
default_avatar: 'system',
|
|
||||||
},
|
|
||||||
version: '',
|
version: '',
|
||||||
revision: '',
|
revision: '',
|
||||||
update: (params) =>
|
update: (params) =>
|
||||||
|
@ -50,6 +52,7 @@ const siteInfo = create<SiteInfoType>((set) => ({
|
||||||
},
|
},
|
||||||
updateUsers: (users) => {
|
updateUsers: (users) => {
|
||||||
set(() => {
|
set(() => {
|
||||||
|
users ||= defaultUsersConf;
|
||||||
return { users };
|
return { users };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue