diff --git a/package.json b/package.json
index 310f9bd..cc37fff 100755
--- a/package.json
+++ b/package.json
@@ -1,13 +1,13 @@
{
"name": "astroplate",
- "version": "2.4.0",
+ "version": "2.5.0",
"description": "Astro and Tailwindcss boilerplate",
"author": "zeon.studio",
"license": "MIT",
"packageManager": "yarn@1.22.19",
"scripts": {
- "dev": "astro dev",
- "build": "astro build",
+ "dev": "yarn generate-json && astro dev",
+ "build": "yarn generate-json && astro build",
"preview": "astro preview",
"format": "prettier -w ./src",
"generate-json": "node scripts/jsonGenerator.js",
@@ -24,7 +24,6 @@
"date-fns": "^2.30.0",
"date-fns-tz": "^2.0.0",
"disqus-react": "^1.1.5",
- "fuse.js": "^7.0.0",
"github-slugger": "^2.0.0",
"gray-matter": "^4.0.3",
"marked": "^11.0.0",
diff --git a/public/images/no-search-found.png b/public/images/no-search-found.png
deleted file mode 100755
index 1e1e6e1..0000000
Binary files a/public/images/no-search-found.png and /dev/null differ
diff --git a/scripts/jsonGenerator.js b/scripts/jsonGenerator.js
index 6c2d20b..5a9f995 100644
--- a/scripts/jsonGenerator.js
+++ b/scripts/jsonGenerator.js
@@ -1,44 +1,80 @@
const fs = require("fs");
const path = require("path");
const matter = require("gray-matter");
-const config = require("../src/config/config.json");
-const { blog_folder } = config.settings;
-const jsonDir = "./.json";
+
+const CONTENT_DEPTH = 2;
+const JSON_FOLDER = "./.json";
+const BLOG_FOLDER = "src/content/blog";
// get data from markdown
-const getData = (folder) => {
+const getData = (folder, groupDepth) => {
const getPath = fs.readdirSync(path.join(folder));
- const sanitizeData = getPath.filter((item) => item.includes(".md"));
- const filterData = sanitizeData.filter((item) => item.match(/^(?!_)/));
- const getData = filterData.map((filename) => {
- const file = fs.readFileSync(path.join(folder, filename), "utf-8");
- const { data } = matter(file);
- const content = matter(file).content;
- const slug = data.slug ? data.slug : filename.replace(".md", "");
+ const removeIndex = getPath.filter((item) => item.match(/^(?!-)/));
- return {
- frontmatter: data,
- content: content,
- slug: slug,
- };
+ const getPaths = removeIndex.map((filename) => {
+ const filepath = path.join(folder, filename);
+ const stats = fs.statSync(filepath);
+ const isFolder = stats.isDirectory();
+
+ if (isFolder) {
+ return getData(filepath, groupDepth);
+ } else if (filename.endsWith(".md") || filename.endsWith(".mdx")) {
+ const file = fs.readFileSync(path.join(folder, filename), "utf-8");
+ const { data } = matter(file);
+ const content = matter(file).content;
+ const removeExtension = filepath.replace(/\.[^/.]+$/, "");
+ const slug = data.slug
+ ? data.slug
+ : removeExtension
+ .split("/")
+ .slice(CONTENT_DEPTH, removeExtension.split("/").length)
+ .join("/");
+
+ const group = removeExtension.split("/")[Number(groupDepth)];
+
+ return {
+ group: group,
+ slug: slug,
+ frontmatter: data,
+ content: content,
+ };
+ }
});
- const publishedPages = getData.filter(
- (page) => !page.frontmatter?.draft && page
+
+ const publishedPages = getPaths.filter(
+ (page) => !page.frontmatter?.draft && page,
);
return publishedPages;
};
-// get post data
-const posts = getData(`src/content/${blog_folder}`);
+// flatten nested arrays
+const flatten = (arr) => {
+ return arr.reduce((result, element) => {
+ if (Array.isArray(element)) {
+ result.push(...flatten(element));
+ } else {
+ result.push(element);
+ }
+ return result;
+ }, []);
+};
try {
- // creare folder if it doesn't exist
- if (!fs.existsSync(jsonDir)) {
- fs.mkdirSync(jsonDir);
+ // create folder if it doesn't exist
+ if (!fs.existsSync(JSON_FOLDER)) {
+ fs.mkdirSync(JSON_FOLDER);
}
- // create posts.json file
- fs.writeFileSync(`${jsonDir}/posts.json`, JSON.stringify(posts));
+ // create json files
+ fs.writeFileSync(
+ `${JSON_FOLDER}/posts.json`,
+ JSON.stringify(flatten(getData(BLOG_FOLDER, 2))),
+ );
+
+ // merger json files for search
+ const posts = require(`../${JSON_FOLDER}/posts.json`);
+ const search = [...posts];
+ fs.writeFileSync(`${JSON_FOLDER}/search.json`, JSON.stringify(search));
} catch (err) {
console.error(err);
}
diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro
index f0f7fe8..52d113f 100755
--- a/src/layouts/Base.astro
+++ b/src/layouts/Base.astro
@@ -6,8 +6,8 @@ import { plainify } from "@/lib/utils/textConverter";
import Footer from "@/partials/Footer.astro";
import Header from "@/partials/Header.astro";
import "@/styles/main.scss";
-import { ViewTransitions } from 'astro:transitions';
-
+import { ViewTransitions } from "astro:transitions";
+import SearchModal from "./helpers/SearchModal";
// font families
const pf = theme.fonts.font_family.primary;
@@ -28,7 +28,7 @@ const { title, meta_title, description, image, noindex, canonical } =
Astro.props;
---
-
+
@@ -80,7 +80,7 @@ const { title, meta_title, description, image, noindex, canonical } =
@@ -93,7 +93,7 @@ const { title, meta_title, description, image, noindex, canonical } =
@@ -101,7 +101,7 @@ const { title, meta_title, description, image, noindex, canonical } =
@@ -114,7 +114,7 @@ const { title, meta_title, description, image, noindex, canonical } =
@@ -122,7 +122,7 @@ const { title, meta_title, description, image, noindex, canonical } =
@@ -146,6 +146,7 @@ const { title, meta_title, description, image, noindex, canonical } =
+
diff --git a/src/layouts/Search.tsx b/src/layouts/Search.tsx
deleted file mode 100755
index 56976e7..0000000
--- a/src/layouts/Search.tsx
+++ /dev/null
@@ -1,181 +0,0 @@
-import config from "@/config/config.json";
-import { humanize, plainify, slugify } from "@/lib/utils/textConverter";
-import Fuse from "fuse.js";
-import React, { useEffect, useRef, useState } from "react";
-import {
- FaRegFolder,
- FaRegUserCircle,
- FaSearch,
-} from "react-icons/fa/index.js";
-
-const { summary_length, blog_folder } = config.settings;
-
-export type SearchItem = {
- slug: string;
- data: any;
- content: any;
-};
-
-interface Props {
- searchList: SearchItem[];
-}
-
-interface SearchResult {
- item: SearchItem;
- refIndex: number;
-}
-
-const SearchLayout = ({ searchList }: Props) => {
- const inputRef = useRef(null);
- const [inputVal, setInputVal] = useState("");
- const [searchResults, setSearchResults] = useState([]);
-
- const handleChange = (e: React.FormEvent) => {
- setInputVal(e.currentTarget.value);
- };
-
- const fuse = new Fuse(searchList, {
- keys: ["data.title", "data.categories", "data.tags"],
- includeMatches: true,
- minMatchCharLength: 3,
- threshold: 0.5,
- });
-
- useEffect(() => {
- const searchUrl = new URLSearchParams(window.location.search);
- const searchStr = searchUrl.get("q");
- if (searchStr) setInputVal(searchStr);
-
- setTimeout(function () {
- inputRef.current!.selectionStart = inputRef.current!.selectionEnd =
- searchStr?.length || 0;
- }, 50);
- }, []);
-
- useEffect(() => {
- let inputResult = inputVal.length > 2 ? fuse.search(inputVal) : [];
- setSearchResults(inputResult);
-
- if (inputVal.length > 0) {
- const searchParams = new URLSearchParams(window.location.search);
- searchParams.set("q", inputVal);
- const newRelativePathQuery =
- window.location.pathname + "?" + searchParams.toString();
- history.pushState(null, "", newRelativePathQuery);
- } else {
- history.pushState(null, "", window.location.pathname);
- }
- }, [inputVal]);
-
- return (
-
-
-
-
- {/* {inputVal.length > 1 && (
-
- Found {searchResults?.length}
- {searchResults?.length && searchResults?.length === 1
- ? " result"
- : " results"}{" "}
- for '{inputVal}'
-
- )} */}
-
- {searchResults?.length < 1 ? (
-
-

-
- {inputVal.length < 1 ? "Search Post Here" : "No Search Found!"}
-
-
- {inputVal.length < 1
- ? "Search for posts by title, category, or tag."
- : "We couldn't find what you searched for. Try searching again."}
-
-
- ) : (
- searchResults?.map(({ item }, index) => (
-
-
- {item.data.image && (
-

- )}
-
-
-
- {plainify(item.content?.slice(0, Number(summary_length)))}
-
-
- read more
-
-
-
- ))
- )}
-
-
-
- );
-};
-
-export default SearchLayout;
diff --git a/src/layouts/helpers/SearchModal.tsx b/src/layouts/helpers/SearchModal.tsx
new file mode 100644
index 0000000..142f242
--- /dev/null
+++ b/src/layouts/helpers/SearchModal.tsx
@@ -0,0 +1,257 @@
+import searchData from ".json/search.json";
+import React, { useEffect, useRef, useState } from "react";
+import SearchResult, { type ISearchItem } from "./SearchResult";
+
+const SearchModal = () => {
+ const searchInputRef = useRef(null);
+ const [searchString, setSearchString] = useState("");
+
+ // handle input change
+ const handleSearch = (e: React.FormEvent) => {
+ setSearchString(e.currentTarget.value.toLowerCase());
+ };
+
+ // set input value from url
+ useEffect(() => {
+ const searchUrl = new URLSearchParams(window.location.search);
+ const searchStr = searchUrl.get("q");
+ searchStr && setSearchString(searchStr.toLowerCase());
+
+ // set cursor position
+ setTimeout(function () {
+ searchInputRef.current!.selectionStart =
+ searchInputRef.current!.selectionEnd = searchStr?.length || 0;
+ }, 50);
+ }, []);
+
+ // update url
+ useEffect(() => {
+ if (searchString.length > 0) {
+ const searchParams = new URLSearchParams(window.location.search);
+ searchParams.set("s", searchString);
+ const newRelativePathQuery =
+ window.location.pathname + "?" + searchParams.toString();
+ history.pushState(null, "", newRelativePathQuery);
+ } else {
+ history.pushState(null, "", window.location.pathname);
+ }
+ }, [searchString]);
+
+ // generate search result
+ const doSearch = (searchData: ISearchItem[]) => {
+ const regex = new RegExp(`${searchString}`, "gi");
+ if (searchString === "") {
+ return [];
+ } else {
+ const searchResult = searchData.filter((item) => {
+ const title = item.frontmatter.title.toLowerCase().match(regex);
+ const description = item.frontmatter.description
+ ?.toLowerCase()
+ .match(regex);
+ const categories = item.frontmatter.categories
+ ?.join(" ")
+ .toLowerCase()
+ .match(regex);
+ const tags = item.frontmatter.tags
+ ?.join(" ")
+ .toLowerCase()
+ .match(regex);
+ const content = item.content.toLowerCase().match(regex);
+
+ if (title || content || description || categories || tags) {
+ return item;
+ }
+ });
+ return searchResult;
+ }
+ };
+
+ // get search result
+ const startTime = performance.now();
+ const searchResult = doSearch(searchData);
+ const endTime = performance.now();
+ const totalTime = ((endTime - startTime) / 1000).toFixed(3);
+
+ // search dom manipulation
+ useEffect(() => {
+ const searchModal = document.getElementById("searchModal");
+ const searchInput = document.getElementById("searchInput");
+ const searchModalOverlay = document.getElementById("searchModalOverlay");
+ const searchResultItems = document.querySelectorAll("#searchItem");
+ const searchModalTriggers = document.querySelectorAll(
+ "[data-search-trigger]",
+ );
+
+ // search modal open
+ searchModalTriggers.forEach((button) => {
+ button.addEventListener("click", function () {
+ const searchModal = document.getElementById("searchModal");
+ searchModal!.classList.add("show");
+ searchInput!.focus();
+ });
+ });
+
+ // search modal close
+ searchModalOverlay!.addEventListener("click", function () {
+ searchModal!.classList.remove("show");
+ });
+
+ // keyboard navigation
+ let selectedIndex = -1;
+
+ const updateSelection = () => {
+ searchResultItems.forEach((item, index) => {
+ if (index === selectedIndex) {
+ item.classList.add("search-result-item-active");
+ } else {
+ item.classList.remove("search-result-item-active");
+ }
+ });
+
+ searchResultItems[selectedIndex]?.scrollIntoView({
+ behavior: "smooth",
+ block: "nearest",
+ });
+ };
+
+ document.addEventListener("keydown", function (event) {
+ if ((event.metaKey || event.ctrlKey) && event.key === "k") {
+ searchModal!.classList.add("show");
+ searchInput!.focus();
+ updateSelection();
+ }
+
+ if (event.key === "ArrowUp" || event.key === "ArrowDown") {
+ event.preventDefault();
+ }
+
+ if (event.key === "Escape") {
+ searchModal!.classList.remove("show");
+ }
+
+ if (event.key === "ArrowUp" && selectedIndex > 0) {
+ selectedIndex--;
+ } else if (
+ event.key === "ArrowDown" &&
+ selectedIndex < searchResultItems.length - 1
+ ) {
+ selectedIndex++;
+ } else if (event.key === "Enter") {
+ const activeLink = document.querySelector(
+ ".search-result-item-active a",
+ ) as HTMLAnchorElement;
+ if (activeLink) {
+ activeLink?.click();
+ }
+ }
+
+ updateSelection();
+ });
+ }, [searchString]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ to navigate
+
+
+
+
+
+ to select
+
+ {searchString && (
+
+ {searchResult.length} results - in{" "}
+ {totalTime} seconds
+
+ )}
+
+ ESC to close
+
+
+
+
+ );
+};
+
+export default SearchModal;
diff --git a/src/layouts/helpers/SearchResult.tsx b/src/layouts/helpers/SearchResult.tsx
new file mode 100755
index 0000000..6c0efae
--- /dev/null
+++ b/src/layouts/helpers/SearchResult.tsx
@@ -0,0 +1,260 @@
+import { plainify, titleify } from "@/lib/utils/textConverter";
+import React from "react";
+
+export interface ISearchItem {
+ group: string;
+ slug: string;
+ frontmatter: {
+ title: string;
+ image?: string;
+ description?: string;
+ categories?: string[];
+ tags?: string[];
+ };
+ content: string;
+}
+
+export interface ISearchGroup {
+ group: string;
+ groupItems: {
+ slug: string;
+ frontmatter: {
+ title: string;
+ image?: string;
+ description?: string;
+ categories?: string[];
+ tags?: string[];
+ };
+ content: string;
+ }[];
+}
+
+// search result component
+const SearchResult = ({
+ searchResult,
+ searchString,
+}: {
+ searchResult: ISearchItem[];
+ searchString: string;
+}) => {
+ // generate search result group
+ const generateSearchGroup = (searchResult: ISearchItem[]) => {
+ const joinDataByGroup: ISearchGroup[] = searchResult.reduce(
+ (groupItems: ISearchGroup[], item: ISearchItem) => {
+ const groupIndex = groupItems.findIndex(
+ (group) => group.group === item.group,
+ );
+ if (groupIndex === -1) {
+ groupItems.push({
+ group: item.group,
+ groupItems: [
+ {
+ frontmatter: { ...item.frontmatter },
+ slug: item.slug,
+ content: item.content,
+ },
+ ],
+ });
+ } else {
+ groupItems[groupIndex].groupItems.push({
+ frontmatter: { ...item.frontmatter },
+ slug: item.slug,
+ content: item.content,
+ });
+ }
+
+ return groupItems;
+ },
+ [],
+ );
+ return joinDataByGroup;
+ };
+ const finalResult = generateSearchGroup(searchResult);
+
+ // match marker
+ const matchMarker = (text: string, substring: string) => {
+ const parts = text.split(new RegExp(`(${substring})`, "gi"));
+ return parts.map((part, index) =>
+ part.toLowerCase() === substring.toLowerCase() ? (
+ {part}
+ ) : (
+ part
+ ),
+ );
+ };
+
+ // match underline
+ const matchUnderline = (text: string, substring: string) => {
+ const parts = text?.split(new RegExp(`(${substring})`, "gi"));
+ return parts?.map((part, index) =>
+ part.toLowerCase() === substring.toLowerCase() ? (
+
+ {part}
+
+ ) : (
+ part
+ ),
+ );
+ };
+
+ // match content
+ const matchContent = (content: string, substring: string) => {
+ const plainContent = plainify(content);
+ const position = plainContent
+ .toLowerCase()
+ .indexOf(substring.toLowerCase());
+
+ // Find the start of the word containing the substring
+ let wordStart = position;
+ while (wordStart > 0 && plainContent[wordStart - 1] !== " ") {
+ wordStart--;
+ }
+
+ const matches = plainContent.substring(
+ wordStart,
+ substring.length + position,
+ );
+ const matchesAfter = plainContent.substring(
+ substring.length + position,
+ substring.length + position + 80,
+ );
+ return (
+ <>
+ {matchMarker(matches, substring)}
+ {matchesAfter}
+ >
+ );
+ };
+
+ return (
+
+ {searchString ? (
+
+ {finalResult.length > 0 ? (
+ finalResult.map((result) => (
+
+
+ {titleify(result.group)}
+
+
+ {result.groupItems.map((item) => (
+
+ {item.frontmatter.image && (
+
+

+
+ )}
+
+
+ {matchUnderline(item.frontmatter.title, searchString)}
+
+ {item.frontmatter.description && (
+
+ {matchUnderline(
+ item.frontmatter.description,
+ searchString,
+ )}
+
+ )}
+ {item.content && (
+
+ {matchContent(item.content, searchString)}
+
+ )}
+
+ {item.frontmatter.categories && (
+
+
+ {item.frontmatter.categories.map(
+ (category, index) => (
+
+ {matchUnderline(category, searchString)}
+ {item.frontmatter.categories &&
+ index !==
+ item.frontmatter.categories.length -
+ 1 && <>, >}
+
+ ),
+ )}
+
+ )}
+ {item.frontmatter.tags && (
+
+
+ {item.frontmatter.tags.map((tag, index) => (
+
+ {matchUnderline(tag, searchString)}
+ {item.frontmatter.tags &&
+ index !==
+ item.frontmatter.tags.length - 1 && <>, >}
+
+ ))}
+
+ )}
+
+
+
+ ))}
+
+ ))
+ ) : (
+
+
+
+ No results for "{searchString}"
+
+
+ )}
+
+ ) : (
+
Type something to search...
+ )}
+
+ );
+};
+
+export default SearchResult;
diff --git a/src/layouts/partials/Header.astro b/src/layouts/partials/Header.astro
index ebb33e4..1f70aad 100755
--- a/src/layouts/partials/Header.astro
+++ b/src/layouts/partials/Header.astro
@@ -119,13 +119,13 @@ const { pathname } = Astro.url;
{
settings.search && (
-
-
+
)
}
diff --git a/src/lib/utils/textConverter.ts b/src/lib/utils/textConverter.ts
index ebf060e..8200bf3 100644
--- a/src/lib/utils/textConverter.ts
+++ b/src/lib/utils/textConverter.ts
@@ -16,14 +16,24 @@ export const humanize = (content: string) => {
return content
.replace(/^[\s_]+|[\s_]+$/g, "")
.replace(/[_\s]+/g, " ")
+ .replace(/[-\s]+/g, " ")
.replace(/^[a-z]/, function (m) {
return m.toUpperCase();
});
};
+// titleify
+export const titleify = (content: string) => {
+ const humanized = humanize(content);
+ return humanized
+ .split(" ")
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(" ");
+};
+
// plainify
export const plainify = (content: string) => {
- const parseMarkdown = marked.parse(content);
+ const parseMarkdown: any = marked.parse(content);
const filterBrackets = parseMarkdown.replace(/<\/?[^>]+(>|$)/gm, "");
const filterSpaces = filterBrackets.replace(/[\r\n]\s*[\r\n]/gm, "");
const stripHTML = htmlEntityDecoder(filterSpaces);
diff --git a/src/pages/search.astro b/src/pages/search.astro
deleted file mode 100755
index 0c15b9a..0000000
--- a/src/pages/search.astro
+++ /dev/null
@@ -1,19 +0,0 @@
----
-import Base from "@/layouts/Base.astro";
-import SearchLayout from "@/layouts/Search";
-import { getSinglePage } from "@/lib/contentParser.astro";
-
-const BLOG_FOLDER = "blog";
-const posts = await getSinglePage(BLOG_FOLDER);
-
-// List of items to search in
-const searchList = posts.map((item) => ({
- slug: item.slug,
- data: item.data,
- content: item.body,
-}));
----
-
-
-
-
diff --git a/src/styles/main.scss b/src/styles/main.scss
index 1875eba..9a3d421 100755
--- a/src/styles/main.scss
+++ b/src/styles/main.scss
@@ -10,6 +10,7 @@
@import "components";
@import "navigation";
@import "buttons";
+ @import "search";
}
@layer utilities {
diff --git a/src/styles/search.scss b/src/styles/search.scss
new file mode 100644
index 0000000..ab22252
--- /dev/null
+++ b/src/styles/search.scss
@@ -0,0 +1,96 @@
+.search {
+ &-modal {
+ @apply z-50 fixed top-0 left-0 w-full h-full flex items-start justify-center invisible opacity-0;
+ &.show {
+ @apply visible opacity-100;
+ }
+ &-overlay {
+ @apply fixed top-0 left-0 w-full h-full bg-black opacity-50;
+ }
+ }
+ &-wrapper {
+ @apply bg-white dark:bg-darkmode-body w-[660px] max-w-[96%] mt-24 rounded shadow-lg relative z-10;
+ &-header {
+ @apply p-4 relative;
+ &-input {
+ @apply border border-solid w-full focus:ring-0 focus:border-dark border-border rounded-[4px] h-12 pr-4 pl-10 transition duration-200 outline-none dark:bg-darkmode-theme-light dark:text-darkmode-text dark:border-darkmode-border dark:focus:border-darkmode-primary;
+ }
+ }
+ &-body {
+ @apply dark:bg-darkmode-theme-light dark:shadow-none max-h-[calc(100vh-350px)] overflow-y-auto bg-theme-light shadow-[inset_0_2px_18px_#ddd] p-4 rounded;
+ }
+ &-footer {
+ @apply text-xs select-none leading-none md:flex items-center px-3.5 py-2 hidden;
+ kbd {
+ @apply bg-theme-light dark:bg-darkmode-theme-light text-xs leading-none text-center mr-[3px] px-1 py-0.5 rounded-[3px];
+ }
+ span:not(:last-child) {
+ @apply mr-4;
+ }
+ span:last-child {
+ @apply ml-auto;
+ }
+ }
+ }
+ &-result {
+ &-empty {
+ @apply text-center cursor-text select-none px-0 py-8;
+ }
+ &-group {
+ @apply mb-4;
+ &-title {
+ @apply text-lg text-dark dark:text-darkmode-dark mb-[5px] px-3;
+ }
+ }
+ &-item {
+ @apply rounded border bg-white dark:bg-darkmode-body dark:border-darkmode-border flex items-start mb-1 p-4 scroll-my-[30px] border-solid border-border relative;
+ mark {
+ @apply bg-yellow-200 rounded-[2px];
+ }
+ &-title {
+ @apply text-lg font-bold text-dark dark:text-darkmode-dark leading-none;
+ }
+ &-link::after {
+ @apply absolute top-0 right-0 bottom-0 left-0 z-10 content-[""];
+ }
+ &-image {
+ @apply shrink-0 mr-3.5;
+ img {
+ @apply w-[60px] h-[60px] md:w-[100px] md:h-[100px] rounded-[4px] object-cover;
+ }
+ }
+ &-description {
+ @apply text-sm line-clamp-1 mt-1;
+ }
+ &-content {
+ @apply mx-0 my-1.5 empty:hidden line-clamp-1;
+ }
+ &-taxonomies {
+ @apply text-sm flex flex-wrap items-center text-light dark:text-darkmode-light;
+ svg {
+ @apply inline-block mr-1;
+ }
+ }
+
+ &-active,
+ &:focus,
+ &:hover {
+ @apply bg-dark dark:bg-dark;
+ .search-result-item {
+ &-title {
+ @apply text-white;
+ }
+ &-description {
+ @apply text-white/80;
+ }
+ &-content {
+ @apply text-white/90;
+ }
+ &-taxonomies {
+ @apply text-white/90;
+ }
+ }
+ }
+ }
+ }
+}