feat: add strategy setting for post slug generation (#4551)

#### What type of PR is this?

/kind improvement

#### What this PR does / why we need it:

添加文章别名自动生成策略

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/1790

#### Special notes for your reviewer:

需要后端提供支持在globalInfo里面添加`gSlugMode`字段。它的类型为(后续可能会支持更多的模式)
<img width="582" alt="image" src="https://github.com/halo-dev/halo/assets/110895612/586c4742-6172-4bbc-a601-ca04c2a9a281">

#### Does this PR introduce a user-facing change?


```release-note
文章支持多别名生成策略。
```
pull/4587/head
Hilary Liu 2023-09-10 22:08:13 +08:00 committed by GitHub
parent 31675dbbba
commit e13563bad0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 123 additions and 19 deletions

View File

@ -79,6 +79,7 @@ public class SystemSetting {
Integer categoryPageSize; Integer categoryPageSize;
Integer tagPageSize; Integer tagPageSize;
Boolean review; Boolean review;
String slugGenerationStrategy;
} }
@Data @Data

View File

@ -53,6 +53,7 @@ public class GlobalInfoEndpoint {
handleCommentSetting(info, configMap); handleCommentSetting(info, configMap);
handleUserSetting(info, configMap); handleUserSetting(info, configMap);
handleBasicSetting(info, configMap); handleBasicSetting(info, configMap);
handlePostSlugGenerationStrategy(info, configMap);
})); }));
return info; return info;
@ -81,6 +82,8 @@ public class GlobalInfoEndpoint {
private boolean dataInitialized; private boolean dataInitialized;
private String postSlugGenerationStrategy;
private List<SocialAuthProvider> socialAuthProviders; private List<SocialAuthProvider> socialAuthProviders;
} }
@ -123,6 +126,13 @@ public class GlobalInfoEndpoint {
} }
} }
private void handlePostSlugGenerationStrategy(GlobalInfo info, ConfigMap configMap) {
var post = SystemSetting.get(configMap, SystemSetting.Post.GROUP, SystemSetting.Post.class);
if (post != null) {
info.setPostSlugGenerationStrategy(post.getSlugGenerationStrategy());
}
}
private void handleBasicSetting(GlobalInfo info, ConfigMap configMap) { private void handleBasicSetting(GlobalInfo info, ConfigMap configMap) {
var basic = SystemSetting.get(configMap, Basic.GROUP, Basic.class); var basic = SystemSetting.get(configMap, Basic.GROUP, Basic.class);
if (basic != null) { if (basic != null) {

View File

@ -31,7 +31,8 @@ data:
"postPageSize": 10, "postPageSize": 10,
"archivePageSize": 10, "archivePageSize": 10,
"categoryPageSize": 10, "categoryPageSize": 10,
"tagPageSize": 10 "tagPageSize": 10,
"slugGenerationStrategy": "generateByTitle"
} }
comment: | comment: |
{ {

View File

@ -55,6 +55,19 @@ spec:
min: 1 min: 1
max: 100 max: 100
validation: required validation: required
- $formkit: select
label: "别名生成策略"
name: slugGenerationStrategy
value: 'generateByTitle'
options:
- label: '根据标题'
value: 'generateByTitle'
- label: '时间戳'
value: 'timestamp'
- label: 'Short UUID'
value: 'shortUUID'
- label: 'UUID'
value: 'UUID'
- group: seo - group: seo
label: SEO 设置 label: SEO 设置
formSchema: formSchema:

View File

@ -92,6 +92,7 @@
"pinia": "^2.1.6", "pinia": "^2.1.6",
"pretty-bytes": "^6.0.0", "pretty-bytes": "^6.0.0",
"qs": "^6.11.1", "qs": "^6.11.1",
"short-unique-id": "^5.0.2",
"transliteration": "^2.3.5", "transliteration": "^2.3.5",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-demi": "^0.14.5", "vue-demi": "^0.14.5",

View File

@ -182,6 +182,9 @@ importers:
qs: qs:
specifier: ^6.11.1 specifier: ^6.11.1
version: 6.11.1 version: 6.11.1
short-unique-id:
specifier: ^5.0.2
version: 5.0.2
transliteration: transliteration:
specifier: ^2.3.5 specifier: ^2.3.5
version: 2.3.5 version: 2.3.5
@ -782,6 +785,7 @@ packages:
/@babel/plugin-proposal-async-generator-functions@7.19.1(@babel/core@7.20.12): /@babel/plugin-proposal-async-generator-functions@7.19.1(@babel/core@7.20.12):
resolution: {integrity: sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==} resolution: {integrity: sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -797,6 +801,7 @@ packages:
/@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.20.12): /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.20.12):
resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -810,6 +815,7 @@ packages:
/@babel/plugin-proposal-class-static-block@7.18.6(@babel/core@7.20.12): /@babel/plugin-proposal-class-static-block@7.18.6(@babel/core@7.20.12):
resolution: {integrity: sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==} resolution: {integrity: sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.12.0 '@babel/core': ^7.12.0
dependencies: dependencies:
@ -824,6 +830,7 @@ packages:
/@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.20.12): /@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.20.12):
resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -835,6 +842,7 @@ packages:
/@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.20.12): /@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.20.12):
resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -846,6 +854,7 @@ packages:
/@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.20.12): /@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.20.12):
resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -857,6 +866,7 @@ packages:
/@babel/plugin-proposal-logical-assignment-operators@7.18.9(@babel/core@7.20.12): /@babel/plugin-proposal-logical-assignment-operators@7.18.9(@babel/core@7.20.12):
resolution: {integrity: sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==} resolution: {integrity: sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -868,6 +878,7 @@ packages:
/@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.20.12): /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.20.12):
resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -879,6 +890,7 @@ packages:
/@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.20.12): /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.20.12):
resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -890,6 +902,7 @@ packages:
/@babel/plugin-proposal-object-rest-spread@7.18.9(@babel/core@7.20.12): /@babel/plugin-proposal-object-rest-spread@7.18.9(@babel/core@7.20.12):
resolution: {integrity: sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==} resolution: {integrity: sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -904,6 +917,7 @@ packages:
/@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.20.12): /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.20.12):
resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -915,6 +929,7 @@ packages:
/@babel/plugin-proposal-optional-chaining@7.18.9(@babel/core@7.20.12): /@babel/plugin-proposal-optional-chaining@7.18.9(@babel/core@7.20.12):
resolution: {integrity: sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==} resolution: {integrity: sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -927,6 +942,7 @@ packages:
/@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.20.12): /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.20.12):
resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -940,6 +956,7 @@ packages:
/@babel/plugin-proposal-private-property-in-object@7.18.6(@babel/core@7.20.12): /@babel/plugin-proposal-private-property-in-object@7.18.6(@babel/core@7.20.12):
resolution: {integrity: sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==} resolution: {integrity: sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -955,6 +972,7 @@ packages:
/@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.20.12): /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.20.12):
resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==}
engines: {node: '>=4'} engines: {node: '>=4'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.
peerDependencies: peerDependencies:
'@babel/core': ^7.0.0-0 '@babel/core': ^7.0.0-0
dependencies: dependencies:
@ -10168,6 +10186,11 @@ packages:
vscode-textmate: 5.2.0 vscode-textmate: 5.2.0
dev: true dev: true
/short-unique-id@5.0.2:
resolution: {integrity: sha512-4wZq1VLV4hsEx8guP5bN7XnY8UDsVXtdUDWFMP1gvEieAXolq5fWGKpuua21PRXaLn3OybTKFQNm7JGcHSWu/Q==}
hasBin: true
dev: false
/side-channel@1.0.4: /side-channel@1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies: dependencies:

View File

@ -1,26 +1,63 @@
import { slugify } from "transliteration"; import { slugify } from "transliteration";
import { watch, type Ref } from "vue"; import { watch, type Ref } from "vue";
import ShortUniqueId from "short-unique-id";
import { FormType } from "@/types/slug";
import { useGlobalInfoStore } from "@/stores/global-info";
import { randomUUID } from "@/utils/id";
const uid = new ShortUniqueId();
const Strategy = {
generateByTitle: (value: string) => {
if (!value) return "";
return slugify(value, { trim: true });
},
shortUUID: (value: string) => {
if (!value) return "";
return uid.randomUUID(8);
},
UUID: (value: string) => {
if (!value) return "";
return randomUUID();
},
timestamp: (value: string) => {
if (!value) return "";
return new Date().getTime().toString();
},
};
const onceList = ["shortUUID", "UUID", "timestamp"];
export default function useSlugify( export default function useSlugify(
source: Ref<string>, source: Ref<string>,
target: Ref<string>, target: Ref<string>,
auto: Ref<boolean> auto: Ref<boolean>,
formType: FormType
) { ) {
watch( watch(
() => source.value, () => source.value,
() => { () => {
if (auto.value) { if (auto.value) {
handleGenerateSlug(); handleGenerateSlug(false, formType);
} }
} }
); );
const handleGenerateSlug = () => { const handleGenerateSlug = (forceUpdate = false, formType: FormType) => {
if (!source.value) { const globalInfoStore = useGlobalInfoStore();
const mode = globalInfoStore.globalInfo?.postSlugGenerationStrategy;
if (!mode) {
return; return;
} }
target.value = slugify(source.value, { if (formType != FormType.POST) {
trim: true, target.value = Strategy["generateByTitle"](source.value);
}); return;
}
if (forceUpdate) {
target.value = Strategy[mode](source.value);
return;
}
if (onceList.includes(mode) && target.value) return;
target.value = Strategy[mode](source.value);
}; };
return { return {

View File

@ -19,6 +19,7 @@ import AnnotationsForm from "@/components/form/AnnotationsForm.vue";
import useSlugify from "@/composables/use-slugify"; import useSlugify from "@/composables/use-slugify";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { usePageUpdateMutate } from "../composables/use-page-update-mutate"; import { usePageUpdateMutate } from "../composables/use-page-update-mutate";
import { FormType } from "@/types/slug";
const initialFormState: SinglePage = { const initialFormState: SinglePage = {
spec: { spec: {
@ -277,7 +278,8 @@ const { handleGenerateSlug } = useSlugify(
formState.value.spec.slug = value; formState.value.spec.slug = value;
}, },
}), }),
computed(() => !isUpdateMode.value) computed(() => !isUpdateMode.value),
FormType.SINGLE_PAGE
); );
</script> </script>
@ -331,7 +333,7 @@ const { handleGenerateSlug } = useSlugify(
$t('core.page.settings.fields.slug.refresh_message') $t('core.page.settings.fields.slug.refresh_message')
" "
class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100" class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100"
@click="handleGenerateSlug" @click="handleGenerateSlug(true, FormType.SINGLE_PAGE)"
> >
<IconRefreshLine <IconRefreshLine
class="h-4 w-4 text-gray-500 group-hover:text-gray-700" class="h-4 w-4 text-gray-500 group-hover:text-gray-700"

View File

@ -24,6 +24,7 @@ import { useThemeCustomTemplates } from "@/modules/interface/themes/composables/
import AnnotationsForm from "@/components/form/AnnotationsForm.vue"; import AnnotationsForm from "@/components/form/AnnotationsForm.vue";
import useSlugify from "@/composables/use-slugify"; import useSlugify from "@/composables/use-slugify";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { FormType } from "@/types/slug";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -199,7 +200,8 @@ const { handleGenerateSlug } = useSlugify(
formState.value.spec.slug = value; formState.value.spec.slug = value;
}, },
}), }),
computed(() => !isUpdateMode.value) computed(() => !isUpdateMode.value),
FormType.CATEGORY
); );
</script> </script>
<template> <template>
@ -260,7 +262,7 @@ const { handleGenerateSlug } = useSlugify(
) )
" "
class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100" class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100"
@click="handleGenerateSlug" @click="handleGenerateSlug(true, FormType.CATEGORY)"
> >
<IconRefreshLine <IconRefreshLine
class="h-4 w-4 text-gray-500 group-hover:text-gray-700" class="h-4 w-4 text-gray-500 group-hover:text-gray-700"

View File

@ -19,6 +19,7 @@ import { submitForm } from "@formkit/core";
import useSlugify from "@/composables/use-slugify"; import useSlugify from "@/composables/use-slugify";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { usePostUpdateMutate } from "../composables/use-post-update-mutate"; import { usePostUpdateMutate } from "../composables/use-post-update-mutate";
import { FormType } from "@/types/slug";
const initialFormState: Post = { const initialFormState: Post = {
spec: { spec: {
@ -240,7 +241,8 @@ const { handleGenerateSlug } = useSlugify(
formState.value.spec.slug = value; formState.value.spec.slug = value;
}, },
}), }),
computed(() => !isUpdateMode.value) computed(() => !isUpdateMode.value),
FormType.POST
); );
</script> </script>
<template> <template>
@ -293,7 +295,7 @@ const { handleGenerateSlug } = useSlugify(
$t('core.post.settings.fields.slug.refresh_message') $t('core.post.settings.fields.slug.refresh_message')
" "
class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100" class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100"
@click="handleGenerateSlug" @click="handleGenerateSlug(true, FormType.POST)"
> >
<IconRefreshLine <IconRefreshLine
class="h-4 w-4 text-gray-500 group-hover:text-gray-700" class="h-4 w-4 text-gray-500 group-hover:text-gray-700"

View File

@ -25,6 +25,7 @@ import { setFocus } from "@/formkit/utils/focus";
import AnnotationsForm from "@/components/form/AnnotationsForm.vue"; import AnnotationsForm from "@/components/form/AnnotationsForm.vue";
import useSlugify from "@/composables/use-slugify"; import useSlugify from "@/composables/use-slugify";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { FormType } from "@/types/slug";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -158,7 +159,8 @@ const { handleGenerateSlug } = useSlugify(
formState.value.spec.slug = value; formState.value.spec.slug = value;
}, },
}), }),
computed(() => !isUpdateMode.value) computed(() => !isUpdateMode.value),
FormType.TAG
); );
</script> </script>
<template> <template>
@ -220,7 +222,7 @@ const { handleGenerateSlug } = useSlugify(
) )
" "
class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100" class="group flex h-full cursor-pointer items-center border-l px-3 transition-all hover:bg-gray-100"
@click="handleGenerateSlug" @click="handleGenerateSlug(true, FormType.TAG)"
> >
<IconRefreshLine <IconRefreshLine
class="h-4 w-4 text-gray-500 group-hover:text-gray-700" class="h-4 w-4 text-gray-500 group-hover:text-gray-700"

View File

@ -12,6 +12,7 @@ import type { ConfigMap, Setting } from "@halo-dev/api-client";
import { useQuery, useQueryClient } from "@tanstack/vue-query"; import { useQuery, useQueryClient } from "@tanstack/vue-query";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useGlobalInfoStore } from "@/stores/global-info";
const SYSTEM_CONFIGMAP_NAME = "system"; const SYSTEM_CONFIGMAP_NAME = "system";
@ -58,7 +59,7 @@ const handleSaveConfigMap = async () => {
Toast.success(t("core.common.toast.save_success")); Toast.success(t("core.common.toast.save_success"));
queryClient.invalidateQueries({ queryKey: ["system-configMap"] }); queryClient.invalidateQueries({ queryKey: ["system-configMap"] });
await useGlobalInfoStore().fetchGlobalInfo();
systemConfigMapStore.configMap = data; systemConfigMapStore.configMap = data;
saving.value = false; saving.value = false;

View File

@ -30,7 +30,6 @@ export async function setupPluginModules(app: App) {
enabledPluginNames.forEach((name) => { enabledPluginNames.forEach((name) => {
const module = window[name]; const module = window[name];
if (module) { if (module) {
registerModule(app, module, false); registerModule(app, module, false);
pluginModuleStore.registerPluginModule(name, module); pluginModuleStore.registerPluginModule(name, module);

View File

@ -1,3 +1,5 @@
import type { ModeType } from "./slug";
export interface GlobalInfo { export interface GlobalInfo {
externalUrl: string; externalUrl: string;
timeZone: string; timeZone: string;
@ -10,6 +12,7 @@ export interface GlobalInfo {
userInitialized: boolean; userInitialized: boolean;
dataInitialized: boolean; dataInitialized: boolean;
favicon?: string; favicon?: string;
postSlugGenerationStrategy: ModeType;
} }
export interface Info { export interface Info {

View File

@ -0,0 +1,7 @@
export type ModeType = "UUID" | "shortUUID" | "timestamp" | "generateByTitle";
export enum FormType {
TAG = "Tag",
CATEGORY = "Category",
POST = "Post",
SINGLE_PAGE = "SinglePage",
}