v1
This commit is contained in:
35
src/config/config.json
Normal file
35
src/config/config.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"site": {
|
||||
"title": "RT",
|
||||
"base_url": "https://blog.currytech.online",
|
||||
"base_path": "/",
|
||||
"trailing_slash": false,
|
||||
"favicon": "/images/favicon.png",
|
||||
"logo": "/images/logo.png",
|
||||
"logo_width": "200",
|
||||
"logo_height": "34",
|
||||
"logo_text": "RT"
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"pagination": 5,
|
||||
"summary_length": 150
|
||||
},
|
||||
|
||||
"metadata": {
|
||||
"meta_author": "CurryTech",
|
||||
"meta_image": "/images/og-image.png",
|
||||
"meta_description": "CurryTech - A Technical Blog"
|
||||
},
|
||||
|
||||
"params": {
|
||||
"contact_form_action": "#",
|
||||
"copyright": "Copyright © 2023 - Build by Astro on K3S "
|
||||
},
|
||||
|
||||
"contactinfo": {
|
||||
"address": "",
|
||||
"email": "info@currytech.oline",
|
||||
"phone": ""
|
||||
}
|
||||
}
|
||||
45
src/config/menu.json
Normal file
45
src/config/menu.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"main": [
|
||||
{
|
||||
"name": "Home",
|
||||
"url": "/"
|
||||
},
|
||||
{
|
||||
"name": "Tags",
|
||||
"url": "/tags"
|
||||
},
|
||||
{
|
||||
"name": "Categories",
|
||||
"url": "/categories"
|
||||
},
|
||||
{
|
||||
"name": "Pages",
|
||||
"url": "",
|
||||
"hasChildren": true,
|
||||
"children": [
|
||||
{
|
||||
"name": "Contact",
|
||||
"url": "/contact"
|
||||
},
|
||||
{
|
||||
"name": "About",
|
||||
"url": "/about"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"footer": [
|
||||
{
|
||||
"name": "About",
|
||||
"url": "/about"
|
||||
},
|
||||
{
|
||||
"name": "Contact",
|
||||
"url": "/contact"
|
||||
},
|
||||
{
|
||||
"name": "Privacy Policy",
|
||||
"url": "/privacy-policy"
|
||||
}
|
||||
]
|
||||
}
|
||||
30
src/config/social.json
Normal file
30
src/config/social.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"facebook": "https://facebook.com/",
|
||||
"twitter": "https://twitter.com/",
|
||||
"instagram": "https://instagram.com/",
|
||||
"youtube": "https://youtube.com/",
|
||||
"linkedin": "https://linkedin.com/",
|
||||
"github": "",
|
||||
"gitlab": "",
|
||||
"medium": "",
|
||||
"codepen": "",
|
||||
"bitbucket": "",
|
||||
"dribbble": "",
|
||||
"behance": "",
|
||||
"pinterest": "",
|
||||
"soundcloud": "",
|
||||
"tumblr": "",
|
||||
"reddit": "",
|
||||
"vk": "",
|
||||
"whatsapp": "",
|
||||
"snapchat": "",
|
||||
"vimeo": "",
|
||||
"tiktok": "",
|
||||
"foursquare": "",
|
||||
"rss": "",
|
||||
"email": "",
|
||||
"phone": "",
|
||||
"address": "",
|
||||
"skype": "",
|
||||
"website": ""
|
||||
}
|
||||
30
src/config/theme.json
Normal file
30
src/config/theme.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"colors": {
|
||||
"default": {
|
||||
"theme_color": {
|
||||
"primary": "#01AD9F",
|
||||
"body": "#fff",
|
||||
"border": "#D5D5D5",
|
||||
"theme_light": "#FAFAFA",
|
||||
"theme_dark": "#152035"
|
||||
},
|
||||
"text_color": {
|
||||
"default": "#747577",
|
||||
"dark": "#152035",
|
||||
"light": "#a1a5ae"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fonts": {
|
||||
"font_family": {
|
||||
"primary": "Mulish:wght@400;600;700",
|
||||
"primary_type": "sans-serif",
|
||||
"secondary": "",
|
||||
"secondary_type": ""
|
||||
},
|
||||
"font_size": {
|
||||
"base": "16",
|
||||
"scale": "1.250"
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/content/about/index.md
Normal file
23
src/content/about/index.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "I’m John Doe, A content writer based in LDN, Currently at Bookworm"
|
||||
meta_title: "About"
|
||||
image: "/images/author.png"
|
||||
draft: false
|
||||
|
||||
what_i_do:
|
||||
title: "What I Do"
|
||||
items:
|
||||
- title: "Content Writing"
|
||||
description: "Purus eget ipsum elementum venenatis, quis rutrum mi semper nonpurus eget ipsum elementum venenatis."
|
||||
|
||||
- title: "Photography"
|
||||
description: "Aenean maximus urna magna elementum, quis rutrum mi semper non purus eget ipsum venenatis."
|
||||
|
||||
- title: "Web Research"
|
||||
description: "Aenean maximus urna magna elementum venenatis, quis semper non purus eget ipsum venenatis."
|
||||
|
||||
---
|
||||
|
||||
A content writer with over 12 years experience working across brand identity, publishing and digital products. Maecenas sit amet purus eget ipsum elementum venenatis. Aenean maximus urna magna elementum venenatis quis non purus.
|
||||
|
||||
Purus eget ipsum elementum venenatis. Aenean maximus urna magna elementum venenatis, quis rutrum mi semper non purus eget ipsum elementum venenatis, aenean maximus urna magna elementum.
|
||||
3
src/content/authors/-index.md
Normal file
3
src/content/authors/-index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: "Authors"
|
||||
---
|
||||
11
src/content/authors/john-doe.md
Normal file
11
src/content/authors/john-doe.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: John Doe
|
||||
image: /images/authors/john-doe.jpg
|
||||
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.
|
||||
11
src/content/authors/mark-dinn.md
Normal file
11
src/content/authors/mark-dinn.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Mark Dinn
|
||||
image: /images/authors/mark-dinn.jpg
|
||||
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.
|
||||
56
src/content/config.ts
Normal file
56
src/content/config.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { defineCollection, z } from "astro:content";
|
||||
|
||||
// Post collection schema
|
||||
const postsCollection = defineCollection({
|
||||
schema: z.object({
|
||||
id: z.string().optional(),
|
||||
title: z.string(),
|
||||
meta_title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
date: z.date().optional(),
|
||||
image: z.string().optional(),
|
||||
authors: z.array(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({
|
||||
id: z.string().optional(),
|
||||
title: z.string(),
|
||||
meta_title: 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({
|
||||
id: z.string().optional(),
|
||||
title: z.string(),
|
||||
meta_title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
image: z.string().optional(),
|
||||
layout: z.string().optional(),
|
||||
draft: z.boolean().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
// Export collections
|
||||
export const collections = {
|
||||
posts: postsCollection,
|
||||
pages: pagesCollection,
|
||||
authors: authorsCollection,
|
||||
};
|
||||
5
src/content/pages/404.md
Normal file
5
src/content/pages/404.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Error 404"
|
||||
---
|
||||
|
||||
## Page Not Found
|
||||
4
src/content/pages/contact.md
Normal file
4
src/content/pages/contact.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Contact"
|
||||
draft: false
|
||||
---
|
||||
255
src/content/pages/elements.mdx
Normal file
255
src/content/pages/elements.mdx
Normal file
@@ -0,0 +1,255 @@
|
||||
---
|
||||
title: "Elements"
|
||||
meta_title: ""
|
||||
description: "this is meta description"
|
||||
draft: false
|
||||
---
|
||||
|
||||
# Heading 1
|
||||
|
||||
## Heading 2
|
||||
|
||||
### Heading 3
|
||||
|
||||
#### Heading 4
|
||||
|
||||
##### Heading 5
|
||||
|
||||
###### Heading 6
|
||||
|
||||
---
|
||||
|
||||
### Paragraph
|
||||
|
||||
Did you come here for something in particular or just general Riker-bashing? And blowing into maximum warp speed, you appeared for an instant to be in two places at once. We have a saboteur aboard. We know you’re dealing in stolen ore. But I wanna talk about the assassination attempt on Lieutenant Worf. Could someone survive inside a transporter buffer for 75 years? Fate. It protects fools, little children, and ships.
|
||||
|
||||
Did you come here for something in particular or just general Riker-bashing? And blowing into maximum warp speed, you appeared for an instant to be in two places at once. We have a saboteur aboard. We know you’re dealing in stolen ore. But I wanna talk about the assassination attempt on Lieutenant Worf. Could someone survive inside a transporter buffer for 75 years? Fate. It protects fools, little children, and ships.
|
||||
|
||||
---
|
||||
|
||||
### Emphasis
|
||||
|
||||
1. Did you come here for something in **particular** or just general
|
||||
|
||||
2. Did you come here for something in <ins>particular</ins>
|
||||
|
||||
3. _Did you come here_
|
||||
|
||||
4. Did you come here for **something** in particular
|
||||
|
||||
5. Did you come here for something in particular
|
||||
|
||||
6. Did you come here for something in particular
|
||||
|
||||
7. URLs and URLs in angle brackets will automatically get turned into links. [http://www.example.com](http://www.example.com) or
|
||||
|
||||
8. [http://www.example.com](http://www.example.com) and sometimes example.com (but not on Github, for example).
|
||||
|
||||
---
|
||||
|
||||
### Link
|
||||
|
||||
[I'm an inline-style link](https://www.google.com)
|
||||
|
||||
[I'm an inline-style link with title](https://www.google.com "Google's Homepage")
|
||||
|
||||
[I'm a reference-style link][arbitrary case-insensitive reference text]
|
||||
|
||||
[I'm a relative reference to a repository file](../blob/master/LICENSE)
|
||||
|
||||
[You can use numbers for reference-style link definitions][1]
|
||||
|
||||
Or leave it empty and use the [link text itself].
|
||||
|
||||
example.com (but not on Github, for example).
|
||||
|
||||
Some text to show that the reference links can follow later.
|
||||
|
||||
[arbitrary case-insensitive reference text]: https://www.themefisher.com
|
||||
[1]: https://gethugothemes.com
|
||||
[link text itself]: https://www.getjekyllthemes.com
|
||||
|
||||
---
|
||||
|
||||
### Ordered List
|
||||
|
||||
1. List item
|
||||
2. List item
|
||||
3. List item
|
||||
4. List item
|
||||
5. List item
|
||||
|
||||
---
|
||||
|
||||
### Unordered List
|
||||
|
||||
- List item
|
||||
- List item
|
||||
- List item
|
||||
- List item
|
||||
- List item
|
||||
|
||||
---
|
||||
|
||||
### Code and Syntax Highlighting
|
||||
|
||||
#### HTML
|
||||
|
||||
```html
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="about/">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### CSS
|
||||
|
||||
```css
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### JavaScript
|
||||
|
||||
```javascript
|
||||
window.addEventListener("load", (e) => {
|
||||
document.querySelector(".preloader").style.display = "none";
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Button
|
||||
|
||||
<Button label="Button" link="#" style="solid" />
|
||||
|
||||
---
|
||||
|
||||
### 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>
|
||||
|
||||
---
|
||||
|
||||
### Tab
|
||||
|
||||
<Tabs client:load>
|
||||
|
||||
<Tab name="Tab 1">
|
||||
|
||||
#### Did you come here for something in particular?
|
||||
|
||||
Did you come here for something in particular or just general Riker-bashing? And blowing into maximum warp speed, you appeared for an instant to be in two places at once. We have a saboteur aboard. We know you’re dealing in stolen ore. But I wanna talk about the assassination attempt on Lieutenant Worf.
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab name="Tab 2">
|
||||
|
||||
#### I wanna talk about the assassination attempt
|
||||
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||
|
||||
</Tab>
|
||||
|
||||
<Tab name="Tab 3">
|
||||
|
||||
#### We know you’re dealing in stolen ore
|
||||
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo
|
||||
|
||||
</Tab>
|
||||
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
### 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 |
|
||||
|
||||
---
|
||||
|
||||
### Accordion
|
||||
|
||||
<Accordion client:load title="Why should you need to do this?">
|
||||
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion client:load title="How can I adjust Horizontal centering">
|
||||
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion client:load title="Should you use Negative margin?">
|
||||
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
- This is a thing.
|
||||
|
||||
</Accordion>
|
||||
|
||||
---
|
||||
|
||||
### Image
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
### Youtube video
|
||||
|
||||
<Youtube client:load id="ZEe-IFezQok" title="Youtube Video Test Title" />
|
||||
|
||||
---
|
||||
|
||||
### Custom video
|
||||
|
||||
<Video
|
||||
width="100%"
|
||||
src="https://joy1.videvo.net/videvo_files/video/free/video0467/large_watermarked/_import_61516692993d77.04238324_preview.mp4"
|
||||
/>
|
||||
29
src/content/pages/privacy-policy.md
Normal file
29
src/content/pages/privacy-policy.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: "Privacy"
|
||||
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.
|
||||
3
src/content/posts/-index.md
Normal file
3
src/content/posts/-index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Blog
|
||||
---
|
||||
22
src/content/posts/post-1.md
Normal file
22
src/content/posts/post-1.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: "How to make toys from old Olarpaper"
|
||||
description: "meta description"
|
||||
date: 2022-04-04T05:00:00Z
|
||||
image: "/images/posts/01.jpg"
|
||||
categories: ["art"]
|
||||
authors: ["Mark Dinn"]
|
||||
tags: ["diy", "toy"]
|
||||
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!
|
||||
22
src/content/posts/post-2.md
Normal file
22
src/content/posts/post-2.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Why a balloon is a flexible bag
|
||||
description: "meta description"
|
||||
date: 2022-04-04T05:00:00Z
|
||||
image: "/images/posts/02.jpg"
|
||||
categories: ["development"]
|
||||
authors: ["John Doe"]
|
||||
tags: ["diy", "toy"]
|
||||
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!
|
||||
22
src/content/posts/post-3.md
Normal file
22
src/content/posts/post-3.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: What you need to know about Photography
|
||||
description: "meta description"
|
||||
date: 2022-04-02T06:00:00+00:00
|
||||
image: "/images/posts/03.jpg"
|
||||
categories: ["art"]
|
||||
authors: ["Mark Dinn"]
|
||||
tags: ["diy", "toy"]
|
||||
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!
|
||||
22
src/content/posts/post-4.md
Normal file
22
src/content/posts/post-4.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: "Why a balloon is a flexible bag that can be inflated with a gas"
|
||||
description: "meta description"
|
||||
image: "/images/posts/04.jpg"
|
||||
date: 2021-02-02T16:56:47+06:00
|
||||
draft: false
|
||||
authors: ["Mark Dinn"]
|
||||
tags: ["Balloon", "Gas"]
|
||||
categories: ["Accessories"]
|
||||
---
|
||||
|
||||
A balloon is a flexible bag that can be inflated with a gas, such as helium, hydrogen, nitrous oxide, oxygen, and air. For special tasks, balloons can be filled with smoke, liquid water, granular media, or light sources.
|
||||
|
||||
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!
|
||||
22
src/content/posts/post-5.md
Normal file
22
src/content/posts/post-5.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: "Banana is the best meal packages in the UK spark outrage online"
|
||||
description: "meta description"
|
||||
image: "/images/posts/05.jpg"
|
||||
date: 2021-01-25T16:56:47+06:00
|
||||
draft: false
|
||||
authors: ["John Doe"]
|
||||
tags: ["Food", "Gold"]
|
||||
categories: ["Food"]
|
||||
---
|
||||
|
||||
A banana is an elongated, edible fruit – botanically a berry – produced by several kinds of large herbaceous flowering plants in the genus Musa. In some countries, bananas used for cooking may be called "plantains", distinguishing them from dessert bananas.
|
||||
|
||||
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!
|
||||
22
src/content/posts/post-6.md
Normal file
22
src/content/posts/post-6.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: "Become a morning person with the help of alarm clock"
|
||||
description: "meta description"
|
||||
image: "/images/posts/06.jpg"
|
||||
date: 2021-02-03T16:56:47+06:00
|
||||
draft: false
|
||||
authors: ["Mark Dinn"]
|
||||
tags: ["Alarm", "Clock"]
|
||||
categories: ["LifeStyle"]
|
||||
---
|
||||
|
||||
Almost every day for the past nine or so months has felt like March 13, and that can sometimes make it difficult to want to wake up for the day ahead of you.
|
||||
|
||||
To make a morning person out of you, the wake-up light simulates the sunrise to gradually ease you awake. This allows you to wake up more naturally rather than being jolted awake by the default iPhone alarm sound, which honestly triggers my fight or flight response.
|
||||
|
||||
### 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!
|
||||
21
src/content/posts/post-7.md
Normal file
21
src/content/posts/post-7.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: "How to make toys from old paper"
|
||||
description: "meta description"
|
||||
date: 2022-06-13T05:00:00Z
|
||||
image: "/images/posts/01.jpg"
|
||||
categories: ["art"]
|
||||
authors: ["Mark Dinn"]
|
||||
tags: ["diy", "toy"]
|
||||
draft: false
|
||||
---
|
||||
|
||||
|
||||
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!
|
||||
2
src/env.d.ts
vendored
Normal file
2
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="@astrojs/image/client" />
|
||||
35
src/layouts/AuthorSingle.astro
Normal file
35
src/layouts/AuthorSingle.astro
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
import Social from "@/components/Social.astro";
|
||||
import { markdownify } from "@/lib/utils/textConverter";
|
||||
|
||||
const { author } = Astro.props;
|
||||
const { title, image, social } = author.data;
|
||||
const { Content } = await author.render();
|
||||
---
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="mb-4 text-center md:px-24">
|
||||
{
|
||||
image && (
|
||||
<div class="mb-8">
|
||||
<Image
|
||||
src={image}
|
||||
class="mx-auto rounded-lg"
|
||||
height={150}
|
||||
width={150}
|
||||
alt={title}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<h1 set:html={markdownify(title)} class="page-heading h2 mb-8" />
|
||||
|
||||
<Social source={social} className="social-icons-simple" />
|
||||
<div class="content">
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
42
src/layouts/Authors.astro
Normal file
42
src/layouts/Authors.astro
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
import { markdownify } from "@/lib/utils/textConverter";
|
||||
import { Image } from "astro:assets";
|
||||
import { BsArrowRightCircle } from "react-icons/bs";
|
||||
import type { TAuthor } from "@/types";
|
||||
const { authors } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="row justify-center">
|
||||
{
|
||||
authors.map((author: TAuthor) => (
|
||||
<div class="col-12 mb-8 sm:col-6 md:col-4">
|
||||
{author.data.image && (
|
||||
<div class="mb-4">
|
||||
<Image
|
||||
src={author.data.image}
|
||||
alt={author.data.title}
|
||||
height={150}
|
||||
width={150}
|
||||
class="mx-auto rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<h3 class="h4 mt-8 mb-3">
|
||||
<a
|
||||
href={`/authors/${author.slug}`}
|
||||
class="block hover:text-primary transition duration-200"
|
||||
>
|
||||
{author.data.title}
|
||||
</a>
|
||||
</h3>
|
||||
<p class="mb-3" set:text={markdownify(author.body.slice(0, 100))} />
|
||||
<a
|
||||
href={`/authors/${author.slug}`}
|
||||
class="inline-flex items-center text-primary transition duration-200 hover:opacity-70"
|
||||
>
|
||||
Read More <BsArrowRightCircle className="inline ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
180
src/layouts/Base.astro
Normal file
180
src/layouts/Base.astro
Normal file
@@ -0,0 +1,180 @@
|
||||
---
|
||||
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";
|
||||
import { AstroFont } from "astro-font";
|
||||
import { ViewTransitions } from "astro:transitions";
|
||||
|
||||
// font families
|
||||
const pf = theme.fonts.font_family.primary;
|
||||
const sf = theme.fonts.font_family.secondary;
|
||||
|
||||
let fontPrimary, fontSecondary;
|
||||
if (theme.fonts.font_family.primary) {
|
||||
fontPrimary = theme.fonts.font_family.primary
|
||||
.replace(/\+/g, " ")
|
||||
.replace(/:[ital,]*[ital@]*[wght@]*[0-9,;]+/gi, "");
|
||||
}
|
||||
if (theme.fonts.font_family.secondary) {
|
||||
fontSecondary = theme.fonts.font_family.secondary
|
||||
.replace(/\+/g, " ")
|
||||
.replace(/:[ital,]*[ital@]*[wght@]*[0-9,;]+/gi, "");
|
||||
}
|
||||
|
||||
// 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="bookworm-light-astro" />
|
||||
<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 -->
|
||||
<AstroFont
|
||||
config={[
|
||||
{
|
||||
src: [],
|
||||
preload: false,
|
||||
display: "swap",
|
||||
name: fontPrimary!,
|
||||
fallback: "sans-serif",
|
||||
cssVariable: "font-primary",
|
||||
googleFontsURL: `https://fonts.googleapis.com/css2?family=${pf}&display=swap`,
|
||||
},
|
||||
{
|
||||
src: [],
|
||||
preload: false,
|
||||
display: "swap",
|
||||
name: fontSecondary!,
|
||||
fallback: "sans-serif",
|
||||
cssVariable: "font-secondary",
|
||||
googleFontsURL: `https://fonts.googleapis.com/css2?family=${sf}&display=swap`,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<!-- 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,
|
||||
)}
|
||||
/>
|
||||
|
||||
<ViewTransitions />
|
||||
|
||||
<!-- 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" class="min-h-[60vh]">
|
||||
<slot />
|
||||
</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
16
src/layouts/Default.astro
Normal file
16
src/layouts/Default.astro
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
import { markdownify } from "@/lib/utils/textConverter";
|
||||
|
||||
const { data } = Astro.props;
|
||||
const { title } = data.data;
|
||||
const { Content } = await data.render();
|
||||
---
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 set:html={markdownify(title)} class="h2 page-heading" />
|
||||
<div class="content">
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
139
src/layouts/PostSingle.astro
Normal file
139
src/layouts/PostSingle.astro
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
import Share from "@/components/Share.astro";
|
||||
import SimilarPosts from "@/components/SimilarPosts.astro";
|
||||
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 { BiCalendarEdit, BiCategoryAlt } from "react-icons/bi";
|
||||
|
||||
const allAuthors = await getSinglePage("authors");
|
||||
const posts = await getSinglePage("posts");
|
||||
const { post } = Astro.props;
|
||||
const similarPosts = similerItems(post, posts, post.slug);
|
||||
const { Content } = await post.render();
|
||||
const { title, description, authors, categories, image, date, tags } =
|
||||
post.data;
|
||||
---
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<article class="row justify-center">
|
||||
<div class="md:col-10 text-center">
|
||||
<h1 set:html={markdownify(title)} class="h2" />
|
||||
<ul class="mt-4 flex flex-wrap items-center justify-center text-text">
|
||||
<li class="mx-3">
|
||||
{
|
||||
allAuthors
|
||||
.filter((author) =>
|
||||
authors
|
||||
.map((author: string) => slugify(author))
|
||||
.includes(slugify(author.data.title)),
|
||||
)
|
||||
.map((author, i) => (
|
||||
<a
|
||||
href={`/authors/${slugify(author.slug)}`}
|
||||
class="flex items-center hover:text-primary font-medium"
|
||||
>
|
||||
{author.data.image && (
|
||||
<Image
|
||||
src={author.data.image}
|
||||
alt={author.data.title}
|
||||
height={50}
|
||||
width={50}
|
||||
class="mr-2 h-6 w-6 rounded-full"
|
||||
/>
|
||||
)}
|
||||
<span>{author.data.title}</span>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</li>
|
||||
<li class="mx-3 flex items-center flex-wrap font-medium">
|
||||
<BiCalendarEdit className="mr-1 h-5 w-5 text-gray-600" />
|
||||
<>{dateFormat(date)}</>
|
||||
</li>
|
||||
<li class="mx-3 flex items-center flex-wrap">
|
||||
<BiCategoryAlt className="mr-1 h-[18px] w-[18px] text-gray-600" />
|
||||
<>
|
||||
<ul>
|
||||
{
|
||||
categories.map((category: string, i: number) => (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
href={`/categories/${slugify(category)}`}
|
||||
class="mr-2 hover:text-primary font-medium"
|
||||
>
|
||||
{humanize(category)}
|
||||
{i !== post.data.categories.length - 1 && ","}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mt-8 mb-2">
|
||||
{
|
||||
image && (
|
||||
<Image
|
||||
src={image}
|
||||
height={500}
|
||||
width={1000}
|
||||
alt={title}
|
||||
class="rounded-lg"
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="md:col-10">
|
||||
<div class="content mb-16 text-left">
|
||||
<Content />
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center justify-between">
|
||||
<ul class="mr-4 mb-4 space-x-3">
|
||||
{
|
||||
tags.map((tag: string) => (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
href={`/tags/${slugify(tag)}`}
|
||||
class="block rounded-lg bg-theme-light px-4 py-2 font-semibold text-dark text-sm hover:text-primary transition duration-300"
|
||||
>
|
||||
#{humanize(tag)}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
<Share
|
||||
className="social-share mb-4"
|
||||
title={title}
|
||||
description={description}
|
||||
slug={post.slug}
|
||||
/>
|
||||
</div>
|
||||
<div class="content mb-16 text-left">
|
||||
<script defer src="https://comment.currytech.online/comentario.js"></script>
|
||||
<comentario-comments></comentario-comments>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- similar posts -->
|
||||
{
|
||||
similarPosts.length > 0 && (
|
||||
<section class="section pt-0">
|
||||
<div class="container">
|
||||
<h2 class="mb-8 text-center h3">Similar Posts</h2>
|
||||
<SimilarPosts posts={similarPosts.slice(0, 3)} />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
87
src/layouts/Posts.astro
Normal file
87
src/layouts/Posts.astro
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
import config from "@/config/config.json";
|
||||
import { getSinglePage } from "@/lib/contentParser.astro";
|
||||
import dateFormat from "@/lib/utils/dateFormat";
|
||||
import { humanize,slugify } from "@/lib/utils/textConverter";
|
||||
import { BiCalendarEdit,BiCategoryAlt } from "react-icons/bi";
|
||||
const authors = await getSinglePage("authors");
|
||||
const { summary_length } = config.settings;
|
||||
const { className, posts, fluid } = Astro.props;
|
||||
---
|
||||
|
||||
<div class={`row gy-5 gx-4 ${className} ${posts.length == 1 ? "justify-center" : ""} `}>
|
||||
{
|
||||
posts.map((post: any, i: number) => (
|
||||
<div class={i === 0 && fluid != false ? "col-12" : "col-12 sm:col-6"}>
|
||||
{post.data.image && (
|
||||
<a href={`/${post.slug}`} class="rounded-lg block hover:text-primary overflow-hidden group">
|
||||
<Image
|
||||
class="group-hover:scale-[1.03] transition duration-300 w-full"
|
||||
src={post.data.image}
|
||||
alt={post.data.title}
|
||||
width={i === 0 ? 925 : 445}
|
||||
height={i === 0 ? 475 : 230}
|
||||
/>
|
||||
</a>
|
||||
)}
|
||||
<ul class="mt-6 mb-4 flex flex-wrap items-center text-text">
|
||||
<li class="mr-5">
|
||||
{authors
|
||||
.filter((author) =>
|
||||
post.data.authors
|
||||
.map((author: string) => slugify(author))
|
||||
.includes(slugify(author.data.title))
|
||||
)
|
||||
.map((author) => (
|
||||
<a
|
||||
href={`/authors/${slugify(author.data.title)}`}
|
||||
class="flex items-center hover:text-primary font-medium"
|
||||
>
|
||||
{author.data.image && (
|
||||
<Image
|
||||
src={author.data.image}
|
||||
alt={author.data.title}
|
||||
height={50}
|
||||
width={50}
|
||||
class="mr-2 h-6 w-6 rounded-full"
|
||||
/>
|
||||
)}
|
||||
<span>{author.data.title}</span>
|
||||
</a>
|
||||
))}
|
||||
</li>
|
||||
<li class="mr-5 flex items-center flex-wrap font-medium">
|
||||
<BiCalendarEdit className="mr-1 h-5 w-5 text-gray-600" />
|
||||
<>{dateFormat(post.data.date)}</>
|
||||
</li>
|
||||
<li class="mr-5 flex items-center flex-wrap">
|
||||
<BiCategoryAlt className="mr-1 h-[18px] w-[18px] text-gray-600" />
|
||||
<>
|
||||
<ul>
|
||||
{post.data.categories.map((category: string, i: number) => (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
href={`/categories/${slugify(category)}`}
|
||||
class="mr-2 hover:text-primary font-medium"
|
||||
>
|
||||
{humanize(category)}{i !== post.data.categories.length - 1 && ","}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
</li>
|
||||
</ul>
|
||||
<h3 class="mb-4">
|
||||
<a href={`/${post.slug}`} class="block hover:text-primary transition duration-300">
|
||||
{post.data.title}
|
||||
</a>
|
||||
</h3>
|
||||
<p class="text-text">
|
||||
{post.body?.slice(0, Number(i === 0 && fluid != false ? summary_length * 2 : summary_length))}...
|
||||
</p>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
152
src/layouts/SearchBar.tsx
Normal file
152
src/layouts/SearchBar.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import React from "react";
|
||||
import config from "@/config/config.json";
|
||||
import dateFormat from "@/lib/utils/dateFormat";
|
||||
import { humanize, slugify } from "@/lib/utils/textConverter";
|
||||
import Fuse from "fuse.js";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { BiCalendarEdit, BiCategoryAlt } from "react-icons/bi";
|
||||
const { summary_length } = config.settings;
|
||||
|
||||
export type SearchItem = {
|
||||
slug: string;
|
||||
data: any;
|
||||
content: any;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
searchList: SearchItem[];
|
||||
}
|
||||
|
||||
interface SearchResult {
|
||||
item: SearchItem;
|
||||
refIndex: number;
|
||||
}
|
||||
|
||||
export default function SearchBar({ searchList }: Props) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [inputVal, setInputVal] = useState("");
|
||||
const [searchResults, setSearchResults] = useState<SearchResult[] | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
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: 2,
|
||||
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 (
|
||||
<div className="min-h-[45vh]">
|
||||
<input
|
||||
className="form-input w-full text-center"
|
||||
placeholder="Type here to Search posts"
|
||||
type="text"
|
||||
name="search"
|
||||
value={inputVal}
|
||||
onChange={handleChange}
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
ref={inputRef}
|
||||
/>
|
||||
|
||||
{inputVal.length > 1 && (
|
||||
<div className="my-6 text-center">
|
||||
Found {searchResults?.length}
|
||||
{searchResults?.length && searchResults?.length === 1
|
||||
? " result"
|
||||
: " results"}{" "}
|
||||
for '{inputVal}'
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="row">
|
||||
{searchResults?.map(({ item }) => (
|
||||
<div key={item.slug} className={"col-12 mb-8 sm:col-6"}>
|
||||
{item.data.image && (
|
||||
<a
|
||||
href={`/${item.slug}`}
|
||||
className="rounded-lg block hover:text-primary overflow-hidden group"
|
||||
>
|
||||
<img
|
||||
className="group-hover:scale-[1.03] transition duration-300 w-full"
|
||||
src={item.data.image}
|
||||
alt={item.data.title}
|
||||
width={445}
|
||||
height={230}
|
||||
/>
|
||||
</a>
|
||||
)}
|
||||
|
||||
<ul className="mt-6 mb-4 flex flex-wrap items-center text-text">
|
||||
<li className="mr-5 flex items-center flex-wrap font-medium">
|
||||
<BiCalendarEdit className="mr-1 h-5 w-5 text-gray-600" />
|
||||
<>{dateFormat(item.data.date)}</>
|
||||
</li>
|
||||
<li className="mr-5 flex items-center flex-wrap">
|
||||
<BiCategoryAlt className="mr-1 h-[18px] w-[18px] text-gray-600" />
|
||||
<>
|
||||
<ul>
|
||||
{item.data.categories.map((category: string, i: number) => (
|
||||
<li key={i} className="inline-block">
|
||||
<a
|
||||
href={`/categories/${slugify(category)}`}
|
||||
className="mr-2 hover:text-primary font-medium"
|
||||
>
|
||||
{humanize(category)}
|
||||
{i !== item.data.categories.length - 1 && ","}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 className="mb-2">
|
||||
<a
|
||||
href={`/${item.slug}`}
|
||||
className="block hover:text-primary transition duration-300"
|
||||
>
|
||||
{item.data.title}
|
||||
</a>
|
||||
</h3>
|
||||
<p className="text-text">
|
||||
{item.content?.slice(0, Number(summary_length))}...
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
39
src/layouts/components/Logo.astro
Normal file
39
src/layouts/components/Logo.astro
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
import config from "@/config/config.json";
|
||||
const { src } = Astro.props;
|
||||
const {
|
||||
logo,
|
||||
logo_width,
|
||||
logo_height,
|
||||
logo_text,
|
||||
title,
|
||||
}: {
|
||||
logo: string;
|
||||
logo_width: any;
|
||||
logo_height: any;
|
||||
logo_text: string;
|
||||
title: string;
|
||||
} = config.site;
|
||||
---
|
||||
|
||||
<a href="/" class="navbar-brand block">
|
||||
{
|
||||
src || logo ? (
|
||||
<Image
|
||||
width={logo_width.replace("px", "") * 2}
|
||||
height={logo_height.replace("px", "") * 2}
|
||||
src={src ? src : logo}
|
||||
alt={title}
|
||||
style={{
|
||||
height: logo_height.replace("px", "") + "px",
|
||||
width: logo_width.replace("px", "") + "px",
|
||||
}}
|
||||
/>
|
||||
) : logo_text ? (
|
||||
logo_text
|
||||
) : (
|
||||
title
|
||||
)
|
||||
}
|
||||
</a>
|
||||
126
src/layouts/components/Pagination.astro
Normal file
126
src/layouts/components/Pagination.astro
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
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="mb-4 flex justify-center space-x-2 text-center" aria-label="Pagination">
|
||||
{/* previous */}
|
||||
{hasPrevPage ? (
|
||||
<a
|
||||
href={
|
||||
indexPageLink
|
||||
? `${section ? "/" + section : "/"}`
|
||||
: `${section ? "/" + section : ""}/page/${currentPage - 1}`
|
||||
}
|
||||
class="border border-primary hover:bg-primary hover:text-white rounded-md h-10 w-10 leading-[36px] font-semibold text-dark flex items-center justify-center transition duration-200"
|
||||
>
|
||||
<span class="sr-only">Previous</span>
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<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="border border-gray-400 opacity-40 rounded-md h-10 w-10 leading-[36px] font-semibold text-dark flex items-center justify-center pointer-events-none">
|
||||
<span class="sr-only">Previous</span>
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<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={`border border-primary bg-primary rounded-md h-10 w-10 leading-[35px] font-semibold text-white`}
|
||||
>
|
||||
{pagination}
|
||||
</span>
|
||||
) : (
|
||||
<a
|
||||
href={
|
||||
i === 0
|
||||
? `${section ? "/" + section : "/"}`
|
||||
: `${section ? "/" + section : ""}/page/${pagination}`
|
||||
}
|
||||
aria-current="page"
|
||||
class={`border border-primary hover:bg-primary hover:text-white rounded-md h-10 w-10 leading-[36px] font-semibold text-dark flex items-center justify-center transition duration-200`}
|
||||
>
|
||||
{pagination}
|
||||
</a>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* next page */}
|
||||
{hasNextPage ? (
|
||||
<a
|
||||
href={`${section ? "/" + section : ""}/page/${currentPage + 1}`}
|
||||
class="border border-primary hover:bg-primary hover:text-white rounded-md h-10 w-10 leading-[36px] font-semibold text-dark flex items-center justify-center transition duration-200"
|
||||
>
|
||||
<span class="sr-only">Next</span>
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<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="border border-gray-400 opacity-40 rounded-md h-10 w-10 leading-[36px] font-semibold text-dark flex items-center justify-center pointer-events-none">
|
||||
<span class="sr-only">Next</span>
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<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
src/layouts/components/Share.astro
Normal file
55
src/layouts/components/Share.astro
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
import config from "@/config/config.json";
|
||||
import {
|
||||
IoLogoFacebook,
|
||||
IoLogoLinkedin,
|
||||
IoLogoPinterest,
|
||||
IoLogoTwitter,
|
||||
} from "react-icons/io5";
|
||||
|
||||
const { base_url } = config.site;
|
||||
const { title, description, slug, className } = Astro.props;
|
||||
---
|
||||
|
||||
<ul class={`${className}`}>
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="facebook share button"
|
||||
href={`https://facebook.com/sharer/sharer.php?u=${base_url}/${slug}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<IoLogoFacebook />
|
||||
</a>
|
||||
</li>
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="twitter share button"
|
||||
href={`https://twitter.com/intent/tweet/?text=${title}&url=${base_url}/${slug}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<IoLogoTwitter />
|
||||
</a>
|
||||
</li>
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="linkedin share button"
|
||||
href={`https://www.linkedin.com/shareArticle?mini=true&url=${base_url}/${slug}&title=${title}&summary=${description}&source=${base_url}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<IoLogoLinkedin />
|
||||
</a>
|
||||
</li>
|
||||
<li class="inline-block">
|
||||
<a
|
||||
aria-label="pinterest share button"
|
||||
href={`https://pinterest.com/pin/create/button/?url=${base_url}/${slug}&media=&description=${description}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<IoLogoPinterest />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
62
src/layouts/components/SimilarPosts.astro
Normal file
62
src/layouts/components/SimilarPosts.astro
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
import dateFormat from "@/lib/utils/dateFormat";
|
||||
import { humanize, slugify } from "@/lib/utils/textConverter";
|
||||
import { BiCalendarEdit, BiCategoryAlt } from "react-icons/bi";
|
||||
const { posts } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="row gy-4 justify-center">
|
||||
{
|
||||
posts.map((post: any, i: number) => (
|
||||
<div class={`col-12 sm:col-6 md:col-4 ${i === 2 && "hidden md:block"}`}>
|
||||
{post.data.image && (
|
||||
<a
|
||||
href={`/${post.slug}`}
|
||||
class="rounded-lg block hover:text-primary overflow-hidden group"
|
||||
>
|
||||
<Image
|
||||
class="group-hover:scale-[1.05] transition duration-300 w-full"
|
||||
src={post.data.image}
|
||||
alt={post.data.title}
|
||||
width={445}
|
||||
height={230}
|
||||
/>
|
||||
</a>
|
||||
)}
|
||||
<ul class="mt-4 text-text flex flex-wrap items-center text-sm">
|
||||
<li class="mb-2 mr-4 flex items-center flex-wrap font-medium">
|
||||
<BiCalendarEdit className="mr-1 h-[16px] w-[16px] text-gray-600" />
|
||||
<>{dateFormat(post.data.date)}</>
|
||||
</li>
|
||||
<li class="mb-2 mr-4 flex items-center flex-wrap">
|
||||
<BiCategoryAlt className="mr-1 h-[16px] w-[16px] text-gray-600" />
|
||||
<>
|
||||
<ul>
|
||||
{post.data.categories.map((category: string, i: number) => (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
href={`/categories/${slugify(category)}`}
|
||||
class="mr-2 hover:text-primary font-medium"
|
||||
>
|
||||
{humanize(category)}
|
||||
{i !== post.data.categories.length - 1 && ","}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
</li>
|
||||
</ul>
|
||||
<h3 class="h5">
|
||||
<a
|
||||
href={`/${post.slug}`}
|
||||
class="block hover:text-primary transition duration-300"
|
||||
>
|
||||
{post.data.title}
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
498
src/layouts/components/Social.astro
Normal file
498
src/layouts/components/Social.astro
Normal 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";
|
||||
|
||||
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>
|
||||
15
src/layouts/components/TwSizeIndicator.astro
Normal file
15
src/layouts/components/TwSizeIndicator.astro
Normal 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>
|
||||
)
|
||||
}
|
||||
28
src/layouts/partials/Footer.astro
Normal file
28
src/layouts/partials/Footer.astro
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
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="section bg-theme-dark">
|
||||
<div class="container text-center">
|
||||
{/* footer menu */}
|
||||
<ul class="mb-8 space-x-4">
|
||||
{
|
||||
menu.footer.map((menu) => (
|
||||
<li class="inline-block">
|
||||
<a href={menu.url} class="p-4 text-white/75 hover:text-white transition-all">
|
||||
{menu.name}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
{/* social icons */}
|
||||
<Social source={social} className="social-icons mb-8" />
|
||||
{/* copyright */}
|
||||
<p set:html={markdownify(config.params.copyright)} class="text-white/75" />
|
||||
</div>
|
||||
</footer>
|
||||
100
src/layouts/partials/Header.astro
Normal file
100
src/layouts/partials/Header.astro
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
import Logo from "@/components/Logo.astro";
|
||||
import menu from "@/config/menu.json";
|
||||
import { IoSearch } from "react-icons/io5";
|
||||
|
||||
export interface ChildNavigationLink {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface NavigationLink {
|
||||
name: string;
|
||||
url: string;
|
||||
hasChildren?: boolean;
|
||||
children?: ChildNavigationLink[];
|
||||
}
|
||||
|
||||
const { main }: { main: NavigationLink[] } = menu;
|
||||
---
|
||||
|
||||
<header class="header pt-6">
|
||||
<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-2 flex cursor-pointer items-center text-black md:order-1 md: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-2 hidden cursor-pointer items-center text-black md: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 md:order-1 md:flex md:w-auto"
|
||||
>
|
||||
{
|
||||
main.map((menu) => (
|
||||
<>
|
||||
{menu.hasChildren ? (
|
||||
<li class="nav-item nav-dropdown group relative cursor-pointer">
|
||||
<span class="nav-link inline-flex items-center">
|
||||
{menu.name}
|
||||
<svg class="h-5 w-5 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">
|
||||
{child.name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
) : (
|
||||
<li class="nav-item">
|
||||
<a href={menu.url} class="nav-link block">
|
||||
{menu.name}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
</>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
<div class="order-1 ml-auto flex md:order-2 md:ml-0">
|
||||
<a
|
||||
class="mr-4 inline-block cursor-pointer p-2 text-xl text-dark transition hover:text-primary md:mr-0"
|
||||
href="/search"
|
||||
title="seach"
|
||||
aria-label="seach"
|
||||
>
|
||||
<IoSearch />
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
36
src/layouts/shortcodes/Accordion.tsx
Normal file
36
src/layouts/shortcodes/Accordion.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
const Accordion = ({
|
||||
title,
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) => {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={`accordion ${show && "active"} ${className}`}>
|
||||
<button 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>
|
||||
</button>
|
||||
<div className="accordion-content">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Accordion;
|
||||
30
src/layouts/shortcodes/Button.tsx
Normal file
30
src/layouts/shortcodes/Button.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
|
||||
const Button = ({
|
||||
label,
|
||||
link,
|
||||
style,
|
||||
rel,
|
||||
}: {
|
||||
label: string;
|
||||
link: string;
|
||||
style?: string;
|
||||
rel?: string;
|
||||
}) => {
|
||||
return (
|
||||
<a
|
||||
href={link}
|
||||
target="_blank"
|
||||
rel={`noopener noreferrer ${
|
||||
rel ? (rel === "follow" ? "" : rel) : "nofollow"
|
||||
}`}
|
||||
className={`btn mb-4 me-4 hover:text-white no-underline ${
|
||||
style === "outline" ? "btn-outline-primary" : "btn-primary"
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
85
src/layouts/shortcodes/Notice.tsx
Normal file
85
src/layouts/shortcodes/Notice.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { humanize } from "@/lib/utils/textConverter";
|
||||
import React from "react";
|
||||
|
||||
function Notice({
|
||||
type,
|
||||
children,
|
||||
}: {
|
||||
type: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
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"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
<p className="my-0 ml-1.5">{humanize(type)}</p>
|
||||
</div>
|
||||
<div className="notice-body">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Notice;
|
||||
7
src/layouts/shortcodes/Tab.tsx
Normal file
7
src/layouts/shortcodes/Tab.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
function Tab({ name, children }: { name: string; children: React.ReactNode }) {
|
||||
return <div data-name={name}>{children}</div>;
|
||||
}
|
||||
|
||||
export default Tab;
|
||||
76
src/layouts/shortcodes/Tabs.tsx
Normal file
76
src/layouts/shortcodes/Tabs.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { marked } from "marked";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
marked.use({
|
||||
mangle: false,
|
||||
headerIds: false,
|
||||
});
|
||||
|
||||
const Tabs = ({ children }: { children: React.ReactElement }) => {
|
||||
const [active, setActive] = useState<number>(0);
|
||||
const [defaultFocus, setDefaultFocus] = useState<boolean>(false);
|
||||
|
||||
const tabRefs: React.RefObject<HTMLElement[]> = useRef([]);
|
||||
useEffect(() => {
|
||||
if (defaultFocus) {
|
||||
//@ts-ignore
|
||||
tabRefs.current[active]?.focus();
|
||||
} else {
|
||||
setDefaultFocus(true);
|
||||
}
|
||||
}, [active]);
|
||||
|
||||
const tabLinks = Array.from(
|
||||
children.props.value.matchAll(
|
||||
/<div\s+data-name="([^"]+)"[^>]*>(.*?)<\/div>/gs,
|
||||
),
|
||||
(match: RegExpMatchArray) => ({ name: match[1], children: match[0] }),
|
||||
);
|
||||
|
||||
const handleKeyDown = (
|
||||
event: React.KeyboardEvent<EventTarget>,
|
||||
index: number,
|
||||
) => {
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
setActive(index);
|
||||
} else if (event.key === "ArrowRight") {
|
||||
setActive((active + 1) % tabLinks.length);
|
||||
} else if (event.key === "ArrowLeft") {
|
||||
setActive((active - 1 + tabLinks.length) % tabLinks.length);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="tab">
|
||||
<ul className="tab-nav">
|
||||
{tabLinks.map(
|
||||
(item: { name: string; children: string }, index: number) => (
|
||||
<li
|
||||
key={index}
|
||||
className={`tab-nav-item ${index === active && "active"}`}
|
||||
role="tab"
|
||||
tabIndex={index === active ? 0 : -1}
|
||||
onKeyDown={(event) => handleKeyDown(event, index)}
|
||||
onClick={() => setActive(index)}
|
||||
//@ts-ignore
|
||||
ref={(ref) => (tabRefs.current[index] = ref)}
|
||||
>
|
||||
{item.name}
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
{tabLinks.map((item: { name: string; children: string }, i: number) => (
|
||||
<div
|
||||
className={active === i ? "tab-content block px-5" : "hidden"}
|
||||
key={i}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: marked.parse(item.children),
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tabs;
|
||||
32
src/layouts/shortcodes/Video.tsx
Normal file
32
src/layouts/shortcodes/Video.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
function Video({
|
||||
title,
|
||||
width = 500,
|
||||
height = "auto",
|
||||
src,
|
||||
...rest
|
||||
}: {
|
||||
title: string;
|
||||
width: number;
|
||||
height: number | "auto";
|
||||
src: string;
|
||||
[key: string]: 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;
|
||||
24
src/layouts/shortcodes/Youtube.tsx
Normal file
24
src/layouts/shortcodes/Youtube.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
import LiteYouTubeEmbed from "react-lite-youtube-embed";
|
||||
import "react-lite-youtube-embed/dist/LiteYouTubeEmbed.css";
|
||||
|
||||
const Youtube = ({
|
||||
id,
|
||||
title,
|
||||
...rest
|
||||
}: {
|
||||
id: string;
|
||||
title: string;
|
||||
[key: string]: any;
|
||||
}) => {
|
||||
return (
|
||||
<LiteYouTubeEmbed
|
||||
wrapperClass="yt-lite rounded-lg"
|
||||
id={id}
|
||||
title={title}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Youtube;
|
||||
16
src/lib/contentParser.astro
Normal file
16
src/lib/contentParser.astro
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
import {
|
||||
getCollection,
|
||||
type CollectionEntry,
|
||||
type CollectionKey,
|
||||
} from "astro:content";
|
||||
|
||||
export const getSinglePage = async <C extends CollectionKey>(
|
||||
collectionName: C,
|
||||
): Promise<CollectionEntry<C>[]> => {
|
||||
const allPages = await getCollection(collectionName);
|
||||
const removeIndex = allPages.filter((data) => data.id.match(/^(?!-)/));
|
||||
const removeDrafts = removeIndex.filter((data) => !data.data.draft);
|
||||
return removeDrafts;
|
||||
};
|
||||
---
|
||||
33
src/lib/taxonomyParser.astro
Normal file
33
src/lib/taxonomyParser.astro
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
import { getSinglePage } from "@/lib/contentParser.astro";
|
||||
import { slugify } from "@/lib/utils/textConverter";
|
||||
|
||||
// get taxonomy from frontmatter
|
||||
export const getTaxonomy = async (collection: any, name: string) => {
|
||||
const singlePages = await getSinglePage(collection);
|
||||
const taxonomyPages = singlePages.map((page: any) => page.data[name]);
|
||||
let taxonomies: string[] = [];
|
||||
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;
|
||||
};
|
||||
|
||||
// get all taxonomies from frontmatter
|
||||
export const getAllTaxonomy = async (collection: any, name: string) => {
|
||||
const singlePages = await getSinglePage(collection);
|
||||
const taxonomyPages = singlePages.map((page: any) => page.data[name]);
|
||||
let taxonomies: string[] = [];
|
||||
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;
|
||||
};
|
||||
---
|
||||
12
src/lib/utils/dateFormat.ts
Normal file
12
src/lib/utils/dateFormat.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { format } from "date-fns";
|
||||
|
||||
const dateFormat = (
|
||||
date: Date | string,
|
||||
pattern: string = "dd MMM, yyyy",
|
||||
): string => {
|
||||
const dateObj = new Date(date);
|
||||
const output = format(dateObj, pattern);
|
||||
return output;
|
||||
};
|
||||
|
||||
export default dateFormat;
|
||||
40
src/lib/utils/readingTime.ts
Normal file
40
src/lib/utils/readingTime.ts
Normal 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
src/lib/utils/similarItems.ts
Normal file
36
src/lib/utils/similarItems.ts
Normal 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
src/lib/utils/sortFunctions.ts
Normal file
25
src/lib/utils/sortFunctions.ts
Normal 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
src/lib/utils/taxonomyFilter.ts
Normal file
8
src/lib/utils/taxonomyFilter.ts
Normal 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
src/lib/utils/textConverter.ts
Normal file
57
src/lib/utils/textConverter.ts
Normal 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 } = {
|
||||
" ": " ",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"&": "&",
|
||||
""": '"',
|
||||
"'": "'",
|
||||
};
|
||||
let htmlWithoutEntities: string = htmlWithEntities.replace(
|
||||
/(&|<|>|"|')/g,
|
||||
(entity: string): string => {
|
||||
return entityList[entity];
|
||||
}
|
||||
);
|
||||
return htmlWithoutEntities;
|
||||
};
|
||||
20
src/pages/404.astro
Normal file
20
src/pages/404.astro
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
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">
|
||||
<div class="container">
|
||||
<div class="flex h-[40vh] items-center justify-center">
|
||||
<div class="text-center">
|
||||
<h1 class="mb-4" set:html={markdownify(entry.data.title)} />
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
42
src/pages/[regular].astro
Normal file
42
src/pages/[regular].astro
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import Default from "@/layouts/Default.astro";
|
||||
import PostSingle from "@/layouts/PostSingle.astro";
|
||||
import { getSinglePage } from "@/lib/contentParser.astro";
|
||||
import type { TPost } from "@/types";
|
||||
|
||||
const getPosts = (await getSinglePage("posts")) as TPost[];
|
||||
const postsSlug = getPosts.map((item) => item.slug);
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getSinglePage("posts");
|
||||
const pages = await getSinglePage("pages");
|
||||
const allPages = [...pages, ...posts];
|
||||
|
||||
const paths = allPages.map((page: any) => ({
|
||||
params: {
|
||||
regular: page.slug,
|
||||
},
|
||||
props: { page },
|
||||
}));
|
||||
return paths;
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
const { title, meta_title, description, image } = page.data;
|
||||
---
|
||||
|
||||
<Base
|
||||
title={title}
|
||||
meta_title={meta_title}
|
||||
description={description}
|
||||
image={image}
|
||||
>
|
||||
{
|
||||
postsSlug.includes(page.slug) ? (
|
||||
<PostSingle post={page} />
|
||||
) : (
|
||||
<Default data={page} />
|
||||
)
|
||||
}
|
||||
</Base>
|
||||
66
src/pages/about.astro
Normal file
66
src/pages/about.astro
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import { markdownify } from "@/lib/utils/textConverter";
|
||||
import { getEntryBySlug } from "astro:content";
|
||||
|
||||
const entry = await getEntryBySlug("about", "index");
|
||||
const { Content } = await entry.render();
|
||||
const { title, description, meta_title, image, what_i_do } = entry.data;
|
||||
---
|
||||
|
||||
<Base
|
||||
title={title}
|
||||
meta_title={meta_title}
|
||||
description={description}
|
||||
image={image}
|
||||
>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="row md:gx-4">
|
||||
<div class="sm:col-5 md:col-4">
|
||||
{
|
||||
image && (
|
||||
<div class="img-cover mb-8">
|
||||
<Image
|
||||
src={image}
|
||||
width={295}
|
||||
height={395}
|
||||
alt={title}
|
||||
class="rounded-lg w-full"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div class="sm:col-7 md:col-8">
|
||||
<h1 set:html={markdownify(title)} class="h3 mb-8" />
|
||||
|
||||
<div class="content">
|
||||
<Content />
|
||||
</div>
|
||||
<a href="/contact" class="btn btn-primary text-white py-2"
|
||||
>Get In Touch</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section pt-0">
|
||||
<div class="container">
|
||||
<h3 class="page-heading mb-20">{what_i_do.title}</h3>
|
||||
<div class="row justify-center gy-4 text-center">
|
||||
{
|
||||
what_i_do.items.map((item: any) => (
|
||||
<div class="lg:col-4 md:col-6">
|
||||
<i class="{{.icon}} fa-3x text-primary mb-4" />
|
||||
<h4 class="text-dark font-weight-700 mb-3">{item.title}</h4>
|
||||
<p>{item.description}</p>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
110
src/pages/authors/[single].astro
Normal file
110
src/pages/authors/[single].astro
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
import AuthorSingle from "@/layouts/AuthorSingle.astro";
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import { getSinglePage } from "@/lib/contentParser.astro";
|
||||
import dateFormat from "@/lib/utils/dateFormat";
|
||||
|
||||
import { sortByDate } from "@/lib/utils/sortFunctions";
|
||||
import { humanize, slugify } from "@/lib/utils/textConverter";
|
||||
import { BiCalendarEdit, BiCategoryAlt } from "react-icons/bi";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const authors = await getSinglePage("authors");
|
||||
|
||||
const paths = authors.map((author: any) => ({
|
||||
params: {
|
||||
single: author.slug,
|
||||
},
|
||||
props: { author },
|
||||
}));
|
||||
return paths;
|
||||
}
|
||||
|
||||
const { author } = Astro.props;
|
||||
const { title, meta_title, description, image } = author.data;
|
||||
|
||||
// Author Posts
|
||||
const posts = await getSinglePage("posts");
|
||||
const sortPostsByDate = sortByDate(posts);
|
||||
const currentPosts = sortPostsByDate.filter((post) => {
|
||||
return post.data.authors
|
||||
.map((author: string) => slugify(author))
|
||||
.includes(slugify(title));
|
||||
});
|
||||
---
|
||||
|
||||
<Base
|
||||
title={title}
|
||||
meta_title={meta_title}
|
||||
description={description}
|
||||
image={image}
|
||||
>
|
||||
<AuthorSingle author={author} />
|
||||
|
||||
{
|
||||
currentPosts.length > 0 && (
|
||||
<section class="section pt-0">
|
||||
<div class="container">
|
||||
<h2 class="mb-8 text-center h3">Recent Posts</h2>
|
||||
<div
|
||||
class={`row gy-4 ${currentPosts.length < 3 ? "justify-center" : ""}`}
|
||||
>
|
||||
{currentPosts.map((post: any, i: number) => (
|
||||
<div class="col-12 sm:col-6 lg:col-4">
|
||||
{post.data.image && (
|
||||
<a
|
||||
href={`/${post.slug}`}
|
||||
class="rounded-lg block hover:text-primary overflow-hidden group"
|
||||
>
|
||||
<Image
|
||||
class="group-hover:scale-[1.05] transition duration-300 w-full"
|
||||
src={post.data.image}
|
||||
alt={post.data.title}
|
||||
width={445}
|
||||
height={230}
|
||||
/>
|
||||
</a>
|
||||
)}
|
||||
<ul class="mt-4 text-text flex flex-wrap items-center text-sm">
|
||||
<li class="mb-2 mr-4 flex items-center flex-wrap font-medium">
|
||||
<BiCalendarEdit className="mr-1 h-[16px] w-[16px] text-gray-600" />
|
||||
<>{dateFormat(post.data.date)}</>
|
||||
</li>
|
||||
<li class="mb-2 mr-4 flex items-center flex-wrap">
|
||||
<BiCategoryAlt className="mr-1 h-[16px] w-[16px] text-gray-600" />
|
||||
<>
|
||||
<ul>
|
||||
{post.data.categories.map(
|
||||
(category: string, i: number) => (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
href={`/categories/${slugify(category)}`}
|
||||
class="mr-2 hover:text-primary font-medium"
|
||||
>
|
||||
{humanize(category)}
|
||||
{i !== post.data.categories.length - 1 && ","}
|
||||
</a>
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
</li>
|
||||
</ul>
|
||||
<h3 class="h5">
|
||||
<a
|
||||
href={`/${post.slug}`}
|
||||
class="block hover:text-primary transition duration-300"
|
||||
>
|
||||
{post.data.title}
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
</Base>
|
||||
24
src/pages/authors/index.astro
Normal file
24
src/pages/authors/index.astro
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
import config from "@/config/config.json";
|
||||
import Authors from "@/layouts/Authors.astro";
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import Pagination from "@/layouts/components/Pagination.astro";
|
||||
import { getSinglePage } from "@/lib/contentParser.astro";
|
||||
import { sortByDate } from "@/lib/utils/sortFunctions";
|
||||
import { markdownify } from "@/lib/utils/textConverter";
|
||||
|
||||
const authors = await getSinglePage("authors");
|
||||
const sortedPosts = sortByDate(authors);
|
||||
const totalPages = Math.ceil(authors.length / config.settings.pagination);
|
||||
const currentPosts = sortedPosts.slice(0, config.settings.pagination);
|
||||
---
|
||||
|
||||
<Base title={"Authors"}>
|
||||
<section class="section">
|
||||
<div class="container text-center">
|
||||
<h1 set:text={markdownify("Authors")} class="page-heading h2" />
|
||||
<Authors authors={currentPosts} />
|
||||
<Pagination section={"authors"} currentPage={1} totalPages={totalPages} />
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
47
src/pages/authors/page/[slug].astro
Normal file
47
src/pages/authors/page/[slug].astro
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
import config from "@/config/config.json";
|
||||
import Authors from "@/layouts/Authors.astro";
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import Pagination from "@/layouts/components/Pagination.astro";
|
||||
import { getSinglePage } from "@/lib/contentParser.astro";
|
||||
import { sortByDate } from "@/lib/utils/sortFunctions";
|
||||
import { markdownify } from "@/lib/utils/textConverter";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const authors = await getSinglePage("authors");
|
||||
const totalPages = Math.ceil(authors.length / config.settings.pagination);
|
||||
const paths = [];
|
||||
|
||||
for (let i = 1; i < totalPages; i++) {
|
||||
paths.push({
|
||||
params: {
|
||||
slug: (i + 1).toString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
const { slug } = Astro.params;
|
||||
const authors = await getSinglePage("authors");
|
||||
const sortedPosts = sortByDate(authors);
|
||||
const totalPages = Math.ceil(authors.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);
|
||||
---
|
||||
|
||||
<Base title={"Authors"}>
|
||||
<section class="section">
|
||||
<div class="container text-center">
|
||||
<h1 set:text={markdownify("Authors")} class="page-heading h2" />
|
||||
<Authors authors={currentPosts} />
|
||||
<Pagination
|
||||
section={"authors"}
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
35
src/pages/categories/[category].astro
Normal file
35
src/pages/categories/[category].astro
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import Posts from "@/layouts/Posts.astro";
|
||||
import { getSinglePage } from "@/lib/contentParser.astro";
|
||||
import { getTaxonomy } from "@/lib/taxonomyParser.astro";
|
||||
import taxonomyFilter from "@/lib/utils/taxonomyFilter";
|
||||
import { humanize } from "@/lib/utils/textConverter";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const categories = await getTaxonomy("posts", "categories");
|
||||
|
||||
return categories.map((category) => {
|
||||
return {
|
||||
params: { category },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const { category } = Astro.params;
|
||||
|
||||
const posts = await getSinglePage("posts");
|
||||
const filterByCategory = taxonomyFilter(posts, "categories", category);
|
||||
|
||||
const title = humanize(category || "");
|
||||
---
|
||||
|
||||
<Base title={title || "Category"}>
|
||||
<div class="section">
|
||||
<div class="container">
|
||||
<p class="text-center mb-4">Showing Posts From</p>
|
||||
<h1 class="h2 mb-16 text-center text-primary">{title}</h1>
|
||||
<Posts posts={filterByCategory} fluid={false} />
|
||||
</div>
|
||||
</div>
|
||||
</Base>
|
||||
32
src/pages/categories/index.astro
Normal file
32
src/pages/categories/index.astro
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import { getTaxonomy } from "@/lib/taxonomyParser.astro";
|
||||
import { humanize } from "@/lib/utils/textConverter";
|
||||
|
||||
const categories = await getTaxonomy("posts", "categories");
|
||||
|
||||
import { BiCategoryAlt } from "react-icons/bi";
|
||||
---
|
||||
|
||||
<Base title={"Categories"}>
|
||||
<section class="section">
|
||||
<div class="container text-center">
|
||||
<h1 class="h2 page-heading">Categories</h1>
|
||||
<ul class="space-x-4">
|
||||
{
|
||||
categories.map((category) => (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
href={`/categories/${category}`}
|
||||
class="rounded-lg bg-theme-light px-4 py-2 text-dark transition hover:bg-primary hover:text-white flex items-center group"
|
||||
>
|
||||
<BiCategoryAlt className="mr-1 text-primary group-hover:text-white transition h-6 w-6 scale-75" />
|
||||
<>{humanize(category || "")}</>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
80
src/pages/contact.astro
Normal file
80
src/pages/contact.astro
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
import config from "@/config/config.json";
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import { markdownify } from "@/lib/utils/textConverter";
|
||||
import { getEntryBySlug } from "astro:content";
|
||||
import { FaAddressCard, FaEnvelope, FaPhoneAlt } from "react-icons/fa";
|
||||
|
||||
const entry = await getEntryBySlug("pages", "contact");
|
||||
const { contact_form_action } = config.params;
|
||||
const { address, email, phone } = config.contactinfo;
|
||||
const { title, description, meta_title, image } = entry.data;
|
||||
---
|
||||
|
||||
<Base
|
||||
title={title}
|
||||
meta_title={meta_title}
|
||||
description={description}
|
||||
image={image}
|
||||
>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 set:html={markdownify(title)} class="h2 page-heading" />
|
||||
<div class="row md:gx-5 gy-5">
|
||||
<div class="sm:col-5 md:col-4">
|
||||
<p class="mb-8 text-2xl font-bold text-theme-dark">Contact Info</p>
|
||||
<ul class="flex flex-col space-y-8">
|
||||
<li>
|
||||
<div class="flex text-theme-dark items-center text-xl">
|
||||
<FaAddressCard className="mr-3 text-primary" />
|
||||
<p class="font-semibold">Address</p>
|
||||
</div>
|
||||
<p set:html={markdownify(address)} class="mt-2 leading-5 pl-8" />
|
||||
</li>
|
||||
<li>
|
||||
<div class="flex text-theme-dark items-center text-xl">
|
||||
<FaEnvelope className="mr-3 text-primary" />
|
||||
<p class="font-semibold">Email</p>
|
||||
</div>
|
||||
<p set:html={markdownify(email)} class="mt-2 leading-5 pl-8 content" />
|
||||
</li>
|
||||
<li>
|
||||
<div class="flex text-theme-dark items-center text-xl">
|
||||
<FaPhoneAlt className="mr-3 text-primary" />
|
||||
<p class="font-semibold">Phone</p>
|
||||
</div>
|
||||
<p set:html={markdownify(phone)} class="mt-2 leading-5 pl-8" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="sm:col-7 md:col-8">
|
||||
<form class="contact-form row gy-2 justify-center" method="POST" action={contact_form_action}>
|
||||
<div class="lg:col-6">
|
||||
<label class="mb-2 block" for="name">Name <span class="text-red-600">*</span></label>
|
||||
<input class="form-input w-full" name="name" type="text" required />
|
||||
</div>
|
||||
<div class="lg:col-6">
|
||||
<label class="mb-2 block" for="email">Email <span class="text-red-600">*</span></label>
|
||||
<input class="form-input w-full" name="email" type="email" required />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="mb-2 block" for="subject">Subject</label>
|
||||
<input
|
||||
class="form-input w-full"
|
||||
name="subject"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="mb-2 block" for="message">Message <span class="text-red-600">*</span></label>
|
||||
<textarea class="form-textarea w-full" rows="4"></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary mt-2">Submit Now</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
22
src/pages/index.astro
Normal file
22
src/pages/index.astro
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
import config from "@/config/config.json";
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import Pagination from "@/layouts/components/Pagination.astro";
|
||||
import Posts from "@/layouts/Posts.astro";
|
||||
import { getSinglePage } from "@/lib/contentParser.astro";
|
||||
import { sortByDate } from "@/lib/utils/sortFunctions";
|
||||
|
||||
const posts = await getSinglePage("posts");
|
||||
const sortedPosts = sortByDate(posts);
|
||||
const totalPages = Math.ceil(posts.length / config.settings.pagination);
|
||||
const currentPosts = sortedPosts.slice(0, config.settings.pagination);
|
||||
---
|
||||
|
||||
<Base>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<Posts posts={currentPosts} className="mb-16" />
|
||||
<Pagination currentPage={1} totalPages={totalPages} />
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
41
src/pages/page/[slug].astro
Normal file
41
src/pages/page/[slug].astro
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
import config from "@/config/config.json";
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import Pagination from "@/layouts/components/Pagination.astro";
|
||||
import Posts from "@/layouts/Posts.astro";
|
||||
import { getSinglePage } from "@/lib/contentParser.astro";
|
||||
import { sortByDate } from "@/lib/utils/sortFunctions";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getSinglePage("posts");
|
||||
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;
|
||||
}
|
||||
|
||||
const { slug } = Astro.params;
|
||||
const posts = await getSinglePage("posts");
|
||||
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);
|
||||
---
|
||||
|
||||
<Base>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<Posts className="mb-16" posts={currentPosts} />
|
||||
<Pagination totalPages={totalPages} currentPage={currentPage} />
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
23
src/pages/search.astro
Normal file
23
src/pages/search.astro
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import SearchBar from "@/layouts/SearchBar";
|
||||
import { getSinglePage } from "@/lib/contentParser.astro";
|
||||
|
||||
// Retrieve all articles
|
||||
const posts = await getSinglePage("posts");
|
||||
|
||||
// List of items to search in
|
||||
const searchList = posts.map((item: any) => ({
|
||||
slug: item.slug,
|
||||
data: item.data,
|
||||
content: item.body,
|
||||
}));
|
||||
---
|
||||
|
||||
<Base title={`Search`}>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<SearchBar client:load searchList={searchList} />
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
35
src/pages/tags/[tag].astro
Normal file
35
src/pages/tags/[tag].astro
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import Posts from "@/layouts/Posts.astro";
|
||||
import { getSinglePage } from "@/lib/contentParser.astro";
|
||||
import { getTaxonomy } from "@/lib/taxonomyParser.astro";
|
||||
import taxonomyFilter from "@/lib/utils/taxonomyFilter";
|
||||
import { humanize } from "@/lib/utils/textConverter";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const tags = await getTaxonomy("posts", "tags");
|
||||
|
||||
return tags.map((tag) => {
|
||||
return {
|
||||
params: { tag },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const { tag } = Astro.params;
|
||||
|
||||
const posts = await getSinglePage("posts");
|
||||
const filterByTags = taxonomyFilter(posts, "tags", tag);
|
||||
|
||||
const title = humanize(tag || "");
|
||||
---
|
||||
|
||||
<Base title={title || "Tag"}>
|
||||
<div class="section">
|
||||
<div class="container">
|
||||
<p class="text-center mb-4">Showing Posts From</p>
|
||||
<h1 class="h2 mb-16 text-center text-primary">{title}</h1>
|
||||
<Posts posts={filterByTags} fluid={false} />
|
||||
</div>
|
||||
</div>
|
||||
</Base>
|
||||
32
src/pages/tags/index.astro
Normal file
32
src/pages/tags/index.astro
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
import Base from "@/layouts/Base.astro";
|
||||
import { getTaxonomy } from "@/lib/taxonomyParser.astro";
|
||||
import { humanize } from "@/lib/utils/textConverter";
|
||||
|
||||
import { FaHashtag } from "react-icons/fa";
|
||||
|
||||
const tags = await getTaxonomy("posts", "tags");
|
||||
---
|
||||
|
||||
<Base title={"Tags"}>
|
||||
<section class="section">
|
||||
<div class="container text-center">
|
||||
<h1 class="h2 page-heading">Tags</h1>
|
||||
<ul class="space-x-4">
|
||||
{
|
||||
tags.map((tag) => (
|
||||
<li class="inline-block">
|
||||
<a
|
||||
href={`/tags/${tag}`}
|
||||
class="rounded-lg bg-theme-light px-4 py-2 text-dark transition hover:bg-primary hover:text-white flex items-center group"
|
||||
>
|
||||
<FaHashtag className="mr-1 text-primary group-hover:text-white transition" />
|
||||
<>{humanize(tag || "")}</>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</Base>
|
||||
46
src/styles/base.scss
Normal file
46
src/styles/base.scss
Normal file
@@ -0,0 +1,46 @@
|
||||
html {
|
||||
@apply text-base;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-body font-primary font-normal leading-relaxed text-text;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@apply font-primary font-bold leading-tight text-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;
|
||||
}
|
||||
|
||||
h5,
|
||||
.h5 {
|
||||
@apply text-h5;
|
||||
}
|
||||
|
||||
h6,
|
||||
.h6 {
|
||||
@apply text-h6;
|
||||
}
|
||||
11
src/styles/buttons.scss
Normal file
11
src/styles/buttons.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
.btn {
|
||||
@apply inline-block rounded-lg border px-6 py-3 font-semibold transition;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply bg-primary text-white hover:text-white;
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
@apply border-primary bg-transparent hover:bg-primary hover:text-white;
|
||||
}
|
||||
187
src/styles/components.scss
Normal file
187
src/styles/components.scss
Normal file
@@ -0,0 +1,187 @@
|
||||
// section style
|
||||
.section {
|
||||
@apply py-20;
|
||||
}
|
||||
|
||||
// container
|
||||
.container {
|
||||
@apply max-w-[1000px] px-4 md:px-8;
|
||||
}
|
||||
|
||||
// page heading
|
||||
.page-heading {
|
||||
@apply mb-20 text-center font-bold leading-10;
|
||||
&:after {
|
||||
@apply mx-auto mt-4 block h-1 w-10 rounded-full bg-primary content-[''];
|
||||
}
|
||||
}
|
||||
|
||||
// form style
|
||||
.form-inputs * {
|
||||
@apply mb-5 leading-10;
|
||||
}
|
||||
|
||||
// image cover
|
||||
.img-cover {
|
||||
@apply leading-none;
|
||||
span {
|
||||
@apply h-full w-full;
|
||||
}
|
||||
img {
|
||||
@apply object-cover;
|
||||
}
|
||||
}
|
||||
|
||||
// author-image
|
||||
.author-image {
|
||||
@apply mr-2 align-top;
|
||||
img {
|
||||
@apply max-h-[25px] max-w-[25px] rounded-full;
|
||||
}
|
||||
}
|
||||
|
||||
// social icon style
|
||||
.social-icons {
|
||||
@apply space-x-4;
|
||||
li {
|
||||
@apply inline-block;
|
||||
a {
|
||||
@apply block h-11 w-11 rounded-lg border border-light bg-transparent text-center text-white transition hover:border-primary hover:bg-primary;
|
||||
svg {
|
||||
@apply m-auto h-11 text-base;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.social-icons-simple {
|
||||
@apply space-x-2;
|
||||
li {
|
||||
@apply inline-block;
|
||||
a {
|
||||
@apply block p-3 text-dark transition hover:text-primary;
|
||||
svg {
|
||||
@apply text-lg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.social-share {
|
||||
@apply space-x-1;
|
||||
li {
|
||||
@apply inline-block;
|
||||
a {
|
||||
@apply block p-3 transition transition duration-300 hover:text-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// form style
|
||||
.form-input,
|
||||
.form-textarea {
|
||||
@apply rounded-md border-border py-3 text-text focus:border-primary focus:ring-transparent;
|
||||
}
|
||||
|
||||
// content style
|
||||
.content {
|
||||
@apply prose max-w-none prose-headings:font-bold prose-h1:mb-4 prose-h1:text-h1-sm prose-h2:mb-4 prose-h2:mt-4 prose-h2:text-h2-sm prose-h3:mt-4 prose-h3:text-h3-sm prose-h4:mt-4 prose-h5:mb-4 prose-h6:mb-6 prose-blockquote:rounded-lg prose-blockquote:border-primary prose-blockquote:bg-theme-light prose-blockquote:px-7 prose-blockquote:py-3 prose-blockquote:text-lg prose-blockquote:leading-8 prose-pre:px-6 prose-pre:py-5 md:prose-h1:text-h1 md:prose-h2:text-h2 md:prose-h3:text-h3;
|
||||
}
|
||||
|
||||
// tab
|
||||
.tab {
|
||||
@apply overflow-hidden rounded-lg border border-border;
|
||||
&-nav {
|
||||
@apply flex border-b border-border bg-theme-light;
|
||||
@apply m-0 #{!important};
|
||||
@apply list-none #{!important};
|
||||
|
||||
&-item {
|
||||
@apply cursor-pointer border-b-[3px] border-border py-2 text-lg text-dark opacity-80;
|
||||
@apply my-0 #{!important};
|
||||
@apply px-8 #{!important};
|
||||
|
||||
&.active {
|
||||
@apply border-b-[3px] border-dark opacity-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-content {
|
||||
&-panel {
|
||||
@apply p-8;
|
||||
p {
|
||||
@apply mb-0;
|
||||
}
|
||||
&.active {
|
||||
@apply block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// accordion
|
||||
.accordion {
|
||||
@apply mb-6 overflow-hidden rounded-lg border border-border bg-theme-light;
|
||||
&-header {
|
||||
@apply flex w-full cursor-pointer items-center justify-between px-8 py-4 text-lg text-dark;
|
||||
}
|
||||
&-icon {
|
||||
@apply ml-auto h-[.8em] w-[.8em] rotate-[-90deg] transition-transform duration-200;
|
||||
}
|
||||
&-content {
|
||||
@apply max-h-0 overflow-hidden px-8 py-0;
|
||||
}
|
||||
&.active {
|
||||
.accordion-icon {
|
||||
@apply rotate-0;
|
||||
}
|
||||
.accordion-content {
|
||||
@apply max-h-screen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// notice
|
||||
.notice {
|
||||
@apply mb-6 rounded-lg border px-8 py-6;
|
||||
&-head {
|
||||
@apply flex items-center;
|
||||
svg {
|
||||
@apply mr-3;
|
||||
}
|
||||
p {
|
||||
@apply font-secondary text-xl font-semibold text-dark;
|
||||
}
|
||||
}
|
||||
.notice-body {
|
||||
@apply mt-3;
|
||||
p {
|
||||
@apply my-0;
|
||||
}
|
||||
}
|
||||
|
||||
&.note {
|
||||
@apply text-[#1B83E2];
|
||||
@apply border-current;
|
||||
}
|
||||
|
||||
&.tip {
|
||||
@apply text-[#40D294];
|
||||
@apply border-current;
|
||||
}
|
||||
|
||||
&.info {
|
||||
@apply text-[#E3A72C];
|
||||
@apply border-current;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
@apply text-[#DB2C23];
|
||||
@apply border-current;
|
||||
}
|
||||
}
|
||||
|
||||
// footer
|
||||
footer p a {
|
||||
@apply transition-all duration-200 hover:text-white;
|
||||
}
|
||||
17
src/styles/main.scss
Normal file
17
src/styles/main.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
@import "base";
|
||||
}
|
||||
|
||||
@layer components {
|
||||
@import "components";
|
||||
@import "navigation";
|
||||
@import "buttons";
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
@import "utilities";
|
||||
}
|
||||
59
src/styles/navigation.scss
Normal file
59
src/styles/navigation.scss
Normal file
@@ -0,0 +1,59 @@
|
||||
// navbar toggler
|
||||
input#nav-toggle:checked ~ label#show-button {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
input#nav-toggle:checked ~ label#hide-button {
|
||||
@apply flex md:hidden;
|
||||
}
|
||||
|
||||
input#nav-toggle:checked ~ #nav-menu {
|
||||
@apply block md:flex;
|
||||
}
|
||||
|
||||
// navbar items
|
||||
.navbar {
|
||||
@apply relative flex max-w-[1260px] flex-wrap items-center justify-between;
|
||||
}
|
||||
|
||||
.navbar-brand img {
|
||||
@apply h-auto max-w-[170px] sm:h-auto sm:max-w-full #{!important};
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
@apply text-center md:text-left;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
@apply mx-1;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
@apply p-3 text-lg font-semibold text-dark transition hover:text-primary md:p-4;
|
||||
}
|
||||
|
||||
.nav-dropdown-list {
|
||||
@apply z-10 rounded-lg bg-white px-6 py-3 shadow transition;
|
||||
}
|
||||
|
||||
.nav-dropdown-item {
|
||||
@apply mb-1;
|
||||
}
|
||||
|
||||
.nav-dropdown-link {
|
||||
@apply block min-w-[150px] py-1 text-[17px] font-semibold text-dark transition hover:text-primary;
|
||||
}
|
||||
|
||||
// search style
|
||||
.search-modal {
|
||||
@apply invisible absolute top-0 left-0 right-0 z-10 h-10 bg-white opacity-0 transition md:h-full;
|
||||
.form-input {
|
||||
@apply h-full w-full border-0 text-lg;
|
||||
}
|
||||
.search-close {
|
||||
@apply absolute top-1/2 right-2 -translate-y-1/2 p-3 text-h4;
|
||||
}
|
||||
&.open {
|
||||
@apply visible opacity-100;
|
||||
}
|
||||
}
|
||||
12
src/styles/utilities.scss
Normal file
12
src/styles/utilities.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
b,
|
||||
strong {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
img {
|
||||
@apply inline-block;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
box-shadow: 0 10px 30px rgb(22 28 45 / 10%);
|
||||
}
|
||||
35
src/types/index.d.ts
vendored
Normal file
35
src/types/index.d.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
export interface TAuthor {
|
||||
id: string;
|
||||
slug: string;
|
||||
body: string;
|
||||
collection: string;
|
||||
data: {
|
||||
title: string;
|
||||
image: string;
|
||||
description: string;
|
||||
social: {
|
||||
facebook: string;
|
||||
twitter: string;
|
||||
instagram: string;
|
||||
}
|
||||
};
|
||||
render: () => Promise<{ Content: any }>;
|
||||
}
|
||||
|
||||
export interface TPost {
|
||||
id: string;
|
||||
slug: string;
|
||||
body: string;
|
||||
collection: string;
|
||||
data: {
|
||||
title: string;
|
||||
description: string;
|
||||
date: any;
|
||||
image: string;
|
||||
authors: string[];
|
||||
categories: string[];
|
||||
tags: string[];
|
||||
draft: boolean;
|
||||
};
|
||||
render: () => Promise<{ Content: any }>;
|
||||
}
|
||||
Reference in New Issue
Block a user