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!337
This commit is contained in:
commit
3468cba95b
|
@ -1189,9 +1189,6 @@ ui:
|
||||||
msg: Contact email cannot be empty.
|
msg: Contact email cannot be empty.
|
||||||
validate: Contact email is not valid.
|
validate: Contact email is not valid.
|
||||||
text: Email address of key contact responsible for this site.
|
text: Email address of key contact responsible for this site.
|
||||||
permalink:
|
|
||||||
label: Permalink
|
|
||||||
text: Custom URL structures can improve the usability, and forward-compatibility of your links.
|
|
||||||
interface:
|
interface:
|
||||||
page_title: Interface
|
page_title: Interface
|
||||||
logo:
|
logo:
|
||||||
|
@ -1288,6 +1285,9 @@ ui:
|
||||||
text: "Reserved tags can only be added to a post by moderator."
|
text: "Reserved tags can only be added to a post by moderator."
|
||||||
seo:
|
seo:
|
||||||
page_title: SEO
|
page_title: SEO
|
||||||
|
permalink:
|
||||||
|
label: Permalink
|
||||||
|
text: Custom URL structures can improve the usability, and forward-compatibility of your links.
|
||||||
robots:
|
robots:
|
||||||
label: robots.txt
|
label: robots.txt
|
||||||
text: This will permanently override any related site settings.
|
text: This will permanently override any related site settings.
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"bootstrap-icons": "1.10.2",
|
"bootstrap-icons": "1.10.2",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"codemirror": "5.65.0",
|
"codemirror": "5.65.0",
|
||||||
|
"color": "^4.2.3",
|
||||||
"copy-to-clipboard": "^3.3.2",
|
"copy-to-clipboard": "^3.3.2",
|
||||||
"dayjs": "^1.11.5",
|
"dayjs": "^1.11.5",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
|
|
|
@ -22,6 +22,7 @@ specifiers:
|
||||||
bootstrap-icons: 1.10.2
|
bootstrap-icons: 1.10.2
|
||||||
classnames: ^2.3.1
|
classnames: ^2.3.1
|
||||||
codemirror: 5.65.0
|
codemirror: 5.65.0
|
||||||
|
color: ^4.2.3
|
||||||
copy-to-clipboard: ^3.3.2
|
copy-to-clipboard: ^3.3.2
|
||||||
customize-cra: ^1.0.0
|
customize-cra: ^1.0.0
|
||||||
dayjs: ^1.11.5
|
dayjs: ^1.11.5
|
||||||
|
@ -74,6 +75,7 @@ dependencies:
|
||||||
bootstrap-icons: 1.10.2
|
bootstrap-icons: 1.10.2
|
||||||
classnames: 2.3.2
|
classnames: 2.3.2
|
||||||
codemirror: 5.65.0
|
codemirror: 5.65.0
|
||||||
|
color: 4.2.3
|
||||||
copy-to-clipboard: 3.3.2
|
copy-to-clipboard: 3.3.2
|
||||||
dayjs: 1.11.5
|
dayjs: 1.11.5
|
||||||
diff: 5.1.0
|
diff: 5.1.0
|
||||||
|
@ -3679,6 +3681,21 @@ packages:
|
||||||
/color-name/1.1.4:
|
/color-name/1.1.4:
|
||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
|
||||||
|
/color-string/1.9.1:
|
||||||
|
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
||||||
|
dependencies:
|
||||||
|
color-name: 1.1.4
|
||||||
|
simple-swizzle: 0.2.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/color/4.2.3:
|
||||||
|
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||||
|
engines: {node: '>=12.5.0'}
|
||||||
|
dependencies:
|
||||||
|
color-convert: 2.0.1
|
||||||
|
color-string: 1.9.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/colord/2.9.3:
|
/colord/2.9.3:
|
||||||
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
|
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
|
||||||
|
|
||||||
|
@ -6294,6 +6311,10 @@ packages:
|
||||||
/is-arrayish/0.2.1:
|
/is-arrayish/0.2.1:
|
||||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||||
|
|
||||||
|
/is-arrayish/0.3.2:
|
||||||
|
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/is-bigint/1.0.4:
|
/is-bigint/1.0.4:
|
||||||
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
|
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -9615,6 +9636,12 @@ packages:
|
||||||
/signal-exit/3.0.7:
|
/signal-exit/3.0.7:
|
||||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||||
|
|
||||||
|
/simple-swizzle/0.2.2:
|
||||||
|
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
|
||||||
|
dependencies:
|
||||||
|
is-arrayish: 0.3.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/sisteransi/1.0.5:
|
/sisteransi/1.0.5:
|
||||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||||
|
|
||||||
|
|
|
@ -279,12 +279,6 @@ export interface AdminSettingsGeneral {
|
||||||
description: string;
|
description: string;
|
||||||
site_url: string;
|
site_url: string;
|
||||||
contact_email: string;
|
contact_email: string;
|
||||||
/**
|
|
||||||
* 0: not set
|
|
||||||
* 1:with title
|
|
||||||
* 2: no title
|
|
||||||
*/
|
|
||||||
permalink: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HelmetBase {
|
export interface HelmetBase {
|
||||||
|
@ -300,7 +294,6 @@ export interface HelmetUpdate extends Omit<HelmetBase, 'pageTitle'> {
|
||||||
|
|
||||||
export interface AdminSettingsInterface {
|
export interface AdminSettingsInterface {
|
||||||
language: string;
|
language: string;
|
||||||
theme: string;
|
|
||||||
time_zone?: string;
|
time_zone?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,6 +315,8 @@ export interface SiteSettings {
|
||||||
interface: AdminSettingsInterface;
|
interface: AdminSettingsInterface;
|
||||||
login: AdminSettingsLogin;
|
login: AdminSettingsLogin;
|
||||||
custom_css_html: AdminSettingsCustom;
|
custom_css_html: AdminSettingsCustom;
|
||||||
|
theme: AdminSettingsTheme;
|
||||||
|
site_seo: AdminSettingsSeo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminSettingBranding {
|
export interface AdminSettingBranding {
|
||||||
|
@ -346,6 +341,12 @@ export interface AdminSettingsWrite {
|
||||||
|
|
||||||
export interface AdminSettingsSeo {
|
export interface AdminSettingsSeo {
|
||||||
robots: string;
|
robots: string;
|
||||||
|
/**
|
||||||
|
* 0: not set
|
||||||
|
* 1:with title
|
||||||
|
* 2: no title
|
||||||
|
*/
|
||||||
|
permalink: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type themeConfig = {
|
export type themeConfig = {
|
||||||
|
@ -355,6 +356,7 @@ export type themeConfig = {
|
||||||
};
|
};
|
||||||
export interface AdminSettingsTheme {
|
export interface AdminSettingsTheme {
|
||||||
theme: string;
|
theme: string;
|
||||||
|
theme_options?: { label: string; value: string }[];
|
||||||
theme_config: Record<string, themeConfig>;
|
theme_config: Record<string, themeConfig>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { Helmet } from 'react-helmet-async';
|
||||||
|
|
||||||
|
import Color from 'color';
|
||||||
|
|
||||||
|
import { shiftColor, tintColor, shadeColor } from '@/utils';
|
||||||
|
import { themeSettingStore } from '@/stores';
|
||||||
|
|
||||||
|
const Index: FC = () => {
|
||||||
|
const { theme, theme_config } = themeSettingStore((_) => _);
|
||||||
|
let primaryColor;
|
||||||
|
if (theme_config[theme]?.primary_color) {
|
||||||
|
primaryColor = Color(theme_config[theme].primary_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Helmet>
|
||||||
|
{primaryColor && (
|
||||||
|
<style>
|
||||||
|
{`
|
||||||
|
:root {
|
||||||
|
--bs-blue: ${primaryColor.hex()};
|
||||||
|
--bs-primary: ${primaryColor.hex()};
|
||||||
|
--bs-primary-rgb: ${primaryColor.rgb().array().join(',')};
|
||||||
|
--bs-link-color: ${primaryColor.hex()};
|
||||||
|
--bs-link-hover-color: ${shiftColor(primaryColor, 0.8)};
|
||||||
|
}
|
||||||
|
.nav-pills {
|
||||||
|
--bs-nav-pills-link-active-bg: ${primaryColor.hex()};
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
--bs-btn-bg: ${primaryColor.hex()};
|
||||||
|
--bs-btn-border-color: ${primaryColor.hex()};
|
||||||
|
--bs-btn-hover-bg: ${tintColor(primaryColor, 0.85)};
|
||||||
|
--bs-btn-hover-border-color: ${tintColor(primaryColor, 0.9)};
|
||||||
|
--bs-btn-focus-shadow-rgb: ${shadeColor(primaryColor, 0.85)};
|
||||||
|
--bs-btn-active-bg: ${tintColor(primaryColor, 0.8)};
|
||||||
|
--bs-btn-active-border-color: ${tintColor(primaryColor, 0.9)};
|
||||||
|
--bs-btn-disabled-bg: ${primaryColor.hex()};
|
||||||
|
--bs-btn-disabled-border-color: ${primaryColor.hex()};
|
||||||
|
}
|
||||||
|
.btn-outline-primary {
|
||||||
|
--bs-btn-color: ${primaryColor.hex()};
|
||||||
|
--bs-btn-border-color: ${primaryColor.hex()};
|
||||||
|
--bs-btn-hover-bg: ${primaryColor.hex()};
|
||||||
|
--bs-btn-hover-border-color: ${primaryColor.hex()};
|
||||||
|
--bs-btn-active-bg: ${primaryColor.hex()};
|
||||||
|
--bs-btn-active-border-color: ${primaryColor.hex()};
|
||||||
|
--bs-btn-disabled-color: ${primaryColor.hex()};
|
||||||
|
--bs-btn-disabled-border-color: ${primaryColor.hex()};
|
||||||
|
}
|
||||||
|
.pagination {
|
||||||
|
--bs-btn-color: ${primaryColor.hex()};
|
||||||
|
--bs-pagination-active-bg: ${primaryColor.hex()};
|
||||||
|
--bs-pagination-active-border-color: ${primaryColor.hex()};
|
||||||
|
}
|
||||||
|
.form-select:focus,
|
||||||
|
.form-control:focus {
|
||||||
|
box-shadow: 0 0 0 0.25rem ${primaryColor.fade(0.75).string()};
|
||||||
|
border-color: ${tintColor(primaryColor, 0.5)};
|
||||||
|
}
|
||||||
|
.badge-tag:not(.badge-tag-reserved, .badge-tag-required) {
|
||||||
|
color: ${shadeColor(primaryColor, 0.4)};
|
||||||
|
background: ${tintColor(primaryColor, 0.8).fade(0.5).string()};
|
||||||
|
}
|
||||||
|
.badge-tag:hover:not(.badge-tag-reserved, .badge-tag-required) {
|
||||||
|
background: ${tintColor(primaryColor, 0.8).hex()};
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
)}
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Index;
|
|
@ -20,9 +20,7 @@ const Index: FC<Props> = ({ redDot, userInfo, logOut }) => {
|
||||||
as={NavLink}
|
as={NavLink}
|
||||||
to="/users/notifications/inbox"
|
to="/users/notifications/inbox"
|
||||||
className="icon-link d-flex align-items-center justify-content-center p-0 me-3 position-relative">
|
className="icon-link d-flex align-items-center justify-content-center p-0 me-3 position-relative">
|
||||||
<div className="text-white text-opacity-75">
|
|
||||||
<Icon name="bell-fill" className="fs-4" />
|
<Icon name="bell-fill" className="fs-4" />
|
||||||
</div>
|
|
||||||
{(redDot?.inbox || 0) > 0 && <div className="unread-dot bg-danger" />}
|
{(redDot?.inbox || 0) > 0 && <div className="unread-dot bg-danger" />}
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
|
|
||||||
|
@ -30,9 +28,7 @@ const Index: FC<Props> = ({ redDot, userInfo, logOut }) => {
|
||||||
as={Link}
|
as={Link}
|
||||||
to="/users/notifications/achievement"
|
to="/users/notifications/achievement"
|
||||||
className="icon-link d-flex align-items-center justify-content-center p-0 me-3 position-relative">
|
className="icon-link d-flex align-items-center justify-content-center p-0 me-3 position-relative">
|
||||||
<div className="text-white text-opacity-75">
|
|
||||||
<Icon name="trophy-fill" className="fs-4" />
|
<Icon name="trophy-fill" className="fs-4" />
|
||||||
</div>
|
|
||||||
{(redDot?.achievement || 0) > 0 && (
|
{(redDot?.achievement || 0) > 0 && (
|
||||||
<div className="unread-dot bg-danger" />
|
<div className="unread-dot bg-danger" />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,32 +1,20 @@
|
||||||
@import 'bootstrap/scss/functions';
|
@import 'bootstrap/scss/functions';
|
||||||
@import 'bootstrap/scss/variables';
|
@import 'bootstrap/scss/variables';
|
||||||
#header {
|
#header {
|
||||||
background: linear-gradient(180deg, #0033FF 0%, rgba(0, 51, 255, 0.95) 100%);
|
|
||||||
--bs-navbar-padding-y: 0.75rem;
|
--bs-navbar-padding-y: 0.75rem;
|
||||||
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15), $box-shadow-sm;
|
|
||||||
.logo {
|
.logo {
|
||||||
max-height: 1.75rem;
|
max-height: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link {
|
.nav-link {
|
||||||
color: rgba(255, 255, 255, 0.7);
|
|
||||||
&.active {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
&.icon-link {
|
&.icon-link {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-search {
|
.placeholder-search {
|
||||||
background-color: rgba(255, 255, 255, .2);
|
box-shadow: none;
|
||||||
border: $border-width $border-style rgba(255, 255, 255, .2);
|
|
||||||
&:focus {
|
|
||||||
border: $border-width $border-style $border-color;
|
|
||||||
}
|
|
||||||
&::placeholder {
|
|
||||||
color: rgba(255, 255, 255, 0.75);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.answer-navBar {
|
.answer-navBar {
|
||||||
|
@ -45,6 +33,53 @@
|
||||||
.hr {
|
.hr {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// style for colored navbar
|
||||||
|
&.theme-colored {
|
||||||
|
background: linear-gradient(180deg, #0033FF 0%, rgba(0, 51, 255, 0.95) 100%);
|
||||||
|
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15), $box-shadow-sm;
|
||||||
|
.nav-link {
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
&.active {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.placeholder-search {
|
||||||
|
color: #fff;
|
||||||
|
background-color: rgba(255, 255, 255, .2);
|
||||||
|
border: $border-width $border-style rgba(255, 255, 255, .2);
|
||||||
|
&:focus {
|
||||||
|
border: $border-width $border-style $border-color;
|
||||||
|
}
|
||||||
|
&::placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// style for light navbar
|
||||||
|
&.theme-light {
|
||||||
|
background: linear-gradient(180deg, #FFFFFF 0%, rgba(255, 255, 255, 0.95) 100%);
|
||||||
|
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15), 0 0.125rem 0.25rem rgb(0 0 0 / 8%);
|
||||||
|
.nav-link {
|
||||||
|
color: rgba(0, 0, 0, 0.55);
|
||||||
|
&.active {
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgba(0, 0, 0, 0.75)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.placeholder-search {
|
||||||
|
color: rgba(0, 0, 0, 0.75);
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #CED4DA;
|
||||||
|
&:focus {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.75);
|
||||||
|
}
|
||||||
|
&::placeholder {
|
||||||
|
color: #6C757D
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,7 +87,7 @@
|
||||||
#header {
|
#header {
|
||||||
.logo {
|
.logo {
|
||||||
max-width: 93px;
|
max-width: 93px;
|
||||||
max-height: auto;
|
max-height: initial;
|
||||||
}
|
}
|
||||||
.nav-grow {
|
.nav-grow {
|
||||||
flex-grow: 1!important;
|
flex-grow: 1!important;
|
||||||
|
@ -73,7 +108,7 @@
|
||||||
#header {
|
#header {
|
||||||
.logo {
|
.logo {
|
||||||
max-width: 93px;
|
max-width: 93px;
|
||||||
max-height: auto;
|
max-height: initial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,14 @@ import {
|
||||||
useLocation,
|
useLocation,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
|
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
loggedUserInfoStore,
|
loggedUserInfoStore,
|
||||||
siteInfoStore,
|
siteInfoStore,
|
||||||
brandingStore,
|
brandingStore,
|
||||||
loginSettingStore,
|
loginSettingStore,
|
||||||
|
themeSettingStore,
|
||||||
} from '@/stores';
|
} from '@/stores';
|
||||||
import { logout, useQueryNotificationStatus } from '@/services';
|
import { logout, useQueryNotificationStatus } from '@/services';
|
||||||
import { RouteAlias } from '@/router/alias';
|
import { RouteAlias } from '@/router/alias';
|
||||||
|
@ -41,6 +44,7 @@ const Header: FC = () => {
|
||||||
const siteInfo = siteInfoStore((state) => state.siteInfo);
|
const siteInfo = siteInfoStore((state) => state.siteInfo);
|
||||||
const brandingInfo = brandingStore((state) => state.branding);
|
const brandingInfo = brandingStore((state) => state.branding);
|
||||||
const loginSetting = loginSettingStore((state) => state.login);
|
const loginSetting = loginSettingStore((state) => state.login);
|
||||||
|
const { theme, theme_config } = themeSettingStore((_) => _);
|
||||||
const { data: redDot } = useQueryNotificationStatus();
|
const { data: redDot } = useQueryNotificationStatus();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const handleInput = (val) => {
|
const handleInput = (val) => {
|
||||||
|
@ -69,8 +73,16 @@ const Header: FC = () => {
|
||||||
}
|
}
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
|
let themeType = 'theme-colored';
|
||||||
|
if (theme && theme_config[theme]) {
|
||||||
|
themeType = `theme-${theme_config[theme].navbar_style}`;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar variant="dark" expand="lg" className="sticky-top" id="header">
|
<Navbar
|
||||||
|
expand="lg"
|
||||||
|
className={classnames('sticky-top', themeType)}
|
||||||
|
id="header">
|
||||||
<Container className="d-flex align-items-center">
|
<Container className="d-flex align-items-center">
|
||||||
<Navbar.Toggle
|
<Navbar.Toggle
|
||||||
aria-controls="navBarContent"
|
aria-controls="navBarContent"
|
||||||
|
@ -105,14 +117,15 @@ const Header: FC = () => {
|
||||||
<NavItems redDot={redDot} userInfo={user} logOut={handleLogout} />
|
<NavItems redDot={redDot} userInfo={user} logOut={handleLogout} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button variant="link" className="me-2" href="/users/login">
|
||||||
variant="link"
|
|
||||||
className="me-2 text-white"
|
|
||||||
href="/users/login">
|
|
||||||
{t('btns.login')}
|
{t('btns.login')}
|
||||||
</Button>
|
</Button>
|
||||||
{loginSetting.allow_new_registrations && (
|
{loginSetting.allow_new_registrations && (
|
||||||
<Button variant="light" href="/users/register">
|
<Button
|
||||||
|
variant={
|
||||||
|
themeType === 'theme-colored' ? 'light' : 'primary'
|
||||||
|
}
|
||||||
|
href="/users/register">
|
||||||
{t('btns.signup')}
|
{t('btns.signup')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
@ -142,7 +155,7 @@ const Header: FC = () => {
|
||||||
<Form action="/search" className="w-75 px-0 px-lg-2">
|
<Form action="/search" className="w-75 px-0 px-lg-2">
|
||||||
<FormControl
|
<FormControl
|
||||||
placeholder={t('header.search.placeholder')}
|
placeholder={t('header.search.placeholder')}
|
||||||
className="text-white placeholder-search"
|
className="placeholder-search"
|
||||||
value={searchStr}
|
value={searchStr}
|
||||||
name="q"
|
name="q"
|
||||||
onChange={(e) => handleInput(e.target.value)}
|
onChange={(e) => handleInput(e.target.value)}
|
||||||
|
@ -166,7 +179,10 @@ const Header: FC = () => {
|
||||||
<Nav.Item className="me-3">
|
<Nav.Item className="me-3">
|
||||||
<Link
|
<Link
|
||||||
to="/questions/ask"
|
to="/questions/ask"
|
||||||
className="text-capitalize text-nowrap btn btn-light">
|
className={classnames('text-capitalize text-nowrap btn', {
|
||||||
|
'btn-light': themeType !== 'theme-light',
|
||||||
|
'btn-primary': themeType === 'theme-light',
|
||||||
|
})}>
|
||||||
{t('btns.add_question')}
|
{t('btns.add_question')}
|
||||||
</Link>
|
</Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
|
@ -181,12 +197,19 @@ const Header: FC = () => {
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
className="me-2 text-white"
|
className={classnames('me-2', {
|
||||||
|
'link-light': themeType === 'theme-colored',
|
||||||
|
'link-primary': themeType !== 'theme-colored',
|
||||||
|
})}
|
||||||
href="/users/login">
|
href="/users/login">
|
||||||
{t('btns.login')}
|
{t('btns.login')}
|
||||||
</Button>
|
</Button>
|
||||||
{loginSetting.allow_new_registrations && (
|
{loginSetting.allow_new_registrations && (
|
||||||
<Button variant="light" href="/users/register">
|
<Button
|
||||||
|
variant={
|
||||||
|
themeType === 'theme-colored' ? 'light' : 'primary'
|
||||||
|
}
|
||||||
|
href="/users/register">
|
||||||
{t('btns.signup')}
|
{t('btns.signup')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -28,6 +28,7 @@ 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';
|
import Customize from './Customize';
|
||||||
|
import CustomizeTheme from './CustomizeTheme';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
@ -62,5 +63,6 @@ export {
|
||||||
Labels,
|
Labels,
|
||||||
DiffContent,
|
DiffContent,
|
||||||
Customize,
|
Customize,
|
||||||
|
CustomizeTheme,
|
||||||
};
|
};
|
||||||
export type { EditorRef, JSONSchema, UISchema };
|
export type { EditorRef, JSONSchema, UISchema };
|
||||||
|
|
|
@ -66,7 +66,7 @@ a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
background: rgba($blue-100, 0.5);
|
background: rgba($blue-100, 0.5);
|
||||||
padding: 0px 7px 1px;
|
padding: 0 7px 1px;
|
||||||
color: $blue-700;
|
color: $blue-700;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -46,14 +46,6 @@ const General: FC = () => {
|
||||||
title: t('contact_email.label'),
|
title: t('contact_email.label'),
|
||||||
description: t('contact_email.text'),
|
description: t('contact_email.text'),
|
||||||
},
|
},
|
||||||
permalink: {
|
|
||||||
type: 'number',
|
|
||||||
title: t('permalink.label'),
|
|
||||||
description: t('permalink.text'),
|
|
||||||
enum: [1, 2],
|
|
||||||
enumNames: ['/questions/123/post-title', '/questions/123'],
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const uiSchema: UISchema = {
|
const uiSchema: UISchema = {
|
||||||
|
@ -92,9 +84,6 @@ const General: FC = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
permalink: {
|
|
||||||
'ui:widget': 'select',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const [formData, setFormData] = useState<Type.FormDataType>(
|
const [formData, setFormData] = useState<Type.FormDataType>(
|
||||||
initFormData(schema),
|
initFormData(schema),
|
||||||
|
@ -108,7 +97,6 @@ const General: FC = () => {
|
||||||
short_description: formData.short_description.value,
|
short_description: formData.short_description.value,
|
||||||
site_url: formData.site_url.value,
|
site_url: formData.site_url.value,
|
||||||
contact_email: formData.contact_email.value,
|
contact_email: formData.contact_email.value,
|
||||||
permalink: Number(formData.permalink.value),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
updateGeneralSetting(reqParams)
|
updateGeneralSetting(reqParams)
|
||||||
|
@ -135,9 +123,6 @@ const General: FC = () => {
|
||||||
Object.keys(formData).forEach((k) => {
|
Object.keys(formData).forEach((k) => {
|
||||||
formMeta[k] = { ...formData[k], value: setting[k] };
|
formMeta[k] = { ...formData[k], value: setting[k] };
|
||||||
});
|
});
|
||||||
if (formMeta.permalink.value !== 1 && formMeta.permalink.value !== 2) {
|
|
||||||
formMeta.permalink.value = 1;
|
|
||||||
}
|
|
||||||
setFormData({ ...formData, ...formMeta });
|
setFormData({ ...formData, ...formMeta });
|
||||||
}, [setting]);
|
}, [setting]);
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,7 @@ import {
|
||||||
import { interfaceStore } from '@/stores';
|
import { interfaceStore } from '@/stores';
|
||||||
import { JSONSchema, SchemaForm, UISchema } from '@/components';
|
import { JSONSchema, SchemaForm, UISchema } from '@/components';
|
||||||
import { DEFAULT_TIMEZONE } from '@/common/constants';
|
import { DEFAULT_TIMEZONE } from '@/common/constants';
|
||||||
import {
|
import { updateInterfaceSetting, useInterfaceSetting } from '@/services';
|
||||||
updateInterfaceSetting,
|
|
||||||
useInterfaceSetting,
|
|
||||||
useThemeOptions,
|
|
||||||
} from '@/services';
|
|
||||||
import {
|
import {
|
||||||
setupAppLanguage,
|
setupAppLanguage,
|
||||||
loadLanguageOptions,
|
loadLanguageOptions,
|
||||||
|
@ -27,7 +23,6 @@ const Interface: FC = () => {
|
||||||
keyPrefix: 'admin.interface',
|
keyPrefix: 'admin.interface',
|
||||||
});
|
});
|
||||||
const storeInterface = interfaceStore.getState().interface;
|
const storeInterface = interfaceStore.getState().interface;
|
||||||
const { data: themes } = useThemeOptions();
|
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const [langs, setLangs] = useState<LangsType[]>();
|
const [langs, setLangs] = useState<LangsType[]>();
|
||||||
const { data: setting } = useInterfaceSetting();
|
const { data: setting } = useInterfaceSetting();
|
||||||
|
@ -51,11 +46,6 @@ const Interface: FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const [formData, setFormData] = useState<FormDataType>({
|
const [formData, setFormData] = useState<FormDataType>({
|
||||||
theme: {
|
|
||||||
value: setting?.theme || storeInterface.theme,
|
|
||||||
isInvalid: false,
|
|
||||||
errorMsg: '',
|
|
||||||
},
|
|
||||||
language: {
|
language: {
|
||||||
value: setting?.language || storeInterface.language,
|
value: setting?.language || storeInterface.language,
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
|
@ -69,9 +59,6 @@ const Interface: FC = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const uiSchema: UISchema = {
|
const uiSchema: UISchema = {
|
||||||
theme: {
|
|
||||||
'ui:widget': 'select',
|
|
||||||
},
|
|
||||||
language: {
|
language: {
|
||||||
'ui:widget': 'select',
|
'ui:widget': 'select',
|
||||||
},
|
},
|
||||||
|
@ -83,30 +70,11 @@ const Interface: FC = () => {
|
||||||
const res: LangsType[] = await loadLanguageOptions(true);
|
const res: LangsType[] = await loadLanguageOptions(true);
|
||||||
setLangs(res);
|
setLangs(res);
|
||||||
};
|
};
|
||||||
// set default theme value
|
|
||||||
if (!formData.theme.value && Array.isArray(themes) && themes.length) {
|
|
||||||
setFormData({
|
|
||||||
...formData,
|
|
||||||
theme: {
|
|
||||||
value: themes[0].value,
|
|
||||||
isInvalid: false,
|
|
||||||
errorMsg: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkValidated = (): boolean => {
|
const checkValidated = (): boolean => {
|
||||||
let ret = true;
|
let ret = true;
|
||||||
const { theme, language } = formData;
|
const { language } = formData;
|
||||||
const formCheckData = { ...formData };
|
const formCheckData = { ...formData };
|
||||||
if (!theme.value) {
|
|
||||||
ret = false;
|
|
||||||
formCheckData.theme = {
|
|
||||||
value: '',
|
|
||||||
isInvalid: true,
|
|
||||||
errorMsg: t('theme.msg'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!language.value) {
|
if (!language.value) {
|
||||||
ret = false;
|
ret = false;
|
||||||
formCheckData.language = {
|
formCheckData.language = {
|
||||||
|
@ -127,7 +95,6 @@ const Interface: FC = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const reqParams: AdminSettingsInterface = {
|
const reqParams: AdminSettingsInterface = {
|
||||||
theme: formData.theme.value,
|
|
||||||
language: formData.language.value,
|
language: formData.language.value,
|
||||||
time_zone: formData.time_zone.value,
|
time_zone: formData.time_zone.value,
|
||||||
};
|
};
|
||||||
|
@ -149,21 +116,6 @@ const Interface: FC = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// const imgUpload = (file: any) => {
|
|
||||||
// return new Promise((resolve) => {
|
|
||||||
// uploadAvatar(file).then((res) => {
|
|
||||||
// setFormData({
|
|
||||||
// ...formData,
|
|
||||||
// logo: {
|
|
||||||
// value: res,
|
|
||||||
// isInvalid: false,
|
|
||||||
// errorMsg: '',
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// resolve(true);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (setting) {
|
if (setting) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { getLoginSetting, putLoginSetting } from '@/services';
|
||||||
import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
|
import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
|
||||||
import { useToast } from '@/hooks';
|
import { useToast } from '@/hooks';
|
||||||
import { handleFormError } from '@/utils';
|
import { handleFormError } from '@/utils';
|
||||||
|
import { loginSettingStore } from '@/stores';
|
||||||
|
|
||||||
const Index: FC = () => {
|
const Index: FC = () => {
|
||||||
const { t } = useTranslation('translation', {
|
const { t } = useTranslation('translation', {
|
||||||
|
@ -40,6 +41,7 @@ const Index: FC = () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const [formData, setFormData] = useState(initFormData(schema));
|
const [formData, setFormData] = useState(initFormData(schema));
|
||||||
|
const { update: updateLoginSetting } = loginSettingStore((_) => _);
|
||||||
|
|
||||||
const onSubmit = (evt) => {
|
const onSubmit = (evt) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
@ -56,6 +58,7 @@ const Index: FC = () => {
|
||||||
msg: t('update', { keyPrefix: 'toast' }),
|
msg: t('update', { keyPrefix: 'toast' }),
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
});
|
});
|
||||||
|
updateLoginSetting(reqParams);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.isError) {
|
if (err.isError) {
|
||||||
|
|
|
@ -15,6 +15,14 @@ const Index: FC = () => {
|
||||||
const schema: JSONSchema = {
|
const schema: JSONSchema = {
|
||||||
title: t('page_title'),
|
title: t('page_title'),
|
||||||
properties: {
|
properties: {
|
||||||
|
permalink: {
|
||||||
|
type: 'number',
|
||||||
|
title: t('permalink.label'),
|
||||||
|
description: t('permalink.text'),
|
||||||
|
enum: [1, 2],
|
||||||
|
enumNames: ['/questions/123/post-title', '/questions/123'],
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
robots: {
|
robots: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
title: t('robots.label'),
|
title: t('robots.label'),
|
||||||
|
@ -23,6 +31,9 @@ const Index: FC = () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const uiSchema: UISchema = {
|
const uiSchema: UISchema = {
|
||||||
|
permalink: {
|
||||||
|
'ui:widget': 'select',
|
||||||
|
},
|
||||||
robots: {
|
robots: {
|
||||||
'ui:widget': 'textarea',
|
'ui:widget': 'textarea',
|
||||||
'ui:options': {
|
'ui:options': {
|
||||||
|
@ -37,6 +48,7 @@ const Index: FC = () => {
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
|
|
||||||
const reqParams: Type.AdminSettingsSeo = {
|
const reqParams: Type.AdminSettingsSeo = {
|
||||||
|
permalink: Number(formData.permalink.value),
|
||||||
robots: formData.robots.value,
|
robots: formData.robots.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,6 +72,10 @@ const Index: FC = () => {
|
||||||
if (setting) {
|
if (setting) {
|
||||||
const formMeta = { ...formData };
|
const formMeta = { ...formData };
|
||||||
formMeta.robots.value = setting.robots;
|
formMeta.robots.value = setting.robots;
|
||||||
|
formMeta.permalink.value = setting.permalink;
|
||||||
|
if (formMeta.permalink.value !== 1 && formMeta.permalink.value !== 2) {
|
||||||
|
formMeta.permalink.value = 1;
|
||||||
|
}
|
||||||
setFormData(formMeta);
|
setFormData(formMeta);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,12 +6,14 @@ import { getThemeSetting, putThemeSetting } from '@/services';
|
||||||
import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
|
import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
|
||||||
import { useToast } from '@/hooks';
|
import { useToast } from '@/hooks';
|
||||||
import { handleFormError } from '@/utils';
|
import { handleFormError } from '@/utils';
|
||||||
|
import { themeSettingStore } from '@/stores';
|
||||||
|
|
||||||
const Index: FC = () => {
|
const Index: FC = () => {
|
||||||
const { t } = useTranslation('translation', {
|
const { t } = useTranslation('translation', {
|
||||||
keyPrefix: 'admin.themes',
|
keyPrefix: 'admin.themes',
|
||||||
});
|
});
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
|
const [themeSetting, setThemeSetting] = useState<Type.AdminSettingsTheme>();
|
||||||
const schema: JSONSchema = {
|
const schema: JSONSchema = {
|
||||||
title: t('page_title'),
|
title: t('page_title'),
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -19,8 +21,8 @@ const Index: FC = () => {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
title: t('themes.label'),
|
title: t('themes.label'),
|
||||||
description: t('themes.text'),
|
description: t('themes.text'),
|
||||||
enum: ['default'],
|
enum: themeSetting?.theme_options?.map((_) => _.value),
|
||||||
enumNames: ['Default'],
|
enumNames: themeSetting?.theme_options?.map((_) => _.label),
|
||||||
default: 'default',
|
default: 'default',
|
||||||
},
|
},
|
||||||
navbar_style: {
|
navbar_style: {
|
||||||
|
@ -51,8 +53,8 @@ const Index: FC = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const [themeSetting, setThemeSetting] = useState<Type.AdminSettingsTheme>();
|
|
||||||
const [formData, setFormData] = useState(initFormData(schema));
|
const [formData, setFormData] = useState(initFormData(schema));
|
||||||
|
const { update: updateThemeSetting } = themeSettingStore((_) => _);
|
||||||
const onSubmit = (evt) => {
|
const onSubmit = (evt) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
|
@ -72,6 +74,7 @@ const Index: FC = () => {
|
||||||
msg: t('update', { keyPrefix: 'toast' }),
|
msg: t('update', { keyPrefix: 'toast' }),
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
});
|
});
|
||||||
|
updateThemeSetting(reqParams);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.isError) {
|
if (err.isError) {
|
||||||
|
@ -97,13 +100,13 @@ const Index: FC = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleOnChange = (cd) => {
|
const handleOnChange = (cd) => {
|
||||||
console.log('cd: ', cd);
|
|
||||||
setFormData(cd);
|
setFormData(cd);
|
||||||
const themeConfig = themeSetting?.theme_config[cd.themes.value];
|
const themeConfig = themeSetting?.theme_config[cd.themes.value];
|
||||||
if (themeConfig) {
|
if (themeConfig) {
|
||||||
themeConfig.navbar_style = cd.navbar_style.value;
|
themeConfig.navbar_style = cd.navbar_style.value;
|
||||||
themeConfig.primary_color = cd.primary_color.value;
|
themeConfig.primary_color = cd.primary_color.value;
|
||||||
setThemeSetting({
|
setThemeSetting({
|
||||||
|
...themeSetting,
|
||||||
theme: themeSetting?.theme,
|
theme: themeSetting?.theme,
|
||||||
theme_config: themeSetting?.theme_config,
|
theme_config: themeSetting?.theme_config,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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, Customize } from '@/components';
|
import { Header, Footer, Toast, Customize, CustomizeTheme } from '@/components';
|
||||||
|
|
||||||
const Layout: FC = () => {
|
const Layout: FC = () => {
|
||||||
const { msg: toastMsg, variant, clear: toastClear } = toastStore();
|
const { msg: toastMsg, variant, clear: toastClear } = toastStore();
|
||||||
|
@ -31,6 +31,7 @@ const Layout: FC = () => {
|
||||||
{keywords && <meta name="keywords" content={keywords} />}
|
{keywords && <meta name="keywords" content={keywords} />}
|
||||||
{description && <meta name="description" content={description} />}
|
{description && <meta name="description" content={description} />}
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
<CustomizeTheme />
|
||||||
<SWRConfig
|
<SWRConfig
|
||||||
value={{
|
value={{
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import urlcat from 'urlcat';
|
import urlcat from 'urlcat';
|
||||||
|
|
||||||
import Pattern from '@/common/pattern';
|
import Pattern from '@/common/pattern';
|
||||||
import { siteInfoStore } from '@/stores';
|
import { seoSettingStore } from '@/stores';
|
||||||
|
|
||||||
const tagLanding = (slugName: string) => {
|
const tagLanding = (slugName: string) => {
|
||||||
if (!slugName) {
|
if (!slugName) {
|
||||||
|
@ -21,8 +21,8 @@ const tagEdit = (tagId: string) => {
|
||||||
return urlcat('/tags/:tagId/edit', { tagId });
|
return urlcat('/tags/:tagId/edit', { tagId });
|
||||||
};
|
};
|
||||||
const questionLanding = (questionId: string, title: string = '') => {
|
const questionLanding = (questionId: string, title: string = '') => {
|
||||||
const { siteInfo } = siteInfoStore.getState();
|
const { seo } = seoSettingStore.getState();
|
||||||
if (siteInfo.permalink === 1) {
|
if (seo.permalink === 1) {
|
||||||
title = title.toLowerCase();
|
title = title.toLowerCase();
|
||||||
title = title.trim().replace(/\s+/g, '-');
|
title = title.trim().replace(/\s+/g, '-');
|
||||||
title = title.replace(Pattern.emoji, '');
|
title = title.replace(Pattern.emoji, '');
|
||||||
|
|
|
@ -22,19 +22,6 @@ export const updateGeneralSetting = (params: Type.AdminSettingsGeneral) => {
|
||||||
return request.put(apiUrl, params);
|
return request.put(apiUrl, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useThemeOptions = () => {
|
|
||||||
const apiUrl = `/answer/admin/api/theme/options`;
|
|
||||||
const { data, error } = useSWR<{ label: string; value: string }[]>(
|
|
||||||
[apiUrl],
|
|
||||||
request.instance.get,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
isLoading: !data && !error,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useInterfaceSetting = () => {
|
export const useInterfaceSetting = () => {
|
||||||
const apiUrl = `/answer/admin/api/siteinfo/interface`;
|
const apiUrl = `/answer/admin/api/siteinfo/interface`;
|
||||||
const { data, error } = useSWR<Type.AdminSettingsInterface, Error>(
|
const { data, error } = useSWR<Type.AdminSettingsInterface, Error>(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import loginSettingStore from '@/stores/loginSetting';
|
import loginSettingStore from '@/stores/loginSetting';
|
||||||
|
import seoSettingStore from '@/stores/seoSetting';
|
||||||
|
|
||||||
import toastStore from './toast';
|
import toastStore from './toast';
|
||||||
import loggedUserInfoStore from './userInfo';
|
import loggedUserInfoStore from './userInfo';
|
||||||
|
@ -7,6 +8,7 @@ import interfaceStore from './interface';
|
||||||
import brandingStore from './branding';
|
import brandingStore from './branding';
|
||||||
import pageTagStore from './pageTags';
|
import pageTagStore from './pageTags';
|
||||||
import customizeStore from './customize';
|
import customizeStore from './customize';
|
||||||
|
import themeSettingStore from './themeSetting';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
toastStore,
|
toastStore,
|
||||||
|
@ -17,4 +19,6 @@ export {
|
||||||
pageTagStore,
|
pageTagStore,
|
||||||
loginSettingStore,
|
loginSettingStore,
|
||||||
customizeStore,
|
customizeStore,
|
||||||
|
themeSettingStore,
|
||||||
|
seoSettingStore,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import create from 'zustand';
|
||||||
|
|
||||||
|
import { AdminSettingsSeo } from '@/common/interface';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
seo: AdminSettingsSeo;
|
||||||
|
update: (params: AdminSettingsSeo) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const siteInfo = create<IProps>((set) => ({
|
||||||
|
seo: {
|
||||||
|
robots: '',
|
||||||
|
permalink: 1,
|
||||||
|
},
|
||||||
|
update: (params) =>
|
||||||
|
set((state) => {
|
||||||
|
const o = { ...state.seo, ...params };
|
||||||
|
if (o.permalink !== 1 && o.permalink !== 2) {
|
||||||
|
o.permalink = 1;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
seo: o,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default siteInfo;
|
|
@ -19,9 +19,6 @@ const siteInfo = create<SiteInfoType>((set) => ({
|
||||||
update: (params) =>
|
update: (params) =>
|
||||||
set((_) => {
|
set((_) => {
|
||||||
const o = { ..._.siteInfo, ...params };
|
const o = { ..._.siteInfo, ...params };
|
||||||
if (o.permalink !== 1 && o.permalink !== 2) {
|
|
||||||
o.permalink = 1;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
siteInfo: o,
|
siteInfo: o,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import create from 'zustand';
|
||||||
|
|
||||||
|
import { AdminSettingsTheme } from '@/common/interface';
|
||||||
|
|
||||||
|
interface IType {
|
||||||
|
theme: AdminSettingsTheme['theme'];
|
||||||
|
theme_config: AdminSettingsTheme['theme_config'];
|
||||||
|
update: (params: AdminSettingsTheme) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = create<IType>((set) => ({
|
||||||
|
theme: '',
|
||||||
|
theme_config: {},
|
||||||
|
update: (params) =>
|
||||||
|
set((state) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...params,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default store;
|
|
@ -0,0 +1,23 @@
|
||||||
|
import Color from 'color';
|
||||||
|
|
||||||
|
const WHITE = Color('#fff');
|
||||||
|
const BLACK = Color('#000');
|
||||||
|
|
||||||
|
export const mixColour = (baseColor, opColor, weight) => {
|
||||||
|
return Color(baseColor).mix(Color(opColor), weight);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tintColor = (color, weight) => {
|
||||||
|
return mixColour(WHITE, color, weight);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shadeColor = (color, weight) => {
|
||||||
|
return mixColour(BLACK, color, weight);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shiftColor = (color, weight) => {
|
||||||
|
if (weight > 0) {
|
||||||
|
return shadeColor(color, weight);
|
||||||
|
}
|
||||||
|
return tintColor(color, weight);
|
||||||
|
};
|
|
@ -6,6 +6,8 @@ import {
|
||||||
brandingStore,
|
brandingStore,
|
||||||
loginSettingStore,
|
loginSettingStore,
|
||||||
customizeStore,
|
customizeStore,
|
||||||
|
themeSettingStore,
|
||||||
|
seoSettingStore,
|
||||||
} from '@/stores';
|
} from '@/stores';
|
||||||
import { RouteAlias } from '@/router/alias';
|
import { RouteAlias } from '@/router/alias';
|
||||||
import Storage from '@/utils/storage';
|
import Storage from '@/utils/storage';
|
||||||
|
@ -258,6 +260,8 @@ export const initAppSettingsStore = async () => {
|
||||||
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);
|
customizeStore.getState().update(appSettings.custom_css_html);
|
||||||
|
themeSettingStore.getState().update(appSettings.theme);
|
||||||
|
seoSettingStore.getState().update(appSettings.site_seo);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,3 +5,4 @@ export { floppyNavigation } from './floppyNavigation';
|
||||||
export * as guard from './guard';
|
export * as guard from './guard';
|
||||||
export * as localize from './localize';
|
export * as localize from './localize';
|
||||||
export * from './common';
|
export * from './common';
|
||||||
|
export * from './color';
|
||||||
|
|
Loading…
Reference in New Issue