mirror of https://gitee.com/answerdev/answer.git
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:
commit
ed242e7569
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
|
@ -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('');
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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;
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue