add template in hard
This commit is contained in:
13
front/src/components/common/Analytics.astro
Normal file
13
front/src/components/common/Analytics.astro
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
import { GoogleAnalytics } from '@astrolib/analytics';
|
||||
import { ANALYTICS } from 'astrowind:config';
|
||||
---
|
||||
|
||||
{
|
||||
ANALYTICS?.vendors?.googleAnalytics?.id ? (
|
||||
<GoogleAnalytics
|
||||
id={String(ANALYTICS.vendors.googleAnalytics.id)}
|
||||
partytown={ANALYTICS?.vendors?.googleAnalytics?.partytown}
|
||||
/>
|
||||
) : null
|
||||
}
|
33
front/src/components/common/ApplyColorMode.astro
Normal file
33
front/src/components/common/ApplyColorMode.astro
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
import { UI } from 'astrowind:config';
|
||||
|
||||
// TODO: This code is temporary
|
||||
---
|
||||
|
||||
<script is:inline define:vars={{ defaultTheme: UI.theme || 'system' }}>
|
||||
function applyTheme(theme) {
|
||||
if (theme === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
const matches = document.querySelectorAll('[data-aw-toggle-color-scheme] > input');
|
||||
|
||||
if (matches && matches.length) {
|
||||
matches.forEach((elem) => {
|
||||
elem.checked = theme !== 'dark';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if ((defaultTheme && defaultTheme.endsWith(':only')) || (!localStorage.theme && defaultTheme !== 'system')) {
|
||||
applyTheme(defaultTheme.replace(':only', ''));
|
||||
} else if (
|
||||
localStorage.theme === 'dark' ||
|
||||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
) {
|
||||
applyTheme('dark');
|
||||
} else {
|
||||
applyTheme('light');
|
||||
}
|
||||
</script>
|
162
front/src/components/common/BasicScripts.astro
Normal file
162
front/src/components/common/BasicScripts.astro
Normal file
@ -0,0 +1,162 @@
|
||||
---
|
||||
import { UI } from 'astrowind:config';
|
||||
---
|
||||
|
||||
<script is:inline define:vars={{ defaultTheme: UI.theme }}>
|
||||
if (window.basic_script) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.basic_script = true;
|
||||
|
||||
function applyTheme(theme) {
|
||||
if (theme === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
}
|
||||
|
||||
const initTheme = function () {
|
||||
if ((defaultTheme && defaultTheme.endsWith(':only')) || (!localStorage.theme && defaultTheme !== 'system')) {
|
||||
applyTheme(defaultTheme.replace(':only', ''));
|
||||
} else if (
|
||||
localStorage.theme === 'dark' ||
|
||||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
||||
) {
|
||||
applyTheme('dark');
|
||||
} else {
|
||||
applyTheme('light');
|
||||
}
|
||||
};
|
||||
initTheme();
|
||||
|
||||
function attachEvent(selector, event, fn) {
|
||||
const matches = typeof selector === 'string' ? document.querySelectorAll(selector) : selector;
|
||||
if (matches && matches.length) {
|
||||
matches.forEach((elem) => {
|
||||
elem.addEventListener(event, (e) => fn(e, elem), false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const onLoad = function () {
|
||||
let lastKnownScrollPosition = window.scrollY;
|
||||
let ticking = true;
|
||||
|
||||
attachEvent('#header nav', 'click', function () {
|
||||
document.querySelector('[data-aw-toggle-menu]')?.classList.remove('expanded');
|
||||
document.body.classList.remove('overflow-hidden');
|
||||
document.getElementById('header')?.classList.remove('h-screen');
|
||||
document.getElementById('header')?.classList.remove('expanded');
|
||||
document.getElementById('header')?.classList.remove('bg-page');
|
||||
document.querySelector('#header nav')?.classList.add('hidden');
|
||||
document.querySelector('#header > div > div:last-child')?.classList.add('hidden');
|
||||
});
|
||||
|
||||
attachEvent('[data-aw-toggle-menu]', 'click', function (_, elem) {
|
||||
elem.classList.toggle('expanded');
|
||||
document.body.classList.toggle('overflow-hidden');
|
||||
document.getElementById('header')?.classList.toggle('h-screen');
|
||||
document.getElementById('header')?.classList.toggle('expanded');
|
||||
document.getElementById('header')?.classList.toggle('bg-page');
|
||||
document.querySelector('#header nav')?.classList.toggle('hidden');
|
||||
document.querySelector('#header > div > div:last-child')?.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
attachEvent('[data-aw-toggle-color-scheme]', 'click', function () {
|
||||
if (defaultTheme.endsWith(':only')) {
|
||||
return;
|
||||
}
|
||||
document.documentElement.classList.toggle('dark');
|
||||
localStorage.theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
|
||||
});
|
||||
|
||||
attachEvent('[data-aw-social-share]', 'click', function (_, elem) {
|
||||
const network = elem.getAttribute('data-aw-social-share');
|
||||
const url = encodeURIComponent(elem.getAttribute('data-aw-url'));
|
||||
const text = encodeURIComponent(elem.getAttribute('data-aw-text'));
|
||||
|
||||
let href;
|
||||
switch (network) {
|
||||
case 'facebook':
|
||||
href = `https://www.facebook.com/sharer.php?u=${url}`;
|
||||
break;
|
||||
case 'twitter':
|
||||
href = `https://twitter.com/intent/tweet?url=${url}&text=${text}`;
|
||||
break;
|
||||
case 'linkedin':
|
||||
href = `https://www.linkedin.com/shareArticle?mini=true&url=${url}&title=${text}`;
|
||||
break;
|
||||
case 'whatsapp':
|
||||
href = `https://wa.me/?text=${text}%20${url}`;
|
||||
break;
|
||||
case 'mail':
|
||||
href = `mailto:?subject=%22${text}%22&body=${text}%20${url}`;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
const newlink = document.createElement('a');
|
||||
newlink.target = '_blank';
|
||||
newlink.href = href;
|
||||
newlink.click();
|
||||
});
|
||||
|
||||
const screenSize = window.matchMedia('(max-width: 767px)');
|
||||
screenSize.addEventListener('change', function () {
|
||||
document.querySelector('[data-aw-toggle-menu]')?.classList.remove('expanded');
|
||||
document.body.classList.remove('overflow-hidden');
|
||||
document.getElementById('header')?.classList.remove('h-screen');
|
||||
document.getElementById('header')?.classList.remove('expanded');
|
||||
document.getElementById('header')?.classList.remove('bg-page');
|
||||
document.querySelector('#header nav')?.classList.add('hidden');
|
||||
document.querySelector('#header > div > div:last-child')?.classList.add('hidden');
|
||||
});
|
||||
|
||||
function applyHeaderStylesOnScroll() {
|
||||
const header = document.querySelector('#header[data-aw-sticky-header]');
|
||||
if (!header) return;
|
||||
if (lastKnownScrollPosition > 60 && !header.classList.contains('scroll')) {
|
||||
header.classList.add('scroll');
|
||||
} else if (lastKnownScrollPosition <= 60 && header.classList.contains('scroll')) {
|
||||
header.classList.remove('scroll');
|
||||
}
|
||||
ticking = false;
|
||||
}
|
||||
applyHeaderStylesOnScroll();
|
||||
|
||||
attachEvent([document], 'scroll', function () {
|
||||
lastKnownScrollPosition = window.scrollY;
|
||||
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(() => {
|
||||
applyHeaderStylesOnScroll();
|
||||
});
|
||||
ticking = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
const onPageShow = function () {
|
||||
document.documentElement.classList.add('motion-safe:scroll-smooth');
|
||||
const elem = document.querySelector('[data-aw-toggle-menu]');
|
||||
if (elem) {
|
||||
elem.classList.remove('expanded');
|
||||
}
|
||||
document.body.classList.remove('overflow-hidden');
|
||||
document.getElementById('header')?.classList.remove('h-screen');
|
||||
document.getElementById('header')?.classList.remove('expanded');
|
||||
document.querySelector('#header nav')?.classList.add('hidden');
|
||||
};
|
||||
|
||||
window.onload = onLoad;
|
||||
window.onpageshow = onPageShow;
|
||||
|
||||
document.addEventListener('astro:after-swap', () => {
|
||||
initTheme();
|
||||
onLoad();
|
||||
onPageShow();
|
||||
});
|
||||
</script>
|
8
front/src/components/common/CommonMeta.astro
Normal file
8
front/src/components/common/CommonMeta.astro
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
import { getAsset } from '~/utils/permalinks';
|
||||
---
|
||||
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<link rel="sitemap" href={getAsset('/sitemap-index.xml')} />
|
61
front/src/components/common/Image.astro
Normal file
61
front/src/components/common/Image.astro
Normal file
@ -0,0 +1,61 @@
|
||||
---
|
||||
import { findImage } from '~/utils/images';
|
||||
import {
|
||||
getImagesOptimized,
|
||||
astroAsseetsOptimizer,
|
||||
unpicOptimizer,
|
||||
isUnpicCompatible,
|
||||
type ImageProps,
|
||||
type AttributesProps,
|
||||
} from '~/utils/images-optimization';
|
||||
|
||||
type Props = ImageProps;
|
||||
type ImageType = {
|
||||
src: string;
|
||||
attributes: AttributesProps;
|
||||
};
|
||||
|
||||
const props = Astro.props;
|
||||
|
||||
if (props.alt === undefined || props.alt === null) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if (typeof props.width === 'string') {
|
||||
props.width = parseInt(props.width);
|
||||
}
|
||||
|
||||
if (typeof props.height === 'string') {
|
||||
props.height = parseInt(props.height);
|
||||
}
|
||||
|
||||
if (!props.loading) {
|
||||
props.loading = 'lazy';
|
||||
}
|
||||
|
||||
if (!props.decoding) {
|
||||
props.decoding = 'async';
|
||||
}
|
||||
|
||||
const _image = await findImage(props.src);
|
||||
|
||||
let image: ImageType | undefined = undefined;
|
||||
|
||||
if (
|
||||
typeof _image === 'string' &&
|
||||
(_image.startsWith('http://') || _image.startsWith('https://')) &&
|
||||
isUnpicCompatible(_image)
|
||||
) {
|
||||
image = await getImagesOptimized(_image, props, unpicOptimizer);
|
||||
} else if (_image) {
|
||||
image = await getImagesOptimized(_image, props, astroAsseetsOptimizer);
|
||||
}
|
||||
---
|
||||
|
||||
{
|
||||
!image ? (
|
||||
<Fragment />
|
||||
) : (
|
||||
<img src={image.src} crossorigin="anonymous" referrerpolicy="no-referrer" {...image.attributes} />
|
||||
)
|
||||
}
|
68
front/src/components/common/Metadata.astro
Normal file
68
front/src/components/common/Metadata.astro
Normal file
@ -0,0 +1,68 @@
|
||||
---
|
||||
import merge from 'lodash.merge';
|
||||
import { AstroSeo } from '@astrolib/seo';
|
||||
|
||||
import type { Props as AstroSeoProps } from '@astrolib/seo';
|
||||
|
||||
import { SITE, METADATA, I18N } from 'astrowind:config';
|
||||
import type { MetaData } from '~/types';
|
||||
import { getCanonical } from '~/utils/permalinks';
|
||||
|
||||
import { adaptOpenGraphImages } from '~/utils/images';
|
||||
|
||||
export interface Props extends MetaData {
|
||||
dontUseTitleTemplate?: boolean;
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
ignoreTitleTemplate = false,
|
||||
canonical = String(getCanonical(String(Astro.url.pathname))),
|
||||
robots = {},
|
||||
description,
|
||||
openGraph = {},
|
||||
twitter = {},
|
||||
} = Astro.props;
|
||||
|
||||
const seoProps: AstroSeoProps = merge(
|
||||
{
|
||||
title: '',
|
||||
titleTemplate: '%s',
|
||||
canonical: canonical,
|
||||
noindex: true,
|
||||
nofollow: true,
|
||||
description: undefined,
|
||||
openGraph: {
|
||||
url: canonical,
|
||||
site_name: SITE?.name,
|
||||
images: [],
|
||||
locale: I18N?.language || 'en',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
cardType: openGraph?.images?.length ? 'summary_large_image' : 'summary',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: METADATA?.title?.default,
|
||||
titleTemplate: METADATA?.title?.template,
|
||||
noindex: typeof METADATA?.robots?.index !== 'undefined' ? !METADATA.robots.index : undefined,
|
||||
nofollow: typeof METADATA?.robots?.follow !== 'undefined' ? !METADATA.robots.follow : undefined,
|
||||
description: METADATA?.description,
|
||||
openGraph: METADATA?.openGraph,
|
||||
twitter: METADATA?.twitter,
|
||||
},
|
||||
{
|
||||
title: title,
|
||||
titleTemplate: ignoreTitleTemplate ? '%s' : undefined,
|
||||
canonical: canonical,
|
||||
noindex: typeof robots?.index !== 'undefined' ? !robots.index : undefined,
|
||||
nofollow: typeof robots?.follow !== 'undefined' ? !robots.follow : undefined,
|
||||
description: description,
|
||||
openGraph: { url: canonical, ...openGraph },
|
||||
twitter: twitter,
|
||||
}
|
||||
);
|
||||
---
|
||||
|
||||
<AstroSeo {...{ ...seoProps, openGraph: await adaptOpenGraphImages(seoProps?.openGraph, Astro.site) }} />
|
5
front/src/components/common/SiteVerification.astro
Normal file
5
front/src/components/common/SiteVerification.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import { SITE } from 'astrowind:config';
|
||||
---
|
||||
|
||||
{SITE.googleSiteVerificationId && <meta name="google-site-verification" content={SITE.googleSiteVerificationId} />}
|
65
front/src/components/common/SocialShare.astro
Normal file
65
front/src/components/common/SocialShare.astro
Normal file
@ -0,0 +1,65 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
|
||||
export interface Props {
|
||||
text: string;
|
||||
url: string | URL;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { text, url, class: className = 'inline-block' } = Astro.props;
|
||||
---
|
||||
|
||||
<div class={className}>
|
||||
<span class="align-super font-bold text-slate-500 dark:text-slate-400">Share:</span>
|
||||
<button
|
||||
class="ml-2 rtl:ml-0 rtl:mr-2"
|
||||
title="Twitter Share"
|
||||
data-aw-social-share="twitter"
|
||||
data-aw-url={url}
|
||||
data-aw-text={text}
|
||||
><Icon
|
||||
name="tabler:brand-x"
|
||||
class="w-6 h-6 text-gray-400 dark:text-slate-500 hover:text-black dark:hover:text-slate-300"
|
||||
/>
|
||||
</button>
|
||||
<button class="ml-2 rtl:ml-0 rtl:mr-2" title="Facebook Share" data-aw-social-share="facebook" data-aw-url={url}
|
||||
><Icon
|
||||
name="tabler:brand-facebook"
|
||||
class="w-6 h-6 text-gray-400 dark:text-slate-500 hover:text-black dark:hover:text-slate-300"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
class="ml-2 rtl:ml-0 rtl:mr-2"
|
||||
title="Linkedin Share"
|
||||
data-aw-social-share="linkedin"
|
||||
data-aw-url={url}
|
||||
data-aw-text={text}
|
||||
><Icon
|
||||
name="tabler:brand-linkedin"
|
||||
class="w-6 h-6 text-gray-400 dark:text-slate-500 hover:text-black dark:hover:text-slate-300"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
class="ml-2 rtl:ml-0 rtl:mr-2"
|
||||
title="Whatsapp Share"
|
||||
data-aw-social-share="whatsapp"
|
||||
data-aw-url={url}
|
||||
data-aw-text={text}
|
||||
><Icon
|
||||
name="tabler:brand-whatsapp"
|
||||
class="w-6 h-6 text-gray-400 dark:text-slate-500 hover:text-black dark:hover:text-slate-300"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
class="ml-2 rtl:ml-0 rtl:mr-2"
|
||||
title="Email Share"
|
||||
data-aw-social-share="mail"
|
||||
data-aw-url={url}
|
||||
data-aw-text={text}
|
||||
><Icon
|
||||
name="tabler:mail"
|
||||
class="w-6 h-6 text-gray-400 dark:text-slate-500 hover:text-black dark:hover:text-slate-300"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
6
front/src/components/common/SplitbeeAnalytics.astro
Normal file
6
front/src/components/common/SplitbeeAnalytics.astro
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
const { doNotTrack = true, noCookieMode = false, url = 'https://cdn.splitbee.io/sb.js' } = Astro.props;
|
||||
---
|
||||
|
||||
<!-- Splitbee Analytics -->
|
||||
<script is:inline data-respect-dnt={doNotTrack} data-no-cookie={noCookieMode} async src={url}></script>
|
29
front/src/components/common/ToggleMenu.astro
Normal file
29
front/src/components/common/ToggleMenu.astro
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
export interface Props {
|
||||
label?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
label = 'Toggle Menu',
|
||||
class: className = 'flex flex-col h-12 w-12 rounded justify-center items-center cursor-pointer group',
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<button type="button" class={className} aria-label={label} data-aw-toggle-menu>
|
||||
<span class="sr-only">{label}</span>
|
||||
<slot>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="h-0.5 w-6 my-1 rounded-full bg-black dark:bg-white transition ease transform duration-200 opacity-80 group-[.expanded]:rotate-45 group-[.expanded]:translate-y-2.5"
|
||||
></span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="h-0.5 w-6 my-1 rounded-full bg-black dark:bg-white transition ease transform duration-200 opacity-80 group-[.expanded]:opacity-0"
|
||||
></span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="h-0.5 w-6 my-1 rounded-full bg-black dark:bg-white transition ease transform duration-200 opacity-80 group-[.expanded]:-rotate-45 group-[.expanded]:-translate-y-2.5"
|
||||
></span>
|
||||
</slot>
|
||||
</button>
|
28
front/src/components/common/ToggleTheme.astro
Normal file
28
front/src/components/common/ToggleTheme.astro
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
|
||||
import { UI } from 'astrowind:config';
|
||||
|
||||
export interface Props {
|
||||
label?: string;
|
||||
class?: string;
|
||||
iconClass?: string;
|
||||
iconName?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
label = 'Toggle between Dark and Light mode',
|
||||
class:
|
||||
className = 'text-muted dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 inline-flex items-center',
|
||||
iconClass = 'w-6 h-6',
|
||||
iconName = 'tabler:sun',
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
!(UI.theme && UI.theme.endsWith(':only')) && (
|
||||
<button type="button" class={className} aria-label={label} data-aw-toggle-color-scheme>
|
||||
<Icon name={iconName} class={iconClass} />
|
||||
</button>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user