Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=
LOG_PERF_METRICS=true
HOST_NAME=webdoky.org
DNS_EMAIL=
NEXT_ALGOLIA_APP_ID=
NEXT_ALGOLIA_INDEX=articles
NEXT_PUBLIC_ALGOLIA_APP_ID=
NEXT_PUBLIC_ALGOLIA_INDEX=articles
ALGOLIA_ADMIN_KEY=
NEXT_ALGOLIA_SEARCH_KEY=
NEXT_PUBLIC_ALGOLIA_SEARCH_KEY=
6 changes: 3 additions & 3 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=""
LOG_PERF_METRICS=true
HOST_NAME=webdoky.org
DNS_EMAIL=webdoky.org@gmail.com
NEXT_ALGOLIA_APP_ID=
NEXT_ALGOLIA_INDEX=articles
NEXT_PUBLIC_ALGOLIA_APP_ID=
NEXT_PUBLIC_ALGOLIA_INDEX=articles
ALGOLIA_ADMIN_KEY=
NEXT_ALGOLIA_SEARCH_KEY=
NEXT_PUBLIC_ALGOLIA_SEARCH_KEY=
10 changes: 10 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ jobs:
PATH_TO_LOCALIZED_CONTENT: "./external/translated-content/files"
PATH_TO_ORIGINAL_CONTENT: "./external/original-content/files"

- name: Populates Algolia index
run: yarn populate-algolia
env:
NEXT_PUBLIC_ALGOLIA_APP_ID: ${{ vars.NEXT_PUBLIC_ALGOLIA_APP_ID }}
NEXT_PUBLIC_ALGOLIA_INDEX: ${{ vars.NEXT_PUBLIC_ALGOLIA_INDEX }}
ALGOLIA_ADMIN_KEY: ${{ secrets.ALGOLIA_ADMIN_KEY }}

- name: Builds static app
run: yarn build
env:
Expand All @@ -56,6 +63,9 @@ jobs:
PATH_TO_LOCALIZED_CONTENT: "./external/translated-content/files"
PATH_TO_ORIGINAL_CONTENT: "./external/original-content/files"
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID: ${{ secrets.GOOGLE_ANALYTICS_ID }}
NEXT_PUBLIC_ALGOLIA_APP_ID: ${{ vars.NEXT_PUBLIC_ALGOLIA_APP_ID }}
NEXT_PUBLIC_ALGOLIA_INDEX: ${{ vars.NEXT_PUBLIC_ALGOLIA_INDEX }}
NEXT_PUBLIC_ALGOLIA_SEARCH_KEY: ${{ vars.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY }}

- name: Pushes to deployment repository
uses: cpina/github-action-push-to-another-repository@main
Expand Down
5 changes: 3 additions & 2 deletions components/layoutHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import classNames from 'classnames';
export default function LayoutHeader() {
const router = useRouter();
const currentRoute = router.asPath;
const nav: { path: string; title: string }[] = process.env
.mainNav as unknown as {
const nav: { path: string; title: string }[] = JSON.parse(
process.env.mainNav
) as {
path: string;
title: string;
}[];
Expand Down
168 changes: 0 additions & 168 deletions components/search.tsx

This file was deleted.

62 changes: 62 additions & 0 deletions components/search/fallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useCallback } from 'react';

import useFocus from './useFocus';

export default function SearchFallback() {
const { isFocused, setIsFocused } = useFocus();
const handleFocus = useCallback(() => setIsFocused(true), [setIsFocused]);
return (
<div className="ais-SearchBox w-full search-dummy" onFocus={handleFocus}>
<form
className="ais-SearchBox-form flex"
noValidate
action=""
role="search"
>
<input
className="ais-SearchBox-input block w-full py-2 pl-10 pr-4 border-2 rounded-lg bg-ui-sidebar border-ui-sidebar focus:bg-ui-background"
type="search"
placeholder="Пошук у документації"
/>
<button className="ais-SearchBox-submit" title="Пошук" type="submit">
<svg
className="ais-SearchBox-submitIcon absolute inset-y-0 left-0 flex items-center justify-center px-3 py-2 opacity-50"
width="10"
height="10"
viewBox="0 0 40 40"
aria-hidden="true"
>
<path d="M26.804 29.01c-2.832 2.34-6.465 3.746-10.426 3.746C7.333 32.756 0 25.424 0 16.378 0 7.333 7.333 0 16.378 0c9.046 0 16.378 7.333 16.378 16.378 0 3.96-1.406 7.594-3.746 10.426l10.534 10.534c.607.607.61 1.59-.004 2.202-.61.61-1.597.61-2.202.004L26.804 29.01zm-10.426.627c7.323 0 13.26-5.936 13.26-13.26 0-7.32-5.937-13.257-13.26-13.257C9.056 3.12 3.12 9.056 3.12 16.378c0 7.323 5.936 13.26 13.258 13.26z"></path>
</svg>
</button>
<span className="ais-SearchBox-loadingIndicator" hidden={!isFocused}>
<svg
aria-label="Результати завантажуються"
width="16"
height="16"
viewBox="0 0 38 38"
stroke="#444"
className="ais-SearchBox-loadingIcon"
aria-hidden="true"
>
<g fill="none" fillRule="evenodd">
<g transform="translate(1 1)" strokeWidth="2">
<circle strokeOpacity=".5" cx="18" cy="18" r="18"></circle>
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"
></animateTransform>
</path>
</g>
</g>
</svg>
</span>
</form>
</div>
);
}
35 changes: 35 additions & 0 deletions components/search/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { lazy, Suspense, useMemo } from 'react';
import { InstantSearch } from 'react-instantsearch';

import { getAlgoliaIndexName, getAlgoliaClient } from '../../utils/algolia';

import { FocusOuterWrapper, FocusProvider } from './useFocus';

import SearchFallback from './fallback';

const SearchWidgets = lazy(() => import('./widgets'));

const FUTURE = {
preserveSharedStateOnUnmount: true,
};

export default function Search() {
const searchClient = useMemo(() => getAlgoliaClient(), []);
const indexName = useMemo(() => getAlgoliaIndexName(), []);

return (
<InstantSearch
future={FUTURE}
indexName={indexName}
searchClient={searchClient}
>
<FocusProvider>
<FocusOuterWrapper className="relative">
<Suspense fallback={<SearchFallback />}>
<SearchWidgets />
</Suspense>
</FocusOuterWrapper>
</FocusProvider>
</InstantSearch>
);
}
68 changes: 68 additions & 0 deletions components/search/useFocus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
type Dispatch,
type SetStateAction,
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';

const FocusContext = createContext<{
isFocused: boolean;
setIsFocused: Dispatch<SetStateAction<boolean>>;
}>({ isFocused: false, setIsFocused: () => {} });

export function FocusProvider({ children }) {
const [isFocused, setIsFocused] = useState(false);

const focusValue = useMemo(
() => ({ isFocused, setIsFocused }),
[isFocused, setIsFocused]
);

return (
<FocusContext.Provider value={focusValue}>{children}</FocusContext.Provider>
);
}

export function FocusOuterWrapper({ children, ...props }) {
const rootReference = useRef(null);
const { isFocused, setIsFocused } = useFocus();

const handleOutsideClick = useCallback(
(event) => {
if (
!rootReference.current ||
!rootReference.current.contains(event.target)
) {
setIsFocused(false);
}
},
[setIsFocused]
);

useEffect(() => {
if (isFocused) {
document.addEventListener('click', handleOutsideClick);
}
return () => {
document.removeEventListener('click', handleOutsideClick);
};
}, [handleOutsideClick, isFocused]);

const handleFocus = useCallback(() => setIsFocused(true), [setIsFocused]);

return (
<div {...props} onFocus={handleFocus} ref={rootReference}>
{children}
</div>
);
}

export default function useFocus() {
const { isFocused, setIsFocused } = useContext(FocusContext);
return { isFocused, setIsFocused };
}
Loading