Skip to content
This repository has been archived by the owner on Jan 21, 2024. It is now read-only.

Commit

Permalink
feat: add AnnotationsForm Component to edit extension annotations (#770)
Browse files Browse the repository at this point in the history
#### 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
ruibaby authored Dec 26, 2022
1 parent eccb6e6 commit d60931b
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 5 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@
"@formkit/inputs": "^1.0.0-beta.12",
"@formkit/themes": "^1.0.0-beta.12",
"@formkit/utils": "^1.0.0-beta.12",
"@formkit/validation": "1.0.0-beta.12",
"@formkit/vue": "^1.0.0-beta.12",
"@halo-dev/api-client": "^0.0.62",
"@halo-dev/api-client": "0.0.64",
"@halo-dev/components": "workspace:*",
"@halo-dev/console-shared": "workspace:*",
"@halo-dev/richtext-editor": "^0.0.0-alpha.17",
Expand Down
34 changes: 30 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

231 changes: 231 additions & 0 deletions src/components/form/AnnotationsForm.vue
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>
39 changes: 39 additions & 0 deletions src/modules/contents/posts/components/PostSettingModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useThemeCustomTemplates } from "@/modules/interface/themes/composables/
import { postLabels } from "@/constants/labels";
import { randomUUID } from "@/utils/id";
import { toDatetimeLocal, toISOString } from "@/utils/date";
import AnnotationsForm from "@/components/form/AnnotationsForm.vue";
import { submitForm } from "@formkit/core";
const initialFormState: Post = {
Expand Down Expand Up @@ -107,6 +108,21 @@ const handlePublishClick = () => {
};
const handleSave = async () => {
annotationsFormRef.value?.handleSubmit();
await nextTick();
const { customAnnotations, annotations, customFormInvalid, specFormInvalid } =
annotationsFormRef.value || {};
if (customFormInvalid || specFormInvalid) {
return;
}
formState.value.metadata.annotations = {
...annotations,
...customAnnotations,
};
if (props.onlyEmit) {
emit("saved", formState.value);
return;
Expand Down Expand Up @@ -204,6 +220,8 @@ const publishTime = computed(() => {
const onPublishTimeChange = (value: string) => {
formState.value.spec.publishTime = value ? toISOString(value) : undefined;
};
const annotationsFormRef = ref<InstanceType<typeof AnnotationsForm>>();
</script>
<template>
<VModal
Expand Down Expand Up @@ -350,6 +368,27 @@ const onPublishTimeChange = (value: string) => {
</div>
</FormKit>

<div class="py-5">
<div class="border-t border-gray-200"></div>
</div>

<div class="md:grid md:grid-cols-4 md:gap-6">
<div class="md:col-span-1">
<div class="sticky top-0">
<span class="text-base font-medium text-gray-900"> 元数据 </span>
</div>
</div>
<div class="mt-5 divide-y divide-gray-100 md:col-span-3 md:mt-0">
<AnnotationsForm
:key="formState.metadata.name"
ref="annotationsFormRef"
:value="formState.metadata.annotations"
kind="Post"
group="content.halo.run"
/>
</div>
</div>

<template #footer>
<VSpace>
<template v-if="publishSupport">
Expand Down
Loading

1 comment on commit d60931b

@vercel
Copy link

@vercel vercel bot commented on d60931b Dec 26, 2022

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

Please sign in to comment.