mirror of https://github.com/halo-dev/halo
Merge branch 'main' into refactor/setting-config-update
commit
982a45bd32
|
@ -26,6 +26,7 @@ import org.springframework.http.codec.ServerCodecConfigurer;
|
||||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.web.filter.reactive.ServerWebExchangeContextFilter;
|
||||||
import org.springframework.web.reactive.config.ResourceHandlerRegistration;
|
import org.springframework.web.reactive.config.ResourceHandlerRegistration;
|
||||||
import org.springframework.web.reactive.config.ResourceHandlerRegistry;
|
import org.springframework.web.reactive.config.ResourceHandlerRegistry;
|
||||||
import org.springframework.web.reactive.config.WebFluxConfigurer;
|
import org.springframework.web.reactive.config.WebFluxConfigurer;
|
||||||
|
@ -252,4 +253,11 @@ public class WebFluxConfig implements WebFluxConfigurer {
|
||||||
return new AdditionalWebFilterChainProxy(extensionGetter);
|
return new AdditionalWebFilterChainProxy(extensionGetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
// We expect this filter to be executed before AdditionalWebFilterChainProxy
|
||||||
|
@Order(-102)
|
||||||
|
ServerWebExchangeContextFilter serverWebExchangeContextFilter() {
|
||||||
|
return new ServerWebExchangeContextFilter();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package run.halo.app.config;
|
package run.halo.app.config;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -18,10 +19,15 @@ import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
|
import org.springframework.web.filter.reactive.ServerWebExchangeContextFilter;
|
||||||
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
|
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||||
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
import org.springframework.web.reactive.socket.WebSocketHandler;
|
import org.springframework.web.reactive.socket.WebSocketHandler;
|
||||||
import org.springframework.web.reactive.socket.WebSocketMessage;
|
import org.springframework.web.reactive.socket.WebSocketMessage;
|
||||||
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
|
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
import run.halo.app.core.endpoint.WebSocketEndpoint;
|
import run.halo.app.core.endpoint.WebSocketEndpoint;
|
||||||
import run.halo.app.core.extension.Role;
|
import run.halo.app.core.extension.Role;
|
||||||
|
@ -31,7 +37,10 @@ import run.halo.app.extension.Metadata;
|
||||||
|
|
||||||
@SpringBootTest(properties = "halo.console.location=classpath:/console/", webEnvironment =
|
@SpringBootTest(properties = "halo.console.location=classpath:/console/", webEnvironment =
|
||||||
SpringBootTest.WebEnvironment.RANDOM_PORT)
|
SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||||
@Import(WebFluxConfigTest.WebSocketSupportTest.TestWebSocketConfiguration.class)
|
@Import({
|
||||||
|
WebFluxConfigTest.WebSocketSupportTest.TestWebSocketConfiguration.class,
|
||||||
|
WebFluxConfigTest.ServerWebExchangeContextFilterTest.TestConfig.class
|
||||||
|
})
|
||||||
@AutoConfigureWebTestClient
|
@AutoConfigureWebTestClient
|
||||||
class WebFluxConfigTest {
|
class WebFluxConfigTest {
|
||||||
|
|
||||||
|
@ -154,4 +163,34 @@ class WebFluxConfigTest {
|
||||||
.expectStatus().isNotFound();
|
.expectStatus().isNotFound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class ServerWebExchangeContextFilterTest {
|
||||||
|
|
||||||
|
@TestConfiguration
|
||||||
|
static class TestConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
RouterFunction<ServerResponse> assertServerWebExchangeRoute() {
|
||||||
|
return RouterFunctions.route()
|
||||||
|
.GET("/assert-server-web-exchange",
|
||||||
|
request -> Mono.deferContextual(contextView -> {
|
||||||
|
var exchange = ServerWebExchangeContextFilter.getExchange(contextView);
|
||||||
|
assertTrue(exchange.isPresent());
|
||||||
|
return ServerResponse.ok().build();
|
||||||
|
}))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetExchangeFromContextView() {
|
||||||
|
webClient.get().uri("/assert-server-web-exchange")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -364,11 +364,9 @@ const handleApproveInBatch = async () => {
|
||||||
:title="$t('core.comment.empty.title')"
|
:title="$t('core.comment.empty.title')"
|
||||||
>
|
>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<VSpace>
|
<VButton @click="refetch">
|
||||||
<VButton @click="refetch">
|
{{ $t("core.common.buttons.refresh") }}
|
||||||
{{ $t("core.common.buttons.refresh") }}
|
</VButton>
|
||||||
</VButton>
|
|
||||||
</VSpace>
|
|
||||||
</template>
|
</template>
|
||||||
</VEmpty>
|
</VEmpty>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
|
@ -281,7 +281,7 @@ watch(
|
||||||
<VButton
|
<VButton
|
||||||
v-permission="['system:singlepages:view']"
|
v-permission="['system:singlepages:view']"
|
||||||
:route="{ name: 'SinglePages' }"
|
:route="{ name: 'SinglePages' }"
|
||||||
type="primary"
|
type="secondary"
|
||||||
>
|
>
|
||||||
{{ $t("core.common.buttons.back") }}
|
{{ $t("core.common.buttons.back") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
|
|
|
@ -432,7 +432,7 @@ watch(selectedPageNames, (newValue) => {
|
||||||
<VButton
|
<VButton
|
||||||
v-permission="['system:singlepages:manage']"
|
v-permission="['system:singlepages:manage']"
|
||||||
:route="{ name: 'SinglePageEditor' }"
|
:route="{ name: 'SinglePageEditor' }"
|
||||||
type="primary"
|
type="secondary"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconAddCircle class="h-full w-full" />
|
<IconAddCircle class="h-full w-full" />
|
||||||
|
|
|
@ -280,7 +280,7 @@ watch(
|
||||||
<VButton @click="refetch">
|
<VButton @click="refetch">
|
||||||
{{ $t("core.common.buttons.refresh") }}
|
{{ $t("core.common.buttons.refresh") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton :route="{ name: 'Posts' }" type="primary">
|
<VButton :route="{ name: 'Posts' }" type="secondary">
|
||||||
{{ $t("core.common.buttons.back") }}
|
{{ $t("core.common.buttons.back") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
</VSpace>
|
</VSpace>
|
||||||
|
|
|
@ -563,7 +563,7 @@ watch(
|
||||||
<VButton
|
<VButton
|
||||||
v-permission="['system:posts:manage']"
|
v-permission="['system:posts:manage']"
|
||||||
:route="{ name: 'PostEditor' }"
|
:route="{ name: 'PostEditor' }"
|
||||||
type="primary"
|
type="secondary"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconAddCircle class="h-full w-full" />
|
<IconAddCircle class="h-full w-full" />
|
||||||
|
|
|
@ -117,7 +117,7 @@ const handleUpdateInBatch = useDebounceFn(async () => {
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton
|
<VButton
|
||||||
v-permission="['system:posts:manage']"
|
v-permission="['system:posts:manage']"
|
||||||
type="primary"
|
type="secondary"
|
||||||
@click="creationModal = true"
|
@click="creationModal = true"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
|
|
@ -27,7 +27,7 @@ const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
category?: Category;
|
category?: Category;
|
||||||
parentCategory?: Category;
|
parentCategory?: Category;
|
||||||
isChildLevelCategory: boolean;
|
isChildLevelCategory?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
category: undefined,
|
category: undefined,
|
||||||
|
|
|
@ -267,7 +267,7 @@ watch(selectedTagNames, (newVal) => {
|
||||||
<VButton @click="() => handleFetchTags">
|
<VButton @click="() => handleFetchTags">
|
||||||
{{ $t("core.common.buttons.refresh") }}
|
{{ $t("core.common.buttons.refresh") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton type="primary" @click="editingModal = true">
|
<VButton type="secondary" @click="editingModal = true">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconAddCircle class="h-full w-full" />
|
<IconAddCircle class="h-full w-full" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -259,7 +259,7 @@ const handleDelete = async (menuItem: MenuTreeItem) => {
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton
|
<VButton
|
||||||
v-permission="['system:menus:manage']"
|
v-permission="['system:menus:manage']"
|
||||||
type="primary"
|
type="secondary"
|
||||||
@click="menuItemEditingModal = true"
|
@click="menuItemEditingModal = true"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
|
|
@ -12,7 +12,6 @@ import {
|
||||||
VEntity,
|
VEntity,
|
||||||
VEntityField,
|
VEntityField,
|
||||||
VLoading,
|
VLoading,
|
||||||
VSpace,
|
|
||||||
VStatusDot,
|
VStatusDot,
|
||||||
VTag,
|
VTag,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
|
@ -187,11 +186,9 @@ const handleSetPrimaryMenu = async (menu: Menu) => {
|
||||||
:title="$t('core.menu.empty.title')"
|
:title="$t('core.menu.empty.title')"
|
||||||
>
|
>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<VSpace>
|
<VButton size="sm" @click="refetch()">
|
||||||
<VButton size="sm" @click="refetch()">
|
{{ $t("core.common.buttons.refresh") }}
|
||||||
{{ $t("core.common.buttons.refresh") }}
|
</VButton>
|
||||||
</VButton>
|
|
||||||
</VSpace>
|
|
||||||
</template>
|
</template>
|
||||||
</VEmpty>
|
</VEmpty>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
|
@ -81,7 +81,7 @@ const handleOpenPreview = (theme: Theme) => {
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton
|
<VButton
|
||||||
v-permission="['system:themes:manage']"
|
v-permission="['system:themes:manage']"
|
||||||
type="primary"
|
type="secondary"
|
||||||
@click="activeTabId = 'local-upload'"
|
@click="activeTabId = 'local-upload'"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Theme } from "@halo-dev/api-client";
|
import type { Theme } from "@halo-dev/api-client";
|
||||||
import { consoleApiClient } from "@halo-dev/api-client";
|
import { consoleApiClient } from "@halo-dev/api-client";
|
||||||
import { VButton, VEmpty, VLoading, VSpace } from "@halo-dev/components";
|
import { VButton, VEmpty, VLoading } from "@halo-dev/components";
|
||||||
import { useQuery } from "@tanstack/vue-query";
|
import { useQuery } from "@tanstack/vue-query";
|
||||||
import ThemeListItem from "../ThemeListItem.vue";
|
import ThemeListItem from "../ThemeListItem.vue";
|
||||||
|
|
||||||
|
@ -29,11 +29,9 @@ const {
|
||||||
<Transition v-else-if="!themes?.length" appear name="fade">
|
<Transition v-else-if="!themes?.length" appear name="fade">
|
||||||
<VEmpty :title="$t('core.theme.list_modal.not_installed_empty.title')">
|
<VEmpty :title="$t('core.theme.list_modal.not_installed_empty.title')">
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<VSpace>
|
<VButton :loading="isFetching" @click="refetch">
|
||||||
<VButton :loading="isFetching" @click="refetch">
|
{{ $t("core.common.buttons.refresh") }}
|
||||||
{{ $t("core.common.buttons.refresh") }}
|
</VButton>
|
||||||
</VButton>
|
|
||||||
</VSpace>
|
|
||||||
</template>
|
</template>
|
||||||
</VEmpty>
|
</VEmpty>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import StickyBlock from "@/components/sticky-block/StickyBlock.vue";
|
||||||
import { useSettingFormConvert } from "@console/composables/use-setting-form";
|
import { useSettingFormConvert } from "@console/composables/use-setting-form";
|
||||||
import { useThemeStore } from "@console/stores/theme";
|
import { useThemeStore } from "@console/stores/theme";
|
||||||
import type {
|
import type {
|
||||||
|
@ -28,7 +29,6 @@ import { storeToRefs } from "pinia";
|
||||||
import { computed, markRaw, onMounted, ref, toRaw } from "vue";
|
import { computed, markRaw, onMounted, ref, toRaw } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import ThemePreviewListItem from "./ThemePreviewListItem.vue";
|
import ThemePreviewListItem from "./ThemePreviewListItem.vue";
|
||||||
import StickyBlock from "@/components/sticky-block/StickyBlock.vue";
|
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -304,6 +304,7 @@ const iframeClasses = computed(() => {
|
||||||
@after-leave="switching = false"
|
@after-leave="switching = false"
|
||||||
>
|
>
|
||||||
<div v-show="settingsVisible" class="mb-20">
|
<div v-show="settingsVisible" class="mb-20">
|
||||||
|
<!-- @vue-skip -->
|
||||||
<VTabbar
|
<VTabbar
|
||||||
v-model:active-id="activeSettingTab"
|
v-model:active-id="activeSettingTab"
|
||||||
:items="settingTabs"
|
:items="settingTabs"
|
||||||
|
|
|
@ -235,7 +235,7 @@ onMounted(() => {
|
||||||
<VButton @click="themesModal = true">
|
<VButton @click="themesModal = true">
|
||||||
{{ $t("core.theme.common.buttons.install") }}
|
{{ $t("core.theme.common.buttons.install") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton type="primary" @click="themesModal = true">
|
<VButton type="secondary" @click="themesModal = true">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconExchange class="h-full w-full" />
|
<IconExchange class="h-full w-full" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -66,6 +66,7 @@ const handleChangePassword = async () => {
|
||||||
:title="$t('core.user.change_password_modal.title')"
|
:title="$t('core.user.change_password_modal.title')"
|
||||||
@close="emit('close')"
|
@close="emit('close')"
|
||||||
>
|
>
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<FormKit
|
<FormKit
|
||||||
id="password-form"
|
id="password-form"
|
||||||
v-model="formState"
|
v-model="formState"
|
||||||
|
|
|
@ -50,6 +50,7 @@ const inputClasses = {
|
||||||
<IconLogo class="mb-8 flex-none" />
|
<IconLogo class="mb-8 flex-none" />
|
||||||
|
|
||||||
<div class="flex w-72 flex-col">
|
<div class="flex w-72 flex-col">
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<FormKit
|
<FormKit
|
||||||
id="setup-form"
|
id="setup-form"
|
||||||
v-model="formState"
|
v-model="formState"
|
||||||
|
|
|
@ -100,7 +100,7 @@
|
||||||
"short-unique-id": "^5.0.2",
|
"short-unique-id": "^5.0.2",
|
||||||
"transliteration": "^2.3.5",
|
"transliteration": "^2.3.5",
|
||||||
"ua-parser-js": "^1.0.38",
|
"ua-parser-js": "^1.0.38",
|
||||||
"vue": "^3.4.27",
|
"vue": "^3.5.8",
|
||||||
"vue-demi": "^0.14.7",
|
"vue-demi": "^0.14.7",
|
||||||
"vue-draggable-plus": "^0.4.1",
|
"vue-draggable-plus": "^0.4.1",
|
||||||
"vue-grid-layout": "3.0.0-beta1",
|
"vue-grid-layout": "3.0.0-beta1",
|
||||||
|
@ -122,7 +122,7 @@
|
||||||
"@types/qs": "^6.9.7",
|
"@types/qs": "^6.9.7",
|
||||||
"@types/randomstring": "^1.1.8",
|
"@types/randomstring": "^1.1.8",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
"@vitejs/plugin-vue": "^5.1.2",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||||
"@vitest/ui": "^0.34.1",
|
"@vitest/ui": "^0.34.1",
|
||||||
"@vue/compiler-sfc": "^3.4.27",
|
"@vue/compiler-sfc": "^3.4.27",
|
||||||
|
@ -151,7 +151,7 @@
|
||||||
"tailwindcss": "^3.2.7",
|
"tailwindcss": "^3.2.7",
|
||||||
"tailwindcss-safe-area": "^0.2.2",
|
"tailwindcss-safe-area": "^0.2.2",
|
||||||
"tailwindcss-themer": "^2.0.3",
|
"tailwindcss-themer": "^2.0.3",
|
||||||
"typescript": "~5.5.4",
|
"typescript": "~5.6.2",
|
||||||
"unplugin-icons": "^0.19.2",
|
"unplugin-icons": "^0.19.2",
|
||||||
"vite": "^5.4.1",
|
"vite": "^5.4.1",
|
||||||
"vite-plugin-externals": "^0.6.2",
|
"vite-plugin-externals": "^0.6.2",
|
||||||
|
@ -160,6 +160,6 @@
|
||||||
"vite-plugin-static-copy": "^1.0.6",
|
"vite-plugin-static-copy": "^1.0.6",
|
||||||
"vite-plugin-vue-devtools": "^7.3.8",
|
"vite-plugin-vue-devtools": "^7.3.8",
|
||||||
"vitest": "^0.34.1",
|
"vitest": "^0.34.1",
|
||||||
"vue-tsc": "^2.0.29"
|
"vue-tsc": "^2.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
"rimraf": "^5.0.7",
|
"rimraf": "^5.0.7",
|
||||||
"typescript": "~5.5.4",
|
"typescript": "~5.5.4",
|
||||||
"unbuild": "^0.7.6",
|
"unbuild": "^0.7.6",
|
||||||
"vite-plugin-dts": "^4.0.3"
|
"vite-plugin-dts": "^4.2.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"axios": "^1.7.x"
|
"axios": "^1.7.x"
|
||||||
|
|
|
@ -58,10 +58,10 @@
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"storybook": "^7.6.3",
|
"storybook": "^7.6.3",
|
||||||
"unplugin-icons": "^0.14.15",
|
"unplugin-icons": "^0.14.15",
|
||||||
"vite-plugin-dts": "^4.0.3"
|
"vite-plugin-dts": "^4.2.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^3.4.27",
|
"vue": "^3.5.8",
|
||||||
"vue-router": "^4.3.2"
|
"vue-router": "^4.3.2"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|
|
@ -12,6 +12,7 @@ const meta: Meta<typeof VDialog> = {
|
||||||
height: 400,
|
height: 400,
|
||||||
setup() {
|
setup() {
|
||||||
const showDialog = () => {
|
const showDialog = () => {
|
||||||
|
// @ts-ignore
|
||||||
args.visible = true;
|
args.visible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,12 @@ const meta: Meta<typeof VDropdown> = {
|
||||||
template: `
|
template: `
|
||||||
<div style="height: 300px">
|
<div style="height: 300px">
|
||||||
<VDropdown>
|
<VDropdown>
|
||||||
${args.default}
|
<VButton>Hello</VButton>
|
||||||
<template #popper>
|
<template #popper>
|
||||||
${args.popper}
|
<VDropdownItem>删除</VDropdownItem>
|
||||||
|
<VDropdownDivider></VDropdownDivider>
|
||||||
|
<VDropdownItem>删除</VDropdownItem>
|
||||||
|
<VDropdownItem>编辑</VDropdownItem>
|
||||||
</template>
|
</template>
|
||||||
</VDropdown>
|
</VDropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,13 +37,5 @@ export default meta;
|
||||||
type Story = StoryObj<typeof VDropdown>;
|
type Story = StoryObj<typeof VDropdown>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {},
|
||||||
default: `<VButton>Hello</VButton>`,
|
|
||||||
popper: `
|
|
||||||
<VDropdownItem>删除</VDropdownItem>
|
|
||||||
<VDropdownDivider></VDropdownDivider>
|
|
||||||
<VDropdownItem>删除</VDropdownItem>
|
|
||||||
<VDropdownItem>编辑</VDropdownItem>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,7 +41,7 @@ defineExpose({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- @vue-ignore -->
|
<!-- @vue-skip -->
|
||||||
<FloatingDropdown
|
<FloatingDropdown
|
||||||
ref="dropdownRef"
|
ref="dropdownRef"
|
||||||
:placement="placement"
|
:placement="placement"
|
||||||
|
|
|
@ -6,7 +6,7 @@ const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
type: "default" | "danger";
|
type?: "default" | "danger";
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
selected: false,
|
selected: false,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"exclude": ["src/**/__tests__/*", "node_modules"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
|
|
@ -87,9 +87,9 @@
|
||||||
"@iconify/json": "^2.2.117",
|
"@iconify/json": "^2.2.117",
|
||||||
"@types/linkifyjs": "^2.1.7",
|
"@types/linkifyjs": "^2.1.7",
|
||||||
"release-it": "^16.1.5",
|
"release-it": "^16.1.5",
|
||||||
"vite-plugin-dts": "^4.0.3"
|
"vite-plugin-dts": "^4.2.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^3.4.27"
|
"vue": "^3.5.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,10 +38,10 @@
|
||||||
"homepage": "https://github.com/halo-dev/halo/tree/main/ui/packages/shared#readme",
|
"homepage": "https://github.com/halo-dev/halo/tree/main/ui/packages/shared#readme",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite-plugin-dts": "^4.0.3"
|
"vite-plugin-dts": "^4.2.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^3.4.27",
|
"vue": "^3.5.8",
|
||||||
"vue-router": "^4.3.2"
|
"vue-router": "^4.3.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,8 +12,6 @@ export interface FileProps {
|
||||||
editor: CoreEditor;
|
editor: CoreEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { currentUserHasPermission } = usePermission();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles file events, determining if the file is an image and triggering the appropriate upload process.
|
* Handles file events, determining if the file is an image and triggering the appropriate upload process.
|
||||||
*
|
*
|
||||||
|
@ -21,6 +19,8 @@ const { currentUserHasPermission } = usePermission();
|
||||||
* @returns {boolean} - True if a file is handled, otherwise false
|
* @returns {boolean} - True if a file is handled, otherwise false
|
||||||
*/
|
*/
|
||||||
export const handleFileEvent = ({ file, editor }: FileProps) => {
|
export const handleFileEvent = ({ file, editor }: FileProps) => {
|
||||||
|
const { currentUserHasPermission } = usePermission();
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,12 @@ import { onMounted, ref } from "vue";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
src: string;
|
src?: string;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
classes?: string | string[];
|
classes?: string | string[];
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
src: "",
|
src: undefined,
|
||||||
alt: "",
|
alt: "",
|
||||||
classes: "",
|
classes: "",
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,12 @@ const isLoading = ref(false);
|
||||||
const error = ref(false);
|
const error = ref(false);
|
||||||
|
|
||||||
const loadImage = async () => {
|
const loadImage = async () => {
|
||||||
|
if (!props.src) {
|
||||||
|
throw new Error("src is required");
|
||||||
|
}
|
||||||
|
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
image.src = props.src;
|
image.src = props.src || "";
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
image.onload = () => resolve(image);
|
image.onload = () => resolve(image);
|
||||||
image.onerror = (err) => reject(err);
|
image.onerror = (err) => reject(err);
|
||||||
|
|
|
@ -3,11 +3,11 @@ import { onMounted, ref } from "vue";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
src: string;
|
src?: string;
|
||||||
classes?: string | string[];
|
classes?: string | string[];
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
src: "",
|
src: undefined,
|
||||||
classes: "",
|
classes: "",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -16,6 +16,10 @@ const isLoading = ref(false);
|
||||||
const error = ref(false);
|
const error = ref(false);
|
||||||
|
|
||||||
const loadVideo = async () => {
|
const loadVideo = async () => {
|
||||||
|
if (!props.src) {
|
||||||
|
throw new Error("src is required");
|
||||||
|
}
|
||||||
|
|
||||||
const video = document.createElement("video");
|
const video = document.createElement("video");
|
||||||
video.src = props.src;
|
video.src = props.src;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import Logo from "@/assets/logo.png";
|
import Logo from "@/assets/logo.png";
|
||||||
|
import DefaultEditor from "@/components/editor/DefaultEditor.vue";
|
||||||
import { usePluginModuleStore } from "@/stores/plugin";
|
import { usePluginModuleStore } from "@/stores/plugin";
|
||||||
import { VLoading } from "@halo-dev/components";
|
|
||||||
import type { EditorProvider } from "@halo-dev/console-shared";
|
import type { EditorProvider } from "@halo-dev/console-shared";
|
||||||
import { defineAsyncComponent, markRaw, ref, type Ref } from "vue";
|
import { markRaw, ref, type Ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
interface useEditorExtensionPointsReturn {
|
interface useEditorExtensionPointsReturn {
|
||||||
|
@ -19,13 +19,7 @@ export function useEditorExtensionPoints(): useEditorExtensionPointsReturn {
|
||||||
{
|
{
|
||||||
name: "default",
|
name: "default",
|
||||||
displayName: t("core.plugin.extension_points.editor.providers.default"),
|
displayName: t("core.plugin.extension_points.editor.providers.default"),
|
||||||
component: markRaw(
|
component: markRaw(DefaultEditor),
|
||||||
defineAsyncComponent({
|
|
||||||
loader: () => import("@/components/editor/DefaultEditor.vue"),
|
|
||||||
loadingComponent: VLoading,
|
|
||||||
delay: 200,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
rawType: "HTML",
|
rawType: "HTML",
|
||||||
logo: Logo,
|
logo: Logo,
|
||||||
},
|
},
|
||||||
|
|
|
@ -485,7 +485,7 @@ core:
|
||||||
cover:
|
cover:
|
||||||
label: 封面图
|
label: 封面图
|
||||||
deleted_page:
|
deleted_page:
|
||||||
title: 自定义页面回收站
|
title: 页面回收站
|
||||||
empty:
|
empty:
|
||||||
title: 没有自定义页面被放入回收站
|
title: 没有自定义页面被放入回收站
|
||||||
message: 你可以尝试刷新或者返回自定义页面管理
|
message: 你可以尝试刷新或者返回自定义页面管理
|
||||||
|
|
|
@ -465,7 +465,7 @@ core:
|
||||||
cover:
|
cover:
|
||||||
label: 封面圖
|
label: 封面圖
|
||||||
deleted_page:
|
deleted_page:
|
||||||
title: 自定義頁面回收站
|
title: 頁面回收站
|
||||||
empty:
|
empty:
|
||||||
title: 沒有自定義頁面被放入回收站
|
title: 沒有自定義頁面被放入回收站
|
||||||
message: 你可以嘗試刷新或者返回自定義頁面管理
|
message: 你可以嘗試刷新或者返回自定義頁面管理
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
"@uc/*": ["./uc-src/*"],
|
"@uc/*": ["./uc-src/*"],
|
||||||
"@console/*": ["./console-src/*"]
|
"@console/*": ["./console-src/*"]
|
||||||
},
|
},
|
||||||
"noStrictGenericChecks": true,
|
|
||||||
"ignoreDeprecations": "5.0",
|
"ignoreDeprecations": "5.0",
|
||||||
"types": ["unplugin-icons/types/vue"]
|
"types": ["unplugin-icons/types/vue"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,7 +175,7 @@ const {
|
||||||
<VButton
|
<VButton
|
||||||
v-permission="['system:posts:manage']"
|
v-permission="['system:posts:manage']"
|
||||||
:route="{ name: 'PostEditor' }"
|
:route="{ name: 'PostEditor' }"
|
||||||
type="primary"
|
type="secondary"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconAddCircle class="h-full w-full" />
|
<IconAddCircle class="h-full w-full" />
|
||||||
|
|
|
@ -78,6 +78,7 @@ const publishTimeHelp = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<FormKit
|
<FormKit
|
||||||
id="post-setting-form"
|
id="post-setting-form"
|
||||||
v-model="internalFormState"
|
v-model="internalFormState"
|
||||||
|
|
|
@ -56,6 +56,7 @@ const handleChangePassword = async () => {
|
||||||
:title="$t('core.uc_profile.change_password_modal.title')"
|
:title="$t('core.uc_profile.change_password_modal.title')"
|
||||||
@close="emit('close')"
|
@close="emit('close')"
|
||||||
>
|
>
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<FormKit
|
<FormKit
|
||||||
id="password-form"
|
id="password-form"
|
||||||
v-model="formState"
|
v-model="formState"
|
||||||
|
|
|
@ -120,6 +120,7 @@ const { mutate, isLoading } = useMutation({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5 divide-y divide-gray-100 md:col-span-3 md:mt-0">
|
<div class="mt-5 divide-y divide-gray-100 md:col-span-3 md:mt-0">
|
||||||
|
<!-- @vue-ignore -->
|
||||||
<FormKit
|
<FormKit
|
||||||
id="pat-creation-form"
|
id="pat-creation-form"
|
||||||
v-model="formState.spec"
|
v-model="formState.spec"
|
||||||
|
|
|
@ -56,7 +56,7 @@ const creationModal = ref(false);
|
||||||
<VButton @click="refetch">
|
<VButton @click="refetch">
|
||||||
{{ $t("core.common.buttons.refresh") }}
|
{{ $t("core.common.buttons.refresh") }}
|
||||||
</VButton>
|
</VButton>
|
||||||
<VButton type="primary" @click="creationModal = true">
|
<VButton type="secondary" @click="creationModal = true">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconAddCircle class="h-full w-full" />
|
<IconAddCircle class="h-full w-full" />
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in New Issue