feat: enable asynchronous resolving for UI extension points (#6018)

#### What type of PR is this?

/area ui
/kind feature
/milestone 2.16.x

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

优化 UI 部分的扩展点获取实现,让部分扩展点支持异步获取,之前的实现与文档不符。

比如:

```ts
import { definePlugin } from "@halo-dev/console-shared";
import axios from "axios";

export default definePlugin({
  components: {},
  routes: [],
  extensionPoints: {
    "attachment:selector:create": async () => {
      const { data } = await axios.get(
        "/apis/v1alpha1/fake.halo.run/attachments/selectors"
      );

      return data;
    },
  },
});
```

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

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

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

```release-note
优化 UI 部分的扩展点获取实现,让部分扩展点支持异步获取。
```
pull/6022/head
Ryan Wang 2024-05-30 15:01:15 +08:00 committed by GitHub
parent bf75a36df7
commit 4c6abdcaa1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 300 additions and 282 deletions

View File

@ -1,14 +1,13 @@
<script lang="ts" setup>
import { usePluginModuleStore } from "@/stores/plugin";
import { VButton, VModal, VSpace, VTabbar } from "@halo-dev/components";
import { ref, markRaw, onMounted, computed } from "vue";
import CoreSelectorProvider from "./selector-providers/CoreSelectorProvider.vue";
import type {
AttachmentLike,
AttachmentSelectProvider,
PluginModule,
} from "@halo-dev/console-shared";
import { usePluginModuleStore } from "@/stores/plugin";
import { computed, markRaw, onMounted, ref } from "vue";
import { useI18n } from "vue-i18n";
import CoreSelectorProvider from "./selector-providers/CoreSelectorProvider.vue";
const { t } = useI18n();
@ -46,23 +45,22 @@ const attachmentSelectProviders = ref<AttachmentSelectProvider[]>([
// resolve plugin extension points
const { pluginModules } = usePluginModuleStore();
onMounted(() => {
pluginModules.forEach((pluginModule: PluginModule) => {
const { extensionPoints } = pluginModule;
if (!extensionPoints?.["attachment:selector:create"]) {
return;
}
onMounted(async () => {
for (const pluginModule of pluginModules) {
try {
const callbackFunction =
pluginModule?.extensionPoints?.["attachment:selector:create"];
const providers = extensionPoints[
"attachment:selector:create"
]() as AttachmentSelectProvider[];
if (typeof callbackFunction !== "function") {
continue;
}
if (providers) {
providers.forEach((provider) => {
attachmentSelectProviders.value.push(provider);
});
const providers = await callbackFunction();
attachmentSelectProviders.value.push(...providers);
} catch (error) {
console.error(`Error processing plugin module:`, pluginModule, error);
}
});
}
});
const activeId = ref(attachmentSelectProviders.value[0].id);

View File

@ -1,4 +1,15 @@
<script lang="ts" setup>
import { usePluginModuleStore } from "@/stores/plugin";
import { apiClient } from "@/utils/api-client";
import { formatDatetime } from "@/utils/date";
import { usePermission } from "@/utils/permission";
import type {
Extension,
ListedComment,
ListedReply,
Post,
SinglePage,
} from "@halo-dev/api-client";
import {
Dialog,
IconAddCircle,
@ -15,28 +26,16 @@ import {
VStatusDot,
VTag,
} from "@halo-dev/components";
import ReplyCreationModal from "./ReplyCreationModal.vue";
import type {
Extension,
ListedComment,
ListedReply,
Post,
SinglePage,
} from "@halo-dev/api-client";
import { formatDatetime } from "@/utils/date";
import { computed, onMounted, provide, ref, type Ref } from "vue";
import ReplyListItem from "./ReplyListItem.vue";
import { apiClient } from "@/utils/api-client";
import { cloneDeep } from "lodash-es";
import { usePermission } from "@/utils/permission";
import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query";
import { useI18n } from "vue-i18n";
import { usePluginModuleStore } from "@/stores/plugin";
import type {
CommentSubjectRefProvider,
CommentSubjectRefResult,
PluginModule,
} from "@halo-dev/console-shared";
import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query";
import { cloneDeep } from "lodash-es";
import { computed, onMounted, provide, ref, type Ref } from "vue";
import { useI18n } from "vue-i18n";
import ReplyCreationModal from "./ReplyCreationModal.vue";
import ReplyListItem from "./ReplyListItem.vue";
const { currentUserHasPermission } = usePermission();
const { t } = useI18n();
@ -250,25 +249,21 @@ const SubjectRefProviders = ref<CommentSubjectRefProvider[]>([
},
]);
const { pluginModules } = usePluginModuleStore();
onMounted(() => {
const { pluginModules } = usePluginModuleStore();
for (const pluginModule of pluginModules) {
const callbackFunction =
pluginModule?.extensionPoints?.["comment:subject-ref:create"];
pluginModules.forEach((pluginModule: PluginModule) => {
const { extensionPoints } = pluginModule;
if (!extensionPoints?.["comment:subject-ref:create"]) {
return;
if (typeof callbackFunction !== "function") {
continue;
}
const providers = extensionPoints[
"comment:subject-ref:create"
]() as CommentSubjectRefProvider[];
const providers = callbackFunction();
if (providers) {
providers.forEach((provider) => {
SubjectRefProviders.value.push(provider);
});
}
});
SubjectRefProviders.value.push(...providers);
}
});
const subjectRefResult = computed(() => {

View File

@ -1,5 +1,10 @@
<script lang="ts" setup>
import { usePluginModuleStore } from "@/stores/plugin";
import { usePermission } from "@/utils/permission";
import type { Theme } from "@halo-dev/api-client";
import { VButton, VModal, VTabbar } from "@halo-dev/components";
import type { ThemeListTab } from "@halo-dev/console-shared";
import { useRouteQuery } from "@vueuse/router";
import {
computed,
inject,
@ -8,19 +13,14 @@ import {
onMounted,
provide,
ref,
type Ref,
watch,
type Ref,
} from "vue";
import type { Theme } from "@halo-dev/api-client";
import { useI18n } from "vue-i18n";
import { useRouteQuery } from "@vueuse/router";
import InstalledThemes from "./list-tabs/InstalledThemes.vue";
import NotInstalledThemes from "./list-tabs/NotInstalledThemes.vue";
import LocalUpload from "./list-tabs/LocalUpload.vue";
import NotInstalledThemes from "./list-tabs/NotInstalledThemes.vue";
import RemoteDownload from "./list-tabs/RemoteDownload.vue";
import { usePluginModuleStore } from "@/stores/plugin";
import type { PluginModule, ThemeListTab } from "@halo-dev/console-shared";
import { usePermission } from "@/utils/permission";
const { t } = useI18n();
const { currentUserHasPermission } = usePermission();
@ -92,22 +92,30 @@ onMounted(() => {
});
const { pluginModules } = usePluginModuleStore();
onMounted(() => {
onMounted(async () => {
const tabsFromPlugins: ThemeListTab[] = [];
pluginModules.forEach((pluginModule: PluginModule) => {
const { extensionPoints } = pluginModule;
if (!extensionPoints?.["theme:list:tabs:create"]) {
return;
for (const pluginModule of pluginModules) {
try {
const callbackFunction =
pluginModule?.extensionPoints?.["theme:list:tabs:create"];
if (typeof callbackFunction !== "function") {
continue;
}
const items = await callbackFunction();
tabsFromPlugins.push(
...items.filter((item) => {
return currentUserHasPermission(item.permissions);
})
);
} catch (error) {
console.error(`Error processing plugin module:`, pluginModule, error);
}
let items = extensionPoints["theme:list:tabs:create"]() as ThemeListTab[];
items = items.filter((item) => {
return currentUserHasPermission(item.permissions);
});
tabsFromPlugins.push(...items);
});
}
tabs.value = tabs.value.concat(tabsFromPlugins).sort((a, b) => {
return a.priority - b.priority;

View File

@ -1,22 +1,21 @@
<script lang="ts" setup>
import {
VPageHeader,
VCard,
VButton,
VTabbar,
IconAddCircle,
IconServerLine,
VButton,
VCard,
VPageHeader,
VTabbar,
} from "@halo-dev/components";
import { onMounted, shallowRef } from "vue";
import ListTab from "./tabs/List.vue";
import RestoreTab from "./tabs/Restore.vue";
import { useRouteQuery } from "@vueuse/router";
import { markRaw } from "vue";
import { useI18n } from "vue-i18n";
import { useBackup } from "./composables/use-backup";
import { usePluginModuleStore } from "@/stores/plugin";
import type { BackupTab } from "@halo-dev/console-shared";
import { useRouteQuery } from "@vueuse/router";
import { markRaw, onMounted, shallowRef } from "vue";
import { useI18n } from "vue-i18n";
import { useBackup } from "./composables/use-backup";
import ListTab from "./tabs/List.vue";
import RestoreTab from "./tabs/Restore.vue";
const { t } = useI18n();
@ -37,21 +36,27 @@ const activeTab = useRouteQuery<string>("tab", tabs.value[0].id);
const { handleCreate } = useBackup();
onMounted(() => {
const { pluginModules } = usePluginModuleStore();
const { pluginModules } = usePluginModuleStore();
pluginModules.forEach((pluginModule) => {
const { extensionPoints } = pluginModule;
if (!extensionPoints?.["backup:tabs:create"]) {
return;
onMounted(async () => {
for (const pluginModule of pluginModules) {
try {
const callbackFunction =
pluginModule?.extensionPoints?.["backup:tabs:create"];
if (typeof callbackFunction !== "function") {
continue;
}
const backupTabs = await callbackFunction();
if (backupTabs) {
tabs.value = tabs.value.concat(backupTabs);
}
} catch (error) {
console.error(`Error processing plugin module:`, pluginModule, error);
}
const backupTabs = extensionPoints["backup:tabs:create"]() as BackupTab[];
if (backupTabs) {
tabs.value = tabs.value.concat(backupTabs);
}
});
}
});
</script>

View File

@ -1,27 +1,27 @@
<script lang="ts" setup>
// core libs
import { provide, ref, computed } from "vue";
import { useRoute } from "vue-router";
import { apiClient } from "@/utils/api-client";
import { computed, provide, ref } from "vue";
import { useRoute } from "vue-router";
// libs
import { cloneDeep } from "lodash-es";
// components
import { VCard, VPageHeader, VTabbar, VAvatar } from "@halo-dev/components";
import { VAvatar, VCard, VPageHeader, VTabbar } from "@halo-dev/components";
// types
import type { Ref } from "vue";
import type { Plugin, Setting, SettingForm } from "@halo-dev/api-client";
import { usePluginModuleStore } from "@/stores/plugin";
import { usePermission } from "@/utils/permission";
import { useI18n } from "vue-i18n";
import { useQuery } from "@tanstack/vue-query";
import type { Plugin, Setting, SettingForm } from "@halo-dev/api-client";
import type { PluginTab } from "@halo-dev/console-shared";
import { useQuery } from "@tanstack/vue-query";
import { useRouteQuery } from "@vueuse/router";
import type { Ref } from "vue";
import { markRaw } from "vue";
import { useI18n } from "vue-i18n";
import DetailTab from "./tabs/Detail.vue";
import SettingTab from "./tabs/Setting.vue";
import { useRouteQuery } from "@vueuse/router";
import { usePluginModuleStore } from "@/stores/plugin";
const { currentUserHasPermission } = usePermission();
const { t } = useI18n();
@ -50,12 +50,12 @@ const { data: plugin } = useQuery({
});
return data;
},
onSuccess(data) {
async onSuccess(data) {
if (
!data.spec.settingName ||
!currentUserHasPermission(["system:plugins:manage"])
) {
tabs.value = [...initialTabs.value, ...getTabsFromExtensions()];
tabs.value = [...initialTabs.value, ...(await getTabsFromExtensions())];
}
},
});
@ -82,7 +82,7 @@ const { data: setting } = useQuery({
const { forms } = data.spec;
tabs.value = [
...initialTabs.value,
...getTabsFromExtensions(),
...(await getTabsFromExtensions()),
...forms.map((item: SettingForm) => {
return {
id: item.group,
@ -97,7 +97,7 @@ const { data: setting } = useQuery({
provide<Ref<Setting | undefined>>("setting", setting);
function getTabsFromExtensions(): PluginTab[] {
async function getTabsFromExtensions() {
const { pluginModuleMap } = usePluginModuleStore();
const currentPluginModule = pluginModuleMap[route.params.name as string];
@ -106,15 +106,14 @@ function getTabsFromExtensions(): PluginTab[] {
return [];
}
const { extensionPoints } = currentPluginModule;
const callbackFunction =
currentPluginModule?.extensionPoints?.["plugin:self:tabs:create"];
if (!extensionPoints?.["plugin:self:tabs:create"]) {
if (typeof callbackFunction !== "function") {
return [];
}
const pluginTabs = extensionPoints[
"plugin:self:tabs:create"
]() as PluginTab[];
const pluginTabs = await callbackFunction();
return pluginTabs.filter((tab) => {
return currentUserHasPermission(tab.permissions);

View File

@ -1,26 +1,23 @@
<script lang="ts" setup>
import { VButton, VModal, VTabbar } from "@halo-dev/components";
import { usePluginModuleStore } from "@/stores/plugin";
import { usePermission } from "@/utils/permission";
import type { Plugin } from "@halo-dev/api-client";
import { VButton, VModal, VTabbar } from "@halo-dev/components";
import type { PluginInstallationTab } from "@halo-dev/console-shared";
import { useRouteQuery } from "@vueuse/router";
import {
computed,
markRaw,
nextTick,
onMounted,
provide,
type Ref,
ref,
toRefs,
type Ref,
} from "vue";
import { useI18n } from "vue-i18n";
import { useRouteQuery } from "@vueuse/router";
import LocalUpload from "./installation-tabs/LocalUpload.vue";
import RemoteDownload from "./installation-tabs/RemoteDownload.vue";
import type {
PluginInstallationTab,
PluginModule,
} from "@halo-dev/console-shared";
import { usePluginModuleStore } from "@/stores/plugin";
import { usePermission } from "@/utils/permission";
const { t } = useI18n();
const { currentUserHasPermission } = usePermission();
@ -82,23 +79,28 @@ onMounted(() => {
});
const { pluginModules } = usePluginModuleStore();
onMounted(() => {
pluginModules.forEach((pluginModule: PluginModule) => {
const { extensionPoints } = pluginModule;
if (!extensionPoints?.["plugin:installation:tabs:create"]) {
return;
onMounted(async () => {
for (const pluginModule of pluginModules) {
try {
const callbackFunction =
pluginModule?.extensionPoints?.["plugin:installation:tabs:create"];
if (typeof callbackFunction !== "function") {
continue;
}
const items = await callbackFunction();
tabs.value.push(
...items.filter((item) => {
return currentUserHasPermission(item.permissions);
})
);
} catch (error) {
console.error(`Error processing plugin module:`, pluginModule, error);
}
let items = extensionPoints[
"plugin:installation:tabs:create"
]() as PluginInstallationTab[];
items = items.filter((item) => {
return currentUserHasPermission(item.permissions);
});
tabs.value.push(...items);
});
}
tabs.value.sort((a, b) => {
return a.priority - b.priority;

View File

@ -1,32 +1,32 @@
<script lang="ts" setup>
import UserAvatar from "@/components/user-avatar/UserAvatar.vue";
import { usePluginModuleStore } from "@/stores/plugin";
import { useUserStore } from "@/stores/user";
import { apiClient } from "@/utils/api-client";
import { usePermission } from "@/utils/permission";
import {
VButton,
VDropdown,
VDropdownItem,
VTabbar,
} from "@halo-dev/components";
import type { UserTab } from "@halo-dev/console-shared";
import { useQuery } from "@tanstack/vue-query";
import { useRouteQuery } from "@vueuse/router";
import {
computed,
markRaw,
onMounted,
provide,
type Ref,
ref,
toRaw,
type Ref,
} from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import UserEditingModal from "./components/UserEditingModal.vue";
import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue";
import { usePermission } from "@/utils/permission";
import { useQuery } from "@tanstack/vue-query";
import { useI18n } from "vue-i18n";
import UserAvatar from "@/components/user-avatar/UserAvatar.vue";
import DetailTab from "./tabs/Detail.vue";
import { useRouteQuery } from "@vueuse/router";
import { useUserStore } from "@/stores/user";
import { usePluginModuleStore } from "@/stores/plugin";
import type { PluginModule, UserTab } from "@halo-dev/console-shared";
const { currentUserHasPermission } = usePermission();
const { t } = useI18n();
@ -62,19 +62,24 @@ const tabs = ref<UserTab[]>([
]);
// Collect user:detail:tabs:create extension points
onMounted(() => {
const { pluginModules } = usePluginModuleStore();
const { pluginModules } = usePluginModuleStore();
pluginModules.forEach((pluginModule: PluginModule) => {
const { extensionPoints } = pluginModule;
if (!extensionPoints?.["user:detail:tabs:create"]) {
return;
onMounted(async () => {
for (const pluginModule of pluginModules) {
try {
const callbackFunction =
pluginModule?.extensionPoints?.["user:detail:tabs:create"];
if (typeof callbackFunction !== "function") {
continue;
}
const providers = await callbackFunction();
tabs.value.push(...providers);
} catch (error) {
console.error(`Error processing plugin module:`, pluginModule, error);
}
const providers = extensionPoints["user:detail:tabs:create"]() as UserTab[];
tabs.value.push(...providers);
});
}
});
const activeTab = useRouteQuery<string>("tab", tabs.value[0].id, {

View File

@ -1,15 +1,10 @@
import type { Component, Ref } from "vue";
import type { RouteRecordName, RouteRecordRaw } from "vue-router";
import type { FunctionalPage } from "../states/pages";
import type { AttachmentSelectProvider } from "../states/attachment-selector";
import type { EditorProvider, PluginTab } from "..";
import type { AnyExtension } from "@halo-dev/richtext-editor";
import type { CommentSubjectRefProvider } from "@/states/comment-subject-ref";
import type { BackupTab } from "@/states/backup";
import type { PluginInstallationTab } from "@/states/plugin-installation-tabs";
import type { CommentSubjectRefProvider } from "@/states/comment-subject-ref";
import type { EntityFieldItem } from "@/states/entity";
import type { OperationItem } from "@/states/operation";
import type { PluginInstallationTab } from "@/states/plugin-installation-tabs";
import type { ThemeListTab } from "@/states/theme-list-tabs";
import type { UserProfileTab, UserTab } from "@/states/user-tab";
import type {
Attachment,
Backup,
@ -17,7 +12,12 @@ import type {
Plugin,
Theme,
} from "@halo-dev/api-client";
import type { UserProfileTab, UserTab } from "@/states/user-tab";
import type { AnyExtension } from "@halo-dev/richtext-editor";
import type { Component, Ref } from "vue";
import type { RouteRecordName, RouteRecordRaw } from "vue-router";
import type { EditorProvider, PluginTab } from "..";
import type { AttachmentSelectProvider } from "../states/attachment-selector";
import type { FunctionalPage } from "../states/pages";
export interface RouteRecordAppend {
parentName: RouteRecordName;
@ -50,33 +50,29 @@ export interface ExtensionPoint {
"post:list-item:operation:create"?: (
post: Ref<ListedPost>
) => OperationItem<ListedPost>[] | Promise<OperationItem<ListedPost>[]>;
) => OperationItem<ListedPost>[];
"plugin:list-item:operation:create"?: (
plugin: Ref<Plugin>
) => OperationItem<Plugin>[] | Promise<OperationItem<Plugin>[]>;
) => OperationItem<Plugin>[];
"backup:list-item:operation:create"?: (
backup: Ref<Backup>
) => OperationItem<Backup>[] | Promise<OperationItem<Backup>[]>;
) => OperationItem<Backup>[];
"attachment:list-item:operation:create"?: (
attachment: Ref<Attachment>
) => OperationItem<Attachment>[] | Promise<OperationItem<Attachment>[]>;
) => OperationItem<Attachment>[];
"plugin:list-item:field:create"?: (
plugin: Ref<Plugin>
) => EntityFieldItem[] | Promise<EntityFieldItem[]>;
"plugin:list-item:field:create"?: (plugin: Ref<Plugin>) => EntityFieldItem[];
"post:list-item:field:create"?: (
post: Ref<ListedPost>
) => EntityFieldItem[] | Promise<EntityFieldItem[]>;
"post:list-item:field:create"?: (post: Ref<ListedPost>) => EntityFieldItem[];
"theme:list:tabs:create"?: () => ThemeListTab[] | Promise<ThemeListTab[]>;
"theme:list-item:operation:create"?: (
theme: Ref<Theme>
) => OperationItem<Theme>[] | Promise<OperationItem<Theme>[]>;
) => OperationItem<Theme>[];
"user:detail:tabs:create"?: () => UserTab[] | Promise<UserTab[]>;

View File

@ -1,62 +1,62 @@
<script lang="ts" setup>
import {
DecorationSet,
Editor,
Extension,
RichTextEditor,
ExtensionBlockquote,
ExtensionBold,
ExtensionBulletList,
ExtensionClearFormat,
ExtensionCode,
ExtensionCodeBlock,
ExtensionColor,
ExtensionColumn,
ExtensionColumns,
ExtensionCommands,
ExtensionDocument,
ExtensionDraggable,
ExtensionDropcursor,
ExtensionFontSize,
ExtensionFormatBrush,
ExtensionGapcursor,
ExtensionHardBreak,
ExtensionHeading,
ExtensionHighlight,
ExtensionHistory,
ExtensionHorizontalRule,
ExtensionIframe,
ExtensionIndent,
ExtensionItalic,
ExtensionOrderedList,
ExtensionStrike,
ExtensionText,
ExtensionTaskList,
ExtensionLink,
ExtensionTextAlign,
ExtensionUnderline,
ExtensionTable,
ExtensionListKeymap,
ExtensionNodeSelected,
ExtensionOrderedList,
ExtensionPlaceholder,
ExtensionSearchAndReplace,
ExtensionStrike,
ExtensionSubscript,
ExtensionSuperscript,
ExtensionPlaceholder,
ExtensionHighlight,
ExtensionCommands,
ExtensionIframe,
ExtensionCodeBlock,
ExtensionFontSize,
ExtensionColor,
ExtensionIndent,
lowlight,
type AnyExtension,
Editor,
ToolboxItem,
ExtensionDraggable,
ExtensionColumns,
ExtensionColumn,
ExtensionNodeSelected,
ExtensionTable,
ExtensionTaskList,
ExtensionText,
ExtensionTextAlign,
ExtensionTrailingNode,
ToolbarItem,
ExtensionUnderline,
Plugin,
PluginKey,
DecorationSet,
ExtensionListKeymap,
ExtensionSearchAndReplace,
ExtensionClearFormat,
ExtensionFormatBrush,
RichTextEditor,
ToolbarItem,
ToolboxItem,
lowlight,
type AnyExtension,
} from "@halo-dev/richtext-editor";
// ui custom extension
import {
UiExtensionAudio,
UiExtensionImage,
UiExtensionUpload,
UiExtensionVideo,
} from "./extensions";
import { i18n } from "@/locales";
import { usePluginModuleStore } from "@/stores/plugin";
import { formatDatetime } from "@/utils/date";
import { usePermission } from "@/utils/permission";
import AttachmentSelectorModal from "@console/modules/contents/attachments/components/AttachmentSelectorModal.vue";
import type { Attachment } from "@halo-dev/api-client";
import {
IconCalendar,
IconCharacterRecognition,
@ -66,8 +66,23 @@ import {
VTabItem,
VTabs,
} from "@halo-dev/components";
import AttachmentSelectorModal from "@console/modules/contents/attachments/components/AttachmentSelectorModal.vue";
import type { AttachmentLike } from "@halo-dev/console-shared";
import ExtensionCharacterCount from "@tiptap/extension-character-count";
import { useDebounceFn, useLocalStorage } from "@vueuse/core";
import type { AxiosRequestConfig } from "axios";
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
import {
inject,
markRaw,
nextTick,
onBeforeUnmount,
onMounted,
ref,
shallowRef,
watch,
type ComputedRef,
} from "vue";
import { useI18n } from "vue-i18n";
import MdiFormatHeader1 from "~icons/mdi/format-header-1";
import MdiFormatHeader2 from "~icons/mdi/format-header-2";
import MdiFormatHeader3 from "~icons/mdi/format-header-3";
@ -75,29 +90,14 @@ import MdiFormatHeader4 from "~icons/mdi/format-header-4";
import MdiFormatHeader5 from "~icons/mdi/format-header-5";
import MdiFormatHeader6 from "~icons/mdi/format-header-6";
import RiLayoutRightLine from "~icons/ri/layout-right-line";
import {
inject,
markRaw,
ref,
watch,
onMounted,
shallowRef,
type ComputedRef,
} from "vue";
import { formatDatetime } from "@/utils/date";
import { useAttachmentSelect } from "./composables/use-attachment";
import type { Attachment } from "@halo-dev/api-client";
import { useI18n } from "vue-i18n";
import { i18n } from "@/locales";
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
import { usePluginModuleStore } from "@/stores/plugin";
import type { AttachmentLike, PluginModule } from "@halo-dev/console-shared";
import { useDebounceFn, useLocalStorage } from "@vueuse/core";
import { onBeforeUnmount } from "vue";
import { usePermission } from "@/utils/permission";
import type { AxiosRequestConfig } from "axios";
import {
UiExtensionAudio,
UiExtensionImage,
UiExtensionUpload,
UiExtensionVideo,
} from "./extensions";
import { getContents } from "./utils/attachment";
import { nextTick } from "vue";
const { t } = useI18n();
const { currentUserHasPermission } = usePermission();
@ -189,20 +189,21 @@ const handleCloseAttachmentSelectorModal = () => {
attachmentOptions.value = initAttachmentOptions;
};
onMounted(() => {
onMounted(async () => {
const extensionsFromPlugins: AnyExtension[] = [];
pluginModules.forEach((pluginModule: PluginModule) => {
const { extensionPoints } = pluginModule;
if (!extensionPoints?.["default:editor:extension:create"]) {
return;
for (const pluginModule of pluginModules) {
const callbackFunction =
pluginModule?.extensionPoints?.["default:editor:extension:create"];
if (typeof callbackFunction !== "function") {
continue;
}
const extensions = extensionPoints[
"default:editor:extension:create"
]() as [];
const extensions = await callbackFunction();
extensionsFromPlugins.push(...extensions);
});
}
// debounce OnUpdate
const debounceOnUpdate = useDebounceFn(() => {

View File

@ -1,8 +1,8 @@
import { usePluginModuleStore } from "@/stores/plugin";
import type { EditorProvider, PluginModule } from "@halo-dev/console-shared";
import { onMounted, ref, type Ref, defineAsyncComponent, markRaw } from "vue";
import { VLoading } from "@halo-dev/components";
import Logo from "@/assets/logo.png";
import { usePluginModuleStore } from "@/stores/plugin";
import { VLoading } from "@halo-dev/components";
import type { EditorProvider } from "@halo-dev/console-shared";
import { defineAsyncComponent, markRaw, onMounted, ref, type Ref } from "vue";
import { useI18n } from "vue-i18n";
interface useEditorExtensionPointsReturn {
@ -30,17 +30,23 @@ export function useEditorExtensionPoints(): useEditorExtensionPointsReturn {
},
]);
onMounted(() => {
pluginModules.forEach((pluginModule: PluginModule) => {
const { extensionPoints } = pluginModule;
if (!extensionPoints?.["editor:create"]) {
return;
onMounted(async () => {
for (const pluginModule of pluginModules) {
try {
const callbackFunction =
pluginModule?.extensionPoints?.["editor:create"];
if (typeof callbackFunction !== "function") {
continue;
}
const providers = await callbackFunction();
editorProviders.value.push(...providers);
} catch (error) {
console.error(`Error processing plugin module:`, pluginModule, error);
}
const providers = extensionPoints["editor:create"]() as EditorProvider[];
editorProviders.value.push(...providers);
});
}
});
return {

View File

@ -1,33 +1,33 @@
<script lang="ts" setup>
import UserAvatar from "@/components/user-avatar/UserAvatar.vue";
import { usePluginModuleStore } from "@/stores/plugin";
import { apiClient } from "@/utils/api-client";
import type { DetailedUser } from "@halo-dev/api-client";
import {
VButton,
VDropdown,
VDropdownItem,
VTabbar,
} from "@halo-dev/components";
import type { UserProfileTab } from "@halo-dev/console-shared";
import { useQuery } from "@tanstack/vue-query";
import { useRouteQuery } from "@vueuse/router";
import {
computed,
markRaw,
onMounted,
provide,
type Ref,
ref,
toRaw,
type Ref,
} from "vue";
import type { DetailedUser } from "@halo-dev/api-client";
import ProfileEditingModal from "./components/ProfileEditingModal.vue";
import PasswordChangeModal from "./components/PasswordChangeModal.vue";
import { useQuery } from "@tanstack/vue-query";
import { useI18n } from "vue-i18n";
import UserAvatar from "@/components/user-avatar/UserAvatar.vue";
import PasswordChangeModal from "./components/PasswordChangeModal.vue";
import ProfileEditingModal from "./components/ProfileEditingModal.vue";
import DetailTab from "./tabs/Detail.vue";
import PersonalAccessTokensTab from "./tabs/PersonalAccessTokens.vue";
import { useRouteQuery } from "@vueuse/router";
import NotificationPreferences from "./tabs/NotificationPreferences.vue";
import PersonalAccessTokensTab from "./tabs/PersonalAccessTokens.vue";
import TwoFactor from "./tabs/TwoFactor.vue";
import type { PluginModule, UserProfileTab } from "@halo-dev/console-shared";
import { usePluginModuleStore } from "@/stores/plugin";
const { t } = useI18n();
@ -76,21 +76,24 @@ const tabs = ref<UserProfileTab[]>([
]);
// Collect uc:profile:tabs:create extension points
onMounted(() => {
const { pluginModules } = usePluginModuleStore();
const { pluginModules } = usePluginModuleStore();
pluginModules.forEach((pluginModule: PluginModule) => {
const { extensionPoints } = pluginModule;
if (!extensionPoints?.["uc:user:profile:tabs:create"]) {
return;
onMounted(async () => {
for (const pluginModule of pluginModules) {
try {
const callbackFunction =
pluginModule?.extensionPoints?.["uc:user:profile:tabs:create"];
if (typeof callbackFunction !== "function") {
continue;
}
const providers = await callbackFunction();
tabs.value.push(...providers);
} catch (error) {
console.error(`Error processing plugin module:`, pluginModule, error);
}
const providers = extensionPoints[
"uc:user:profile:tabs:create"
]() as UserProfileTab[];
tabs.value.push(...providers);
});
}
});
const tabbarItems = computed(() => {