mirror of https://github.com/halo-dev/halo
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
parent
bf75a36df7
commit
4c6abdcaa1
|
@ -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);
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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[]>;
|
||||
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
Loading…
Reference in New Issue