mirror of https://github.com/halo-dev/halo
feat: add a title input box in the editor (#5465)
#### What type of PR is this? /area editor /area ui /kind feature #### What this PR does / why we need it: 为默认编辑器添加标题输入框。 <img width="1665" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/df903e02-76b0-45fe-89d9-6ac81af8f041"> #### Which issue(s) this PR fixes: Fixes #5427 #### Special notes for your reviewer: #### Does this PR introduce a user-facing change? ```release-note 为默认编辑器添加标题输入框。 ```pull/5523/head
parent
5e073bfe3c
commit
581a738423
|
@ -32,15 +32,6 @@ export default function useSlugify(
|
|||
auto: Ref<boolean>,
|
||||
formType: FormType
|
||||
) {
|
||||
watch(
|
||||
() => source.value,
|
||||
() => {
|
||||
if (auto.value) {
|
||||
handleGenerateSlug(false, formType);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const handleGenerateSlug = (forceUpdate = false, formType: FormType) => {
|
||||
const globalInfoStore = useGlobalInfoStore();
|
||||
const mode = globalInfoStore.globalInfo?.postSlugGenerationStrategy;
|
||||
|
@ -60,6 +51,18 @@ export default function useSlugify(
|
|||
target.value = Strategy[mode](source.value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => source.value,
|
||||
() => {
|
||||
if (auto.value) {
|
||||
handleGenerateSlug(false, formType);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
handleGenerateSlug,
|
||||
};
|
||||
|
|
|
@ -21,10 +21,10 @@ import {
|
|||
ref,
|
||||
toRef,
|
||||
type ComputedRef,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { useRouter } from "vue-router";
|
||||
import { randomUUID } from "@/utils/id";
|
||||
import { useContentCache } from "@/composables/use-content-cache";
|
||||
|
@ -69,7 +69,7 @@ const handleChangeEditorProvider = async (provider: EditorProvider) => {
|
|||
};
|
||||
|
||||
// SinglePage form
|
||||
const initialFormState: SinglePageRequest = {
|
||||
const formState = ref<SinglePageRequest>({
|
||||
page: {
|
||||
spec: {
|
||||
title: "",
|
||||
|
@ -101,13 +101,19 @@ const initialFormState: SinglePageRequest = {
|
|||
content: "",
|
||||
rawType: "HTML",
|
||||
},
|
||||
};
|
||||
|
||||
const formState = ref<SinglePageRequest>(cloneDeep(initialFormState));
|
||||
});
|
||||
const saving = ref(false);
|
||||
const publishing = ref(false);
|
||||
const settingModal = ref(false);
|
||||
|
||||
const isTitleChanged = ref(false);
|
||||
watch(
|
||||
() => formState.value.page.spec.title,
|
||||
(newValue, oldValue) => {
|
||||
isTitleChanged.value = newValue !== oldValue;
|
||||
}
|
||||
);
|
||||
|
||||
const isUpdateMode = computed(() => {
|
||||
return !!formState.value.page.metadata.creationTimestamp;
|
||||
});
|
||||
|
@ -143,15 +149,23 @@ const handleSave = async (options?: { mute?: boolean }) => {
|
|||
}
|
||||
|
||||
if (isUpdateMode.value) {
|
||||
if (isTitleChanged.value) {
|
||||
formState.value.page = (
|
||||
await singlePageUpdateMutate(formState.value.page)
|
||||
).data;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.singlePage.updateSinglePageContent({
|
||||
name: formState.value.page.metadata.name,
|
||||
content: formState.value.content,
|
||||
});
|
||||
|
||||
formState.value.page = data;
|
||||
isTitleChanged.value = false;
|
||||
} else {
|
||||
// Clear new page content cache
|
||||
handleClearCache();
|
||||
|
||||
const { data } = await apiClient.singlePage.draftSinglePage({
|
||||
singlePageRequest: formState.value,
|
||||
});
|
||||
|
@ -184,6 +198,12 @@ const handlePublish = async () => {
|
|||
const { name: singlePageName } = formState.value.page.metadata;
|
||||
const { permalink } = formState.value.page.status || {};
|
||||
|
||||
if (isTitleChanged.value) {
|
||||
formState.value.page = (
|
||||
await singlePageUpdateMutate(formState.value.page)
|
||||
).data;
|
||||
}
|
||||
|
||||
await apiClient.singlePage.updateSinglePageContent({
|
||||
name: singlePageName,
|
||||
content: formState.value.content,
|
||||
|
@ -224,6 +244,7 @@ const handlePublishClick = () => {
|
|||
if (isUpdateMode.value) {
|
||||
handlePublish();
|
||||
} else {
|
||||
// Set editor title to page
|
||||
settingModal.value = true;
|
||||
}
|
||||
};
|
||||
|
@ -477,6 +498,7 @@ async function handleUploadImage(file: File, options?: AxiosRequestConfig) {
|
|||
v-if="currentEditorProvider"
|
||||
v-model:raw="formState.content.raw"
|
||||
v-model:content="formState.content.content"
|
||||
v-model:title="formState.page.spec.title"
|
||||
:upload-image="handleUploadImage"
|
||||
class="h-full"
|
||||
@update="handleSetContentCache"
|
||||
|
|
|
@ -255,6 +255,9 @@ watch(
|
|||
formState.value = toRaw(value);
|
||||
publishTime.value = toDatetimeLocal(formState.value.spec.publishTime);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -21,8 +21,8 @@ import {
|
|||
ref,
|
||||
toRef,
|
||||
type ComputedRef,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import { useRouter } from "vue-router";
|
||||
|
@ -80,7 +80,7 @@ interface PostRequestWithContent extends PostRequest {
|
|||
}
|
||||
|
||||
// Post form
|
||||
const initialFormState: PostRequestWithContent = {
|
||||
const formState = ref<PostRequestWithContent>({
|
||||
post: {
|
||||
spec: {
|
||||
title: "",
|
||||
|
@ -114,13 +114,19 @@ const initialFormState: PostRequestWithContent = {
|
|||
content: "",
|
||||
rawType: "HTML",
|
||||
},
|
||||
};
|
||||
|
||||
const formState = ref<PostRequestWithContent>(cloneDeep(initialFormState));
|
||||
});
|
||||
const settingModal = ref(false);
|
||||
const saving = ref(false);
|
||||
const publishing = ref(false);
|
||||
|
||||
const isTitleChanged = ref(false);
|
||||
watch(
|
||||
() => formState.value.post.spec.title,
|
||||
(newValue, oldValue) => {
|
||||
isTitleChanged.value = newValue !== oldValue;
|
||||
}
|
||||
);
|
||||
|
||||
const isUpdateMode = computed(() => {
|
||||
return !!formState.value.post.metadata.creationTimestamp;
|
||||
});
|
||||
|
@ -155,15 +161,25 @@ const handleSave = async (options?: { mute?: boolean }) => {
|
|||
}
|
||||
|
||||
if (isUpdateMode.value) {
|
||||
// Save post title
|
||||
if (isTitleChanged.value) {
|
||||
formState.value.post = (
|
||||
await postUpdateMutate(formState.value.post)
|
||||
).data;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.post.updatePostContent({
|
||||
name: formState.value.post.metadata.name,
|
||||
content: formState.value.content,
|
||||
});
|
||||
|
||||
formState.value.post = data;
|
||||
|
||||
isTitleChanged.value = false;
|
||||
} else {
|
||||
// Clear new post content cache
|
||||
handleClearCache();
|
||||
|
||||
const { data } = await apiClient.post.draftPost({
|
||||
postRequest: formState.value,
|
||||
});
|
||||
|
@ -195,6 +211,12 @@ const handlePublish = async () => {
|
|||
const { name: postName } = formState.value.post.metadata;
|
||||
const { permalink } = formState.value.post.status || {};
|
||||
|
||||
if (isTitleChanged.value) {
|
||||
formState.value.post = (
|
||||
await postUpdateMutate(formState.value.post)
|
||||
).data;
|
||||
}
|
||||
|
||||
await apiClient.post.updatePostContent({
|
||||
name: postName,
|
||||
content: formState.value.content,
|
||||
|
@ -240,6 +262,7 @@ const handlePublishClick = () => {
|
|||
if (isUpdateMode.value) {
|
||||
handlePublish();
|
||||
} else {
|
||||
// Set editor title to post
|
||||
settingModal.value = true;
|
||||
}
|
||||
};
|
||||
|
@ -503,6 +526,7 @@ async function handleUploadImage(file: File, options?: AxiosRequestConfig) {
|
|||
v-if="currentEditorProvider"
|
||||
v-model:raw="formState.content.raw"
|
||||
v-model:content="formState.content.content"
|
||||
v-model:title="formState.post.spec.title"
|
||||
:upload-image="handleUploadImage"
|
||||
class="h-full"
|
||||
@update="handleSetContentCache"
|
||||
|
|
|
@ -216,6 +216,9 @@ watch(
|
|||
formState.value = toRaw(value);
|
||||
publishTime.value = toDatetimeLocal(formState.value.spec.publishTime);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -37,14 +37,20 @@ watch(
|
|||
<editor-bubble-menu :editor="editor" />
|
||||
<editor-header :editor="editor" />
|
||||
<div class="h-full flex flex-row w-full overflow-hidden">
|
||||
<editor-content
|
||||
:editor="editor"
|
||||
:style="contentStyles"
|
||||
class="editor-content markdown-body flex-1 relative bg-white overflow-y-auto"
|
||||
/>
|
||||
<div class="overflow-y-auto flex-1 bg-white">
|
||||
<div v-if="$slots.content" class="editor-header-extra">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
|
||||
<editor-content
|
||||
:editor="editor"
|
||||
:style="contentStyles"
|
||||
class="editor-content markdown-body relative"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="$slots.extra"
|
||||
class="h-full hidden sm:!block w-72 flex-shrink-0"
|
||||
class="h-full hidden sm:!block w-72 flex-shrink-0 flex-none"
|
||||
>
|
||||
<slot name="extra"></slot>
|
||||
</div>
|
||||
|
|
|
@ -30,15 +30,24 @@ export class SearchAndReplacePluginView {
|
|||
|
||||
public containerElement: HTMLElement;
|
||||
|
||||
public init: boolean;
|
||||
|
||||
constructor({ view, editor, element }: SearchAndReplacePluginViewProps) {
|
||||
this.editor = editor;
|
||||
this.view = view;
|
||||
this.containerElement = element;
|
||||
const { element: editorElement } = this.editor.options;
|
||||
editorElement.insertAdjacentElement("afterbegin", this.containerElement);
|
||||
this.init = false;
|
||||
}
|
||||
|
||||
update() {
|
||||
const { parentElement: editorParentElement } = this.editor.options.element;
|
||||
if (!this.init && editorParentElement) {
|
||||
editorParentElement.insertAdjacentElement(
|
||||
"afterbegin",
|
||||
this.containerElement
|
||||
);
|
||||
this.init = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,11 @@
|
|||
height: 48px;
|
||||
}
|
||||
|
||||
.editor-header-extra {
|
||||
width: 100%;
|
||||
padding: $editorVerticalPadding 20px;
|
||||
}
|
||||
|
||||
.editor-content {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
@ -78,7 +83,8 @@
|
|||
}
|
||||
|
||||
@media screen {
|
||||
.ProseMirror {
|
||||
.ProseMirror,
|
||||
.editor-header-extra {
|
||||
@media (min-width: 640px) {
|
||||
padding: $editorVerticalPadding min($editorHorizontalPadding, 10%) !important;
|
||||
}
|
||||
|
|
|
@ -98,12 +98,14 @@ import { onBeforeUnmount } from "vue";
|
|||
import { usePermission } from "@/utils/permission";
|
||||
import type { AxiosRequestConfig } from "axios";
|
||||
import { getContents } from "./utils/attachment";
|
||||
import { nextTick } from "vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { currentUserHasPermission } = usePermission();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
raw?: string;
|
||||
content: string;
|
||||
uploadImage?: (
|
||||
|
@ -112,6 +114,7 @@ const props = withDefaults(
|
|||
) => Promise<Attachment>;
|
||||
}>(),
|
||||
{
|
||||
title: "",
|
||||
raw: "",
|
||||
content: "",
|
||||
uploadImage: undefined,
|
||||
|
@ -119,6 +122,7 @@ const props = withDefaults(
|
|||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "update:title", value: string): void;
|
||||
(event: "update:raw", value: string): void;
|
||||
(event: "update:content", value: string): void;
|
||||
(event: "update", value: string): void;
|
||||
|
@ -404,7 +408,6 @@ onMounted(() => {
|
|||
UiExtensionUpload,
|
||||
ExtensionSearchAndReplace,
|
||||
],
|
||||
autofocus: "start",
|
||||
parseOptions: {
|
||||
preserveWhitespace: true,
|
||||
},
|
||||
|
@ -441,6 +444,26 @@ const currentLocale = i18n.global.locale.value as
|
|||
| "en"
|
||||
| "zh"
|
||||
| "en-US";
|
||||
|
||||
function onTitleInput(event: Event) {
|
||||
emit("update:title", (event.target as HTMLInputElement).value);
|
||||
}
|
||||
|
||||
// Set focus
|
||||
const editorTitleRef = ref();
|
||||
onMounted(() => {
|
||||
// if name is empty, it means the editor is in the creation mode
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const name = urlParams.get("name");
|
||||
|
||||
if (!name) {
|
||||
nextTick(() => {
|
||||
editorTitleRef.value.focus();
|
||||
});
|
||||
} else {
|
||||
editor.value?.commands.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -452,6 +475,17 @@ const currentLocale = i18n.global.locale.value as
|
|||
@close="handleCloseAttachmentSelectorModal"
|
||||
/>
|
||||
<RichTextEditor v-if="editor" :editor="editor" :locale="currentLocale">
|
||||
<template #content>
|
||||
<input
|
||||
ref="editorTitleRef"
|
||||
:value="title"
|
||||
type="text"
|
||||
:placeholder="$t('core.components.default_editor.title_placeholder')"
|
||||
class="w-full border-x-0 !border-b border-t-0 !border-solid !border-gray-100 p-0 !py-2 text-4xl font-semibold placeholder:text-gray-300"
|
||||
@input="onTitleInput"
|
||||
@keydown.enter="() => editor?.commands.focus('start')"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="showSidebar" #extra>
|
||||
<OverlayScrollbarsComponent
|
||||
element="div"
|
||||
|
|
|
@ -1414,6 +1414,7 @@ core:
|
|||
toolbox:
|
||||
attachment: Attachment
|
||||
show_hide_sidebar: Show/Hide Sidebar
|
||||
title_placeholder: Please enter the title
|
||||
global_search:
|
||||
placeholder: Enter keywords to search
|
||||
no_results: No search results
|
||||
|
|
|
@ -436,7 +436,7 @@ core:
|
|||
description: 该操作会将自定义页面恢复到被删除之前的状态。
|
||||
page_editor:
|
||||
title: 页面编辑
|
||||
untitled: Untitled page
|
||||
untitled: 未命名页面
|
||||
comment:
|
||||
title: 评论
|
||||
empty:
|
||||
|
@ -1362,6 +1362,7 @@ core:
|
|||
toolbox:
|
||||
attachment: 选择附件
|
||||
show_hide_sidebar: 显示 / 隐藏侧边栏
|
||||
title_placeholder: 请输入标题
|
||||
global_search:
|
||||
placeholder: 输入关键词以搜索
|
||||
no_results: 没有搜索结果
|
||||
|
|
|
@ -1328,6 +1328,7 @@ core:
|
|||
toolbox:
|
||||
attachment: 選擇附件
|
||||
show_hide_sidebar: 顯示 / 隱藏側邊欄
|
||||
title_placeholder: 請輸入標題
|
||||
global_search:
|
||||
placeholder: 輸入關鍵字以搜尋
|
||||
no_results: 沒有搜尋結果
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
VSpace,
|
||||
} from "@halo-dev/components";
|
||||
import EditorProviderSelector from "@/components/dropdown-selector/EditorProviderSelector.vue";
|
||||
import { ref, toRef } from "vue";
|
||||
import { ref, toRef, watch } from "vue";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
import type { Post, Content, Snapshot } from "@halo-dev/api-client";
|
||||
import { randomUUID } from "@/utils/id";
|
||||
|
@ -82,6 +82,14 @@ const content = ref<Content>({
|
|||
});
|
||||
const snapshot = ref<Snapshot>();
|
||||
|
||||
const isTitleChanged = ref(false);
|
||||
watch(
|
||||
() => formState.value.spec.title,
|
||||
(newValue, oldValue) => {
|
||||
isTitleChanged.value = newValue !== oldValue;
|
||||
}
|
||||
);
|
||||
|
||||
// provide some data to editor
|
||||
provide<ComputedRef<string | undefined>>(
|
||||
"owner",
|
||||
|
@ -166,21 +174,7 @@ useAutoSaveContent(currentCache, toRef(content.value, "raw"), async () => {
|
|||
if (isUpdateMode.value) {
|
||||
handleSave({ mute: true });
|
||||
} else {
|
||||
formState.value.metadata.annotations = {
|
||||
...formState.value.metadata.annotations,
|
||||
[contentAnnotations.CONTENT_JSON]: JSON.stringify(content.value),
|
||||
};
|
||||
// Set default title and slug
|
||||
if (!formState.value.spec.title) {
|
||||
formState.value.spec.title = t("core.post_editor.untitled");
|
||||
}
|
||||
if (!formState.value.spec.slug) {
|
||||
formState.value.spec.slug = new Date().getTime().toString();
|
||||
}
|
||||
const { data: createdPost } = await apiClient.uc.post.createMyPost({
|
||||
post: formState.value,
|
||||
});
|
||||
onCreatePostSuccess(createdPost);
|
||||
handleCreate();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -271,16 +265,34 @@ async function handleSetEditorProviderFromRemote() {
|
|||
}
|
||||
|
||||
// Create post
|
||||
const postCreationModal = ref(false);
|
||||
|
||||
function handleSaveClick() {
|
||||
if (isUpdateMode.value) {
|
||||
handleSave({ mute: false });
|
||||
} else {
|
||||
postCreationModal.value = true;
|
||||
handleCreate();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreate() {
|
||||
formState.value.metadata.annotations = {
|
||||
...formState.value.metadata.annotations,
|
||||
[contentAnnotations.CONTENT_JSON]: JSON.stringify(content.value),
|
||||
};
|
||||
// Set default title and slug
|
||||
if (!formState.value.spec.title) {
|
||||
formState.value.spec.title = t("core.post_editor.untitled");
|
||||
}
|
||||
if (!formState.value.spec.slug) {
|
||||
formState.value.spec.slug = new Date().getTime().toString();
|
||||
}
|
||||
|
||||
const { data: createdPost } = await apiClient.uc.post.createMyPost({
|
||||
post: formState.value,
|
||||
});
|
||||
|
||||
await onCreatePostSuccess(createdPost);
|
||||
}
|
||||
|
||||
async function onCreatePostSuccess(data: Post) {
|
||||
formState.value = data;
|
||||
// Update route query params
|
||||
|
@ -300,6 +312,17 @@ const { mutateAsync: handleSave, isLoading: isSaving } = useMutation({
|
|||
mute: false,
|
||||
},
|
||||
mutationFn: async () => {
|
||||
// Update title
|
||||
// TODO: needs retry
|
||||
if (isTitleChanged.value) {
|
||||
const { data: updatedPost } = await apiClient.uc.post.updateMyPost({
|
||||
name: formState.value.metadata.name,
|
||||
post: formState.value,
|
||||
});
|
||||
formState.value = updatedPost;
|
||||
isTitleChanged.value = false;
|
||||
}
|
||||
|
||||
// Snapshot always exists in update mode
|
||||
if (!snapshot.value) {
|
||||
return;
|
||||
|
@ -345,6 +368,7 @@ function handlePublishClick() {
|
|||
if (isUpdateMode.value) {
|
||||
handlePublish();
|
||||
} else {
|
||||
// Set editor title to post
|
||||
postPublishModal.value = true;
|
||||
}
|
||||
}
|
||||
|
@ -396,24 +420,7 @@ async function handleUploadImage(file: File, options?: AxiosRequestConfig) {
|
|||
return;
|
||||
}
|
||||
if (!isUpdateMode.value) {
|
||||
formState.value.metadata.annotations = {
|
||||
...formState.value.metadata.annotations,
|
||||
[contentAnnotations.CONTENT_JSON]: JSON.stringify(content.value),
|
||||
};
|
||||
|
||||
if (!formState.value.spec.title) {
|
||||
formState.value.spec.title = t("core.post_editor.untitled");
|
||||
}
|
||||
|
||||
if (!formState.value.spec.slug) {
|
||||
formState.value.spec.slug = new Date().getTime().toString();
|
||||
}
|
||||
|
||||
const { data } = await apiClient.uc.post.createMyPost({
|
||||
post: formState.value,
|
||||
});
|
||||
|
||||
await onCreatePostSuccess(data);
|
||||
await handleCreate();
|
||||
}
|
||||
|
||||
const { data } = await apiClient.uc.attachment.createAttachmentForPost(
|
||||
|
@ -487,23 +494,17 @@ useSessionKeepAlive();
|
|||
v-if="currentEditorProvider"
|
||||
v-model:raw="content.raw"
|
||||
v-model:content="content.content"
|
||||
v-model:title="formState.spec.title"
|
||||
:upload-image="handleUploadImage"
|
||||
class="h-full"
|
||||
@update="handleSetContentCache"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PostCreationModal
|
||||
v-if="postCreationModal"
|
||||
:title="$t('core.uc_post.creation_modal.title')"
|
||||
:content="content"
|
||||
@close="postCreationModal = false"
|
||||
@success="onCreatePostSuccess"
|
||||
/>
|
||||
|
||||
<PostCreationModal
|
||||
v-if="postPublishModal"
|
||||
:title="$t('core.uc_post.publish_modal.title')"
|
||||
:post="formState"
|
||||
:content="content"
|
||||
publish
|
||||
@close="postPublishModal = false"
|
||||
|
|
|
@ -17,6 +17,7 @@ const props = withDefaults(
|
|||
title: string;
|
||||
content: Content;
|
||||
publish?: boolean;
|
||||
post: Post;
|
||||
}>(),
|
||||
{
|
||||
publish: false,
|
||||
|
@ -107,7 +108,17 @@ function onSubmit(data: PostFormState) {
|
|||
centered
|
||||
@close="emit('close')"
|
||||
>
|
||||
<PostSettingForm @submit="onSubmit" />
|
||||
<PostSettingForm
|
||||
:form-state="{
|
||||
title: props.post.spec.title,
|
||||
slug: props.post.spec.slug,
|
||||
allowComment: props.post.spec.allowComment,
|
||||
visible: props.post.spec.visible,
|
||||
pinned: props.post.spec.pinned,
|
||||
excerptAutoGenerate: props.post.spec.excerpt.autoGenerate,
|
||||
}"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
|
||||
<template #footer>
|
||||
<VSpace>
|
||||
|
|
Loading…
Reference in New Issue