diff --git a/api/src/main/java/run/halo/app/search/SearchOption.java b/api/src/main/java/run/halo/app/search/SearchOption.java index 2c0edf6ba67..1c84ce430ce 100644 --- a/api/src/main/java/run/halo/app/search/SearchOption.java +++ b/api/src/main/java/run/halo/app/search/SearchOption.java @@ -53,22 +53,22 @@ public class SearchOption { private Boolean filterPublished; /** - * Types to include. If null, it will include all types. + * Types to include(or). If null, it will include all types. */ private List includeTypes; /** - * Owner names to include. If null, it will include all owners. + * Owner names to include(or). If null, it will include all owners. */ private List includeOwnerNames; /** - * Category names to include. If null, it will include all categories. + * Category names to include(and). If null, it will include all categories. */ private List includeCategoryNames; /** - * Tag names to include. If null, it will include all tags. + * Tag names to include(and). If null, it will include all tags. */ private List includeTagNames; diff --git a/application/src/main/java/run/halo/app/search/lucene/LuceneSearchEngine.java b/application/src/main/java/run/halo/app/search/lucene/LuceneSearchEngine.java index 42698cc4217..b8e54fde782 100644 --- a/application/src/main/java/run/halo/app/search/lucene/LuceneSearchEngine.java +++ b/application/src/main/java/run/halo/app/search/lucene/LuceneSearchEngine.java @@ -17,6 +17,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import lombok.extern.slf4j.Slf4j; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.charfilter.HTMLStripCharFilterFactory; @@ -170,6 +171,45 @@ public SearchResult search(SearchOption option) { new TermQuery(new Term("published", filterPublished.toString())), FILTER ); } + + Optional.ofNullable(option.getIncludeTypes()) + .filter(types -> !types.isEmpty()) + .ifPresent(types -> { + var typeTerms = types.stream() + .distinct() + .map(BytesRef::new) + .toList(); + queryBuilder.add(new TermInSetQuery("type", typeTerms), FILTER); + }); + + Optional.ofNullable(option.getIncludeOwnerNames()) + .filter(ownerNames -> !ownerNames.isEmpty()) + .ifPresent(ownerNames -> { + var ownerTerms = ownerNames.stream() + .distinct() + .map(BytesRef::new) + .toList(); + queryBuilder.add(new TermInSetQuery("ownerName", ownerTerms), FILTER); + }); + + Optional.ofNullable(option.getIncludeTagNames()) + .filter(tagNames -> !tagNames.isEmpty()) + .ifPresent(tagNames -> tagNames + .stream() + .distinct() + .forEach(tagName -> + queryBuilder.add(new TermQuery(new Term("tag", tagName)), FILTER) + )); + + Optional.ofNullable(option.getIncludeCategoryNames()) + .filter(categoryNames -> !categoryNames.isEmpty()) + .ifPresent(categoryNames -> categoryNames + .stream() + .distinct() + .forEach(categoryName -> + queryBuilder.add(new TermQuery(new Term("category", categoryName)), FILTER) + )); + var finalQuery = queryBuilder.build(); var limit = option.getLimit(); diff --git a/application/src/test/java/run/halo/app/search/lucene/LuceneSearchEngineIntegrationTest.java b/application/src/test/java/run/halo/app/search/lucene/LuceneSearchEngineIntegrationTest.java index 91e41523589..9437af0fea9 100644 --- a/application/src/test/java/run/halo/app/search/lucene/LuceneSearchEngineIntegrationTest.java +++ b/application/src/test/java/run/halo/app/search/lucene/LuceneSearchEngineIntegrationTest.java @@ -6,6 +6,7 @@ import static run.halo.app.core.extension.content.Post.VisibleEnum.PUBLIC; import java.time.Duration; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -122,6 +123,9 @@ void assertNoResult(int maxAttempts) { option.setKeyword("halo"); option.setHighlightPreTag(""); option.setHighlightPostTag(""); + option.setIncludeTagNames(List.of("search")); + option.setIncludeCategoryNames(List.of("halo")); + option.setIncludeOwnerNames(List.of("admin")); retryTemplate.execute(context -> { webClient.post().uri("/apis/api.halo.run/v1alpha1/indices/-/search") .bodyValue(option) @@ -218,6 +222,8 @@ void createPost(String postName) { spec.setPriority(0); spec.setSlug("/first-post"); spec.setDeleted(false); + spec.setTags(List.of("search")); + spec.setCategories(List.of("halo")); var excerpt = new Post.Excerpt(); excerpt.setRaw("first post description"); excerpt.setAutoGenerate(false); diff --git a/ui/console-src/modules/interface/themes/components/preview/ThemePreviewModal.vue b/ui/console-src/modules/interface/themes/components/preview/ThemePreviewModal.vue index 56d485e7261..d7864dd53b0 100644 --- a/ui/console-src/modules/interface/themes/components/preview/ThemePreviewModal.vue +++ b/ui/console-src/modules/interface/themes/components/preview/ThemePreviewModal.vue @@ -7,9 +7,8 @@ import type { SettingForm, Theme, } from "@halo-dev/api-client"; -import { axiosInstance, consoleApiClient } from "@halo-dev/api-client"; +import { consoleApiClient } from "@halo-dev/api-client"; import { - IconArrowLeft, IconComputer, IconLink, IconPalette, @@ -29,6 +28,7 @@ import { storeToRefs } from "pinia"; import { computed, markRaw, onMounted, ref, toRaw } from "vue"; import { useI18n } from "vue-i18n"; import ThemePreviewListItem from "./ThemePreviewListItem.vue"; +import StickyBlock from "@/components/sticky-block/StickyBlock.vue"; const props = withDefaults( defineProps<{ @@ -54,6 +54,7 @@ interface SettingTab { const { activatedTheme } = storeToRefs(useThemeStore()); +const previewFrame = ref(null); const themesVisible = ref(false); const switching = ref(false); const selectedTheme = ref(); @@ -99,26 +100,6 @@ const modalTitle = computed(() => { }); }); -const { - data: previewHTML, - isLoading, - refetch: refetchPreviewHTML, -} = useQuery({ - queryKey: ["site-preview", previewUrl], - queryFn: async () => { - const { data } = await axiosInstance.get(previewUrl.value, { - headers: { - Accept: "text/html", - "Cache-Control": "no-cache", - Pragma: "no-cache", - Expires: "0", - }, - }); - return data; - }, - enabled: computed(() => !!previewUrl.value), -}); - // theme settings const saving = ref(false); const settingTabs = ref([] as SettingTab[]); @@ -169,6 +150,10 @@ const { formSchema, configMapFormData, convertToSave } = useSettingFormConvert( activeSettingTab ); +const handleRefresh = () => { + previewFrame.value?.contentWindow?.location.reload(); +}; + const handleSaveConfigMap = async () => { saving.value = true; @@ -190,7 +175,7 @@ const handleSaveConfigMap = async () => { saving.value = false; - refetchPreviewHTML(); + handleRefresh(); }; const handleOpenSettings = (theme?: Theme) => { @@ -271,7 +256,7 @@ const iframeClasses = computed(() => { content: $t('core.common.buttons.refresh'), delay: 300, }" - @click="refetchPreviewHTML()" + @click="handleRefresh()" > @@ -348,21 +333,23 @@ const iframeClasses = computed(() => { /> -
-
- - {{ $t("core.common.buttons.save") }} - -
-
+ + + {{ $t("core.common.buttons.save") }} + + @@ -398,36 +385,18 @@ const iframeClasses = computed(() => { - -
- - - -
-
- +
diff --git a/ui/packages/editor/src/extensions/paragraph/index.ts b/ui/packages/editor/src/extensions/paragraph/index.ts index fe03c34dc5a..d304c2bc32a 100644 --- a/ui/packages/editor/src/extensions/paragraph/index.ts +++ b/ui/packages/editor/src/extensions/paragraph/index.ts @@ -147,7 +147,7 @@ export function deleteCurrentNodeAndSetSelection( const { tr } = state; if (deleteNodeByPos($from)(tr) && dispatch) { if (beforePos !== 0) { - tr.setSelection(TextSelection.create(tr.doc, beforePos - 1)); + tr.setSelection(TextSelection.near(tr.doc.resolve(beforePos - 1), -1)); } dispatch(tr); return true; diff --git a/ui/src/components/preview/UrlPreviewModal.vue b/ui/src/components/preview/UrlPreviewModal.vue index ad02d83d25e..585554bc91e 100644 --- a/ui/src/components/preview/UrlPreviewModal.vue +++ b/ui/src/components/preview/UrlPreviewModal.vue @@ -1,5 +1,4 @@
- +