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:
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:
label: Email
msg: Email is not valid.

View File

@ -321,6 +321,7 @@ export interface SiteSettings {
general: AdminSettingsGeneral;
interface: AdminSettingsInterface;
login: AdminSettingsLogin;
custom_css_html: AdminSettingsCustom;
}
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';
let scrollTop = 0;
marked.setOptions({
breaks: true,
});
const Index = ({ value }, ref) => {
const [html, setHtml] = useState('');

View File

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

View File

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

View File

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

View File

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

View File

@ -45,7 +45,10 @@ const useAddUserModal = (props: IProps = {}) => {
display_name: {
'ui:options': {
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 true;

View File

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

View File

@ -5,7 +5,7 @@ import { Helmet, HelmetProvider } from 'react-helmet-async';
import { SWRConfig } from 'swr';
import { toastStore, brandingStore, pageTagStore } from '@/stores';
import { Header, Footer, Toast } from '@/components';
import { Header, Footer, Toast, Customize } from '@/components';
const Layout: FC = () => {
const { msg: toastMsg, variant, clear: toastClear } = toastStore();
@ -41,6 +41,7 @@ const Layout: FC = () => {
</div>
<Toast msg={toastMsg} variant={variant} onClose={closeToast} />
<Footer />
<Customize />
</SWRConfig>
</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 brandingStore from './branding';
import pageTagStore from './pageTags';
import customizeStore from './customize';
export {
toastStore,
@ -15,4 +16,5 @@ export {
brandingStore,
pageTagStore,
loginSettingStore,
customizeStore,
};

View File

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