initialize project

This commit is contained in:
somrat sorkar
2023-04-17 12:50:29 +06:00
commit 8badd3f3e7
106 changed files with 4564 additions and 0 deletions
+14
View File
@@ -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
Executable
+26
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
{
"MD033": false,
"MD013": false
}
Executable
+10
View File
@@ -0,0 +1,10 @@
{
"overrides": [
{
"files": ["*.astro"],
"options": {
"parser": "astro"
}
}
]
}
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["astro-build.astro-vscode"]
}
+5
View File
@@ -0,0 +1,5 @@
{
"files.associations": {
"*.mdx": "markdown"
}
}
+55
View File
@@ -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,
},
});
Executable
+12
View File
@@ -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"
Executable
+52
View File
@@ -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"
}
}
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
+88
View File
@@ -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>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Disallow: /api/*
Executable
+20
View File
@@ -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
```
+44
View File
@@ -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);
}
+47
View File
@@ -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"
}
}
+61
View File
@@ -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"
}
]
}
+30
View File
@@ -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": ""
}
+44
View File
@@ -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"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
---
title: "Authors"
---
+12
View File
@@ -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.
+12
View File
@@ -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.
+12
View File
@@ -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.
+53
View File
@@ -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,
};
+53
View File
@@ -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: ""
---
+5
View File
@@ -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.
+9
View File
@@ -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.
+6
View File
@@ -0,0 +1,6 @@
---
title: "Contact"
meta_title: ""
description: "this is meta description"
draft: false
---
+224
View File
@@ -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 youre 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 youre 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
![image](/images/image-placeholder.png)
---
### 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" />
+30
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Blog Posts"
meta_title: ""
description: "this is meta description"
---
+23
View File
@@ -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!
+23
View File
@@ -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!
+23
View File
@@ -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!
+23
View File
@@ -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!
+10
View File
@@ -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: "#"
---
+27
View File
@@ -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."
---
Vendored Executable
+2
View File
@@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="@astrojs/image/client" />
+13
View File
@@ -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;
+150
View File
@@ -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>
+117
View File
@@ -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>
+181
View File
@@ -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;
+31
View File
@@ -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>
+55
View File
@@ -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>
+43
View File
@@ -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>
+64
View File
@@ -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>
+129
View File
@@ -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>
)
}
+55
View File
@@ -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}&amp;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>
+498
View File
@@ -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>
+73
View File
@@ -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>
+15
View File
@@ -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;
+45
View File
@@ -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>
)
}
+37
View File
@@ -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>
+150
View File
@@ -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>
+17
View File
@@ -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>
+55
View File
@@ -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>
+103
View File
@@ -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>
+36
View File
@@ -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;
+30
View File
@@ -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;
+78
View File
@@ -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;
+31
View File
@@ -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;
+23
View File
@@ -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;
+10
View File
@@ -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;
};
---
+32
View File
@@ -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;
};
---
+18
View File
@@ -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;
+40
View File
@@ -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;
+36
View File
@@ -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;
+25
View File
@@ -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;
};
+8
View File
@@ -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;
+57
View File
@@ -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 } = {
"&nbsp;": " ",
"&lt;": "<",
"&gt;": ">",
"&amp;": "&",
"&quot;": '"',
"&#39;": "'",
};
let htmlWithoutEntities: string = htmlWithEntities.replace(
/(&amp;|&lt;|&gt;|&quot;|&#39;)/g,
(entity: string): string => {
return entityList[entity];
}
);
return htmlWithoutEntities;
};
+22
View File
@@ -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>
+41
View File
@@ -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>
+41
View File
@@ -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>
+74
View File
@@ -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>
+27
View File
@@ -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>
+43
View File
@@ -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>
+39
View File
@@ -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>
+62
View File
@@ -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>
+111
View File
@@ -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>
+30
View File
@@ -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>
+61
View File
@@ -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>
+80
View File
@@ -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>
+22
View File
@@ -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>
+40
View File
@@ -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>
+39
View File
@@ -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>
+55
View File
@@ -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;
}
+15
View File
@@ -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;
}

Some files were not shown because too many files have changed in this diff Show More