This repository has been archived by the owner on Jan 21, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 482
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add AnnotationsForm Component to edit extension annotations (#770)
#### What type of PR is this? /kind feature #### What this PR does / why we need it: 提供一个 Annotations 编辑组件,支持由主题或者插件提供表单定义,也支持使用者自定义 key-value,用于扩展资源字段。 #### Which issue(s) this PR fixes: Fixes halo-dev/halo#2858 #### Screenshots: <img width="789" alt="image" src="https://user-images.githubusercontent.com/21301288/209517622-80111fd2-8e79-480f-8ca0-9f7073300b2b.png"> #### Special notes for your reviewer: 测试方式: 1. 将一下内容放到任意一个主题下,后缀为 `yaml`,文件名随意。 ```yaml spec: targetRef: group: content.halo.run kind: Post formSchema: - $formkit: "text" name: "download" label: "下载地址" - $formkit: "text" name: "version" label: "版本" apiVersion: v1alpha1 kind: AnnotationSetting metadata: generateName: annotation- ``` 3. 后端需要使用 halo-dev/halo#3028 4. 测试为文章设置 Annotations 和自定义的 Annotations。 5. 检查是否可以设置正常。 #### Does this PR introduce a user-facing change? ```release-note 文章设置支持设置元数据。 ```
- Loading branch information
Showing
5 changed files
with
308 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
<script lang="ts" setup> | ||
import { | ||
reset, | ||
submitForm, | ||
type FormKitNode, | ||
type FormKitSchemaCondition, | ||
type FormKitSchemaNode, | ||
} from "@formkit/core"; | ||
import { computed, nextTick, onMounted, ref, watch } from "vue"; | ||
import { apiClient } from "@/utils/api-client"; | ||
import type { AnnotationSetting } from "@halo-dev/api-client"; | ||
import cloneDeep from "lodash.clonedeep"; | ||
import { getValidationMessages } from "@formkit/validation"; | ||
import { useThemeStore } from "@/stores/theme"; | ||
const themeStore = useThemeStore(); | ||
function keyValidationRule(node: FormKitNode) { | ||
return !annotations?.[node.value as string]; | ||
} | ||
const props = withDefaults( | ||
defineProps<{ | ||
group: string; | ||
kind: string; | ||
value?: { | ||
[key: string]: string; | ||
} | null; | ||
}>(), | ||
{ | ||
value: null, | ||
} | ||
); | ||
const annotationSettings = ref<AnnotationSetting[]>([] as AnnotationSetting[]); | ||
const avaliableAnnotationSettings = computed(() => { | ||
return annotationSettings.value.filter((setting) => { | ||
if (!setting.metadata.labels?.["theme.halo.run/theme-name"]) { | ||
return true; | ||
} | ||
return ( | ||
setting.metadata.labels?.["theme.halo.run/theme-name"] === | ||
themeStore.activatedTheme?.metadata.name | ||
); | ||
}); | ||
}); | ||
const handleFetchAnnotationSettings = async () => { | ||
try { | ||
const { data } = | ||
await apiClient.extension.annotationSetting.listv1alpha1AnnotationSetting( | ||
{ | ||
labelSelector: [ | ||
`halo.run/target-ref=${[props.group, props.kind].join("/")}`, | ||
], | ||
} | ||
); | ||
annotationSettings.value = data.items; | ||
} catch (error) { | ||
console.log("Failed to fetch annotation settings", error); | ||
} | ||
}; | ||
const annotations = ref<{ | ||
[key: string]: string; | ||
}>({}); | ||
const customAnnotationsState = ref<{ key: string; value: string }[]>([]); | ||
const customAnnotations = computed(() => { | ||
return customAnnotationsState.value.reduce((acc, cur) => { | ||
acc[cur.key] = cur.value; | ||
return acc; | ||
}, {} as { [key: string]: string }); | ||
}); | ||
const handleProcessCustomAnnotations = () => { | ||
let formSchemas: FormKitSchemaNode[] = []; | ||
avaliableAnnotationSettings.value.forEach((annotationSetting) => { | ||
formSchemas = formSchemas.concat( | ||
annotationSetting.spec?.formSchema as FormKitSchemaNode[] | ||
); | ||
}); | ||
customAnnotationsState.value = Object.entries(props.value || {}) | ||
.map(([key, value]) => { | ||
const fromThemeSpec = formSchemas.some((item) => { | ||
if (typeof item === "object" && "$formkit" in item) { | ||
return item.name === key; | ||
} | ||
return false; | ||
}); | ||
if (!fromThemeSpec) { | ||
return { | ||
key, | ||
value, | ||
}; | ||
} | ||
}) | ||
.filter((item) => item) as { key: string; value: string }[]; | ||
annotations.value = Object.entries(props.value || {}) | ||
.map(([key, value]) => { | ||
const fromThemeSpec = formSchemas.some((item) => { | ||
if (typeof item === "object" && "$formkit" in item) { | ||
return item.name === key; | ||
} | ||
return false; | ||
}); | ||
if (fromThemeSpec) { | ||
return { | ||
key, | ||
value, | ||
}; | ||
} | ||
}) | ||
.filter((item) => item) | ||
.reduce((acc, cur) => { | ||
if (cur) { | ||
acc[cur.key] = cur.value; | ||
} | ||
return acc; | ||
}, {} as { [key: string]: string }); | ||
}; | ||
onMounted(async () => { | ||
annotations.value = cloneDeep(props.value) || {}; | ||
await handleFetchAnnotationSettings(); | ||
handleProcessCustomAnnotations(); | ||
}); | ||
watch( | ||
() => props.value, | ||
(value) => { | ||
reset("specForm"); | ||
reset("customForm"); | ||
annotations.value = cloneDeep(props.value) || {}; | ||
if (value) { | ||
handleProcessCustomAnnotations(); | ||
} | ||
} | ||
); | ||
// submit | ||
const specFormInvalid = ref(true); | ||
const customFormInvalid = ref(true); | ||
const handleSubmit = async () => { | ||
submitForm("specForm"); | ||
submitForm("customForm"); | ||
await nextTick(); | ||
}; | ||
const onSpecFormSubmitCheck = async (node?: FormKitNode) => { | ||
if (!node) { | ||
return; | ||
} | ||
const validations = getValidationMessages(node); | ||
specFormInvalid.value = validations.size > 0; | ||
}; | ||
const onCustomFormSubmitCheck = async (node?: FormKitNode) => { | ||
if (!node) { | ||
return; | ||
} | ||
const validations = getValidationMessages(node); | ||
customFormInvalid.value = validations.size > 0; | ||
}; | ||
defineExpose({ | ||
handleSubmit, | ||
specFormInvalid, | ||
customFormInvalid, | ||
annotations, | ||
customAnnotations, | ||
}); | ||
</script> | ||
|
||
<template> | ||
<div class="flex flex-col gap-3 divide-y divide-gray-100"> | ||
<FormKit | ||
v-if="annotations && avaliableAnnotationSettings.length > 0" | ||
id="specForm" | ||
v-model="annotations" | ||
type="form" | ||
:preserve="true" | ||
@submit-invalid="onSpecFormSubmitCheck" | ||
@submit="specFormInvalid = false" | ||
> | ||
<template | ||
v-for="(annotationSetting, index) in avaliableAnnotationSettings" | ||
> | ||
<FormKitSchema | ||
v-if="annotationSetting.spec?.formSchema" | ||
:key="index" | ||
:schema="annotationSetting.spec?.formSchema as (FormKitSchemaCondition| FormKitSchemaNode[])" | ||
/> | ||
</template> | ||
</FormKit> | ||
<FormKit | ||
v-if="annotations" | ||
id="customForm" | ||
type="form" | ||
:preserve="true" | ||
:form-class="`${avaliableAnnotationSettings.length ? 'py-4' : ''}`" | ||
@submit-invalid="onCustomFormSubmitCheck" | ||
@submit="customFormInvalid = false" | ||
> | ||
<FormKit v-model="customAnnotationsState" type="repeater" label="自定义"> | ||
<FormKit | ||
type="text" | ||
label="Key" | ||
name="key" | ||
validation="required|keyValidationRule" | ||
:validation-rules="{ keyValidationRule }" | ||
:validation-messages="{ | ||
keyValidationRule: '当前 Key 已被占用', | ||
}" | ||
></FormKit> | ||
<FormKit | ||
type="text" | ||
label="Value" | ||
name="value" | ||
validation="required" | ||
></FormKit> | ||
</FormKit> | ||
</FormKit> | ||
</div> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
d60931b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
ui – ./
halo-admin-ui.vercel.app
ui-halo-dev.vercel.app
ui-git-main-halo-dev.vercel.app