Merge branch 'feat/ui-0.7.0' into 'test'

Feat/ui 0.7.0

See merge request opensource/answer!332
This commit is contained in:
Ren Yubin 2022-12-13 09:43:22 +00:00
commit ed242e7569
14 changed files with 145 additions and 8 deletions

View File

@ -1127,7 +1127,7 @@ ui:
fields: fields:
display_name: display_name:
label: Display Name label: Display Name
msg: display_name must be at maximum 30 characters in length. msg: display_name must be at 4 - 30 characters in length.
email: email:
label: Email label: Email
msg: Email is not valid. msg: Email is not valid.

View File

@ -321,6 +321,7 @@ export interface SiteSettings {
general: AdminSettingsGeneral; general: AdminSettingsGeneral;
interface: AdminSettingsInterface; interface: AdminSettingsInterface;
login: AdminSettingsLogin; login: AdminSettingsLogin;
custom_css_html: AdminSettingsCustom;
} }
export interface AdminSettingBranding { export interface AdminSettingBranding {

View File

@ -0,0 +1,84 @@
import { FC, memo, useEffect } from 'react';
import { customizeStore } from '@/stores';
const getElementByAttr = (attr: string, elName: string) => {
let el = document.querySelector(`[${attr}]`);
if (!el) {
el = document.createElement(elName);
el.setAttribute(attr, '');
}
return el;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const textToDf = (t) => {
const dummyDoc = document.createElement('div');
dummyDoc.innerHTML = t;
const frag = document.createDocumentFragment();
while (dummyDoc.childNodes.length) {
frag.appendChild(dummyDoc.children[0]);
}
return frag;
};
const injectCustomCSS = (t: string) => {
if (!t) {
return;
}
const el = getElementByAttr('data-custom-css', 'style');
el.textContent = t;
document.head.insertBefore(el, document.head.lastChild);
};
const injectCustomHead = (t: string) => {
if (!t) {
return;
}
setTimeout(() => {
const el = getElementByAttr('data-custom-head', 'style');
el.textContent = t;
document.head.appendChild(el);
}, 200);
};
const injectCustomHeader = (t: string) => {
if (!t) {
return;
}
// const frag = textToDf(t);
t = ' Customize Header ';
document.body.insertBefore(
document.createComment(t),
document.body.firstChild,
);
};
const injectCustomFooter = (t: string) => {
if (!t) {
return;
}
// FIXME
t = ' Customize Footer ';
// const frag = textToDf(t);
document.documentElement.appendChild(document.createComment(t));
};
const Index: FC = () => {
const { custom_css, custom_head, custom_header, custom_footer } =
customizeStore((state) => state);
useEffect(() => {
injectCustomCSS(custom_css);
injectCustomHead(custom_head);
injectCustomHeader(custom_header);
injectCustomFooter(custom_footer);
}, []);
return (
<>
{null}
{/* App customize */}
</>
);
};
export default memo(Index);

View File

@ -12,6 +12,9 @@ import { marked } from 'marked';
import { htmlRender } from './utils'; import { htmlRender } from './utils';
let scrollTop = 0; let scrollTop = 0;
marked.setOptions({
breaks: true,
});
const Index = ({ value }, ref) => { const Index = ({ value }, ref) => {
const [html, setHtml] = useState(''); const [html, setHtml] = useState('');

View File

@ -46,7 +46,7 @@ const Index: FC = () => {
{t('save')} {t('save')}
</Button> </Button>
</Card.Header> </Card.Header>
<Card.Body className="my-n1"> <Card.Body>
<TagSelector <TagSelector
value={followingTags} value={followingTags}
onChange={handleTagsChange} onChange={handleTagsChange}
@ -67,14 +67,14 @@ const Index: FC = () => {
{t('edit')} {t('edit')}
</Button> </Button>
</Card.Header> </Card.Header>
<Card.Body className="m-n1"> <Card.Body>
{followingTags?.length ? ( {followingTags?.length ? (
<> <div className="m-n1">
{followingTags.map((item) => { {followingTags.map((item) => {
const slugName = item?.slug_name; const slugName = item?.slug_name;
return <Tag key={slugName} className="m-1" data={item} />; return <Tag key={slugName} className="m-1" data={item} />;
})} })}
</> </div>
) : ( ) : (
<> <>
<div className="text-muted">{t('follow_tag_tip')}</div> <div className="text-muted">{t('follow_tag_tip')}</div>

View File

@ -58,6 +58,7 @@ export interface UISchema {
| 'url' | 'url'
| 'week'; | 'week';
empty?: string; empty?: string;
className?: string | string[];
validator?: ( validator?: (
value, value,
formData?, formData?,
@ -459,6 +460,7 @@ const SchemaForm: ForwardRefRenderFunction<IRef, IProps> = (
onChange={handleInputChange} onChange={handleInputChange}
isInvalid={formData[key].isInvalid} isInvalid={formData[key].isInvalid}
rows={options?.rows || 3} rows={options?.rows || 3}
className={classnames(options.className)}
/> />
<Form.Control.Feedback type="invalid"> <Form.Control.Feedback type="invalid">
{formData[key]?.errorMsg} {formData[key]?.errorMsg}

View File

@ -181,7 +181,7 @@ const TagSelector: FC<IProps> = ({
onFocus={onFocus} onFocus={onFocus}
onBlur={onBlur} onBlur={onBlur}
onKeyDown={handleKeyDown}> onKeyDown={handleKeyDown}>
<div className="d-flex flex-wrap mx-n1"> <div className="d-flex flex-wrap m-n1">
{initialValue?.map((item, index) => { {initialValue?.map((item, index) => {
return ( return (
<Button <Button

View File

@ -27,6 +27,7 @@ import BrandUpload from './BrandUpload';
import SchemaForm, { JSONSchema, UISchema, initFormData } from './SchemaForm'; import SchemaForm, { JSONSchema, UISchema, initFormData } from './SchemaForm';
import Labels from './LabelsCard'; import Labels from './LabelsCard';
import DiffContent from './DiffContent'; import DiffContent from './DiffContent';
import Customize from './Customize';
export { export {
Avatar, Avatar,
@ -60,5 +61,6 @@ export {
initFormData, initFormData,
Labels, Labels,
DiffContent, DiffContent,
Customize,
}; };
export type { EditorRef, JSONSchema, UISchema }; export type { EditorRef, JSONSchema, UISchema };

View File

@ -45,7 +45,10 @@ const useAddUserModal = (props: IProps = {}) => {
display_name: { display_name: {
'ui:options': { 'ui:options': {
validator: (value) => { validator: (value) => {
if (value.length > 30) { const MIN_LENGTH = 4;
const MAX_LENGTH = 30;
if (value.length < MIN_LENGTH || value.length > MAX_LENGTH) {
return t('form.fields.display_name.msg'); return t('form.fields.display_name.msg');
} }
return true; return true;

View File

@ -42,24 +42,28 @@ const Index: FC = () => {
'ui:widget': 'textarea', 'ui:widget': 'textarea',
'ui:options': { 'ui:options': {
rows: 10, rows: 10,
className: ['fs-14', 'font-monospace'],
}, },
}, },
custom_head: { custom_head: {
'ui:widget': 'textarea', 'ui:widget': 'textarea',
'ui:options': { 'ui:options': {
rows: 10, rows: 10,
className: ['fs-14', 'font-monospace'],
}, },
}, },
custom_header: { custom_header: {
'ui:widget': 'textarea', 'ui:widget': 'textarea',
'ui:options': { 'ui:options': {
rows: 10, rows: 10,
className: ['fs-14', 'font-monospace'],
}, },
}, },
custom_footer: { custom_footer: {
'ui:widget': 'textarea', 'ui:widget': 'textarea',
'ui:options': { 'ui:options': {
rows: 10, rows: 10,
className: ['fs-14', 'font-monospace'],
}, },
}, },
}; };
@ -82,6 +86,9 @@ const Index: FC = () => {
msg: t('update', { keyPrefix: 'toast' }), msg: t('update', { keyPrefix: 'toast' }),
variant: 'success', variant: 'success',
}); });
setTimeout(() => {
window.location.reload();
}, 3000);
}) })
.catch((err) => { .catch((err) => {
if (err.isError) { if (err.isError) {

View File

@ -5,7 +5,7 @@ import { Helmet, HelmetProvider } from 'react-helmet-async';
import { SWRConfig } from 'swr'; import { SWRConfig } from 'swr';
import { toastStore, brandingStore, pageTagStore } from '@/stores'; import { toastStore, brandingStore, pageTagStore } from '@/stores';
import { Header, Footer, Toast } from '@/components'; import { Header, Footer, Toast, Customize } from '@/components';
const Layout: FC = () => { const Layout: FC = () => {
const { msg: toastMsg, variant, clear: toastClear } = toastStore(); const { msg: toastMsg, variant, clear: toastClear } = toastStore();
@ -41,6 +41,7 @@ const Layout: FC = () => {
</div> </div>
<Toast msg={toastMsg} variant={variant} onClose={closeToast} /> <Toast msg={toastMsg} variant={variant} onClose={closeToast} />
<Footer /> <Footer />
<Customize />
</SWRConfig> </SWRConfig>
</HelmetProvider> </HelmetProvider>
); );

View File

@ -0,0 +1,30 @@
import create from 'zustand';
interface IType {
custom_css: string;
custom_head: string;
custom_header: string;
custom_footer: string;
update: (params: {
custom_css?: string;
custom_head?: string;
custom_header?: string;
custom_footer?: string;
}) => void;
}
const loginSetting = create<IType>((set) => ({
custom_css: '',
custom_head: '',
custom_header: '',
custom_footer: '',
update: (params) =>
set((state) => {
return {
...state,
...params,
};
}),
}));
export default loginSetting;

View File

@ -6,6 +6,7 @@ import siteInfoStore from './siteInfo';
import interfaceStore from './interface'; import interfaceStore from './interface';
import brandingStore from './branding'; import brandingStore from './branding';
import pageTagStore from './pageTags'; import pageTagStore from './pageTags';
import customizeStore from './customize';
export { export {
toastStore, toastStore,
@ -15,4 +16,5 @@ export {
brandingStore, brandingStore,
pageTagStore, pageTagStore,
loginSettingStore, loginSettingStore,
customizeStore,
}; };

View File

@ -5,6 +5,7 @@ import {
interfaceStore, interfaceStore,
brandingStore, brandingStore,
loginSettingStore, loginSettingStore,
customizeStore,
} from '@/stores'; } from '@/stores';
import { RouteAlias } from '@/router/alias'; import { RouteAlias } from '@/router/alias';
import Storage from '@/utils/storage'; import Storage from '@/utils/storage';
@ -256,6 +257,7 @@ export const initAppSettingsStore = async () => {
interfaceStore.getState().update(appSettings.interface); interfaceStore.getState().update(appSettings.interface);
brandingStore.getState().update(appSettings.branding); brandingStore.getState().update(appSettings.branding);
loginSettingStore.getState().update(appSettings.login); loginSettingStore.getState().update(appSettings.login);
customizeStore.getState().update(appSettings.custom_css_html);
} }
}; };