initialize project
@@ -0,0 +1,14 @@
|
||||
; https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,26 @@
|
||||
# build output
|
||||
dist/
|
||||
.output/
|
||||
.json/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# ignore .astro directory
|
||||
.astro
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"MD033": false,
|
||||
"MD013": false
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.astro"],
|
||||
"options": {
|
||||
"parser": "astro"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.mdx": "markdown"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import image from "@astrojs/image";
|
||||
import mdx from "@astrojs/mdx";
|
||||
import react from "@astrojs/react";
|
||||
import sitemap from "@astrojs/sitemap";
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
import AutoImport from "astro-auto-import";
|
||||
import { defineConfig } from "astro/config";
|
||||
import remarkCollapse from "remark-collapse";
|
||||
import remarkToc from "remark-toc";
|
||||
import config from "./src/config/config.json";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: config.site.base_url ? config.site.base_url : "http://examplesite.com",
|
||||
base: config.site.base_path ? config.site.base_path : "/",
|
||||
trailingSlash: config.site.trailing_slash ? "always" : "never",
|
||||
integrations: [
|
||||
react(),
|
||||
sitemap(),
|
||||
tailwind({
|
||||
config: {
|
||||
applyBaseStyles: false,
|
||||
},
|
||||
}),
|
||||
image({
|
||||
serviceEntryPoint: "@astrojs/image/sharp",
|
||||
}),
|
||||
AutoImport({
|
||||
imports: [
|
||||
"@shortcodes/Button",
|
||||
"@shortcodes/Accordion",
|
||||
"@shortcodes/Notice",
|
||||
"@shortcodes/Video",
|
||||
"@shortcodes/Youtube",
|
||||
],
|
||||
}),
|
||||
mdx(),
|
||||
],
|
||||
markdown: {
|
||||
remarkPlugins: [
|
||||
remarkToc,
|
||||
[
|
||||
remarkCollapse,
|
||||
{
|
||||
test: "Table of contents",
|
||||
},
|
||||
],
|
||||
],
|
||||
shikiConfig: {
|
||||
theme: "one-dark-pro",
|
||||
wrap: true,
|
||||
},
|
||||
extendDefaultPlugins: true,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
[build]
|
||||
publish = "dist"
|
||||
command = "yarn build"
|
||||
|
||||
[[headers]]
|
||||
for = "/*" # This defines which paths this specific [[headers]] block will cover.
|
||||
|
||||
[headers.values]
|
||||
X-Frame-Options = "DENY"
|
||||
X-XSS-Protection = "1; mode=block"
|
||||
Referrer-Policy = "same-origin"
|
||||
Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload"
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "astroplate",
|
||||
"version": "1.0.0",
|
||||
"description": "Astro and Tailwindcss boilerplate",
|
||||
"author": "zeon.studio",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"json": "node scripts/jsonGenerator.js",
|
||||
"format": "prettier -w ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/image": "^0.16.6",
|
||||
"@astrojs/mdx": "^0.19.0",
|
||||
"@astrojs/rss": "^2.3.2",
|
||||
"astro": "^2.3.0",
|
||||
"astro-auto-import": "^0.2.1",
|
||||
"date-fns": "^2.29.3",
|
||||
"date-fns-tz": "^2.0.0",
|
||||
"disqus-react": "^1.1.5",
|
||||
"fuse.js": "^6.6.2",
|
||||
"github-slugger": "^2.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"marked": "^4.3.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-lite-youtube-embed": "^2.3.52",
|
||||
"remark-collapse": "^0.1.2",
|
||||
"remark-toc": "^8.0.1",
|
||||
"swiper": "^9.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/react": "^2.1.1",
|
||||
"@astrojs/sitemap": "^1.2.2",
|
||||
"@astrojs/tailwind": "^3.1.1",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/marked": "^4.0.8",
|
||||
"@types/react": "^18.0.35",
|
||||
"postcss": "^8.4.21",
|
||||
"prettier": "^2.8.7",
|
||||
"prettier-plugin-astro": "^0.8.0",
|
||||
"prettier-plugin-tailwindcss": "^0.2.7",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sass": "^1.62.0",
|
||||
"sharp": "^0.32.0",
|
||||
"tailwind-bootstrap-grid": "^5.0.1",
|
||||
"tailwindcss": "^3.3.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
##### Optimize default expiration time - BEGIN
|
||||
<IfModule mod_expires.c>
|
||||
|
||||
## Enable expiration control
|
||||
ExpiresActive On
|
||||
|
||||
## CSS and JS expiration: 1 week after request
|
||||
ExpiresByType text/css "now plus 1 week"
|
||||
ExpiresByType application/javascript "now plus 1 week"
|
||||
ExpiresByType application/x-javascript "now plus 1 week"
|
||||
|
||||
## Image files expiration: 1 month after request
|
||||
ExpiresByType image/bmp "now plus 1 month"
|
||||
ExpiresByType image/gif "now plus 1 month"
|
||||
ExpiresByType image/jpeg "now plus 1 month"
|
||||
ExpiresByType image/webp "now plus 1 month"
|
||||
ExpiresByType image/jp2 "now plus 1 month"
|
||||
ExpiresByType image/pipeg "now plus 1 month"
|
||||
ExpiresByType image/png "now plus 1 month"
|
||||
ExpiresByType image/svg+xml "now plus 1 month"
|
||||
ExpiresByType image/tiff "now plus 1 month"
|
||||
ExpiresByType image/x-icon "now plus 1 month"
|
||||
ExpiresByType image/ico "now plus 1 month"
|
||||
ExpiresByType image/icon "now plus 1 month"
|
||||
ExpiresByType text/ico "now plus 1 month"
|
||||
ExpiresByType application/ico "now plus 1 month"
|
||||
ExpiresByType image/vnd.wap.wbmp "now plus 1 month"
|
||||
|
||||
## Font files expiration: 1 month after request
|
||||
ExpiresByType application/x-font-ttf "now plus 1 month"
|
||||
ExpiresByType application/x-font-opentype "now plus 1 month"
|
||||
ExpiresByType application/x-font-woff "now plus 1 month"
|
||||
ExpiresByType font/woff2 "now plus 1 month"
|
||||
ExpiresByType image/svg+xml "now plus 1 month"
|
||||
|
||||
## Audio files expiration: 1 month after request
|
||||
ExpiresByType audio/ogg "now plus 1 month"
|
||||
ExpiresByType application/ogg "now plus 1 month"
|
||||
ExpiresByType audio/basic "now plus 1 month"
|
||||
ExpiresByType audio/mid "now plus 1 month"
|
||||
ExpiresByType audio/midi "now plus 1 month"
|
||||
ExpiresByType audio/mpeg "now plus 1 month"
|
||||
ExpiresByType audio/mp3 "now plus 1 month"
|
||||
ExpiresByType audio/x-aiff "now plus 1 month"
|
||||
ExpiresByType audio/x-mpegurl "now plus 1 month"
|
||||
ExpiresByType audio/x-pn-realaudio "now plus 1 month"
|
||||
ExpiresByType audio/x-wav "now plus 1 month"
|
||||
|
||||
## Movie files expiration: 1 month after request
|
||||
ExpiresByType application/x-shockwave-flash "now plus 1 month"
|
||||
ExpiresByType x-world/x-vrml "now plus 1 month"
|
||||
ExpiresByType video/x-msvideo "now plus 1 month"
|
||||
ExpiresByType video/mpeg "now plus 1 month"
|
||||
ExpiresByType video/mp4 "now plus 1 month"
|
||||
ExpiresByType video/quicktime "now plus 1 month"
|
||||
ExpiresByType video/x-la-asf "now plus 1 month"
|
||||
ExpiresByType video/x-ms-asf "now plus 1 month"
|
||||
</IfModule>
|
||||
##### Optimize default expiration time - END
|
||||
|
||||
##### 1 Month for most static resources
|
||||
<filesMatch ".(css|jpg|jpeg|png|webp|gif|js|ico|woff|woff2|eot|ttf)$">
|
||||
Header set Cache-Control "max-age=2592000, public"
|
||||
</filesMatch>
|
||||
|
||||
##### Enable gzip compression for resources
|
||||
<ifModule mod_gzip.c>
|
||||
mod_gzip_on Yes
|
||||
mod_gzip_dechunk Yes
|
||||
mod_gzip_item_include file .(html?|txt|css|js|php)$
|
||||
mod_gzip_item_include handler ^cgi-script$
|
||||
mod_gzip_item_include mime ^text/.*
|
||||
mod_gzip_item_include mime ^application/x-javascript.*
|
||||
mod_gzip_item_exclude mime ^image/.*
|
||||
mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
|
||||
</ifModule>
|
||||
|
||||
##### Or, compress certain file types by extension:
|
||||
<FilesMatch ".(html|css|jpg|jpeg|webp|png|gif|js|ico)">
|
||||
SetOutputFilter DEFLATE
|
||||
</FilesMatch>
|
||||
|
||||
##### Set Header Vary: Accept-Encoding
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch ".(js|css|xml|gz|html)$">
|
||||
Header append Vary: Accept-Encoding
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Disallow: /api/*
|
||||
@@ -0,0 +1,20 @@
|
||||
# Astro Tailwind Boilerplate
|
||||
|
||||
## Dependency
|
||||
|
||||
- astro 2.3+
|
||||
- node v18+
|
||||
- npm v9.5+
|
||||
- tailwind v3.3+
|
||||
|
||||
## Development Command
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Build Command
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
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";
|
||||
|
||||
// get data from markdown
|
||||
const getData = (folder) => {
|
||||
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", "");
|
||||
|
||||
return {
|
||||
frontmatter: data,
|
||||
content: content,
|
||||
slug: slug,
|
||||
};
|
||||
});
|
||||
const publishedPages = getData.filter(
|
||||
(page) => !page.frontmatter?.draft && page
|
||||
);
|
||||
return publishedPages;
|
||||
};
|
||||
|
||||
// get post data
|
||||
const posts = getData(`src/content/${blog_folder}`);
|
||||
|
||||
try {
|
||||
// creare folder if it doesn't exist
|
||||
if (!fs.existsSync(jsonDir)) {
|
||||
fs.mkdirSync(jsonDir);
|
||||
}
|
||||
|
||||
// create posts.json file
|
||||
fs.writeFileSync(`${jsonDir}/posts.json`, JSON.stringify(posts));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"site": {
|
||||
"title": "Astroplate",
|
||||
"base_url": "https://astroplate.netlify.app",
|
||||
"base_path": "/",
|
||||
"trailing_slash": false,
|
||||
"favicon": "/images/favicon.png",
|
||||
"logo": "/images/logo.png",
|
||||
"logo_darkmode": "/images/logo-darkmode.png",
|
||||
"logo_width": "150",
|
||||
"logo_height": "30",
|
||||
"logo_text": "Astroplate"
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"search": true,
|
||||
"sticky_header": true,
|
||||
"theme_switcher": true,
|
||||
"default_theme": "system",
|
||||
"pagination": 2,
|
||||
"summary_length": 200,
|
||||
"blog_folder": "posts"
|
||||
},
|
||||
|
||||
"params": {
|
||||
"contact_form_action": "#",
|
||||
"copyright": "Designed And Developed by [Zeon Studio](https://zeon.studio)"
|
||||
},
|
||||
|
||||
"navigation_button": {
|
||||
"enable": true,
|
||||
"label": "Get Started",
|
||||
"link": "#"
|
||||
},
|
||||
|
||||
"disqus": {
|
||||
"enable": true,
|
||||
"shortname": "themefisher-template",
|
||||
"settings": {}
|
||||
},
|
||||
|
||||
"metadata": {
|
||||
"meta_author": "zeon.studio",
|
||||
"meta_image": "/images/og-image.png",
|
||||
"meta_description": "astro and tailwind boilerplate"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"main": [
|
||||
{
|
||||
"name": "Home",
|
||||
"url": "/"
|
||||
},
|
||||
{
|
||||
"name": "About",
|
||||
"url": "/about"
|
||||
},
|
||||
{
|
||||
"name": "Elements",
|
||||
"url": "/elements"
|
||||
},
|
||||
{
|
||||
"name": "Pages",
|
||||
"url": "",
|
||||
"hasChildren": true,
|
||||
"children": [
|
||||
{
|
||||
"name": "Contact",
|
||||
"url": "/contact"
|
||||
},
|
||||
{
|
||||
"name": "Blog",
|
||||
"url": "/posts"
|
||||
},
|
||||
{
|
||||
"name": "Authors",
|
||||
"url": "/authors"
|
||||
},
|
||||
{
|
||||
"name": "Categories",
|
||||
"url": "/categories"
|
||||
},
|
||||
{
|
||||
"name": "Search",
|
||||
"url": "/search"
|
||||
},
|
||||
{
|
||||
"name": "404 Page",
|
||||
"url": "/404"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"footer": [
|
||||
{
|
||||
"name": "About",
|
||||
"url": "/about"
|
||||
},
|
||||
{
|
||||
"name": "Elements",
|
||||
"url": "/elements"
|
||||
},
|
||||
{
|
||||
"name": "Privacy Policy",
|
||||
"url": "/privacy-policy"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"facebook": "https://facebook.com/",
|
||||
"twitter": "https://twitter.com/",
|
||||
"instagram": "https://instagram.com/",
|
||||
"youtube": "https://youtube.com/",
|
||||
"linkedin": "",
|
||||
"github": "",
|
||||
"gitlab": "",
|
||||
"medium": "",
|
||||
"codepen": "",
|
||||
"bitbucket": "",
|
||||
"dribbble": "",
|
||||
"behance": "",
|
||||
"pinterest": "",
|
||||
"soundcloud": "",
|
||||
"tumblr": "",
|
||||
"reddit": "",
|
||||
"vk": "",
|
||||
"whatsapp": "",
|
||||
"snapchat": "",
|
||||
"vimeo": "",
|
||||
"tiktok": "",
|
||||
"foursquare": "",
|
||||
"rss": "",
|
||||
"email": "",
|
||||
"phone": "",
|
||||
"address": "",
|
||||
"skype": "",
|
||||
"website": ""
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"colors": {
|
||||
"default": {
|
||||
"theme_color": {
|
||||
"primary": "#121212",
|
||||
"body": "#fff",
|
||||
"border": "#eaeaea",
|
||||
"theme_light": "#f6f6f6",
|
||||
"theme_dark": ""
|
||||
},
|
||||
"text_color": {
|
||||
"default": "#555",
|
||||
"dark": "#040404",
|
||||
"light": "#999"
|
||||
}
|
||||
},
|
||||
"darkmode": {
|
||||
"theme_color": {
|
||||
"primary": "#fff",
|
||||
"body": "#1c1c1c",
|
||||
"border": "#3E3E3E",
|
||||
"theme_light": "#222222",
|
||||
"theme_dark": ""
|
||||
},
|
||||
"text_color": {
|
||||
"default": "#B4AFB6",
|
||||
"dark": "#fff",
|
||||
"light": "#B4AFB6"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fonts": {
|
||||
"font_family": {
|
||||
"primary": "Heebo:wght@400;600",
|
||||
"primary_type": "sans-serif",
|
||||
"secondary": "Signika:wght@500;700",
|
||||
"secondary_type": "sans-serif"
|
||||
},
|
||||
"font_size": {
|
||||
"base": "16",
|
||||
"scale": "1.250"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: "Authors"
|
||||
---
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: John Doe
|
||||
email: johndoe@email.com
|
||||
image: "/images/avatar.png"
|
||||
description: this is meta description
|
||||
social:
|
||||
facebook: https://www.facebook.com/
|
||||
twitter: https://www.twitter.com/
|
||||
instagram: https://www.instagram.com/
|
||||
---
|
||||
|
||||
lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostr navigation et dolore magna aliqua.
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Sam Wilson
|
||||
email: samwilson@email.com
|
||||
image: "/images/avatar.png"
|
||||
description: this is meta description
|
||||
social:
|
||||
facebook: https://www.facebook.com/
|
||||
twitter: https://www.twitter.com/
|
||||
instagram: https://www.instagram.com/
|
||||
---
|
||||
|
||||
lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostr navigation et dolore magna aliqua.
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: William Jacob
|
||||
email: williamjacob@email.com
|
||||
image: "/images/avatar.png"
|
||||
description: this is meta description
|
||||
social:
|
||||
facebook: https://www.facebook.com/
|
||||
twitter: https://www.twitter.com/
|
||||
instagram: https://www.instagram.com/
|
||||
---
|
||||
|
||||
lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostr navigation et dolore magna aliqua.
|
||||
@@ -0,0 +1,53 @@
|
||||
import { defineCollection, z } from "astro:content";
|
||||
|
||||
// Post collection schema
|
||||
const postsCollection = defineCollection({
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
meta_title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
date: z.date().optional(),
|
||||
image: z.string().optional(),
|
||||
author: z.string().default("Admin"),
|
||||
categories: z.array(z.string()).default(["others"]),
|
||||
tags: z.array(z.string()).default(["others"]),
|
||||
draft: z.boolean().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
// Author collection schema
|
||||
const authorsCollection = defineCollection({
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
meta_title: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
image: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
social: z
|
||||
.object({
|
||||
facebook: z.string().optional(),
|
||||
twitter: z.string().optional(),
|
||||
instagram: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
draft: z.boolean().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
// Pages collection schema
|
||||
const pagesCollection = defineCollection({
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
meta_title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
image: z.string().optional(),
|
||||
draft: z.boolean().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
// Export collections
|
||||
export const collections = {
|
||||
posts: postsCollection,
|
||||
pages: pagesCollection,
|
||||
authors: authorsCollection,
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
# Banner
|
||||
banner:
|
||||
title: "The Ultimate Starter Template You Need To Start Your Astro Project"
|
||||
content: "Astroplate is a free starter template built with Astro and TailwindCSS, providing everything you need to jumpstart your Astro project and save valuable time."
|
||||
image: "/images/banner.png"
|
||||
button:
|
||||
enable: true
|
||||
label: "Get Started For Free"
|
||||
link: "#"
|
||||
|
||||
# Features
|
||||
features:
|
||||
- title: "What's Included in Astroplate"
|
||||
image: "/images/service-1.png"
|
||||
content: "Astroplate is a comprehensive starter template that includes everything you need to get started with your Astro project. What's Included in Astroplate"
|
||||
bulletpoints:
|
||||
- "10+ Pre-build pages"
|
||||
- "95+ Google Pagespeed Score"
|
||||
- "Build with Astro and TailwindCSS for easy and customizable styling"
|
||||
- "Fully responsive on all devices"
|
||||
- "SEO-optimized for better search engine rankings"
|
||||
- "Open-source and free for personal and commercial use"
|
||||
button:
|
||||
enable: false
|
||||
label: "Get Started Now"
|
||||
link: "#"
|
||||
|
||||
- title: "Discover the Key Features Of Astro"
|
||||
image: "/images/service-2.png"
|
||||
content: "Astro is an all-in-one web framework for building fast, content-focused websites. It offers a range of exciting features for developers and website creators. Some of the key features are:"
|
||||
bulletpoints:
|
||||
- "Zero JS, by default: No JavaScript runtime overhead to slow you down."
|
||||
- "Customizable: Tailwind, MDX, and 100+ other integrations to choose from."
|
||||
- "UI-agnostic: Supports React, Preact, Svelte, Vue, Solid, Lit and more."
|
||||
button:
|
||||
enable: true
|
||||
label: "Get Started Now"
|
||||
link: "#"
|
||||
|
||||
- title: "The Top Reasons to Choose Astro for Your Next Project"
|
||||
image: "/images/service-3.png"
|
||||
content: "With Astro, you can build modern and content-focused websites without sacrificing performance or ease of use."
|
||||
bulletpoints:
|
||||
- "Instantly load static sites for better user experience and SEO."
|
||||
- "Intuitive syntax and support for popular frameworks make learning and using Astro a breeze."
|
||||
- "Use any front-end library or framework, or build custom components, for any project size."
|
||||
- "Built on cutting-edge technology to keep your projects up-to-date with the latest web standards."
|
||||
button:
|
||||
enable: false
|
||||
label: ""
|
||||
link: ""
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Something Went Wrong!"
|
||||
---
|
||||
|
||||
This page doesn't exist or has been removed, <br /> we suggest you go back to Home.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: "Hey, I am John Doe!"
|
||||
meta_title: "About"
|
||||
description: "this is meta description"
|
||||
image: "images/avatar.png"
|
||||
draft: false
|
||||
---
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Corporis illum nesciunt commodi vel nisi ut alias excepturi ipsum, totam, labore tempora, odit ex iste tempore sed. Fugit voluptatibus perspiciatis assumenda nulla ad nihil, omnis vel, doloremque sit quam autem optio maiores, illum eius facilis et quo consectetur provident dolor similique! Enim voluptatem dicta expedita veritatis repellat dolorum impedit, provident quasi at.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: "Contact"
|
||||
meta_title: ""
|
||||
description: "this is meta description"
|
||||
draft: false
|
||||
---
|
||||
@@ -0,0 +1,224 @@
|
||||
---
|
||||
title: "Elements"
|
||||
meta_title: ""
|
||||
description: "this is meta description"
|
||||
draft: false
|
||||
---
|
||||
|
||||
# Heading 1
|
||||
|
||||
## Heading 2
|
||||
|
||||
### Heading 3
|
||||
|
||||
#### Heading 4
|
||||
|
||||
##### Heading 5
|
||||
|
||||
###### Heading 6
|
||||
|
||||
---
|
||||
|
||||
### Paragraph
|
||||
|
||||
Did you come here for something in particular or just general Riker-bashing? And blowing into maximum warp speed, you appeared for an instant to be in two places at once. We have a saboteur aboard. We know you’re dealing in stolen ore. But I wanna talk about the assassination attempt on Lieutenant Worf. Could someone survive inside a transporter buffer for 75 years? Fate. It protects fools, little children, and ships.
|
||||
|
||||
Did you come here for something in particular or just general Riker-bashing? And blowing into maximum warp speed, you appeared for an instant to be in two places at once. We have a saboteur aboard. We know you’re dealing in stolen ore. But I wanna talk about the assassination attempt on Lieutenant Worf. Could someone survive inside a transporter buffer for 75 years? Fate. It protects fools, little children, and ships.
|
||||
|
||||
---
|
||||
|
||||
### Emphasis
|
||||
|
||||
1. Did you come here for something in **particular** or just general
|
||||
|
||||
2. Did you come here for something in <ins>particular</ins>
|
||||
|
||||
3. _Did you come here_
|
||||
|
||||
4. Did you come here for **something** in particular
|
||||
|
||||
5. Did you come here for something in particular
|
||||
|
||||
6. Did you come here for something in particular
|
||||
|
||||
7. URLs and URLs in angle brackets will automatically get turned into links. [http://www.example.com](http://www.example.com) or
|
||||
|
||||
8. [http://www.example.com](http://www.example.com) and sometimes example.com (but not on Github, for example).
|
||||
|
||||
---
|
||||
|
||||
### Link
|
||||
|
||||
[I'm an inline-style link](https://www.google.com)
|
||||
|
||||
[I'm an inline-style link with title](https://www.google.com "Google's Homepage")
|
||||
|
||||
[I'm a reference-style link][arbitrary case-insensitive reference text]
|
||||
|
||||
[I'm a relative reference to a repository file](../blob/master/LICENSE)
|
||||
|
||||
[You can use numbers for reference-style link definitions][1]
|
||||
|
||||
Or leave it empty and use the [link text itself].
|
||||
|
||||
example.com (but not on Github, for example).
|
||||
|
||||
Some text to show that the reference links can follow later.
|
||||
|
||||
[arbitrary case-insensitive reference text]: https://www.themefisher.com
|
||||
[1]: https://gethugothemes.com
|
||||
[link text itself]: https://www.getjekyllthemes.com
|
||||
|
||||
---
|
||||
|
||||
### Ordered List
|
||||
|
||||
1. List item
|
||||
2. List item
|
||||
3. List item
|
||||
4. List item
|
||||
5. List item
|
||||
|
||||
---
|
||||
|
||||
### Unordered List
|
||||
|
||||
- List item
|
||||
- List item
|
||||
- List item
|
||||
- List item
|
||||
- List item
|
||||
|
||||
---
|
||||
|
||||
### Code and Syntax Highlighting
|
||||
|
||||
#### HTML
|
||||
|
||||
```html
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="about/">About</a>
|
||||
</li>
|
||||
</ul>`}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### CSS
|
||||
|
||||
```css
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### JavaScript
|
||||
|
||||
```javascript
|
||||
window.addEventListener("load", (e) => {
|
||||
document.querySelector(".preloader").style.display = "none";
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Button
|
||||
|
||||
<Button href="#" style="solid">Button</Button>
|
||||
|
||||
---
|
||||
|
||||
### Quote
|
||||
|
||||
> Did you come here for something in particular or just general Riker-bashing? And blowing into maximum warp speed, you appeared for an instant to be in two places at once.
|
||||
|
||||
---
|
||||
|
||||
### Notice
|
||||
|
||||
<Notice type="note">
|
||||
This is a simple note.
|
||||
</Notice>
|
||||
|
||||
<Notice type="tip">
|
||||
This is a simple note.
|
||||
</Notice>
|
||||
|
||||
<Notice type="info">
|
||||
This is a simple note.
|
||||
</Notice>
|
||||
|
||||
<Notice type="warning">
|
||||
This is a simple note.
|
||||
</Notice>
|
||||
|
||||
---
|
||||
|
||||
### Table
|
||||
|
||||
| # | First | Last | Handle |
|
||||
| :-- | :----------: | :----------: | -----------: |
|
||||
| 1 | Row:1 Cell:1 | Row:1 Cell:2 | Row:1 Cell:3 |
|
||||
| 2 | Row:2 Cell:1 | Row:2 Cell:2 | Row:2 Cell:3 |
|
||||
| 3 | Row:3 Cell:1 | Row:3 Cell:2 | Row:3 Cell:3 |
|
||||
|
||||
---
|
||||
|
||||
### Collapse
|
||||
|
||||
<Accordion client:load title="Why should you need to do this?">
|
||||
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion client:load title="How can I adjust Horizontal centering">
|
||||
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion client:load title="Should you use Negative margin?">
|
||||
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
|
||||
</Accordion>
|
||||
|
||||
---
|
||||
|
||||
### Image
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Youtube video
|
||||
|
||||
<Youtube client:load id="ZEe-IFezQok" title="Youtube Video Test Title" />
|
||||
|
||||
---
|
||||
|
||||
### Custom video
|
||||
|
||||
<Video width="100%" src="https://joy1.videvo.net/videvo_files/video/free/video0467/large_watermarked/_import_61516692993d77.04238324_preview.mp4" />
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
title: "Privacy"
|
||||
meta_title: ""
|
||||
description: "this is meta description"
|
||||
draft: false
|
||||
---
|
||||
|
||||
#### Responsibility of Contributors
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Purus, donec nunc eros, ullamcorper id feugiat quisque aliquam sagittis. Sem turpis sed viverra massa gravida pharetra. Non dui dolor potenti eu dignissim fusce. Ultrices amet, in curabitur a arcu a lectus morbi id. Iaculis erat sagittis in tortor cursus. Molestie urna eu tortor, erat scelerisque eget. Nunc hendrerit sed interdum lacus. Lorem quis viverra sed
|
||||
|
||||
pretium, aliquam sit. Praesent elementum magna amet, tincidunt eros, nibh in leo. Malesuada purus, lacus, at aliquam suspendisse tempus. Quis tempus amet, velit nascetur sollicitudin. At sollicitudin eget amet in. Eu velit nascetur sollicitudin erhdfvssfvrgss eget viverra nec elementum. Lacus, facilisis tristique lectus in.
|
||||
|
||||
#### Gathering of Personal Information
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Purus, donec nunc eros, ullamcorper id feugiat quisque aliquam sagittis. Sem turpis sed viverra massa gravida pharetra. Non dui dolor potenti eu dignissim fusce. Ultrices amet, in curabitur a arcu a lectus morbi id. Iaculis erat sagittis in tortor cursus. Molestie urna eu tortor, erat scelerisque eget. Nunc hendrerit sed interdum lacus. Lorem quis viverra sed
|
||||
|
||||
#### Protection of Personal- Information
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Purus, donec nunc eros, ullamcorper id feugiat quisque aliquam sagittis. Sem turpis sed viverra massa gravida pharetra. Non dui dolor potenti eu dignissim fusce. Ultrices amet, in curabitur a arcu a lectus morbi id. Iaculis erat sagittis in tortor cursus.
|
||||
|
||||
Molestie urna eu tortor, erat scelerisque eget. Nunc hendrerit sed interdum lacus. Lorem quis viverra sed
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Purus, donec nunc eros, ullamcorper id feugiat
|
||||
|
||||
#### Privacy Policy Changes
|
||||
|
||||
1. Sll the Themefisher items are designed to be with the latest , We check all
|
||||
2. comments that threaten or harm the reputation of any person or organization
|
||||
3. personal information including, but limited to, email addresses, telephone numbers
|
||||
4. Any Update come in The technology Customer will get automatic Notification.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Blog Posts"
|
||||
meta_title: ""
|
||||
description: "this is meta description"
|
||||
---
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "How to build an Application with modern Technology"
|
||||
meta_title: ""
|
||||
description: "this is meta description"
|
||||
date: 2022-04-04T05:00:00Z
|
||||
image: "/images/image-placeholder.png"
|
||||
categories: ["Application", "Data"]
|
||||
author: "John Doe"
|
||||
tags: ["nextjs", "tailwind"]
|
||||
draft: false
|
||||
---
|
||||
|
||||
Nemo vel ad consectetur namut rutrum ex, venenatis sollicitudin urna. Aliquam erat volutpat. Integer eu ipsum sem. Ut bibendum lacus vestibulum maximus suscipit. Quisque vitae nibh iaculis neque blandit euismod.
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo vel ad consectetur ut aperiam. Itaque eligendi natus aperiam? Excepturi repellendus consequatur quibusdam optio expedita praesentium est adipisci dolorem ut eius!
|
||||
|
||||
## Creative Design
|
||||
|
||||
Nam ut rutrum ex, venenatis sollicitudin urna. Aliquam erat volutpat. Integer eu ipsum sem. Ut bibendum lacus vestibulum maximus suscipit. Quisque vitae nibh iaculis neque blandit euismod.
|
||||
|
||||
> Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo vel ad consectetur ut aperiam. Itaque eligendi natus aperiam? Excepturi repellendus consequatur quibusdam optio expedita praesentium est adipisci dolorem ut eius!
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo vel ad consectetur ut aperiam. Itaque eligendi natus aperiam? Excepturi repellendus consequatur quibusdam optio expedita praesentium est adipisci dolorem ut eius!
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "How to build an Application with modern Technology"
|
||||
meta_title: ""
|
||||
description: "this is meta description"
|
||||
date: 2022-04-04T05:00:00Z
|
||||
image: "/images/image-placeholder.png"
|
||||
categories: ["Technology", "Data"]
|
||||
author: "Sam Wilson"
|
||||
tags: ["technology", "tailwind"]
|
||||
draft: false
|
||||
---
|
||||
|
||||
Nemo vel ad consectetur namut rutrum ex, venenatis sollicitudin urna. Aliquam erat volutpat. Integer eu ipsum sem. Ut bibendum lacus vestibulum maximus suscipit. Quisque vitae nibh iaculis neque blandit euismod.
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo vel ad consectetur ut aperiam. Itaque eligendi natus aperiam? Excepturi repellendus consequatur quibusdam optio expedita praesentium est adipisci dolorem ut eius!
|
||||
|
||||
## Creative Design
|
||||
|
||||
Nam ut rutrum ex, venenatis sollicitudin urna. Aliquam erat volutpat. Integer eu ipsum sem. Ut bibendum lacus vestibulum maximus suscipit. Quisque vitae nibh iaculis neque blandit euismod.
|
||||
|
||||
> Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo vel ad consectetur ut aperiam. Itaque eligendi natus aperiam? Excepturi repellendus consequatur quibusdam optio expedita praesentium est adipisci dolorem ut eius!
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo vel ad consectetur ut aperiam. Itaque eligendi natus aperiam? Excepturi repellendus consequatur quibusdam optio expedita praesentium est adipisci dolorem ut eius!
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "How to build an Application with modern Technology"
|
||||
meta_title: ""
|
||||
description: "this is meta description"
|
||||
date: 2022-04-04T05:00:00Z
|
||||
image: "/images/image-placeholder.png"
|
||||
categories: ["Software"]
|
||||
author: "John Doe"
|
||||
tags: ["software", "tailwind"]
|
||||
draft: false
|
||||
---
|
||||
|
||||
Nemo vel ad consectetur namut rutrum ex, venenatis sollicitudin urna. Aliquam erat volutpat. Integer eu ipsum sem. Ut bibendum lacus vestibulum maximus suscipit. Quisque vitae nibh iaculis neque blandit euismod.
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo vel ad consectetur ut aperiam. Itaque eligendi natus aperiam? Excepturi repellendus consequatur quibusdam optio expedita praesentium est adipisci dolorem ut eius!
|
||||
|
||||
## Creative Design
|
||||
|
||||
Nam ut rutrum ex, venenatis sollicitudin urna. Aliquam erat volutpat. Integer eu ipsum sem. Ut bibendum lacus vestibulum maximus suscipit. Quisque vitae nibh iaculis neque blandit euismod.
|
||||
|
||||
> Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo vel ad consectetur ut aperiam. Itaque eligendi natus aperiam? Excepturi repellendus consequatur quibusdam optio expedita praesentium est adipisci dolorem ut eius!
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo vel ad consectetur ut aperiam. Itaque eligendi natus aperiam? Excepturi repellendus consequatur quibusdam optio expedita praesentium est adipisci dolorem ut eius!
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "How to build an Application with modern Technology"
|
||||
meta_title: ""
|
||||
description: "this is meta description"
|
||||
date: 2022-04-04T05:00:00Z
|
||||
image: "/images/image-placeholder.png"
|
||||
categories: ["Architecture"]
|
||||
author: "John Doe"
|
||||
tags: ["silicon", "technology"]
|
||||
draft: false
|
||||
---
|
||||
|
||||
Nemo vel ad consectetur namut rutrum ex, venenatis sollicitudin urna. Aliquam erat volutpat. Integer eu ipsum sem. Ut bibendum lacus vestibulum maximus suscipit. Quisque vitae nibh iaculis neque blandit euismod.
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo vel ad consectetur ut aperiam. Itaque eligendi natus aperiam? Excepturi repellendus consequatur quibusdam optio expedita praesentium est adipisci dolorem ut eius!
|
||||
|
||||
## Creative Design
|
||||
|
||||
Nam ut rutrum ex, venenatis sollicitudin urna. Aliquam erat volutpat. Integer eu ipsum sem. Ut bibendum lacus vestibulum maximus suscipit. Quisque vitae nibh iaculis neque blandit euismod.
|
||||
|
||||
> Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo vel ad consectetur ut aperiam. Itaque eligendi natus aperiam? Excepturi repellendus consequatur quibusdam optio expedita praesentium est adipisci dolorem ut eius!
|
||||
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo vel ad consectetur ut aperiam. Itaque eligendi natus aperiam? Excepturi repellendus consequatur quibusdam optio expedita praesentium est adipisci dolorem ut eius!
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
enable: true
|
||||
title: "Ready to build your next project with Astro?"
|
||||
image: "/images/call-to-action.png"
|
||||
description: "Experience the future of web development with Astroplate and Astro. Build lightning-fast static sites with ease and flexibility."
|
||||
button:
|
||||
enable: true
|
||||
label: "Get Started Now"
|
||||
link: "#"
|
||||
---
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
enable: true
|
||||
title: "What Users Are Saying About Astroplate"
|
||||
description: "Don't just take our word for it - hear from some of our satisfied users! Check out some of our testimonials below to see what others are saying about Astroplate."
|
||||
|
||||
# Testimonials
|
||||
testimonials:
|
||||
- name: "Marvin McKinney"
|
||||
designation: "Web Designer"
|
||||
avatar: "/images/avatar-sm.png"
|
||||
content: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui iusto illo molestias, assumenda expedita commodi inventore non itaque molestiae voluptatum dolore, facilis sapiente, repellat veniam."
|
||||
|
||||
- name: "Marvin McKinney"
|
||||
designation: "Web Designer"
|
||||
avatar: "/images/avatar-sm.png"
|
||||
content: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui iusto illo molestias, assumenda expedita commodi inventore non itaque molestiae voluptatum dolore, facilis sapiente, repellat veniam."
|
||||
|
||||
- name: "Marvin McKinney"
|
||||
designation: "Web Designer"
|
||||
avatar: "/images/avatar-sm.png"
|
||||
content: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui iusto illo molestias, assumenda expedita commodi inventore non itaque molestiae voluptatum dolore, facilis sapiente, repellat veniam."
|
||||
|
||||
- name: "Marvin McKinney"
|
||||
designation: "Web Designer"
|
||||
avatar: "/images/avatar-sm.png"
|
||||
content: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui iusto illo molestias, assumenda expedita commodi inventore non itaque molestiae voluptatum dolore, facilis sapiente, repellat veniam."
|
||||
---
|
||||
@@ -0,0 +1,2 @@
|
||||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="@astrojs/image/client" />
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const useTheme = (): string => {
|
||||
const [themeValue, setThemeValue] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setThemeValue(document.documentElement.classList.contains('dark')? 'dark' : 'light');
|
||||
}, []);
|
||||
|
||||
return themeValue;
|
||||
};
|
||||
|
||||
export default useTheme;
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
import TwSizeIndicator from "@components/TwSizeIndicator.astro";
|
||||
import config from "@config/config.json";
|
||||
import theme from "@config/theme.json";
|
||||
import { plainify } from "@lib/utils/textConverter";
|
||||
import Footer from "@partials/Footer.astro";
|
||||
import Header from "@partials/Header.astro";
|
||||
import "@styles/main.scss";
|
||||
|
||||
// font families
|
||||
const pf = theme.fonts.font_family.primary;
|
||||
const sf = theme.fonts.font_family.secondary;
|
||||
|
||||
// types for frontmatters
|
||||
export interface Props {
|
||||
title?: string;
|
||||
meta_title?: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
noindex?: boolean;
|
||||
canonical?: string;
|
||||
}
|
||||
|
||||
// distructure frontmatters
|
||||
const { title, meta_title, description, image, noindex, canonical } =
|
||||
Astro.props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- favicon -->
|
||||
<link rel="shortcut icon" href={config.site.favicon} />
|
||||
<!-- theme meta -->
|
||||
<meta name="theme-name" content="astroplate" />
|
||||
<meta name="msapplication-TileColor" content="#000000" />
|
||||
<meta
|
||||
name="theme-color"
|
||||
media="(prefers-color-scheme: light)"
|
||||
content="#fff"
|
||||
/>
|
||||
<meta
|
||||
name="theme-color"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
content="#000"
|
||||
/>
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
|
||||
<!-- google font css -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href={`https://fonts.googleapis.com/css2?family=${pf}${
|
||||
sf ? "&family=" + sf : ""
|
||||
}&display=swap`}
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- responsive meta -->
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=5"
|
||||
/>
|
||||
|
||||
<!-- title -->
|
||||
<title>
|
||||
{plainify(meta_title ? meta_title : title ? title : config.site.title)}
|
||||
</title>
|
||||
|
||||
<!-- canonical url -->
|
||||
{canonical && <link rel="canonical" href={canonical} item-prop="url" />}
|
||||
|
||||
<!-- noindex robots -->
|
||||
{noindex && <meta name="robots" content="noindex,nofollow" />}
|
||||
|
||||
<!-- meta-description -->
|
||||
<meta
|
||||
name="description"
|
||||
content={plainify(
|
||||
description ? description : config.metadata.meta_description
|
||||
)}
|
||||
/>
|
||||
|
||||
<!-- author from config.json -->
|
||||
<meta name="author" content={config.metadata.meta_author} />
|
||||
|
||||
<!-- og-title -->
|
||||
<meta
|
||||
property="og:title"
|
||||
content={plainify(
|
||||
meta_title ? meta_title : title ? title : config.site.title
|
||||
)}
|
||||
/>
|
||||
|
||||
<!-- og-description -->
|
||||
<meta
|
||||
property="og:description"
|
||||
content={plainify(
|
||||
description ? description : config.metadata.meta_description
|
||||
)}
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta
|
||||
property="og:url"
|
||||
content={`${config.site.base_url}/${Astro.url.pathname.replace("/", "")}`}
|
||||
/>
|
||||
|
||||
<!-- twitter-title -->
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content={plainify(
|
||||
meta_title ? meta_title : title ? title : config.site.title
|
||||
)}
|
||||
/>
|
||||
|
||||
<!-- twitter-description -->
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content={plainify(
|
||||
description ? description : config.metadata.meta_description
|
||||
)}
|
||||
/>
|
||||
|
||||
<!-- og-image -->
|
||||
<meta
|
||||
property="og:image"
|
||||
content={`${config.site.base_url}${
|
||||
image ? image : config.metadata.meta_image
|
||||
}`}
|
||||
/>
|
||||
|
||||
<!-- twitter-image -->
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content={`${config.site.base_url}${
|
||||
image ? image : config.metadata.meta_image
|
||||
}`}
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
</head>
|
||||
<body>
|
||||
<TwSizeIndicator />
|
||||
<Header />
|
||||
<main id="main-content">
|
||||
<slot />
|
||||
</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
import { Image } from "@astrojs/image/components";
|
||||
import BlogCard from "@components/BlogCard.astro";
|
||||
import Share from "@components/Share.astro";
|
||||
import config from "@config/config.json";
|
||||
import Disqus from "@function-components/Disqus";
|
||||
import { getSinglePage } from "@lib/contentParser.astro";
|
||||
import dateFormat from "@lib/utils/dateFormat";
|
||||
import similerItems from "@lib/utils/similarItems";
|
||||
import { humanize, markdownify, slugify } from "@lib/utils/textConverter";
|
||||
import {
|
||||
FaRegClock,
|
||||
FaRegFolder,
|
||||
FaRegUserCircle,
|
||||
} from "react-icons/fa/index.js";
|
||||
|
||||
const { blog_folder } = config.settings;
|
||||
const posts = await getSinglePage(blog_folder);
|
||||
const { post } = Astro.props;
|
||||
const similarPosts = similerItems(post, posts, post.slug);
|
||||
const { Content } = await post.render();
|
||||
const { title, description, author, categories, image, date, tags } = post.data;
|
||||
---
|
||||
|
||||
<section class="section pt-7">
|
||||
<div class="container">
|
||||
<div class="row justify-center">
|
||||
<article class="lg:col-10">
|
||||
{
|
||||
image && (
|
||||
<div class="mb-10">
|
||||
<Image
|
||||
src={image}
|
||||
height={500}
|
||||
width={1200}
|
||||
alt={title}
|
||||
class="w-full rounded"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<h1 set:html={markdownify(title)} class="h2 mb-4" />
|
||||
<ul class="mb-4">
|
||||
<li class="mr-4 inline-block">
|
||||
<a href={`/authors/${slugify(author)}`}>
|
||||
<FaRegUserCircle className={"mr-2 -mt-1 inline-block"} />
|
||||
{humanize(author)}
|
||||
</a>
|
||||
</li>
|
||||
<li class="mr-4 inline-block">
|
||||
<FaRegFolder className={"mr-2 -mt-1 inline-block"} />
|
||||
{
|
||||
categories.map((category: string, index: number) => (
|
||||
<a href={`/categories/${slugify(category)}`}>
|
||||
{humanize(category)}
|
||||
{index !== categories.length - 1 && ","}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</li>
|
||||
<li class="mr-4 inline-block">
|
||||
<FaRegClock className={"mr-2 -mt-1 inline-block"} />
|
||||
{dateFormat(date)}
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content mb-10">
|
||||
<Content />
|
||||
</div>
|
||||
<div class="row items-start justify-between">
|
||||
<div class="mb-10 flex items-center lg:col-5 lg:mb-0">
|
||||
<h5 class="mr-3">Tags :</h5>
|
||||
<ul>
|
||||
{
|
||||
tags.map((tag: string) => (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
class="m-1 block rounded bg-theme-light px-3 py-1 hover:bg-primary hover:text-white dark:bg-darkmode-theme-light dark:hover:bg-darkmode-primary dark:hover:text-dark"
|
||||
href={`/tags/${slugify(tag)}`}
|
||||
>
|
||||
{humanize(tag)}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex items-center lg:col-4">
|
||||
<h5 class="mr-3">Share :</h5>
|
||||
<Share
|
||||
className="social-icons"
|
||||
title={title}
|
||||
description={description}
|
||||
slug={post.slug}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-20">
|
||||
<Disqus client:load />
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- Related posts -->
|
||||
<div class="section pb-0">
|
||||
<h2 class="h3 mb-12 text-center">Related Posts</h2>
|
||||
<div class="row justify-center">
|
||||
{
|
||||
similarPosts.map((post) => (
|
||||
<div class="lg:col-4">
|
||||
<BlogCard data={post} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,181 @@
|
||||
import config from "@config/config.json";
|
||||
import { humanize, plainify, slugify } from "@lib/utils/textConverter";
|
||||
import Fuse from "fuse.js";
|
||||
import { 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 Search = ({ searchList }: Props) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [inputVal, setInputVal] = useState("");
|
||||
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
|
||||
|
||||
const handleChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
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 (
|
||||
<section className="section-sm">
|
||||
<div className="container">
|
||||
<div className="row mb-10 justify-center">
|
||||
<div className="lg:col-8">
|
||||
<div className="flex flex-nowrap">
|
||||
<input
|
||||
className="form-input rounded-r-none"
|
||||
placeholder="Search posts"
|
||||
type="search"
|
||||
name="search"
|
||||
value={inputVal}
|
||||
onChange={handleChange}
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
ref={inputRef}
|
||||
/>
|
||||
<button className="btn btn-primary rounded-l-none" type="submit">
|
||||
<FaSearch />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* {inputVal.length > 1 && (
|
||||
<div className="mt-8">
|
||||
Found {searchResults?.length}
|
||||
{searchResults?.length && searchResults?.length === 1
|
||||
? " result"
|
||||
: " results"}{" "}
|
||||
for '{inputVal}'
|
||||
</div>
|
||||
)} */}
|
||||
<div className="row">
|
||||
{searchResults?.length < 1 ? (
|
||||
<div className="mx-auto text-center">
|
||||
<img
|
||||
className="mx-auto mb-6"
|
||||
src="images/no-search-found.png"
|
||||
alt="no-search-found"
|
||||
/>
|
||||
<h1 className="h2 mb-4">
|
||||
{inputVal.length < 1 ? "Search Post Here" : "No Search Found!"}
|
||||
</h1>
|
||||
<p>
|
||||
{inputVal.length < 1
|
||||
? "Search for posts by title, category, or tag."
|
||||
: "We couldn't find what you searched for. Try searching again."}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
searchResults?.map(({ item }, index) => (
|
||||
<div className="mb-12 md:col-6 lg:col-4" key={`search-${index}`}>
|
||||
<div className="bg-body dark:bg-darkmode-body">
|
||||
{item.data.image && (
|
||||
<img
|
||||
className="mb-6 w-full rounded"
|
||||
src={item.data.image}
|
||||
alt={item.data.title}
|
||||
width={445}
|
||||
height={230}
|
||||
/>
|
||||
)}
|
||||
<h4 className="mb-3">
|
||||
<a href={`/${blog_folder}/${item.data.slug}`}>
|
||||
{item.data.title}
|
||||
</a>
|
||||
</h4>
|
||||
<ul className="mb-4">
|
||||
<li className="mr-4 inline-block">
|
||||
<a href={`/authors/${slugify(item.data.author)}`}>
|
||||
<FaRegUserCircle
|
||||
className={"-mt-1 mr-2 inline-block"}
|
||||
/>
|
||||
{humanize(item.data.author)}
|
||||
</a>
|
||||
</li>
|
||||
<li className="mr-4 inline-block">
|
||||
<FaRegFolder className={"-mt-1 mr-2 inline-block"} />
|
||||
{item.data.categories.map(
|
||||
(category: string, index: number) => (
|
||||
<a
|
||||
href={`/categories/${slugify(category)}`}
|
||||
key={category}
|
||||
>
|
||||
{humanize(category)}
|
||||
{index !== item.data.categories.length - 1 && ", "}
|
||||
</a>
|
||||
)
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
<p className="mb-6">
|
||||
{plainify(item.content?.slice(0, Number(summary_length)))}
|
||||
</p>
|
||||
<a
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
href={`/${blog_folder}/${item.data.slug}`}
|
||||
>
|
||||
read more
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Search;
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
import { Image } from "@astrojs/image/components";
|
||||
import { plainify } from "@lib/utils/textConverter";
|
||||
import Social from "./Social.astro";
|
||||
|
||||
const { data } = Astro.props;
|
||||
const { title, image, social } = data.data;
|
||||
---
|
||||
|
||||
<div
|
||||
class="rounded bg-theme-light p-8 text-center dark:bg-darkmode-theme-light"
|
||||
>
|
||||
{
|
||||
image && (
|
||||
<Image
|
||||
class="mx-auto mb-6 rounded"
|
||||
src={image}
|
||||
alt={title}
|
||||
width={120}
|
||||
height={120}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<h4 class="mb-3">
|
||||
<a href={`/authors/${data.slug}`}>{title}</a>
|
||||
</h4>
|
||||
<p class="mb-4">
|
||||
{plainify(data.body?.slice(0, 100))}
|
||||
</p>
|
||||
<Social source={social} className="social-icons" />
|
||||
</div>
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
import { Image } from "@astrojs/image/components";
|
||||
import config from "@config/config.json";
|
||||
import { humanize, plainify, slugify } from "@lib/utils/textConverter";
|
||||
import { FaRegFolder, FaRegUserCircle } from "react-icons/fa/index.js";
|
||||
|
||||
const { summary_length, blog_folder } = config.settings;
|
||||
const { data } = Astro.props;
|
||||
const { title, image, date, author, categories } = data.data;
|
||||
---
|
||||
|
||||
<div class="bg-body dark:bg-darkmode-body">
|
||||
{
|
||||
image && (
|
||||
<Image
|
||||
class="mb-6 w-full rounded"
|
||||
src={image}
|
||||
alt={title}
|
||||
width={445}
|
||||
height={230}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<h4 class="mb-3">
|
||||
<a href={`/${blog_folder}/${data.slug}`}>
|
||||
{title}
|
||||
</a>
|
||||
</h4>
|
||||
<ul class="mb-4">
|
||||
<li class="mr-4 inline-block">
|
||||
<a href={`/authors/${slugify(author)}`}>
|
||||
<FaRegUserCircle className={"mr-2 -mt-1 inline-block"} />
|
||||
{humanize(author)}
|
||||
</a>
|
||||
</li>
|
||||
<li class="mr-4 inline-block">
|
||||
<FaRegFolder className={"mr-2 -mt-1 inline-block"} />
|
||||
{
|
||||
categories.map((category: string, index: number) => (
|
||||
<a href={`/categories/${slugify(category)}`}>
|
||||
{humanize(category)}
|
||||
{index !== categories.length - 1 && ","}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mb-6">{plainify(data.body?.slice(0, Number(summary_length)))}</p>
|
||||
<a
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
href={`/${blog_folder}/${data.slug}`}
|
||||
>
|
||||
read more
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
import { humanize } from "@lib/utils/textConverter";
|
||||
|
||||
const { className } = Astro.props;
|
||||
|
||||
const paths = Astro.url.pathname.split("/").filter((x) => x);
|
||||
let parts = [
|
||||
{
|
||||
label: "Home",
|
||||
href: "/",
|
||||
"aria-label": Astro.url.pathname === "/" ? "page" : undefined,
|
||||
},
|
||||
];
|
||||
|
||||
paths.forEach((label: string, i: number) => {
|
||||
const href = `/${paths.slice(0, i + 1).join("/")}`;
|
||||
label !== "page" &&
|
||||
parts.push({
|
||||
label: humanize(label.replace(/[-_]/g, " ")) || "",
|
||||
href,
|
||||
"aria-label": Astro.url.pathname === href ? "page" : undefined,
|
||||
});
|
||||
});
|
||||
---
|
||||
|
||||
<nav aria-label="Breadcrumb" class={className}>
|
||||
<ol class="inline-flex" role="list">
|
||||
{
|
||||
parts.map(({ label, ...attrs }, index) => (
|
||||
<li class="mx-1 capitalize" role="listitem">
|
||||
{index > 0 && <span class="inlin-block mr-1">/</span>}
|
||||
{index !== parts.length - 1 ? (
|
||||
<a class="text-primary dark:text-darkmode-primary" {...attrs}>
|
||||
{label}
|
||||
</a>
|
||||
) : (
|
||||
<span class="text-light dark:text-darkmode-light">{label}</span>
|
||||
)}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
</nav>
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
import { Image } from "@astrojs/image/components";
|
||||
import config from "@config/config.json";
|
||||
|
||||
const { src, srcDarkmode } = Astro.props;
|
||||
const {
|
||||
logo,
|
||||
logo_darkmode,
|
||||
logo_width,
|
||||
logo_height,
|
||||
logo_text,
|
||||
title,
|
||||
}: {
|
||||
logo: string;
|
||||
logo_darkmode: string;
|
||||
logo_width: any;
|
||||
logo_height: any;
|
||||
logo_text: string;
|
||||
title: string;
|
||||
} = config.site;
|
||||
|
||||
const { theme_switcher }: { theme_switcher: boolean } = config.settings;
|
||||
---
|
||||
|
||||
<a href="/" class="navbar-brand inline-block">
|
||||
{
|
||||
src || srcDarkmode || logo || logo_darkmode ? (
|
||||
<>
|
||||
<Image
|
||||
src={src ? src : logo}
|
||||
class={`${theme_switcher && "dark:hidden"}`}
|
||||
width={logo_width.replace("px", "") * 2}
|
||||
height={logo_height.replace("px", "") * 2}
|
||||
alt={title}
|
||||
fit={"contain"}
|
||||
background={"rgba(0,0,0,0)"}
|
||||
style={{
|
||||
height: logo_height.replace("px", "") + "px",
|
||||
width: logo_width.replace("px", "") + "px",
|
||||
}}
|
||||
/>
|
||||
{theme_switcher && (
|
||||
<Image
|
||||
src={srcDarkmode ? srcDarkmode : logo_darkmode}
|
||||
class={"hidden dark:inline-block"}
|
||||
width={logo_width.replace("px", "") * 2}
|
||||
height={logo_height.replace("px", "") * 2}
|
||||
alt={title}
|
||||
fit={"contain"}
|
||||
background={"rgba(0,0,0,0)"}
|
||||
style={{
|
||||
height: logo_height.replace("px", "") + "px",
|
||||
width: logo_width.replace("px", "") + "px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : logo_text ? (
|
||||
logo_text
|
||||
) : (
|
||||
title
|
||||
)
|
||||
}
|
||||
</a>
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
const { section, currentPage, totalPages } = Astro.props;
|
||||
|
||||
const indexPageLink = currentPage === 2;
|
||||
const hasPrevPage = currentPage > 1;
|
||||
const hasNextPage = totalPages > currentPage;
|
||||
|
||||
let pageList = [];
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
pageList.push(i);
|
||||
}
|
||||
---
|
||||
|
||||
{
|
||||
totalPages > 1 && (
|
||||
<nav
|
||||
class="flex items-center justify-center space-x-3"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
{/* previous */}
|
||||
{hasPrevPage ? (
|
||||
<a
|
||||
href={
|
||||
indexPageLink
|
||||
? `${section ? "/" + section : "/"}`
|
||||
: `${section ? "/" + section : ""}/page/${currentPage - 1}`
|
||||
}
|
||||
class="rounded px-2 py-1.5 text-dark hover:bg-theme-light dark:text-darkmode-dark dark:hover:bg-darkmode-theme-light"
|
||||
>
|
||||
<span class="sr-only">Previous</span>
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
height="30"
|
||||
width="30"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
) : (
|
||||
<span class="rounded px-2 py-1.5 text-light">
|
||||
<span class="sr-only">Previous</span>
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
height="30"
|
||||
width="30"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* page index */}
|
||||
{pageList.map((pagination, i) =>
|
||||
pagination === currentPage ? (
|
||||
<span
|
||||
aria-current="page"
|
||||
class="rounded bg-primary px-4 py-2 text-white dark:bg-darkmode-primary dark:text-dark"
|
||||
>
|
||||
{pagination}
|
||||
</span>
|
||||
) : (
|
||||
<a
|
||||
href={
|
||||
i === 0
|
||||
? `${section ? "/" + section : "/"}`
|
||||
: `${section ? "/" + section : ""}/page/${pagination}`
|
||||
}
|
||||
aria-current="page"
|
||||
class="rounded px-4 py-2 text-dark hover:bg-theme-light dark:text-darkmode-dark dark:hover:bg-darkmode-theme-light"
|
||||
>
|
||||
{pagination}
|
||||
</a>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* next page */}
|
||||
{hasNextPage ? (
|
||||
<a
|
||||
href={`${section ? "/" + section : ""}/page/${currentPage + 1}`}
|
||||
class="rounded px-2 py-1.5 text-dark hover:bg-theme-light dark:text-darkmode-dark dark:hover:bg-darkmode-theme-light"
|
||||
>
|
||||
<span class="sr-only">Next</span>
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
height="30"
|
||||
width="30"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
) : (
|
||||
<span class="rounded px-2 py-1.5 text-light">
|
||||
<span class="sr-only">Next</span>
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
height="30"
|
||||
width="30"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
import config from "@config/config.json";
|
||||
import {
|
||||
IoLogoFacebook,
|
||||
IoLogoLinkedin,
|
||||
IoLogoPinterest,
|
||||
IoLogoTwitter,
|
||||
} from "react-icons/io5/index.js";
|
||||
|
||||
const { base_url } = config.site;
|
||||
const { title, description, slug, className } = Astro.props;
|
||||
---
|
||||
|
||||
<ul class={`${className}`}>
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="facebook share button"
|
||||
href={`https://facebook.com/sharer/sharer.php?u=${base_url}/${slug}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<IoLogoFacebook />
|
||||
</a>
|
||||
</li>
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="twitter share button"
|
||||
href={`https://twitter.com/intent/tweet/?text=${title}&url=${base_url}/${slug}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<IoLogoTwitter />
|
||||
</a>
|
||||
</li>
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="linkedin share button"
|
||||
href={`https://www.linkedin.com/shareArticle?mini=true&url=${base_url}/${slug}&title=${title}&summary=${description}&source=${base_url}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<IoLogoLinkedin />
|
||||
</a>
|
||||
</li>
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="pinterest share button"
|
||||
href={`https://pinterest.com/pin/create/button/?url=${base_url}/${slug}&media=&description=${description}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<IoLogoPinterest />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -0,0 +1,498 @@
|
||||
---
|
||||
const { source, className } = Astro.props;
|
||||
|
||||
import {
|
||||
IoCall,
|
||||
IoGlobeOutline,
|
||||
IoLocation,
|
||||
IoLogoBehance,
|
||||
IoLogoBitbucket,
|
||||
IoLogoCodepen,
|
||||
IoLogoDiscord,
|
||||
IoLogoDribbble,
|
||||
IoLogoFacebook,
|
||||
IoLogoFoursquare,
|
||||
IoLogoGithub,
|
||||
IoLogoGitlab,
|
||||
IoLogoInstagram,
|
||||
IoLogoLinkedin,
|
||||
IoLogoMastodon,
|
||||
IoLogoMedium,
|
||||
IoLogoPinterest,
|
||||
IoLogoReddit,
|
||||
IoLogoRss,
|
||||
IoLogoSkype,
|
||||
IoLogoSlack,
|
||||
IoLogoSnapchat,
|
||||
IoLogoSoundcloud,
|
||||
IoLogoTiktok,
|
||||
IoLogoTumblr,
|
||||
IoLogoTwitter,
|
||||
IoLogoVimeo,
|
||||
IoLogoVk,
|
||||
IoLogoWhatsapp,
|
||||
IoLogoYoutube,
|
||||
IoMail,
|
||||
} from "react-icons/io5/index.js";
|
||||
|
||||
const {
|
||||
facebook,
|
||||
twitter,
|
||||
mastodon,
|
||||
instagram,
|
||||
youtube,
|
||||
linkedin,
|
||||
github,
|
||||
gitlab,
|
||||
discord,
|
||||
slack,
|
||||
medium,
|
||||
codepen,
|
||||
bitbucket,
|
||||
dribbble,
|
||||
behance,
|
||||
pinterest,
|
||||
soundcloud,
|
||||
tumblr,
|
||||
reddit,
|
||||
vk,
|
||||
whatsapp,
|
||||
snapchat,
|
||||
vimeo,
|
||||
tiktok,
|
||||
foursquare,
|
||||
rss,
|
||||
email,
|
||||
phone,
|
||||
address,
|
||||
skype,
|
||||
website,
|
||||
} = source;
|
||||
---
|
||||
|
||||
<ul class={className}>
|
||||
{
|
||||
facebook && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="facebook"
|
||||
href={facebook}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoFacebook />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
twitter && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="twitter"
|
||||
href={twitter}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoTwitter />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
mastodon && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="mastodon"
|
||||
href={mastodon}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoMastodon />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
instagram && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="instagram"
|
||||
href={instagram}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoInstagram />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
youtube && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="youtube"
|
||||
href={youtube}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoYoutube />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
linkedin && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="linkedin"
|
||||
href={linkedin}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoLinkedin />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
github && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="github"
|
||||
href={github}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoGithub />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
gitlab && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="gitlab"
|
||||
href={gitlab}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoGitlab />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
discord && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="discord"
|
||||
href={discord}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoDiscord />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
slack && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="slack"
|
||||
href={slack}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoSlack />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
medium && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="medium"
|
||||
href={medium}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoMedium />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
codepen && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="codepen"
|
||||
href={codepen}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoCodepen />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
bitbucket && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="bitbucket"
|
||||
href={bitbucket}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoBitbucket />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
dribbble && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="dribbble"
|
||||
href={dribbble}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoDribbble />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
behance && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="behance"
|
||||
href={behance}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoBehance />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
pinterest && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="pinterest"
|
||||
href={pinterest}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoPinterest />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
soundcloud && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="soundcloud"
|
||||
href={soundcloud}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoSoundcloud />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
tumblr && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="tumblr"
|
||||
href={tumblr}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoTumblr />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
reddit && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="reddit"
|
||||
href={reddit}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoReddit />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
vk && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="vk"
|
||||
href={vk}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoVk />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
whatsapp && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="whatsapp"
|
||||
href={whatsapp}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoWhatsapp />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
snapchat && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="snapchat"
|
||||
href={snapchat}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoSnapchat />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
vimeo && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="vimeo"
|
||||
href={vimeo}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoVimeo />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
tiktok && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="tiktok"
|
||||
href={tiktok}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoTiktok />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
foursquare && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="foursquare"
|
||||
href={foursquare}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoFoursquare />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
skype && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="skype"
|
||||
href={skype}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoSkype />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
website && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="website"
|
||||
href={website}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoGlobeOutline />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
rss && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="rss feed"
|
||||
href={rss}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLogoRss />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
email && (
|
||||
<li class="inline-block">
|
||||
<a aria-label="email" href={`mailto:${email}`}>
|
||||
<IoMail />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
phone && (
|
||||
<li class="inline-block">
|
||||
<a aria-label="telephone" href={`tel:${phone}`}>
|
||||
<IoCall />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
{
|
||||
address && (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="location"
|
||||
href={address}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
>
|
||||
<IoLocation />
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
import config from "@config/config.json";
|
||||
|
||||
const { theme_switcher, default_theme } = config.settings;
|
||||
const {className} = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
theme_switcher && (
|
||||
<div class={`theme-switcher ${className}`}>
|
||||
<input id="theme-switcher" data-theme-switcher type="checkbox" />
|
||||
<label for="theme-switcher">
|
||||
<span>
|
||||
<!-- sun -->
|
||||
<svg
|
||||
class="absolute left-[4px] top-[4px] z-10 opacity-100 dark:opacity-0"
|
||||
viewBox="0 0 56 56"
|
||||
fill="#fff"
|
||||
height="16"
|
||||
width="16"
|
||||
>
|
||||
<path
|
||||
d="M30 4.6c0-1-.9-2-2-2a2 2 0 0 0-2 2v5c0 1 .9 2 2 2s2-1 2-2Zm9.6 9a2 2 0 0 0 0 2.8c.8.8 2 .8 2.9 0L46 13a2 2 0 0 0 0-2.9 2 2 0 0 0-3 0Zm-26 2.8c.7.8 2 .8 2.8 0 .8-.7.8-2 0-2.9L13 10c-.7-.7-2-.8-2.9 0-.7.8-.7 2.1 0 3ZM28 16a12 12 0 0 0-12 12 12 12 0 0 0 12 12 12 12 0 0 0 12-12 12 12 0 0 0-12-12Zm23.3 14c1.1 0 2-.9 2-2s-.9-2-2-2h-4.9a2 2 0 0 0-2 2c0 1.1 1 2 2 2ZM4.7 26a2 2 0 0 0-2 2c0 1.1.9 2 2 2h4.9c1 0 2-.9 2-2s-1-2-2-2Zm37.8 13.6a2 2 0 0 0-3 0 2 2 0 0 0 0 2.9l3.6 3.5a2 2 0 0 0 2.9 0c.8-.8.8-2.1 0-3ZM10 43.1a2 2 0 0 0 0 2.9c.8.7 2.1.8 3 0l3.4-3.5c.8-.8.8-2.1 0-2.9-.8-.8-2-.8-2.9 0Zm20 3.4c0-1.1-.9-2-2-2a2 2 0 0 0-2 2v4.9c0 1 .9 2 2 2s2-1 2-2Z"
|
||||
></path>
|
||||
</svg>
|
||||
<!-- moon -->
|
||||
<svg
|
||||
class="absolute left-[4px] top-[4px] z-10 opacity-0 dark:opacity-100"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
height="16"
|
||||
width="16"
|
||||
>
|
||||
<path
|
||||
fill="#000"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.2 2.2c1-.4 2 .6 1.6 1.5-1 3-.4 6.4 1.8 8.7a8.4 8.4 0 0 0 8.7 1.8c1-.3 2 .5 1.5 1.5v.1a10.3 10.3 0 0 1-9.4 6.2A10.3 10.3 0 0 1 3.2 6.7c1-2 2.9-3.5 4.9-4.4Z"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<script is:inline define:vars={{ defaultTheme: default_theme }}>
|
||||
var darkMode = defaultTheme === "dark" ? true : false;
|
||||
var themeSwitch = document.querySelectorAll("[data-theme-switcher]");
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
darkMode = true;
|
||||
}
|
||||
if (localStorage.getItem("theme") === "dark") {
|
||||
darkMode = true;
|
||||
} else if (localStorage.getItem("theme") === "light") {
|
||||
darkMode = false;
|
||||
}
|
||||
if (darkMode) {
|
||||
document.documentElement.classList.toggle("dark");
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
[].forEach.call(themeSwitch, function (ts) {
|
||||
ts.checked = darkMode ? true : false;
|
||||
ts.addEventListener("click", () => {
|
||||
document.documentElement.classList.toggle("dark");
|
||||
localStorage.setItem(
|
||||
"theme",
|
||||
document.documentElement.classList.contains("dark") ? "dark" : "light"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
---
|
||||
|
||||
{
|
||||
process.env.NODE_ENV === "development" && (
|
||||
<div class="fixed top-0 left-0 z-50 flex w-[30px] items-center justify-center bg-gray-200 py-[2.5px] text-[12px] uppercase text-black sm:bg-red-200 md:bg-yellow-200 lg:bg-green-200 xl:bg-blue-200 2xl:bg-pink-200">
|
||||
<span class="block sm:hidden">all</span>
|
||||
<span class="hidden sm:block md:hidden">sm</span>
|
||||
<span class="hidden md:block lg:hidden">md</span>
|
||||
<span class="hidden lg:block xl:hidden">lg</span>
|
||||
<span class="hidden xl:block 2xl:hidden">xl</span>
|
||||
<span class="hidden 2xl:block">2xl</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import config from "@config/config.json";
|
||||
import { DiscussionEmbed } from "disqus-react";
|
||||
import React from "react";
|
||||
|
||||
const Disqus = () => {
|
||||
const { disqus } = config;
|
||||
return (
|
||||
<>
|
||||
{disqus.enable && (
|
||||
<DiscussionEmbed
|
||||
shortname={disqus.shortname}
|
||||
config={disqus.settings}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Disqus;
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
import { Image } from "@astrojs/image/components";
|
||||
import { markdownify } from "@lib/utils/textConverter";
|
||||
const { call_to_action } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
call_to_action.data.enable && (
|
||||
<section class="mb-28">
|
||||
<div class="container">
|
||||
<div class="rounded-xl bg-theme-light px-4 py-16 dark:bg-darkmode-theme-light xl:p-20">
|
||||
<div class="row items-center justify-between">
|
||||
<div class="mb-10 md:col-5 lg:col-4 md:order-2 md:mb-0">
|
||||
<Image
|
||||
class="w-full"
|
||||
src={call_to_action.data.image}
|
||||
width={392}
|
||||
height={390}
|
||||
alt="cta-image"
|
||||
/>
|
||||
</div>
|
||||
<div class="md:col-7 md:order-1">
|
||||
<h2
|
||||
set:html={markdownify(call_to_action.data.title)}
|
||||
class="mb-2"
|
||||
/>
|
||||
<p
|
||||
set:html={markdownify(call_to_action.data.description)}
|
||||
class="mb-6"
|
||||
/>
|
||||
{call_to_action.data.button.enable && (
|
||||
<a
|
||||
class="btn btn-primary"
|
||||
href={call_to_action.data.button.link}
|
||||
>
|
||||
{call_to_action.data.button.label}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
import Logo from "@components/Logo.astro";
|
||||
import Social from "@components/Social.astro";
|
||||
import config from "@config/config.json";
|
||||
import menu from "@config/menu.json";
|
||||
import social from "@config/social.json";
|
||||
import { markdownify } from "@lib/utils/textConverter";
|
||||
---
|
||||
|
||||
<footer class="bg-theme-light dark:bg-darkmode-theme-light">
|
||||
<div class="container">
|
||||
<div class="row items-center py-10">
|
||||
<div class="mb-8 text-center lg:col-3 lg:mb-0 lg:text-left">
|
||||
<Logo />
|
||||
</div>
|
||||
<div class="mb-8 text-center lg:col-6 lg:mb-0">
|
||||
<ul>
|
||||
{
|
||||
menu.footer.map((menu) => (
|
||||
<li class="m-3 inline-block">
|
||||
<a href={menu.url}>{menu.name}</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mb-8 text-center lg:col-3 lg:mb-0 lg:mt-0 lg:text-right">
|
||||
<Social source={social} className="social-icons" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-border py-7 dark:border-darkmode-border">
|
||||
<div class="container text-center text-light dark:text-darkmode-light">
|
||||
<p set:html={markdownify(config.params.copyright)} />
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
import Logo from "@components/Logo.astro";
|
||||
import ThemeSwitcher from "@components/ThemeSwitcher.astro";
|
||||
import config from "@config/config.json";
|
||||
import menu from "@config/menu.json";
|
||||
import { IoSearch } from "react-icons/io5/index.js";
|
||||
|
||||
export interface ChildNavigationLink {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface NavigationLink {
|
||||
name: string;
|
||||
url: string;
|
||||
hasChildren?: boolean;
|
||||
children?: ChildNavigationLink[];
|
||||
}
|
||||
|
||||
const { main }: { main: NavigationLink[] } = menu;
|
||||
const { navigation_button, settings } = config;
|
||||
const { pathname } = Astro.url;
|
||||
---
|
||||
|
||||
<header class={`header z-30 ${settings.sticky_header && "sticky top-0"}`}>
|
||||
<nav class="navbar container">
|
||||
<!-- logo -->
|
||||
<div class="order-0">
|
||||
<Logo />
|
||||
</div>
|
||||
<!-- navbar toggler -->
|
||||
<input id="nav-toggle" type="checkbox" class="hidden" />
|
||||
<label
|
||||
id="show-button"
|
||||
for="nav-toggle"
|
||||
class="order-3 flex cursor-pointer items-center text-dark dark:text-white lg:order-1 lg:hidden"
|
||||
>
|
||||
<svg class="h-6 fill-current" viewBox="0 0 20 20">
|
||||
<title>Menu Open</title>
|
||||
<path d="M0 3h20v2H0V3z m0 6h20v2H0V9z m0 6h20v2H0V0z"></path>
|
||||
</svg>
|
||||
</label>
|
||||
<label
|
||||
id="hide-button"
|
||||
for="nav-toggle"
|
||||
class="order-3 hidden cursor-pointer items-center text-dark dark:text-white lg:order-1"
|
||||
>
|
||||
<svg class="h-6 fill-current" viewBox="0 0 20 20">
|
||||
<title>Menu Close</title>
|
||||
<polygon
|
||||
points="11 9 22 9 22 11 11 11 11 22 9 22 9 11 -2 11 -2 9 9 9 9 -2 11 -2"
|
||||
transform="rotate(45 10 10)"></polygon>
|
||||
</svg>
|
||||
</label>
|
||||
<!-- /navbar toggler -->
|
||||
<ul
|
||||
id="nav-menu"
|
||||
class="navbar-nav order-3 hidden w-full pb-6 lg:order-1 lg:flex lg:w-auto lg:space-x-2 lg:pb-0 xl:space-x-8"
|
||||
>
|
||||
{
|
||||
main.map((menu) => (
|
||||
<>
|
||||
{menu.hasChildren ? (
|
||||
<li class="nav-item nav-dropdown group relative">
|
||||
<span
|
||||
class={`nav-link inline-flex items-center ${
|
||||
menu.children?.map(({ url }) => url).includes(pathname) ||
|
||||
menu.children
|
||||
?.map(({ url }) => `${url}/`)
|
||||
.includes(pathname)
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{menu.name}
|
||||
<svg class="h-4 w-4 fill-current" viewBox="0 0 20 20">
|
||||
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" />
|
||||
</svg>
|
||||
</span>
|
||||
<ul class="nav-dropdown-list hidden group-hover:block md:invisible md:absolute md:block md:opacity-0 md:group-hover:visible md:group-hover:opacity-100">
|
||||
{menu.children?.map((child) => (
|
||||
<li class="nav-dropdown-item">
|
||||
<a
|
||||
href={child.url}
|
||||
class={`nav-dropdown-link block ${
|
||||
(pathname === `${child.url}/` ||
|
||||
pathname === child.url) &&
|
||||
"active"
|
||||
}`}
|
||||
>
|
||||
{child.name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
) : (
|
||||
<li class="nav-item">
|
||||
<a
|
||||
href={menu.url}
|
||||
class={`nav-link block ${
|
||||
(pathname === `${menu.url}/` || pathname === menu.url) &&
|
||||
"active"
|
||||
}`}
|
||||
>
|
||||
{menu.name}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
</>
|
||||
))
|
||||
}
|
||||
{
|
||||
navigation_button.enable && (
|
||||
<li class="mt-4 inline-block lg:hidden">
|
||||
<a
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
href={navigation_button.link}
|
||||
>
|
||||
{navigation_button.label}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
<div class="order-1 ml-auto flex items-center md:order-2 lg:ml-0">
|
||||
{
|
||||
settings.search && (
|
||||
<a
|
||||
class="mr-5 inline-block border-r border-border pr-5 text-xl text-dark hover:text-primary dark:border-darkmode-border dark:text-white"
|
||||
href="search"
|
||||
>
|
||||
<IoSearch />
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<ThemeSwitcher className="mr-5" />
|
||||
{
|
||||
navigation_button.enable && (
|
||||
<a
|
||||
class="btn btn-outline-primary btn-sm hidden lg:inline-block"
|
||||
href={navigation_button.link}
|
||||
>
|
||||
{navigation_button.label}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
import Breadcrumbs from "@components/Breadcrumbs.astro";
|
||||
import { humanize } from "@lib/utils/textConverter";
|
||||
|
||||
const { title } = Astro.props;
|
||||
---
|
||||
|
||||
<section>
|
||||
<div class="container text-center">
|
||||
<div
|
||||
class="rounded-2xl bg-gradient-to-b from-body to-theme-light px-8 py-14 dark:from-darkmode-body dark:to-darkmode-theme-light"
|
||||
>
|
||||
<h1 set:text={humanize(title)} />
|
||||
<Breadcrumbs className="mt-6" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
import { humanize } from "@lib/utils/textConverter";
|
||||
|
||||
const { tags, categories, allCategories } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="lg:col-4">
|
||||
<!-- categories -->
|
||||
<div class="mb-8">
|
||||
<h5 class="mb-6">Categories</h5>
|
||||
<div class="rounded bg-theme-light p-8 dark:bg-darkmode-theme-light">
|
||||
<ul class="space-y-4">
|
||||
{
|
||||
categories.map((category: any) => {
|
||||
const count = allCategories.filter(
|
||||
(c: any) => c === category
|
||||
).length;
|
||||
return (
|
||||
<li>
|
||||
<a
|
||||
class="flex justify-between hover:text-primary dark:hover:text-darkmode-primary"
|
||||
href={`/categories/${category}`}
|
||||
>
|
||||
{humanize(category)} <span>({count})</span>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- tags -->
|
||||
<div class="mb-8">
|
||||
<h5 class="mb-6">Tags</h5>
|
||||
<div class="rounded bg-theme-light p-6 dark:bg-darkmode-theme-light">
|
||||
<ul>
|
||||
{
|
||||
tags.map((tag: any) => {
|
||||
return (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
class="m-1 block rounded bg-white px-3 py-1 hover:bg-primary hover:text-white dark:bg-darkmode-body dark:hover:bg-darkmode-primary dark:hover:text-dark"
|
||||
href={`/tags/${tag}`}
|
||||
>
|
||||
{humanize(tag)}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
import { Image } from "@astrojs/image/components";
|
||||
import { markdownify } from "@lib/utils/textConverter";
|
||||
const { testimonial } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
testimonial.data.enable && (
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="mx-auto mb-12 text-center md:col-10 lg:col-8 xl:col-6">
|
||||
<h2 set:html={markdownify(testimonial.data.title)} class="mb-4" />
|
||||
<p set:html={markdownify(testimonial.data.description)} />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="swiper testimonial-slider">
|
||||
<div class="swiper-wrapper">
|
||||
{testimonial.data.testimonials.map(
|
||||
(item: {
|
||||
avatar: string;
|
||||
content: string;
|
||||
name: string;
|
||||
designation: string;
|
||||
}) => (
|
||||
<div class="swiper-slide">
|
||||
<div class="rounded-lg bg-theme-light px-7 py-10 dark:bg-darkmode-theme-light">
|
||||
<div class="text-dark dark:text-white">
|
||||
<svg
|
||||
width="33"
|
||||
height="20"
|
||||
viewBox="0 0 33 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1.28375 19.41L0.79375 18.64C1.21375 17.0067 1.75042 15.07 2.40375 12.83C3.05708 10.5433 3.75708 8.28 4.50375 6.04C5.29708 3.75333 6.06708 1.77 6.81375 0.0899959H15.3538C14.9338 2.09666 14.4904 4.26667 14.0238 6.6C13.5571 8.88666 13.1371 11.15 12.7638 13.39C12.4371 15.5833 12.1571 17.59 11.9238 19.41H1.28375ZM31.69 0.0899959L32.18 0.859998C31.76 2.54 31.2233 4.5 30.57 6.74C29.9167 8.98 29.2167 11.2433 28.47 13.53C27.7233 15.77 26.9533 17.73 26.16 19.41H17.69C18.0167 17.9167 18.3433 16.33 18.67 14.65C18.9967 12.9233 19.3 11.22 19.58 9.54C19.9067 7.81333 20.1867 6.15667 20.42 4.57C20.7 2.93666 20.91 1.44333 21.05 0.0899959H31.69Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<blockquote class="mt-8">
|
||||
{markdownify(item.content)}
|
||||
</blockquote>
|
||||
<div class="mt-11 flex items-center">
|
||||
<div class="text-dark dark:text-white">
|
||||
<Image
|
||||
height={50}
|
||||
width={50}
|
||||
class="rounded-full border border-primary dark:border-darkmode-primary"
|
||||
src={item.avatar}
|
||||
alt={item.name}
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<h3
|
||||
set:text={item.name}
|
||||
class="h5 font-primary font-semibold"
|
||||
/>
|
||||
<p
|
||||
set:text={item.designation}
|
||||
class="text-dark dark:text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div class="testimonial-slider-pagination mt-9 flex items-center justify-center text-center" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
<script>
|
||||
import Swiper, { Autoplay, Pagination } from "swiper";
|
||||
import "swiper/css";
|
||||
import "swiper/css/pagination";
|
||||
|
||||
new Swiper(".testimonial-slider", {
|
||||
modules: [Pagination, Autoplay],
|
||||
spaceBetween: 24,
|
||||
loop: true,
|
||||
pagination: {
|
||||
el: ".testimonial-slider-pagination",
|
||||
type: "bullets",
|
||||
clickable: true,
|
||||
},
|
||||
breakpoints: {
|
||||
768: {
|
||||
slidesPerView: 2,
|
||||
},
|
||||
992: {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useState } from "react";
|
||||
|
||||
const Accordion = ({
|
||||
title,
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
title: string;
|
||||
children: any;
|
||||
className: string | null;
|
||||
}) => {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={`accordion ${show && "active"} ${className}`}>
|
||||
<div className="accordion-header" onClick={() => setShow(!show)}>
|
||||
{title}
|
||||
<svg
|
||||
className="accordion-icon"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 512 512"
|
||||
xmlSpace="preserve"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M505.755,123.592c-8.341-8.341-21.824-8.341-30.165,0L256.005,343.176L36.421,123.592c-8.341-8.341-21.824-8.341-30.165,0 s-8.341,21.824,0,30.165l234.667,234.667c4.16,4.16,9.621,6.251,15.083,6.251c5.462,0,10.923-2.091,15.083-6.251l234.667-234.667 C514.096,145.416,514.096,131.933,505.755,123.592z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="accordion-content">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Accordion;
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
|
||||
const Button = ({
|
||||
href,
|
||||
style,
|
||||
rel,
|
||||
children,
|
||||
}: {
|
||||
href: string;
|
||||
style: string | null;
|
||||
rel: string | null;
|
||||
children: any;
|
||||
}) => {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel={`noopener noreferrer ${
|
||||
rel ? (rel === "follow" ? "" : rel) : "nofollow"
|
||||
}`}
|
||||
className={`btn mb-4 me-4 ${
|
||||
style === "outline" ? "btn-outline-primary" : "btn-primary"
|
||||
} border-primary hover:text-white hover:no-underline`}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
@@ -0,0 +1,78 @@
|
||||
import { humanize } from "@lib/utils/textConverter";
|
||||
|
||||
function Notice({ type, children }: { type: string; children: any }) {
|
||||
return (
|
||||
<div className={`notice ${type}`}>
|
||||
<div className="notice-head">
|
||||
{type === "tip" ? (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12 0C18.6274 0 24 5.37258 24 12C24 18.6274 18.6274 24 12 24C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0ZM12 2.4C6.69807 2.4 2.4 6.69807 2.4 12C2.4 17.3019 6.69807 21.6 12 21.6C17.3019 21.6 21.6 17.3019 21.6 12C21.6 6.69807 17.3019 2.4 12 2.4ZM15.9515 7.55147L9.6 13.9029L8.04853 12.3515C7.5799 11.8828 6.8201 11.8828 6.35147 12.3515C5.88284 12.8201 5.88284 13.5799 6.35147 14.0485L8.75147 16.4485C9.2201 16.9172 9.9799 16.9172 10.4485 16.4485L17.6485 9.24853C18.1172 8.7799 18.1172 8.0201 17.6485 7.55147C17.1799 7.08284 16.4201 7.08284 15.9515 7.55147Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
) : type === "info" ? (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 18 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.16109 0.993016C9.97971 1.03952 10.6611 1.42989 11.0721 2.22339L17.7981 15.8014C18.4502 17.1739 17.4403 19.0208 15.7832 19.0474H2.23859C0.730337 19.0234 -0.507163 17.3108 0.231587 15.7864L7.08321 2.20877C7.21146 1.96502 7.26996 1.89452 7.38059 1.76664C7.82534 1.25102 8.31171 0.975016 9.16109 0.993016ZM9.05046 2.49189C8.79284 2.50464 8.55696 2.64902 8.42834 2.87327C6.06134 7.36539 3.77946 11.9036 1.56546 16.4734C1.36071 16.9328 1.71209 17.5223 2.22621 17.547C6.74871 17.6201 11.2731 17.6201 15.7956 17.547C16.2925 17.523 16.666 16.953 16.459 16.4783C14.2866 11.9093 12.0471 7.37102 9.72171 2.87814C9.58446 2.63402 9.38309 2.48739 9.05046 2.49189Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M9.61323 13.2153H8.35773L8.21973 7.04688H9.75723L9.61323 13.2153ZM8.17773 15.1015C8.17773 14.8731 8.25161 14.6841 8.39973 14.5338C8.54823 14.3838 8.75036 14.3084 9.00648 14.3084C9.26298 14.3084 9.46511 14.3838 9.61323 14.5338C9.76136 14.6841 9.83561 14.8731 9.83561 15.1015C9.83561 15.3216 9.76323 15.5057 9.61923 15.6539C9.47486 15.802 9.27086 15.8762 9.00648 15.8762C8.74211 15.8762 8.53811 15.802 8.39373 15.6539C8.24973 15.5057 8.17773 15.3216 8.17773 15.1015Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
) : type === "warning" ? (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 0C15.522 0 20 4.478 20 10C20 15.522 15.522 20 10 20C4.478 20 0 15.522 0 10C0 4.478 4.478 0 10 0ZM10 2C5.589 2 2 5.589 2 10C2 14.411 5.589 18 10 18C14.411 18 18 14.411 18 10C18 5.589 14.411 2 10 2ZM12.293 6.293L13.707 7.707L11.414 10L13.707 12.293L12.293 13.707L10 11.414L7.707 13.707L6.293 12.293L8.586 10L6.293 7.707L7.707 6.293L10 8.586L12.293 6.293Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10 9V14M10 19C5.02944 19 1 14.9706 1 10C1 5.02944 5.02944 1 10 1C14.9706 1 19 5.02944 19 10C19 14.9706 14.9706 19 10 19ZM10.0498 6V6.1L9.9502 6.1002V6H10.0498Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
<p className="my-0 ml-1.5">{humanize(type)}</p>
|
||||
</div>
|
||||
<div className="notice-body">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Notice;
|
||||
@@ -0,0 +1,31 @@
|
||||
function Video({
|
||||
title,
|
||||
width = 500,
|
||||
height = "auto",
|
||||
src,
|
||||
...rest
|
||||
}: {
|
||||
title: string;
|
||||
width?: number;
|
||||
height?: string;
|
||||
src: string;
|
||||
rest: any;
|
||||
}) {
|
||||
return (
|
||||
<video
|
||||
className="overflow-hidden rounded-lg"
|
||||
width={width}
|
||||
height={height}
|
||||
controls
|
||||
{...rest}
|
||||
>
|
||||
<source
|
||||
src={src.match(/^http/) ? src : `/videos/${src}`}
|
||||
type="video/mp4"
|
||||
/>
|
||||
{title}
|
||||
</video>
|
||||
);
|
||||
}
|
||||
|
||||
export default Video;
|
||||
@@ -0,0 +1,23 @@
|
||||
import LiteYouTubeEmbed from "react-lite-youtube-embed";
|
||||
import "react-lite-youtube-embed/dist/LiteYouTubeEmbed.css";
|
||||
|
||||
const Youtube = ({
|
||||
id,
|
||||
title,
|
||||
...rest
|
||||
}: {
|
||||
id: string;
|
||||
title: string;
|
||||
rest: any;
|
||||
}) => {
|
||||
return (
|
||||
<LiteYouTubeEmbed
|
||||
wrapperClass="yt-lite rounded-lg"
|
||||
id={id}
|
||||
title={title}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Youtube;
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
import { getCollection } from "astro:content";
|
||||
|
||||
export const getSinglePage = async (collection: any) => {
|
||||
const allPage = await getCollection(collection);
|
||||
const removeIndex = allPage.filter((data) => data.id.match(/^(?!_)/));
|
||||
const removeDrafts = removeIndex.filter((data) => !data.data.draft);
|
||||
return removeDrafts;
|
||||
};
|
||||
---
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
import { getSinglePage } from "./contentParser.astro";
|
||||
import { slugify } from "./utils/textConverter";
|
||||
|
||||
// get all taxonomies from frontmatter
|
||||
export const getTaxonomy = async (collection: string, name: string) => {
|
||||
const singlePages = await getSinglePage(collection);
|
||||
const taxonomyPages = singlePages.map((page) => page.data[name]);
|
||||
let taxonomies = [];
|
||||
for (let i = 0; i < taxonomyPages.length; i++) {
|
||||
const categoryArray = taxonomyPages[i];
|
||||
for (let j = 0; j < categoryArray.length; j++) {
|
||||
taxonomies.push(slugify(categoryArray[j]));
|
||||
}
|
||||
}
|
||||
const taxonomy = [...new Set(taxonomies)];
|
||||
return taxonomy;
|
||||
};
|
||||
|
||||
export const getAllTaxonomy = async (collection: string, name: string) => {
|
||||
const singlePages = await getSinglePage(collection);
|
||||
const taxonomyPages = singlePages.map((page) => page.data[name]);
|
||||
let taxonomies = [];
|
||||
for (let i = 0; i < taxonomyPages.length; i++) {
|
||||
const categoryArray = taxonomyPages[i];
|
||||
for (let j = 0; j < categoryArray.length; j++) {
|
||||
taxonomies.push(slugify(categoryArray[j]));
|
||||
}
|
||||
}
|
||||
return taxonomies;
|
||||
};
|
||||
---
|
||||
@@ -0,0 +1,18 @@
|
||||
const dateFormat = (datetime: string | Date) => {
|
||||
const dateTime = new Date(datetime);
|
||||
|
||||
const date = dateTime.toLocaleDateString([], {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
const time = dateTime.toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
|
||||
return date;
|
||||
};
|
||||
|
||||
export default dateFormat;
|
||||
@@ -0,0 +1,40 @@
|
||||
// content reading
|
||||
const readingTime = (content: string) => {
|
||||
const WPS = 275 / 60;
|
||||
|
||||
let images = 0;
|
||||
const regex = /\w/;
|
||||
|
||||
let words = content.split(" ").filter((word) => {
|
||||
if (word.includes("<img")) {
|
||||
images += 1;
|
||||
}
|
||||
return regex.test(word);
|
||||
}).length;
|
||||
|
||||
let imageAdjust = images * 4;
|
||||
let imageSecs = 0;
|
||||
let imageFactor = 12;
|
||||
|
||||
while (images) {
|
||||
imageSecs += imageFactor;
|
||||
if (imageFactor > 3) {
|
||||
imageFactor -= 1;
|
||||
}
|
||||
images -= 1;
|
||||
}
|
||||
|
||||
const minutes = Math.ceil(((words - imageAdjust) / WPS + imageSecs) / 60);
|
||||
|
||||
if (minutes < 10) {
|
||||
if (minutes < 2) {
|
||||
return "0" + minutes + ` Min read`;
|
||||
} else {
|
||||
return "0" + minutes + ` Mins read`;
|
||||
}
|
||||
} else {
|
||||
return minutes + ` Mins read`;
|
||||
}
|
||||
};
|
||||
|
||||
export default readingTime;
|
||||
@@ -0,0 +1,36 @@
|
||||
// similer products
|
||||
const similerItems = (currentItem: any, allItems: any, slug: string) => {
|
||||
let categories: [] = [];
|
||||
let tags: [] = [];
|
||||
|
||||
// set categories
|
||||
if (currentItem.data.categories.length > 0) {
|
||||
categories = currentItem.data.categories;
|
||||
}
|
||||
|
||||
// set tags
|
||||
if (currentItem.data.tags.length > 0) {
|
||||
tags = currentItem.data.tags;
|
||||
}
|
||||
|
||||
// filter by categories
|
||||
const filterByCategories = allItems.filter(
|
||||
(item: { data: { categories: string } }) =>
|
||||
categories.find((category) => item.data.categories.includes(category))
|
||||
);
|
||||
|
||||
// filter by tags
|
||||
const filterByTags = allItems.filter((item: { data: { tags: string } }) =>
|
||||
tags.find((tag) => item.data.tags.includes(tag))
|
||||
);
|
||||
|
||||
// merged after filter
|
||||
const mergedItems = [...new Set([...filterByCategories, ...filterByTags])];
|
||||
|
||||
// filter by slug
|
||||
const filterBySlug = mergedItems.filter((product) => product.slug !== slug);
|
||||
|
||||
return filterBySlug;
|
||||
};
|
||||
|
||||
export default similerItems;
|
||||
@@ -0,0 +1,25 @@
|
||||
// sort by date
|
||||
export const sortByDate = (array: any[]) => {
|
||||
const sortedArray = array.sort(
|
||||
(a:any, b:any) =>
|
||||
new Date(b.data.date && b.data.date) -
|
||||
new Date(a.data.date && a.data.date)
|
||||
);
|
||||
return sortedArray;
|
||||
};
|
||||
|
||||
// sort product by weight
|
||||
export const sortByWeight = (array: any[]) => {
|
||||
const withWeight = array.filter(
|
||||
(item: { data: { weight: any } }) => item.data.weight
|
||||
);
|
||||
const withoutWeight = array.filter(
|
||||
(item: { data: { weight: any } }) => !item.data.weight
|
||||
);
|
||||
const sortedWeightedArray = withWeight.sort(
|
||||
(a: { data: { weight: number } }, b: { data: { weight: number } }) =>
|
||||
a.data.weight - b.data.weight
|
||||
);
|
||||
const sortedArray = [...new Set([...sortedWeightedArray, ...withoutWeight])];
|
||||
return sortedArray;
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { slugify } from "@lib/utils/textConverter";
|
||||
|
||||
const taxonomyFilter = (posts: any[], name: string, key: any) =>
|
||||
posts.filter((post) =>
|
||||
post.data[name].map((name: string) => slugify(name)).includes(key)
|
||||
);
|
||||
|
||||
export default taxonomyFilter;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { slug } from 'github-slugger';
|
||||
import { marked } from "marked";
|
||||
|
||||
// slugify
|
||||
export const slugify = (content: string) => {
|
||||
if (!content) return null;
|
||||
|
||||
return slug(content);
|
||||
};
|
||||
|
||||
// markdownify
|
||||
export const markdownify = (content: string) => {
|
||||
if (!content) return null;
|
||||
|
||||
return marked.parseInline(content);
|
||||
};
|
||||
|
||||
// humanize
|
||||
export const humanize = (content: string) => {
|
||||
if (!content) return null;
|
||||
|
||||
return content
|
||||
.replace(/^[\s_]+|[\s_]+$/g, "")
|
||||
.replace(/[_\s]+/g, " ")
|
||||
.replace(/^[a-z]/, function (m) {
|
||||
return m.toUpperCase();
|
||||
});
|
||||
};
|
||||
|
||||
// plainify
|
||||
export const plainify = (content: string) => {
|
||||
if (!content) return null;
|
||||
|
||||
const filterBrackets = content.replace(/<\/?[^>]+(>|$)/gm, "");
|
||||
const filterSpaces = filterBrackets.replace(/[\r\n]\s*[\r\n]/gm, "");
|
||||
const stripHTML = htmlEntityDecoder(filterSpaces);
|
||||
return stripHTML;
|
||||
};
|
||||
|
||||
// strip entities for plainify
|
||||
const htmlEntityDecoder = (htmlWithEntities: string): string => {
|
||||
let entityList: { [key: string]: string } = {
|
||||
" ": " ",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"&": "&",
|
||||
""": '"',
|
||||
"'": "'",
|
||||
};
|
||||
let htmlWithoutEntities: string = htmlWithEntities.replace(
|
||||
/(&|<|>|"|')/g,
|
||||
(entity: string): string => {
|
||||
return entityList[entity];
|
||||
}
|
||||
);
|
||||
return htmlWithoutEntities;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
import Base from "@layouts/Base.astro";
|
||||
import { markdownify } from "@lib/utils/textConverter";
|
||||
import { getEntryBySlug } from "astro:content";
|
||||
const entry = await getEntryBySlug("pages", "404");
|
||||
const { Content } = await entry.render();
|
||||
---
|
||||
|
||||
<Base title={entry.data.title}>
|
||||
<section class="section-sm">
|
||||
<div class="container">
|
||||
<div class="row justify-center">
|
||||
<div class="text-center sm:col-10 md:col-8 lg:col-6">
|
||||
<img class="mb-8 w-full" src="images/404.png" alt="page not found" />
|
||||
<h1 class="h2 mb-4" set:html={markdownify(entry.data.title)} />
|
||||
<Content />
|
||||
<a href="/" class="btn btn-primary mt-8">Back To Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
import Base from "@layouts/Base.astro";
|
||||
import { getSinglePage } from "@lib/contentParser.astro";
|
||||
import PageHeader from "@partials/PageHeader.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const pages = await getSinglePage("pages");
|
||||
|
||||
const paths = pages.map((page) => ({
|
||||
params: {
|
||||
regular: page.slug,
|
||||
},
|
||||
props: { page },
|
||||
}));
|
||||
return paths;
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
const { title, meta_title, description, image } = page.data;
|
||||
const { Content } = await page.render();
|
||||
---
|
||||
|
||||
<Base
|
||||
title={title}
|
||||
meta_title={meta_title}
|
||||
description={description}
|
||||
image={image}
|
||||
>
|
||||
<PageHeader title={title} />
|
||||
<section class="section-sm">
|
||||
<div class="container">
|
||||
<div class="row justify-center">
|
||||
<div class="lg:col-10">
|
||||
<div class="content">
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
import { Image } from "@astrojs/image/components";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import { markdownify } from "@lib/utils/textConverter";
|
||||
import { getEntryBySlug } from "astro:content";
|
||||
|
||||
const entry = await getEntryBySlug("pages", "about");
|
||||
const { Content } = await entry.render();
|
||||
const { title, description, meta_title, image } = entry.data;
|
||||
---
|
||||
|
||||
<Base
|
||||
title={title}
|
||||
meta_title={meta_title}
|
||||
description={description}
|
||||
image={image}
|
||||
>
|
||||
<section class="section-sm">
|
||||
<div class="container">
|
||||
<div class="row justify-center">
|
||||
<div class="text-center md:col-10 lg:col-7">
|
||||
{
|
||||
image && (
|
||||
<Image
|
||||
class="mx-auto mb-6 rounded-lg"
|
||||
src={image}
|
||||
width={200}
|
||||
height={200}
|
||||
alt={title}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<h2 set:html={markdownify(title)} class="h3 mb-6" />
|
||||
<div class="content">
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
import { Image } from "@astrojs/image/components";
|
||||
import BlogCard from "@components/BlogCard.astro";
|
||||
import Social from "@components/Social.astro";
|
||||
import config from "@config/config.json";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import { getSinglePage } from "@lib/contentParser.astro";
|
||||
import { slugify } from "@lib/utils/textConverter";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const authors = await getSinglePage("authors");
|
||||
|
||||
const paths = authors.map((author) => ({
|
||||
params: {
|
||||
single: author.slug,
|
||||
},
|
||||
props: { author },
|
||||
}));
|
||||
return paths;
|
||||
}
|
||||
|
||||
const { blog_folder } = config.settings;
|
||||
const { author } = Astro.props;
|
||||
const { title, social, meta_title, description, image } = author.data;
|
||||
const { Content } = await author.render();
|
||||
const posts = await getSinglePage(blog_folder);
|
||||
const postFilterByAuthor = posts.filter(
|
||||
(post) => slugify(post.data.author) === slugify(title)
|
||||
);
|
||||
---
|
||||
|
||||
<Base
|
||||
title={title}
|
||||
meta_title={meta_title}
|
||||
description={description}
|
||||
image={image}
|
||||
>
|
||||
<section class="section-sm pb-0">
|
||||
<div class="container">
|
||||
<div
|
||||
class="row justify-center border-b border-border pb-14 dark:border-darkmode-border"
|
||||
>
|
||||
<div class="text-center lg:col-4">
|
||||
{
|
||||
image && (
|
||||
<Image
|
||||
src={image}
|
||||
class="mx-auto mb-10 rounded"
|
||||
height={200}
|
||||
width={200}
|
||||
alt={title}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<h1 class="h3 mb-6">{title}</h1>
|
||||
<div class="content">
|
||||
<Content />
|
||||
</div>
|
||||
<Social source={social} className="social-icons" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-center pb-16 pt-14">
|
||||
{
|
||||
postFilterByAuthor.map((post) => (
|
||||
<div class="mb-12 md:col-6 lg:col-4">
|
||||
<BlogCard data={post} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
import AuthorCard from "@components/AuthorCard.astro";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import { getSinglePage } from "@lib/contentParser.astro";
|
||||
import PageHeader from "@partials/PageHeader.astro";
|
||||
import { getEntryBySlug } from "astro:content";
|
||||
|
||||
const authorIndex = await getEntryBySlug<any, string>("authors", "_index");
|
||||
const authors = await getSinglePage("authors");
|
||||
---
|
||||
|
||||
<Base title={authorIndex.data.title}>
|
||||
<PageHeader title={authorIndex.data.title} />
|
||||
<section class="section-sm pb-0">
|
||||
<div class="container">
|
||||
<div class="row justify-center">
|
||||
{
|
||||
authors.map((author) => (
|
||||
<div class="mb-14 md:col-6 lg:col-4">
|
||||
<AuthorCard data={author} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
import BlogCard from "@components/BlogCard.astro";
|
||||
import config from "@config/config.json";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import { getSinglePage } from "@lib/contentParser.astro";
|
||||
import { getTaxonomy } from "@lib/taxonomyParser.astro";
|
||||
import taxonomyFilter from "@lib/utils/taxonomyFilter";
|
||||
import PageHeader from "@partials/PageHeader.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const categories = await getTaxonomy(
|
||||
config.settings.blog_folder,
|
||||
"categories"
|
||||
);
|
||||
|
||||
return categories.map((category) => {
|
||||
return {
|
||||
params: { category },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const { category } = Astro.params;
|
||||
const posts = await getSinglePage(config.settings.blog_folder);
|
||||
const filterByCategories = taxonomyFilter(posts, "categories", category);
|
||||
---
|
||||
|
||||
<Base title={category}>
|
||||
<PageHeader title={category} />
|
||||
<div class="section-sm pb-0">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
{
|
||||
filterByCategories.map((post) => (
|
||||
<div class="mb-14 md:col-6 lg:col-4">
|
||||
<BlogCard data={post} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Base>
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
import config from "@config/config.json";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import { getAllTaxonomy, getTaxonomy } from "@lib/taxonomyParser.astro";
|
||||
import { humanize } from "@lib/utils/textConverter";
|
||||
import PageHeader from "@partials/PageHeader.astro";
|
||||
|
||||
const { blog_folder } = config.settings;
|
||||
const categories = await getTaxonomy(blog_folder, "categories");
|
||||
const allCategories = await getAllTaxonomy(blog_folder, "categories");
|
||||
---
|
||||
|
||||
<Base title={"Categories"}>
|
||||
<PageHeader title={"Categories"} />
|
||||
<section class="section">
|
||||
<div class="container text-center">
|
||||
<ul class="space-x-4">
|
||||
{
|
||||
categories.map((category: any) => {
|
||||
const count = allCategories.filter((c) => c === category).length;
|
||||
return (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
href={`/categories/${category}`}
|
||||
class="rounded bg-theme-light px-4 py-2 text-xl text-dark dark:bg-darkmode-theme-light dark:text-darkmode-dark"
|
||||
>
|
||||
{humanize(category)}{" "}
|
||||
<span class="ml-2 rounded bg-body px-2 dark:bg-darkmode-body">
|
||||
{count}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
import config from "@config/config.json";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import PageHeader from "@partials/PageHeader.astro";
|
||||
import { getEntryBySlug } from "astro:content";
|
||||
|
||||
const entry = await getEntryBySlug("pages", "contact");
|
||||
const { contact_form_action } = config.params;
|
||||
const { title, description, meta_title, image } = entry.data;
|
||||
---
|
||||
|
||||
<Base
|
||||
title={title}
|
||||
meta_title={meta_title}
|
||||
description={description}
|
||||
image={image}
|
||||
>
|
||||
<PageHeader title={title} />
|
||||
<section class="section-sm">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="mx-auto md:col-10 lg:col-6">
|
||||
<form action={contact_form_action} method="POST">
|
||||
<div class="mb-6">
|
||||
<label for="name" class="form-label">
|
||||
Full Name <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
class="form-input"
|
||||
placeholder="John Doe"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label for="mail" class="form-label">
|
||||
Working Mail <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="mail"
|
||||
class="form-input"
|
||||
placeholder="john.doe@email.com"
|
||||
type="email"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<label for="message" class="form-label">
|
||||
Anything else? <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
class="form-input"
|
||||
placeholder="Message goes here..."
|
||||
id="message"
|
||||
rows="8"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
import { Image } from "@astrojs/image/components";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import { markdownify } from "@lib/utils/textConverter";
|
||||
import CallToAction from "@partials/CallToAction.astro";
|
||||
import Testimonial from "@partials/Testimonial.astro";
|
||||
import { getEntryBySlug } from "astro:content";
|
||||
import { FaCheck } from "react-icons/fa/index.js";
|
||||
|
||||
const homepage = await getEntryBySlug("homepage", "index");
|
||||
const testimonial = await getEntryBySlug("sections", "testimonial");
|
||||
const call_to_action = await getEntryBySlug("sections", "call-to-action");
|
||||
const { banner, features } = homepage.data;
|
||||
---
|
||||
|
||||
<Base>
|
||||
<!-- Banner -->
|
||||
<section class="section pt-14">
|
||||
<div class="container">
|
||||
<div class="row justify-center">
|
||||
<div class="mb-16 text-center lg:col-7">
|
||||
<h1 set:html={markdownify(banner.title)} class="mb-4" />
|
||||
<p set:html={markdownify(banner.content)} class="mb-8" />
|
||||
{
|
||||
banner.button.enable && (
|
||||
<a class="btn btn-primary" href={banner.button.link}>
|
||||
{banner.button.label}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
banner.image && (
|
||||
<div class="col-12">
|
||||
<img
|
||||
src={banner.image}
|
||||
width="1272"
|
||||
height="403"
|
||||
alt="banner image"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- /Banner -->
|
||||
|
||||
<!-- Features -->
|
||||
{
|
||||
features.map(
|
||||
(
|
||||
feature: {
|
||||
button: any;
|
||||
image: string;
|
||||
bulletpoints: any;
|
||||
content: string;
|
||||
title: string;
|
||||
},
|
||||
index: number
|
||||
) => (
|
||||
<section class={`section-sm ${index % 2 === 0 && "bg-gradient"}`}>
|
||||
<div class="container">
|
||||
<div class="row items-center justify-between">
|
||||
<div
|
||||
class={`mb:md-0 mb-6 md:col-5 ${
|
||||
index % 2 !== 0 && "md:order-2"
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={feature.image}
|
||||
height={480}
|
||||
width={520}
|
||||
fit="contain"
|
||||
background="rgba(0,0,0,0)"
|
||||
alt={feature.title}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class={`md:col-7 lg:col-6 ${index % 2 !== 0 && "md:order-1"}`}
|
||||
>
|
||||
<h2 set:html={markdownify(feature.title)} class="mb-4" />
|
||||
<p
|
||||
set:html={markdownify(feature.content)}
|
||||
class="mb-8 text-lg"
|
||||
/>
|
||||
<ul>
|
||||
{feature.bulletpoints.map((bullet: string) => (
|
||||
<li class="relative mb-4 pl-6">
|
||||
<FaCheck className={"absolute left-0 top-1.5"} />
|
||||
{markdownify(bullet)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{feature.button.enable && (
|
||||
<a class="btn btn-primary mt-5" href={feature.button.link}>
|
||||
{feature.button.label}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
)
|
||||
}
|
||||
<!-- /Features -->
|
||||
|
||||
<Testimonial testimonial={testimonial} />
|
||||
<CallToAction call_to_action={call_to_action} />
|
||||
</Base>
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
import config from "@config/config.json";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import PostSingle from "@layouts/PostSingle.astro";
|
||||
import { getSinglePage } from "@lib/contentParser.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getSinglePage(config.settings.blog_folder);
|
||||
|
||||
const paths = posts.map((post) => ({
|
||||
params: {
|
||||
single: post.slug,
|
||||
},
|
||||
props: { post },
|
||||
}));
|
||||
return paths;
|
||||
}
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { title, meta_title, description, image } = post.data;
|
||||
---
|
||||
|
||||
<Base
|
||||
title={title}
|
||||
meta_title={meta_title}
|
||||
description={description}
|
||||
image={image}
|
||||
>
|
||||
<PostSingle post={post} />
|
||||
</Base>
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
import BlogCard from "@components/BlogCard.astro";
|
||||
import Pagination from "@components/Pagination.astro";
|
||||
import config from "@config/config.json";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import { getSinglePage } from "@lib/contentParser.astro";
|
||||
import { getAllTaxonomy, getTaxonomy } from "@lib/taxonomyParser.astro";
|
||||
import { sortByDate } from "@lib/utils/sortFunctions";
|
||||
import PageHeader from "@partials/PageHeader.astro";
|
||||
import PostSidebar from "@partials/PostSidebar.astro";
|
||||
import { getEntryBySlug } from "astro:content";
|
||||
|
||||
const { blog_folder } = config.settings;
|
||||
const postIndex = await getEntryBySlug<any, string>(blog_folder, "_index");
|
||||
const posts = await getSinglePage(blog_folder);
|
||||
const allCategories = await getAllTaxonomy(blog_folder, "categories");
|
||||
const categories = await getTaxonomy(blog_folder, "categories");
|
||||
const tags = await getTaxonomy(blog_folder, "tags");
|
||||
const sortedPosts = sortByDate(posts);
|
||||
const totalPages = Math.ceil(posts.length / config.settings.pagination);
|
||||
const currentPosts = sortedPosts.slice(0, config.settings.pagination);
|
||||
---
|
||||
|
||||
<Base
|
||||
title={postIndex.data.title}
|
||||
meta_title={postIndex.data.meta_title}
|
||||
image={postIndex.data.image}
|
||||
description={postIndex.data.description}
|
||||
>
|
||||
<PageHeader title={postIndex.data.title} />
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="row gx-5">
|
||||
<!-- blog posts -->
|
||||
<div class="lg:col-8">
|
||||
<div class="row">
|
||||
{
|
||||
currentPosts.map((post) => (
|
||||
<div class="mb-14 md:col-6">
|
||||
<BlogCard data={post} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<Pagination
|
||||
section={blog_folder}
|
||||
currentPage={1}
|
||||
totalPages={totalPages}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- sidebar -->
|
||||
<PostSidebar
|
||||
categories={categories}
|
||||
tags={tags}
|
||||
allCategories={allCategories}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
import BlogCard from "@components/BlogCard.astro";
|
||||
import Pagination from "@components/Pagination.astro";
|
||||
import config from "@config/config.json";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import { getSinglePage } from "@lib/contentParser.astro";
|
||||
import { getAllTaxonomy, getTaxonomy } from "@lib/taxonomyParser.astro";
|
||||
import { sortByDate } from "@lib/utils/sortFunctions";
|
||||
import PageHeader from "@partials/PageHeader.astro";
|
||||
import PostSidebar from "@partials/PostSidebar.astro";
|
||||
import { getEntryBySlug } from "astro:content";
|
||||
|
||||
const { blog_folder } = config.settings;
|
||||
const { slug } = Astro.params;
|
||||
const postIndex = await getEntryBySlug<any, string>(blog_folder, "_index");
|
||||
const posts = await getSinglePage(blog_folder);
|
||||
const allCategories = await getAllTaxonomy(blog_folder, "categories");
|
||||
const categories = await getTaxonomy(blog_folder, "categories");
|
||||
const tags = await getTaxonomy(blog_folder, "tags");
|
||||
const sortedPosts = sortByDate(posts);
|
||||
const totalPages = Math.ceil(posts.length / config.settings.pagination);
|
||||
const currentPage = slug && !isNaN(Number(slug)) ? Number(slug) : 1;
|
||||
const indexOfLastPost = currentPage * config.settings.pagination;
|
||||
const indexOfFirstPost = indexOfLastPost - config.settings.pagination;
|
||||
const currentPosts = sortedPosts.slice(indexOfFirstPost, indexOfLastPost);
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getSinglePage(config.settings.blog_folder);
|
||||
const totalPages = Math.ceil(posts.length / config.settings.pagination);
|
||||
const paths = [];
|
||||
|
||||
for (let i = 1; i < totalPages; i++) {
|
||||
paths.push({
|
||||
params: {
|
||||
slug: (i + 1).toString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
---
|
||||
|
||||
<Base
|
||||
title={postIndex.data.title}
|
||||
meta_title={postIndex.data.meta_title}
|
||||
image={postIndex.data.image}
|
||||
description={postIndex.data.description}
|
||||
>
|
||||
<PageHeader title={postIndex.data.title} />
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="row gx-5">
|
||||
<!-- blog posts -->
|
||||
<div class="lg:col-8">
|
||||
<div class="row">
|
||||
{
|
||||
currentPosts.map((post) => (
|
||||
<div class="mb-14 md:col-6">
|
||||
<BlogCard data={post} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<Pagination
|
||||
section={blog_folder}
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- sidebar -->
|
||||
<PostSidebar
|
||||
categories={categories}
|
||||
tags={tags}
|
||||
allCategories={allCategories}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
import config from "@config/config.json";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import Search from "@layouts/Search";
|
||||
import { getSinglePage } from "@lib/contentParser.astro";
|
||||
|
||||
const { blog_folder } = config.settings;
|
||||
|
||||
// Retrieve all articles
|
||||
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,
|
||||
}));
|
||||
---
|
||||
|
||||
<Base title={`Search`}>
|
||||
<Search client:load searchList={searchList} />
|
||||
</Base>
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
import BlogCard from "@components/BlogCard.astro";
|
||||
import config from "@config/config.json";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import { getSinglePage } from "@lib/contentParser.astro";
|
||||
import { getTaxonomy } from "@lib/taxonomyParser.astro";
|
||||
import taxonomyFilter from "@lib/utils/taxonomyFilter";
|
||||
import PageHeader from "@partials/PageHeader.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const tags = await getTaxonomy(config.settings.blog_folder, "tags");
|
||||
|
||||
return tags.map((tag) => {
|
||||
return {
|
||||
params: { tag },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const { tag } = Astro.params;
|
||||
const posts = await getSinglePage(config.settings.blog_folder);
|
||||
const filterByCategories = taxonomyFilter(posts, "tags", tag);
|
||||
---
|
||||
|
||||
<Base title={tag}>
|
||||
<PageHeader title={tag} />
|
||||
<div class="section-sm pb-0">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
{
|
||||
filterByCategories.map((post) => (
|
||||
<div class="mb-14 md:col-6 lg:col-4">
|
||||
<BlogCard data={post} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Base>
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
import config from "@config/config.json";
|
||||
import Base from "@layouts/Base.astro";
|
||||
import { getAllTaxonomy, getTaxonomy } from "@lib/taxonomyParser.astro";
|
||||
import { humanize } from "@lib/utils/textConverter";
|
||||
import PageHeader from "@partials/PageHeader.astro";
|
||||
|
||||
const { blog_folder } = config.settings;
|
||||
const tags = await getTaxonomy(blog_folder, "tags");
|
||||
const allTags = await getAllTaxonomy(blog_folder, "tags");
|
||||
---
|
||||
|
||||
<Base title={"Tags"}>
|
||||
<PageHeader title={"Tags"} />
|
||||
<section class="section">
|
||||
<div class="container text-center">
|
||||
<ul class="space-x-4">
|
||||
{
|
||||
tags.map((tag: any) => {
|
||||
const count = allTags.filter((c) => c === tag).length;
|
||||
return (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
href={`/tags/${tag}`}
|
||||
class="rounded bg-theme-light px-4 py-2 text-xl text-dark dark:bg-darkmode-theme-light dark:text-darkmode-dark"
|
||||
>
|
||||
{humanize(tag)}{" "}
|
||||
<span class="ml-2 rounded bg-body px-2 dark:bg-darkmode-body">
|
||||
{count}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
@@ -0,0 +1,55 @@
|
||||
html {
|
||||
@apply text-base;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-body font-primary font-normal leading-relaxed text-text dark:bg-darkmode-body dark:text-darkmode-text;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@apply font-secondary font-bold leading-tight text-dark dark:text-darkmode-dark;
|
||||
}
|
||||
|
||||
h1,
|
||||
.h1 {
|
||||
@apply text-h1-sm md:text-h1;
|
||||
}
|
||||
|
||||
h2,
|
||||
.h2 {
|
||||
@apply text-h2-sm md:text-h2;
|
||||
}
|
||||
|
||||
h3,
|
||||
.h3 {
|
||||
@apply text-h3-sm md:text-h3;
|
||||
}
|
||||
|
||||
h4,
|
||||
.h4 {
|
||||
@apply text-h4 font-medium;
|
||||
}
|
||||
|
||||
h5,
|
||||
.h5 {
|
||||
@apply text-h5 font-medium;
|
||||
}
|
||||
|
||||
h6,
|
||||
.h6 {
|
||||
@apply text-h6 font-medium;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
code {
|
||||
@apply after:border-none;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
.btn {
|
||||
@apply inline-block rounded border border-transparent px-5 py-2 font-semibold capitalize transition;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
@apply rounded-sm px-4 py-1.5 text-sm;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply border-primary bg-primary text-white no-underline dark:border-darkmode-primary dark:bg-white dark:text-dark;
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
@apply border-dark bg-transparent text-dark no-underline hover:bg-dark hover:text-white dark:border-white dark:text-white dark:hover:bg-white dark:hover:text-dark;
|
||||
}
|
||||