Merge pull request #425 from answerdev/feat/1.1.1/ui

Feat/1.1.1/UI
This commit is contained in:
haitao.jarvis
2023-07-03 17:09:13 +08:00
committed by GitHub
29 changed files with 136 additions and 57 deletions
+4 -2
View File
@@ -504,6 +504,7 @@ ui:
title: Notifications
inbox: Inbox
achievement: Achievements
new_alerts: New alerts
all_read: Mark all as read
show_more: Show more
someone: Someone
@@ -729,7 +730,7 @@ ui:
tip_answer: >-
Use comments to reply to other users or notify them of changes. If you are
adding new information, edit your post instead of commenting.
tip_vote: It adds something useful to the post
tip_vote: It is useful
edit_answer:
title: Edit Answer
default_reason: Edit answer
@@ -940,7 +941,6 @@ ui:
gravatar: Gravatar
gravatar_text: You can change image on
custom: Custom
btn_refresh: Refresh
custom_text: You can upload your image.
default: System
msg: Please upload an avatar
@@ -1025,6 +1025,7 @@ ui:
useful: Useful
question_useful: It is useful and clear
question_un_useful: It is unclear or not useful
question_bookmark: Bookmark this question
answer_useful: It is useful
answer_un_useful: It is not useful
answers:
@@ -1092,6 +1093,7 @@ ui:
question: Question
answer: Answer
comment: Comment
refresh: Refresh
search:
title: Search Results
keywords: Keywords
+1
View File
@@ -131,6 +131,7 @@ const Index: FC<Props> = ({ className, data, source }) => {
{!data?.hideCollect && (
<Button
variant="outline-secondary ms-3"
title={t('question_detail.question_bookmark')}
active={bookmarkState.state}
onClick={handleBookmark}>
<Icon name="bookmark-fill" />
+19 -8
View File
@@ -11,9 +11,16 @@ interface IProps {
size: string;
searchStr?: string;
className?: string;
alt: string;
}
const Index: FC<IProps> = ({ avatar, size, className, searchStr = '' }) => {
const Index: FC<IProps> = ({
avatar,
size,
className,
searchStr = '',
alt,
}) => {
let url = '';
if (typeof avatar === 'string') {
if (avatar.length > 1) {
@@ -31,13 +38,17 @@ const Index: FC<IProps> = ({ avatar, size, className, searchStr = '' }) => {
className && className.indexOf('rounded') !== -1 ? '' : 'rounded';
return (
<img
src={url || DefaultAvatar}
width={size}
height={size}
className={classNames(roundedCls, className)}
alt=""
/>
<>
{/* eslint-disable jsx-a11y/no-noninteractive-element-to-interactive-role,jsx-a11y/control-has-associated-label */}
<img
role="button"
src={url || DefaultAvatar}
width={size}
height={size}
className={classNames(roundedCls, className)}
alt={alt}
/>
</>
);
};
+2
View File
@@ -33,6 +33,7 @@ const Index: FC<Props> = ({
size={avatarSize}
className={`me-1 ${avatarClass}`}
searchStr={avatarSearchStr}
alt={data?.display_name}
/>
)}
<span className="me-1 text-break">{data?.display_name}</span>
@@ -45,6 +46,7 @@ const Index: FC<Props> = ({
size={avatarSize}
className={`me-1 ${avatarClass}`}
searchStr={avatarSearchStr}
alt={data?.display_name}
/>
)}
<span className="me-1 text-break">{data?.display_name}</span>
+4 -1
View File
@@ -34,7 +34,10 @@ const Index: FC = () => {
--bs-primary-rgb: ${primaryColor.rgb().array().join(',')};
--bs-link-color: ${primaryColor.hex()};
--bs-link-color-rgb: ${primaryColor.rgb().array().join(',')};
--bs-link-hover-color: ${shiftColor(primaryColor, 0.8)};
--bs-link-hover-color: ${shiftColor(primaryColor, 0.8).hex()};
--bs-link-hover-color-rgb: ${shiftColor(primaryColor, 0.8)
.round()
.array()}
}
.nav-pills {
--bs-nav-pills-link-active-bg: ${primaryColor.hex()};
@@ -10,7 +10,7 @@ import { userCenterStore } from '@/stores';
interface Props {
redDot: Type.NotificationStatus | undefined;
userInfo;
userInfo: Type.UserInfoRes;
logOut: () => void;
}
@@ -32,18 +32,30 @@ const Index: FC<Props> = ({ redDot, userInfo, logOut }) => {
<Nav.Link
as={NavLink}
to="/users/notifications/inbox"
title={t('inbox', { keyPrefix: 'notifications' })}
className="icon-link d-flex align-items-center justify-content-center p-0 me-3 position-relative">
<Icon name="bell-fill" className="fs-4" />
{(redDot?.inbox || 0) > 0 && <div className="unread-dot bg-danger" />}
{(redDot?.inbox || 0) > 0 && (
<div className="unread-dot bg-danger">
<span className="visually-hidden">
{t('new_alerts', { keyPrefix: 'notifications' })}
</span>
</div>
)}
</Nav.Link>
<Nav.Link
as={NavLink}
to="/users/notifications/achievement"
title={t('achievement', { keyPrefix: 'notifications' })}
className="icon-link d-flex align-items-center justify-content-center p-0 me-3 position-relative">
<Icon name="trophy-fill" className="fs-4" />
{(redDot?.achievement || 0) > 0 && (
<div className="unread-dot bg-danger" />
<div className="unread-dot bg-danger">
<span className="visually-hidden">
{t('new_alerts', { keyPrefix: 'notifications' })}
</span>
</div>
)}
</Nav.Link>
</Nav>
@@ -54,7 +66,12 @@ const Index: FC<Props> = ({ redDot, userInfo, logOut }) => {
id="dropdown-basic"
as="a"
className="no-toggle pointer">
<Avatar size="36px" avatar={userInfo?.avatar} searchStr="s=96" />
<Avatar
size="36px"
avatar={userInfo?.avatar}
alt={userInfo?.display_name}
searchStr="s=96"
/>
</Dropdown.Toggle>
<Dropdown.Menu>
+5 -4
View File
@@ -45,7 +45,7 @@ const Header: FC = () => {
const siteInfo = siteInfoStore((state) => state.siteInfo);
const brandingInfo = brandingStore((state) => state.branding);
const loginSetting = loginSettingStore((state) => state.login);
const { updateReiview, updateVisible } = sideNavStore();
const { updateReview, updateVisible } = sideNavStore();
const { data: redDot } = useQueryNotificationStatus();
/**
* Automatically append `tag` information when creating a question
@@ -57,7 +57,7 @@ const Header: FC = () => {
}
useEffect(() => {
updateReiview({
updateReview({
can_revision: Boolean(redDot?.can_revision),
revision: Number(redDot?.revision),
});
@@ -126,13 +126,13 @@ const Header: FC = () => {
<img
className="d-none d-lg-block logo me-0"
src={brandingInfo.logo}
alt=""
alt={siteInfo.name}
/>
<img
className="lg-none logo me-0"
src={brandingInfo.mobile_logo || brandingInfo.logo}
alt=""
alt={siteInfo.name}
/>
</>
) : (
@@ -178,6 +178,7 @@ const Header: FC = () => {
className="w-100 maxw-400"
onSubmit={handleSearch}>
<FormControl
type="search"
placeholder={t('header.search.placeholder')}
className="placeholder-search"
value={searchStr}
+1 -1
View File
@@ -17,7 +17,7 @@ const Icon: FC<IProps> = ({
className,
size,
onClick,
title = '',
title,
}) => {
return (
<i
+1 -1
View File
@@ -2,6 +2,6 @@
cursor: zoom-out !important;
}
.img-viewer img:not(a img, img.broken) {
.img-viewer img:not(a img, img.broken, img.invisible) {
cursor: zoom-in;
}
@@ -73,6 +73,7 @@ const Index: React.FC<IProps> = ({
<Button
onClick={refreshImgCode}
variant="outline-secondary"
title={t('refresh', { keyPrefix: 'btns' })}
style={{
borderTopRightRadius: '0.375rem',
borderBottomRightRadius: '0.375rem',
+2 -1
View File
@@ -246,8 +246,9 @@ const TagSelector: FC<IProps> = ({
e.preventDefault();
}}>
<FormControl
placeholder={t('search_tag')}
autoFocus
type="search"
placeholder={t('search_tag')}
value={searchValue}
onChange={handleSearch}
/>
+4
View File
@@ -32,6 +32,7 @@ const Index: FC<Props> = ({
size="40px"
className="me-2 d-none d-md-block"
searchStr="s=96"
alt={data?.display_name}
/>
<Avatar
@@ -39,6 +40,7 @@ const Index: FC<Props> = ({
size="24px"
className="me-2 d-block d-md-none"
searchStr="s=48"
alt={data?.display_name}
/>
</Link>
) : (
@@ -48,6 +50,7 @@ const Index: FC<Props> = ({
size="40px"
className="me-2 d-none d-md-block"
searchStr="s=96"
alt={data?.display_name}
/>
<Avatar
@@ -55,6 +58,7 @@ const Index: FC<Props> = ({
size="24px"
className="me-2 d-block d-md-none"
searchStr="s=48"
alt={data?.display_name}
/>
</>
)}
+2
View File
@@ -201,6 +201,7 @@ const useTagModal = (props: IProps = {}) => {
<Form.Group controlId="displayName" className="mb-3">
<Form.Label>{t('form.fields.display_name.label')}</Form.Label>
<Form.Control
type="text"
value={formData.displayName.value}
onChange={handleDisplayNameChange}
isInvalid={formData.displayName.isInvalid}
@@ -212,6 +213,7 @@ const useTagModal = (props: IProps = {}) => {
<Form.Group controlId="slugName" className="mb-3">
<Form.Label>{t('form.fields.slug_name.label')}</Form.Label>
<Form.Control
type="text"
value={formData.slugName.value}
onChange={handleSlugNameChange}
isInvalid={formData.slugName.isInvalid}
+21
View File
@@ -10,6 +10,27 @@ const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement,
);
/**
* Uniformly hide broken images
*/
document.addEventListener(
'error',
(err) => {
const { target } = err;
if (target === null || !(target instanceof Element)) {
return;
}
if (/IMG/.test(target.nodeName)) {
const altText = target.getAttribute('alt')?.trim();
if (!altText) {
target.classList.add('invisible');
}
}
},
true,
);
root.render(
<React.StrictMode>
<App />
+1 -1
View File
@@ -103,7 +103,7 @@ const Answers: FC = () => {
value={curQuery}
onChange={handleFilter}
size="sm"
type="input"
type="search"
placeholder={t('filter.placeholder')}
style={{ width: '12.25rem' }}
/>
@@ -112,7 +112,7 @@ const Users: FC = () => {
<td className="text-end">
<Dropdown>
<Dropdown.Toggle variant="link" className="no-toggle">
<Icon name="three-dots-vertical" />
<Icon name="three-dots-vertical" title={t('action')} />
</Dropdown.Toggle>
<Dropdown.Menu>
{plugin.enabled ? (
+1 -1
View File
@@ -112,7 +112,7 @@ const Questions: FC = () => {
<Form.Control
value={curQuery}
size="sm"
type="input"
type="search"
placeholder={t('filter.placeholder')}
onChange={handleFilter}
style={{ width: '12.25rem' }}
+2 -1
View File
@@ -193,6 +193,7 @@ const Users: FC = () => {
<Form.Control
size="sm"
type="search"
value={curQuery}
onChange={handleFilter}
placeholder={t('filter.placeholder')}
@@ -270,7 +271,7 @@ const Users: FC = () => {
<td className="text-end">
<Dropdown>
<Dropdown.Toggle variant="link" className="no-toggle">
<Icon name="three-dots-vertical" />
<Icon name="three-dots-vertical" title={t('action')} />
</Dropdown.Toggle>
<Dropdown.Menu>
{showActionPassword ? (
+1
View File
@@ -347,6 +347,7 @@ const Ask = () => {
<Form.Group controlId="title" className="mb-3">
<Form.Label>{t('form.fields.title.label')}</Form.Label>
<Form.Control
type="text"
value={formData.title.value}
isInvalid={formData.title.isInvalid}
onChange={handleTitleChange}
@@ -67,9 +67,11 @@ const Index: FC<Props> = ({
}, 100);
}
}, [data.id, answerRef.current]);
if (!data?.id) {
return null;
}
return (
<div id={data.id} ref={answerRef} className="answer-item py-4">
{data.status === 10 && (
@@ -130,6 +130,7 @@ const Index: FC<Props> = ({
{toggleState ? (
<Form.Control
autoFocus
type="search"
placeholder={t('search')}
value={searchValue}
onChange={handleSearch}
@@ -144,7 +145,12 @@ const Index: FC<Props> = ({
active={idx === currentIndex}
className={idx === 0 ? 'mt-2' : ''}>
<div className="d-flex align-items-center text-nowrap">
<Avatar avatar={p.avatar} size="24" className="rounded-1" />
<Avatar
avatar={p.avatar}
size="24"
alt={p.display_name}
className="rounded-1"
/>
<div className="d-flex flex-wrap text-truncate">
<span className="ms-2 text-truncate">{p.display_name}</span>
<small className="text-secondary text-truncate ms-2">
@@ -119,6 +119,7 @@ const Index: FC<Props> = ({ questionId, readOnly = false }) => {
avatar={user.avatar}
size="20"
className="rounded-1"
alt={user.display_name}
/>
<span className="text-nowrap ms-2">{user.display_name}</span>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
@@ -135,7 +136,12 @@ const Index: FC<Props> = ({ questionId, readOnly = false }) => {
key={user.username}
to={`/users/${user.username}`}
className="mx-2 my-1 d-inline-flex flex-nowrap">
<Avatar avatar={user.avatar} size="24" className="rounded-1" />
<Avatar
avatar={user.avatar}
size="24"
alt={user.display_name}
className="rounded-1"
/>
<small className="text-nowrap ms-2">{user.display_name}</small>
</Link>
);
+2
View File
@@ -147,6 +147,7 @@ const Index = () => {
<Form.Group controlId="display_name" className="mb-3">
<Form.Label>{t('form.fields.display_name.label')}</Form.Label>
<Form.Control
type="text"
value={formData.displayName.value}
isInvalid={formData.displayName.isInvalid}
disabled={role_id !== 2 && role_id !== 3}
@@ -160,6 +161,7 @@ const Index = () => {
<Form.Group controlId="slug_name" className="mb-3">
<Form.Label>{t('form.fields.slug_name.label')}</Form.Label>
<Form.Control
type="text"
value={formData.slugName.value}
isInvalid={formData.slugName.isInvalid}
disabled={role_id !== 2 && role_id !== 3}
+1 -1
View File
@@ -62,7 +62,7 @@ const Tags = () => {
<Form.Control
value={searchTag}
placeholder={t('search_placeholder')}
type="text"
type="search"
onChange={handleChange}
size="sm"
/>
@@ -39,10 +39,20 @@ const Index: FC<Props> = ({ data }) => {
<div className="d-flex flex-column flex-md-row mb-4">
{data?.status !== 'deleted' ? (
<Link to={`/users/${data.username}`} reloadDocument>
<Avatar avatar={data.avatar} size="160px" searchStr="s=256" />
<Avatar
avatar={data.avatar}
size="160px"
searchStr="s=256"
alt={data.display_name}
/>
</Link>
) : (
<Avatar avatar={data.avatar} size="160px" searchStr="s=256" />
<Avatar
avatar={data.avatar}
size="160px"
searchStr="s=256"
alt={data.display_name}
/>
)}
<div className="ms-0 ms-md-4 mt-4 mt-md-0">
@@ -62,29 +62,7 @@ const Index: React.FC<Props> = ({ callback }) => {
const checkValidated = (): boolean => {
let bol = true;
const { name, e_mail, pass } = formData;
if (!name.value) {
bol = false;
formData.name = {
value: '',
isInvalid: true,
errorMsg: t('name.msg.empty'),
};
} else if (/[^a-z0-9\-._]/.test(name.value)) {
bol = false;
formData.name = {
value: name.value,
isInvalid: true,
errorMsg: t('name.msg.character'),
};
} else if ([...name.value].length > 30) {
bol = false;
formData.name = {
value: name.value,
isInvalid: true,
errorMsg: t('name.msg.range'),
};
}
const { e_mail, pass } = formData;
if (!e_mail.value) {
bol = false;
@@ -353,6 +353,7 @@ const Index: React.FC = () => {
count > 0 ? `&t=${new Date().valueOf()}` : ''
}`}
className="me-3 rounded"
alt={formData.display_name.value}
/>
<Form.Text className="text-muted mt-1">
<span>{t('avatar.gravatar_text')}</span>
@@ -381,6 +382,7 @@ const Index: React.FC = () => {
searchStr="s=256"
avatar={formData.avatar.custom}
className="me-2 bg-gray-300 "
alt={formData.display_name.value}
/>
<ButtonGroup vertical className="fit-content">
<UploadImg
@@ -405,7 +407,11 @@ const Index: React.FC = () => {
</Stack>
)}
{formData.avatar.type === 'default' && (
<Avatar size="160px" avatar="" />
<Avatar
size="160px"
avatar=""
alt={formData.display_name.value}
/>
)}
</div>
</ImgViewer>
+1
View File
@@ -55,6 +55,7 @@ const Users = () => {
size="48px"
avatar={user?.avatar}
searchStr="s=96"
alt={user.display_name}
/>
</Link>
<div className="ms-2">
+2 -2
View File
@@ -10,7 +10,7 @@ interface ErrorCodeType {
can_revision: boolean;
revision: number;
updateVisible: () => void;
updateReiview: (params: reviewData) => void;
updateReview: (params: reviewData) => void;
}
const Index = create<ErrorCodeType>((set) => ({
@@ -22,7 +22,7 @@ const Index = create<ErrorCodeType>((set) => ({
return { visible: !state.visible };
});
},
updateReiview: (params: reviewData) => {
updateReview: (params: reviewData) => {
set(() => {
return { ...params };
});