Compare commits

...

2 Commits

Author SHA1 Message Date
Kunthawat Greethong
61daea846c chore: Remove .next folder from git (build artifact) 2026-03-03 10:22:40 +07:00
Kunthawat Greethong
45961b8d76 refactor: Move Astro project to root directory
- Move all Astro files from dealplustech-astro/ to root
- Archive Next.js code in _nextjs-backup/
- Update .gitignore for Astro project
- Simplify project structure

This completes the migration from Next.js to Astro.
The Astro project is now at the root level.
2026-03-03 10:21:42 +07:00
303 changed files with 89 additions and 30894 deletions

View File

@@ -1,42 +0,0 @@
# Environment Configuration for Deal Plus Tech Website
# Copy this file to .env.local and fill in the values
# Site Configuration
NEXT_PUBLIC_SITE_URL=https://dealplustech.co.th
# Contact Information
NEXT_PUBLIC_PHONE=090-555-1415
NEXT_PUBLIC_EMAIL=info@dealplustech.co.th
NEXT_PUBLIC_LINE_ID=@dealplustech
NEXT_PUBLIC_FACEBOOK_URL=https://facebook.com/dealplustech
# Company Information
NEXT_PUBLIC_COMPANY_ADDRESS=บริษัท ดีล พลัส เทค จำกัด 9/70 ซอยนครลุง 17 แขวงบางไผ่ เขตบางแค กทม. 10160
# Google Analytics 4 (Required for tracking)
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
# Google Search Console Verification (optional)
# NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION=your-verification-code
# WordPress API (optional - for blog integration)
# NEXT_PUBLIC_WORDPRESS_API_URL=https://your-wordpress-site.com/wp-json/wp/v2
# Copy this file to .env.local and fill in the values
# Site Configuration
NEXT_PUBLIC_SITE_URL=https://dealplustech.co.th
# Contact Information
NEXT_PUBLIC_PHONE=090-555-1415
NEXT_PUBLIC_EMAIL=dealplustech@gmail.com
NEXT_PUBLIC_LINE_ID=jppselection
NEXT_PUBLIC_FACEBOOK_URL=https://www.facebook.com/Dealplustech/
# Company Information
NEXT_PUBLIC_COMPANY_ADDRESS=9/70 ซอยนครลุง 17 แขวงบางไผ่ เขตบางแค กทม. 10160
# Google Analytics (optional)
# NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
# Google Search Console Verification (optional)
# NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION=your-verification-code

63
.gitignore vendored
View File

@@ -1,39 +1,48 @@
# dependencies # Dependencies
/node_modules node_modules/
/.pnp .pnp
.pnp.js .pnp.js
.yarn/install-state.gz
# testing # Testing
/coverage coverage/
# next.js # Next.js (backup only)
/.next/ _nextjs-backup/
/out/
# production # Astro
/build .astro/
# misc # Build
dist/
build/
# Production
*.local
# Editor
.vscode/*
!.vscode/extensions.json
.idea/
.DS_Store .DS_Store
*.pem *.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# debug # Logs
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# local env files # Uploads
.env*.local ./upload/*
# Misc
.env .env
.env.local
# vercel .env.development.local
.vercel .env.test.local
.env.production.local
# typescript
*.tsbuildinfo
next-env.d.ts
# IDE
.idea/
.vscode/

140
README.md
View File

@@ -1,125 +1,43 @@
# Deal Plus Tech - Next.js Website # Astro Starter Kit: Minimal
Complete redesign of dealplustech.co.th website built with Next.js 14, TypeScript, and Tailwind CSS. ```sh
npm create astro@latest -- --template minimal
## Features
- 🚀 Next.js 14 with App Router
- 💅 Tailwind CSS for styling
- 📝 Headless WordPress integration for Blog
- 🌐 Thai URL support for product categories
- 📱 Fully responsive design
- 🔍 SEO optimized
## Getting Started
### Prerequisites
- Node.js 18.17 or later
- npm or yarn
### Installation
```bash
npm install
``` ```
### Environment Variables > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
Copy `.env.example` to `.env.local` and fill in your values: ## 🚀 Project Structure
```bash Inside of your Astro project, you'll see the following folders and files:
cp .env.example .env.local
```text
/
├── public/
├── src/
│ └── pages/
│ └── index.astro
└── package.json
``` ```
Required environment variables: Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
- `NEXT_PUBLIC_WORDPRESS_API_URL` - Your WordPress REST API URL (e.g., https://your-domain.com/wp-json/wp/v2)
- `NEXT_PUBLIC_SITE_URL` - Your production site URL
- `NEXT_PUBLIC_PHONE` - Contact phone number
- `NEXT_PUBLIC_EMAIL` - Contact email
- `NEXT_PUBLIC_LINE_ID` - LINE ID for contact
- `NEXT_PUBLIC_FACEBOOK_URL` - Facebook page URL
- `NEXT_PUBLIC_COMPANY_ADDRESS` - Company address
### Development There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
```bash Any static assets, like images, can be placed in the `public/` directory.
npm run dev
```
Open [http://localhost:3000](http://localhost:3000) in your browser. ## 🧞 Commands
### Production Build All commands are run from the root of the project, from a terminal:
```bash | Command | Action |
npm run build | :------------------------ | :----------------------------------------------- |
npm start | `npm install` | Installs dependencies |
``` | `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## Project Structure ## 👀 Want to learn more?
``` Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
src/
├── app/ # Next.js App Router pages
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Homepage
│ ├── about-us/ # About page
│ ├── services/ # Services page
│ ├── portfolio/ # Portfolio page
│ ├── contact-us/ # Contact page
│ ├── all-projects/ # FAQ page
│ ├── product/ # Products listing
│ ├── blog/ # Blog pages
│ └── [[...slug]]/ # Product category catch-all
├── components/
│ ├── layout/ # Header, Footer, FloatingContact
│ ├── sections/ # Page sections
│ └── ui/ # UI components
├── lib/
│ ├── utils.ts # Utility functions
│ └── wordpress.ts # WordPress API client
├── types/
│ └── index.ts # TypeScript types
├── data/
│ └── site-config.ts # Site configuration
└── styles/
└── globals.css # Global styles
```
## Deployment
### Vercel (Recommended)
1. Push your code to GitHub
2. Import your repository on Vercel
3. Add environment variables in Vercel dashboard
4. Deploy!
### Other Platforms
The project can be deployed to any platform that supports Next.js:
- Netlify
- AWS Amplify
- Docker
- Self-hosted Node.js server
## WordPress Integration
This site uses WordPress as a headless CMS for the Blog section.
### Setup WordPress
1. Install WordPress on your server
2. Ensure the REST API is enabled (default in modern WordPress)
3. Set `NEXT_PUBLIC_WORDPRESS_API_URL` to your WordPress API endpoint
4. Create posts with featured images for best display
### API Endpoints Used
- `/posts` - Blog posts
- `/categories` - Post categories
- `/_embed` - Embedded author and media data
## License
Private project for Deal Plus Tech Co., Ltd.

View File

@@ -1,168 +0,0 @@
{
"$ref": "#/definitions/products",
"definitions": {
"products": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"nameEn": {
"type": "string"
},
"slug": {
"type": "string"
},
"description": {
"type": "string"
},
"shortDescription": {
"type": "string"
},
"keywords": {
"type": "array",
"items": {
"type": "string"
}
},
"seoContent": {
"type": "string"
},
"image": {
"type": "string"
},
"specifications": {
"type": "array",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string"
},
"value": {
"type": "string"
},
"unit": {
"type": "string"
}
},
"required": [
"label",
"value"
],
"additionalProperties": false
}
},
"features": {
"type": "array",
"items": {
"type": "string"
}
},
"applications": {
"type": "array",
"items": {
"type": "string"
}
},
"certifications": {
"type": "array",
"items": {
"type": "string"
}
},
"productTables": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tableName": {
"type": "string"
},
"headers": {
"type": "array",
"items": {
"type": "string"
}
},
"rows": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"required": [
"tableName",
"headers",
"rows"
],
"additionalProperties": false
}
},
"faq": {
"type": "array",
"items": {
"type": "object",
"properties": {
"question": {
"type": "string"
},
"answer": {
"type": "string"
}
},
"required": [
"question",
"answer"
],
"additionalProperties": false
}
},
"relatedProductIds": {
"type": "array",
"items": {
"type": "string"
}
},
"schemaData": {
"type": "object",
"properties": {
"brand": {
"type": "string"
},
"manufacturer": {
"type": "string"
},
"material": {
"type": "string"
},
"category": {
"type": "string"
}
},
"additionalProperties": false
},
"$schema": {
"type": "string"
}
},
"required": [
"id",
"name",
"nameEn",
"slug",
"description",
"image"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View File

@@ -1 +0,0 @@
export default new Map();

View File

@@ -1 +0,0 @@
export default new Map();

View File

@@ -1,215 +0,0 @@
declare module 'astro:content' {
export interface RenderResult {
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}
interface Render {
'.md': Promise<RenderResult>;
}
export interface RenderedContent {
html: string;
metadata?: {
imagePaths: Array<string>;
[key: string]: unknown;
};
}
}
declare module 'astro:content' {
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof AnyEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
export type ContentCollectionKey = keyof ContentEntryMap;
export type DataCollectionKey = keyof DataEntryMap;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
ContentEntryMap[C]
>['slug'];
export type ReferenceDataEntry<
C extends CollectionKey,
E extends keyof DataEntryMap[C] = string,
> = {
collection: C;
id: E;
};
export type ReferenceContentEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}) = string,
> = {
collection: C;
slug: E;
};
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
collection: C;
id: string;
};
/** @deprecated Use `getEntry` instead. */
export function getEntryBySlug<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
// Note that this has to accept a regular string too, for SSR
entrySlug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
/** @deprecated Use `getEntry` instead. */
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
collection: C,
entryId: E,
): Promise<CollectionEntry<C>>;
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E,
): Promise<E[]>;
export function getCollection<C extends keyof AnyEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown,
): Promise<CollectionEntry<C>[]>;
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter?: LiveLoaderCollectionFilterType<C>,
): Promise<
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
entry: ReferenceContentEntry<C, E>,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
entry: ReferenceDataEntry<C, E>,
): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
slug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E,
): E extends keyof DataEntryMap[C]
? string extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]> | undefined
: Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter: string | LiveLoaderEntryFilterType<C>,
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof ContentEntryMap>(
entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[],
): Promise<CollectionEntry<C>[]>;
export function getEntries<C extends keyof DataEntryMap>(
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
): Promise<CollectionEntry<C>[]>;
export function render<C extends keyof AnyEntryMap>(
entry: AnyEntryMap[C][string],
): Promise<RenderResult>;
export function reference<C extends keyof AnyEntryMap>(
collection: C,
): import('astro/zod').ZodEffects<
import('astro/zod').ZodString,
C extends keyof ContentEntryMap
? ReferenceContentEntry<C, ValidContentEntrySlug<C>>
: ReferenceDataEntry<C, keyof DataEntryMap[C]>
>;
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
export function reference<C extends string>(
collection: C,
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ContentEntryMap = {
};
type DataEntryMap = {
"blog": Record<string, {
id: string;
body?: string;
collection: "blog";
data: any;
rendered?: RenderedContent;
filePath?: string;
}>;
"products": Record<string, {
id: string;
body?: string;
collection: "products";
data: any;
rendered?: RenderedContent;
filePath?: string;
}>;
};
type AnyEntryMap = ContentEntryMap & DataEntryMap;
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
infer TData,
infer TEntryFilter,
infer TCollectionFilter,
infer TError
>
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
: { data: never; entryFilter: never; collectionFilter: never; error: never };
type ExtractDataType<T> = ExtractLoaderTypes<T>['data'];
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
LiveContentConfig['collections'][C]['schema'] extends undefined
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
: import('astro/zod').infer<
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
>;
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
LiveContentConfig['collections'][C]['loader']
>;
export type ContentConfig = typeof import("../src/content.config.mjs");
export type LiveContentConfig = never;
}

View File

@@ -1,2 +0,0 @@
/// <reference types="astro/client" />
/// <reference path="content.d.ts" />

View File

@@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
dealplustech-astro/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View File

@@ -1,43 +0,0 @@
# Astro Starter Kit: Minimal
```sh
npm create astro@latest -- --template minimal
```
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
/
├── public/
├── src/
│ └── pages/
│ └── index.astro
└── package.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).

View File

@@ -1,3 +0,0 @@
const onRequest = (_, next) => next();
export { onRequest };

File diff suppressed because one or more lines are too long

View File

@@ -1,112 +0,0 @@
<!DOCTYPE html><html lang="th"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator" content="Astro v5.18.0"><meta name="description" content="ท่อ HDPE (High Density Polyethylene) เป็นท่อที่ได้รับความนิยมสูงในงานระบบน้ำ เนื่องจากความทนทานและความยืดหยุ่นที่เหนือกว่าท่อชนิดอื่น"><!-- Favicon --><link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="alternate icon" href="/favicon.ico" sizes="any"><link rel="apple-touch-icon" href="/favicon.svg"><!-- Google Fonts: Kanit for Thai --><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;500;600;700&display=swap" rel="stylesheet"><!-- SEO --><meta property="og:title" content="ข้อดีของท่อ HDPE ในงานระบบน้ำ ทำไมถึงเป็นตัวเลือกยอดนิยม"><meta property="og:description" content="ท่อ HDPE (High Density Polyethylene) เป็นท่อที่ได้รับความนิยมสูงในงานระบบน้ำ เนื่องจากความทนทานและความยืดหยุ่นที่เหนือกว่าท่อชนิดอื่น"><meta property="og:image" content="/og-image.jpg"><meta property="og:type" content="website"><meta name="twitter:card" content="summary_large_image"><title>ข้อดีของท่อ HDPE ในงานระบบน้ำ ทำไมถึงเป็นตัวเลือกยอดนิยม | ดีล พลัส เทค</title><style>html{font-family:Kanit,system-ui,sans-serif}
</style></head> <body class="flex flex-col min-h-screen"> <main class="pt-32 pb-16"> <article class="container mx-auto px-4 max-w-4xl"> <!-- Header --> <header class="mb-8"> <div class="flex items-center gap-4 mb-4"> <span class="industrial-badge">ท่อ HDPE</span> <time class="text-secondary-500"> 10 มกราคม 2567 </time> <span class="text-secondary-500"></span> <span class="text-secondary-500">Deal Plus Tech</span> </div> <h1 class="text-4xl md:text-5xl font-bold text-secondary-900 mb-4"> ข้อดีของท่อ HDPE ในงานระบบน้ำ ทำไมถึงเป็นตัวเลือกยอดนิยม </h1> </header> <!-- Featured Image --> <div class="relative aspect-video bg-secondary-100 rounded-xl overflow-hidden mb-8"> <img src="/images/2021/03/hdpe-pipe_000C.jpg" alt="ข้อดีของท่อ HDPE ในงานระบบน้ำ ทำไมถึงเป็นตัวเลือกยอดนิยม" class="object-cover w-full h-full" loading="lazy"> </div> <!-- Content --> <div class="prose prose-lg max-w-none prose-headings:font-bold prose-a:text-primary-600 hover:prose-a:text-primary-700 prose-img:rounded-xl"> <h2 id="ท่อ-hdpe-คืออะไร">ท่อ HDPE คืออะไร?</h2>
<p>ท่อ HDPE (High Density Polyethylene) หรือท่อเอชดีพีอี เป็นท่อที่ผลิตจากโพลิเอทิลีนความหนาแน่นสูง เป็นวัสดุพลาสติกที่มีความแข็งแรงและทนทานเป็นอย่างมาก</p>
<h2 id="ข้อดีของท่อ-hdpe">ข้อดีของท่อ HDPE</h2>
<h3 id="1-ความยืดหยุ่นสูง">1. ความยืดหยุ่นสูง</h3>
<p>ท่อ HDPE สามารถโค้งงอได้ถึง 45 องศา ทำให้เหมาะสำหรับพื้นที่ติดตั้งจำกัด และสามารถรองรับการเคลื่อนไหวของดินได้ดี</p>
<h3 id="2-ทนทานต่อสารเคมี">2. ทนทานต่อสารเคมี</h3>
<p>ท่อ HDPE ทนทานต่อการกัดกร่อนของสารเคมี กรด และด่าง ทำให้เหมาะสำหรับงานอุตสาหกรรม</p>
<h3 id="3-อายุการใช้งานยาวนาน">3. อายุการใช้งานยาวนาน</h3>
<p>ท่อ HDPE มีอายุการใช้งานมากกว่า 50 ปี เมื่อติดตั้งและใช้งานอย่างถูกต้อง</p>
<h3 id="4-น้ำหนักเบา">4. น้ำหนักเบา</h3>
<p>ท่อ HDPE มีน้ำหนักเบากว่าท่อโลหะ ทำให้ง่ายต่อการขนส่งและติดตั้ง</p>
<h3 id="5-การเชื่อมต่อที่แน่นหนา">5. การเชื่อมต่อที่แน่นหนา</h3>
<p>การเชื่อมท่อ HDPE ด้วยวิธี Butt Fusion ทำให้ท่อเชื่อมต่อกันเป็นเนื้อเดียว ไม่มีรอยต่อ ป้องกันการรั่วซึม</p>
<h3 id="6-ปลอดภัยต่อสุขภาพ">6. ปลอดภัยต่อสุขภาพ</h3>
<p>ท่อ HDPE ไม่เป็นสนิม ไม่ปล่อยสารพิษ ปลอดภัยสำหรับน้ำดื่ม</p>
<h2 id="การใช้งานท่อ-hdpe">การใช้งานท่อ HDPE</h2>
<h3 id="งานประปา">งานประปา</h3>
<ul>
<li>ท่อส่งน้ำประปา</li>
<li>ระบบประปาในบ้านเรือน</li>
<li>ระบบประปาในอาคาร</li>
</ul>
<h3 id="งานเกษตร">งานเกษตร</h3>
<ul>
<li>ระบบน้ำหยด</li>
<li>ระบบสปริงเกลอร์</li>
<li>ระบบน้ำเพื่อการเกษตร</li>
</ul>
<h3 id="งานอุตสาหกรรม">งานอุตสาหกรรม</h3>
<ul>
<li>ท่อส่งสารเคมี</li>
<li>ระบบบำบัดน้ำเสีย</li>
<li>งานโรงงานอุตสาหกรรม</li>
</ul>
<h3 id="งานโครงสร้างพื้นฐาน">งานโครงสร้างพื้นฐาน</h3>
<ul>
<li>งานท่อใต้ดิน</li>
<li>ท่อร้อยสายไฟ</li>
<li>งานสาธารณูปโภค</li>
</ul>
<h2 id="ขนาดท่อ-hdpe-ที่นิยมใช้">ขนาดท่อ HDPE ที่นิยมใช้</h2>
<table><thead><tr><th>ขนาด (มม.)</th><th>การใช้งาน</th></tr></thead><tbody><tr><td>16-32</td><td>งานประปาภายในบ้าน</td></tr><tr><td>40-63</td><td>งานประปาอาคารขนาดเล็ก</td></tr><tr><td>75-110</td><td>งานประปาอาคารขนาดใหญ่</td></tr><tr><td>125-315</td><td>งานท่อส่งน้ำหลัก</td></tr><tr><td>355-1200</td><td>งานโครงสร้างพื้นฐาน</td></tr></tbody></table>
<h2 id="เกรดของท่อ-hdpe">เกรดของท่อ HDPE</h2>
<h3 id="pe80">PE80</h3>
<ul>
<li>เหมาะสำหรับงานทั่วไป</li>
<li>ทนแรงดันสูงสุด 8 MPa</li>
</ul>
<h3 id="pe100">PE100</h3>
<ul>
<li>เหมาะสำหรับงานที่ต้องการความแข็งแรงสูง</li>
<li>ทนแรงดันสูงสุด 10 MPa</li>
<li>เป็นเกรดที่นิยมใช้ในปัจจุบัน</li>
</ul>
<h2 id="การติดตั้งท่อ-hdpe">การติดตั้งท่อ HDPE</h2>
<h3 id="วิธี-butt-fusion">วิธี Butt Fusion</h3>
<ol>
<li>ตัดท่อให้ตรง</li>
<li>ทำความสะอาดผิวท่อ</li>
<li>ใช้เครื่องเชื่อมท่อ HDPE</li>
<li>ให้ความร้อนจนผิวท่อละลาย</li>
<li>กดท่อเข้าด้วยกัน</li>
<li>รอให้เย็นตัวลง</li>
</ol>
<h3 id="วิธี-electrofusion">วิธี Electrofusion</h3>
<ol>
<li>ใช้ข้อต่อแบบ Electrofusion</li>
<li>เสียบปลั๊กไฟเข้ากับข้อต่อ</li>
<li>รอจนกระบวนการเชื่อมเสร็จสิ้น</li>
</ol>
<h2 id="สรุป">สรุป</h2>
<p>ท่อ HDPE เป็นตัวเลือกที่ยอดเยี่ยมสำหรับงานระบบน้ำ เนื่องจากมีความทนทาน ความยืดหยุ่น และอายุการใช้งานที่ยาวนาน ไม่ว่าจะเป็นงานประปา งานเกษตร หรืองานอุตสาหกรรม ท่อ HDPE สามารถตอบโจทย์ได้ทุกการใช้งาน</p>
<hr>
<p><strong>สนใจสินค้าท่อ HDPE?</strong>
ติดต่อเราได้ที่:</p>
<ul>
<li>โทร: 090-555-1415</li>
<li>LINE: jppselection</li>
</ul>
<p><a href="/%E0%B8%97%E0%B9%88%E0%B8%ADhdpe">ดูสินค้าท่อ HDPE ทั้งหมด</a></p> </div> <!-- Back to Blog --> <div class="mt-12 pt-8 border-t border-secondary-200"> <a href="/blog/" class="inline-flex items-center text-primary-600 font-medium hover:text-primary-700 transition-colors"> <svg class="w-5 h-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path> </svg>
กลับสู่หน้าบทความ
</a> </div> </article> </main> </body></html>

View File

@@ -1,64 +0,0 @@
<!DOCTYPE html><html lang="th"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator" content="Astro v5.18.0"><meta name="description" content="ท่อ PPR (Polypropylene Random Copolymer) เป็นท่อพลาสติกที่ได้รับความนิยมสูงในการใช้งานระบบประปา บทความนี้จะอธิบายทุกสิ่งที่คุณต้องรู้เกี่ยวกับท่อ PPR"><!-- Favicon --><link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="alternate icon" href="/favicon.ico" sizes="any"><link rel="apple-touch-icon" href="/favicon.svg"><!-- Google Fonts: Kanit for Thai --><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;500;600;700&display=swap" rel="stylesheet"><!-- SEO --><meta property="og:title" content="ท่อ PPR คืออะไร? คู่มือฉบับสมบูรณ์สำหรับการเลือกใช้งาน"><meta property="og:description" content="ท่อ PPR (Polypropylene Random Copolymer) เป็นท่อพลาสติกที่ได้รับความนิยมสูงในการใช้งานระบบประปา บทความนี้จะอธิบายทุกสิ่งที่คุณต้องรู้เกี่ยวกับท่อ PPR"><meta property="og:image" content="/og-image.jpg"><meta property="og:type" content="website"><meta name="twitter:card" content="summary_large_image"><title>ท่อ PPR คืออะไร? คู่มือฉบับสมบูรณ์สำหรับการเลือกใช้งาน | ดีล พลัส เทค</title><style>html{font-family:Kanit,system-ui,sans-serif}
</style></head> <body class="flex flex-col min-h-screen"> <main class="pt-32 pb-16"> <article class="container mx-auto px-4 max-w-4xl"> <!-- Header --> <header class="mb-8"> <div class="flex items-center gap-4 mb-4"> <span class="industrial-badge">ท่อ PPR</span> <time class="text-secondary-500"> 15 มกราคม 2567 </time> <span class="text-secondary-500"></span> <span class="text-secondary-500">Deal Plus Tech</span> </div> <h1 class="text-4xl md:text-5xl font-bold text-secondary-900 mb-4"> ท่อ PPR คืออะไร? คู่มือฉบับสมบูรณ์สำหรับการเลือกใช้งาน </h1> </header> <!-- Featured Image --> <div class="relative aspect-video bg-secondary-100 rounded-xl overflow-hidden mb-8"> <img src="/images/2021/03/ppr-pipe_000C.jpg" alt="ท่อ PPR คืออะไร? คู่มือฉบับสมบูรณ์สำหรับการเลือกใช้งาน" class="object-cover w-full h-full" loading="lazy"> </div> <!-- Content --> <div class="prose prose-lg max-w-none prose-headings:font-bold prose-a:text-primary-600 hover:prose-a:text-primary-700 prose-img:rounded-xl"> <h2 id="ท่อ-ppr-คืออะไร">ท่อ PPR คืออะไร?</h2>
<p>ท่อ PPR (Polypropylene Random Copolymer) หรือท่อพีพีอาร์ เป็นท่อพลาสติกที่ผลิตจากเม็ดพลาสติก PP-R 80 (Polypropylene Random Copolymer 80) ซึ่งเป็นวัสดุพลาสติกคุณภาพสูงที่มีความแข็งแรงและทนทานเป็นอย่างดี</p>
<h2 id="ข้อดีของท่อ-ppr">ข้อดีของท่อ PPR</h2>
<h3 id="1-ทนแรงดันและอุณหภูมิสูง">1. ทนแรงดันและอุณหภูมิสูง</h3>
<p>ท่อ PPR สามารถทนแรงดันได้สูงถึง 20 บาร์ และทนต่ออุณหภูมิได้สูงถึง 95°C ทำให้เหมาะสำหรับใช้งานทั้งระบบน้ำเย็นและน้ำร้อน</p>
<h3 id="2-สะอาดและปลอดภัย">2. สะอาดและปลอดภัย</h3>
<p>ท่อ PPR ไม่เป็นสนิม ปราศจากโลหะหนักและสิ่งปนเปื้อน ทำให้น้ำที่ไหลผ่านสะอาดและปลอดภัยต่อการบริโภค</p>
<h3 id="3-อายุการใช้งานยาวนาน">3. อายุการใช้งานยาวนาน</h3>
<p>ด้วยคุณสมบัติที่ทนทาน ท่อ PPR มีอายุการใช้งานยาวนานกว่า 50 ปี</p>
<h3 id="4-ติดตั้งง่าย">4. ติดตั้งง่าย</h3>
<p>การเชื่อมต่อท่อ PPR ใช้วิธีเชื่อมด้วยความร้อน ทำให้ท่อและข้อต่อเป็นเนื้อเดียวกัน ไม่มีปัญหารั่วซึม</p>
<h3 id="5-ประหยัดพลังงาน">5. ประหยัดพลังงาน</h3>
<p>ท่อ PPR เป็นฉนวนกันความร้อนที่ดี ช่วยรักษาอุณหภูมิของน้ำได้ดีกว่าท่อโลหะ</p>
<h2 id="การเลือกท่อ-ppr-ที่เหมาะสม">การเลือกท่อ PPR ที่เหมาะสม</h2>
<h3 id="ขนาดท่อ">ขนาดท่อ</h3>
<p>เลือกขนาดท่อให้เหมาะสมกับปริมาณน้ำที่ต้องการใช้งาน:</p>
<ul>
<li>ท่อขนาด 20-25 มม. เหมาะสำหรับบ้านเรือนทั่วไป</li>
<li>ท่อขนาด 32-63 มม. เหมาะสำหรับอาคารขนาดใหญ่</li>
</ul>
<h3 id="เกรดของท่อ">เกรดของท่อ</h3>
<ul>
<li><strong>PN10</strong> - สำหรับน้ำเย็น ทนแรงดัน 10 บาร์</li>
<li><strong>PN16</strong> - สำหรับน้ำอุ่น ทนแรงดัน 16 บาร์</li>
<li><strong>PN20</strong> - สำหรับน้ำร้อน ทนแรงดัน 20 บาร์</li>
</ul>
<h2 id="การติดตั้งท่อ-ppr">การติดตั้งท่อ PPR</h2>
<h3 id="ขั้นตอนการเชื่อมท่อ">ขั้นตอนการเชื่อมท่อ</h3>
<ol>
<li>ตัดท่อให้ตรงและเรียบ</li>
<li>ทำความสะอาดผิวท่อและข้อต่อ</li>
<li>ใช้เครื่องเชื่อมท่ออุณหภูมิ 260°C</li>
<li>สอดท่อและข้อต่อเข้าด้วยกัน</li>
<li>รอให้เย็นตัวลงประมาณ 2-3 นาที</li>
</ol>
<h3 id="ข้อควรระวัง">ข้อควรระวัง</h3>
<ul>
<li>หลีกเลี่ยงการติดตั้งในพื้นที่ที่มีแสงแดดโดยตรง</li>
<li>ควรทิ้งระยะห่างสำหรับการขยายตัวของท่อ</li>
<li>ตรวจสอบความร้อนของเครื่องเชื่อมก่อนใช้งาน</li>
</ul>
<h2 id="ท่อ-ppr-ตราช้าง">ท่อ PPR ตราช้าง</h2>
<p>ท่อ PPR ตราช้าง เป็นท่อ PPR คุณภาพสูงที่ผลิตจากเม็ดพลาสติก PP-R 80 วัตถุดิบคุณภาพสูงมาตรฐานยุโรปจาก lyondellbasell</p>
<p><strong>คุณสมบัติเด่น:</strong></p>
<ul>
<li>ทนแรงดันได้สูงสุด 20 บาร์</li>
<li>ทนต่ออุณหภูมิได้สูงถึง 95°C</li>
<li>ผลิตตามมาตรฐาน DIN8077 และ DIN8078 ของประเทศเยอรมัน</li>
<li>รับประกันคุณภาพ</li>
</ul>
<h2 id="สรุป">สรุป</h2>
<p>ท่อ PPR เป็นตัวเลือกที่ดีสำหรับระบบประปาในปัจจุบัน เนื่องจากมีความทนทานสูง ติดตั้งง่าย และมีอายุการใช้งานยาวนาน หากคุณกำลังมองหาท่อสำหรับงานระบบน้ำ ท่อ PPR เป็นตัวเลือกที่คุ้มค่าและเหมาะสม</p>
<hr>
<p><strong>สนใจสินค้าท่อ PPR?</strong>
ติดต่อเราได้ที่:</p>
<ul>
<li>โทร: 090-555-1415</li>
<li>LINE: jppselection</li>
<li>อีเมล: <a href="mailto:dealplustech@gmail.com">dealplustech@gmail.com</a></li>
</ul>
<p><a href="/%E0%B8%97%E0%B9%88%E0%B8%AD%E0%B8%9E%E0%B8%B5%E0%B8%9E%E0%B8%B5%E0%B8%AD%E0%B8%B2%E0%B8%A3%E0%B9%8C%E0%B8%95%E0%B8%A3%E0%B8%B2%E0%B8%8A%E0%B9%89%E0%B8%B2%E0%B8%87">ดูสินค้าท่อ PPR ทั้งหมด</a></p> </div> <!-- Back to Blog --> <div class="mt-12 pt-8 border-t border-secondary-200"> <a href="/blog/" class="inline-flex items-center text-primary-600 font-medium hover:text-primary-700 transition-colors"> <svg class="w-5 h-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path> </svg>
กลับสู่หน้าบทความ
</a> </div> </article> </main> </body></html>

View File

@@ -1,156 +0,0 @@
<!DOCTYPE html><html lang="th"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator" content="Astro v5.18.0"><meta name="description" content="ปั๊มน้ำเป็นอุปกรณ์สำคัญในระบบน้ำทุกบ้าน การบำรุงรักษาที่ถูกต้องจะช่วยยืดอายุการใช้งานและประหยัดค่าไฟฟ้า"><!-- Favicon --><link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="alternate icon" href="/favicon.ico" sizes="any"><link rel="apple-touch-icon" href="/favicon.svg"><!-- Google Fonts: Kanit for Thai --><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;500;600;700&display=swap" rel="stylesheet"><!-- SEO --><meta property="og:title" content="การบำรุงรักษาปั๊มน้ำให้มีอายุการใช้งานยาวนาน"><meta property="og:description" content="ปั๊มน้ำเป็นอุปกรณ์สำคัญในระบบน้ำทุกบ้าน การบำรุงรักษาที่ถูกต้องจะช่วยยืดอายุการใช้งานและประหยัดค่าไฟฟ้า"><meta property="og:image" content="/og-image.jpg"><meta property="og:type" content="website"><meta name="twitter:card" content="summary_large_image"><title>การบำรุงรักษาปั๊มน้ำให้มีอายุการใช้งานยาวนาน | ดีล พลัส เทค</title><style>html{font-family:Kanit,system-ui,sans-serif}
</style></head> <body class="flex flex-col min-h-screen"> <main class="pt-32 pb-16"> <article class="container mx-auto px-4 max-w-4xl"> <!-- Header --> <header class="mb-8"> <div class="flex items-center gap-4 mb-4"> <span class="industrial-badge">ปั๊มน้ำ</span> <time class="text-secondary-500"> 5 มกราคม 2567 </time> <span class="text-secondary-500"></span> <span class="text-secondary-500">Deal Plus Tech</span> </div> <h1 class="text-4xl md:text-5xl font-bold text-secondary-900 mb-4"> การบำรุงรักษาปั๊มน้ำให้มีอายุการใช้งานยาวนาน </h1> </header> <!-- Featured Image --> <div class="relative aspect-video bg-secondary-100 rounded-xl overflow-hidden mb-8"> <img src="/images/2021/02/Water-Pump1.jpg" alt="การบำรุงรักษาปั๊มน้ำให้มีอายุการใช้งานยาวนาน" class="object-cover w-full h-full" loading="lazy"> </div> <!-- Content --> <div class="prose prose-lg max-w-none prose-headings:font-bold prose-a:text-primary-600 hover:prose-a:text-primary-700 prose-img:rounded-xl"> <h2 id="ความสำคัญของการบำรุงรักษาปั๊มน้ำ">ความสำคัญของการบำรุงรักษาปั๊มน้ำ</h2>
<p>ปั๊มน้ำเป็นหัวใจของระบบน้ำในบ้าน การบำรุงรักษาอย่างสม่ำเสมอจะช่วย:</p>
<ul>
<li>ยืดอายุการใช้งานของปั๊มน้ำ</li>
<li>ลดปัญหาการเสีย</li>
<li>ประหยัดค่าไฟฟ้า</li>
<li>ป้องกันอุบัติเหตุจากการรั่วซึม</li>
</ul>
<h2 id="การบำรุงรักษาปั๊มน้ำแบบทำเอง">การบำรุงรักษาปั๊มน้ำแบบทำเอง</h2>
<h3 id="1-ตรวจสอบสายไฟและสวิตช์">1. ตรวจสอบสายไฟและสวิตช์</h3>
<ul>
<li>ตรวจสอบสายไฟว่ามีรอยชำรุดหรือไม่</li>
<li>ตรวจสอบสวิตช์ว่าทำงานปกติหรือไม่</li>
<li>หากพบความผิดปกติควรเรียกช่าง</li>
</ul>
<h3 id="2-ทำความสะอาดตัวกรอง">2. ทำความสะอาดตัวกรอง</h3>
<ul>
<li>ปิดวาล์วน้ำเข้าก่อนทำความสะอาด</li>
<li>ถอดตัวกรองออกมาล้าง</li>
<li>ตรวจสอบว่ามีสิ่งปนเปื้อนหรือไม่</li>
<li>ติดตั้งกลับเข้าที่เดิม</li>
</ul>
<h3 id="3-ตรวจสอบแรงดันน้ำ">3. ตรวจสอบแรงดันน้ำ</h3>
<ul>
<li>สังเกตแรงดันน้ำว่าลดลงหรือไม่</li>
<li>ตรวจสอบว่ามีเสียงผิดปกติหรือไม่</li>
<li>หากแรงดันลดลงอาจมีการรั่วซึม</li>
</ul>
<h3 id="4-ตรวจสอบถังแรงดัน-pressure-tank">4. ตรวจสอบถังแรงดัน (Pressure Tank)</h3>
<ul>
<li>ตรวจสอบว่าถังมีอากาศเพียงพอหรือไม่</li>
<li>หากปั๊มเปิด-ปิดบ่อยผิดปกติ อาจต้องเติมอากาศ</li>
<li>ควรตรวจสอบทุก 6 เดือน</li>
</ul>
<h2 id="ปัญหาที่พบบ่อยและวิธีแก้ไข">ปัญหาที่พบบ่อยและวิธีแก้ไข</h2>
<h3 id="ปั๊มไม่ทำงาน">ปั๊มไม่ทำงาน</h3>
<p><strong>สาเหตุ:</strong></p>
<ul>
<li>ไฟดับหรือสายไฟขาด</li>
<li>สวิตช์เสีย</li>
<li>มอเตอร์เสีย</li>
</ul>
<p><strong>วิธีแก้:</strong></p>
<ul>
<li>ตรวจสอบไฟและสายไฟ</li>
<li>เปลี่ยนสวิตช์</li>
<li>เรียกช่างซ่อมมอเตอร์</li>
</ul>
<h3 id="แรงดันน้ำต่ำ">แรงดันน้ำต่ำ</h3>
<p><strong>สาเหตุ:</strong></p>
<ul>
<li>ตัวกรองอุดตัน</li>
<li>ท่อรั่ว</li>
<li>ใบพัดสึกหรอ</li>
</ul>
<p><strong>วิธีแก้:</strong></p>
<ul>
<li>ทำความสะอาดตัวกรอง</li>
<li>ตรวจสอบและซ่อมท่อ</li>
<li>เปลี่ยนใบพัด</li>
</ul>
<h3 id="ปั๊มเปิด-ปิดบ่อย">ปั๊มเปิด-ปิดบ่อย</h3>
<p><strong>สาเหตุ:</strong></p>
<ul>
<li>ถังแรงดันอากาศรั่ว</li>
<li>แผ่นไดอะแฟรมแตก</li>
<li>วาล์วตรวจสอบแรงดันเสีย</li>
</ul>
<p><strong>วิธีแก้:</strong></p>
<ul>
<li>เติมอากาศในถัง</li>
<li>เปลี่ยนแผ่นไดอะแฟรม</li>
<li>เปลี่ยนวาล์ว</li>
</ul>
<h3 id="ปั๊มมีเสียงดังผิดปกติ">ปั๊มมีเสียงดังผิดปกติ</h3>
<p><strong>สาเหตุ:</strong></p>
<ul>
<li>ลูกปืนเสีย</li>
<li>ใบพัดชำรุด</li>
<li>การติดตั้งไม่แน่นหนา</li>
</ul>
<p><strong>วิธีแก้:</strong></p>
<ul>
<li>เปลี่ยนลูกปืน</li>
<li>เปลี่ยนใบพัด</li>
<li>ตรวจสอบการยึดแน่น</li>
</ul>
<h2 id="ตารางการบำรุงรักษา">ตารางการบำรุงรักษา</h2>
<table><thead><tr><th>รายการ</th><th>ความถี่</th><th>หมายเหตุ</th></tr></thead><tbody><tr><td>ตรวจสอบสายไฟ</td><td>ทุกเดือน</td><td>มองหารอยชำรุด</td></tr><tr><td>ทำความสะอาดตัวกรอง</td><td>ทุก 3 เดือน</td><td>หรือเมื่อแรงดันลด</td></tr><tr><td>ตรวจสอบถังแรงดัน</td><td>ทุก 6 เดือน</td><td>เติมอากาศหากจำเป็น</td></tr><tr><td>ตรวจสอบสวิตช์</td><td>ทุกปี</td><td>เปลี่ยนหากเสีย</td></tr><tr><td>ตรวจสอบใบพัด</td><td>ทุก 2 ปี</td><td>โดยช่างผู้เชี่ยวชาญ</td></tr></tbody></table>
<h2 id="เคล็ดลับการใช้งานปั๊มน้ำ">เคล็ดลับการใช้งานปั๊มน้ำ</h2>
<h3 id="ประหยัดไฟฟ้า">ประหยัดไฟฟ้า</h3>
<ul>
<li>เลือกขนาดปั๊มที่เหมาะสมกับการใช้งาน</li>
<li>ติดตั้งถังแรงดันขนาดเหมาะสม</li>
<li>หลีกเลี่ยงการเปิด-ปิดปั๊มบ่อย</li>
</ul>
<h3 id="ป้องกันปัญหา">ป้องกันปัญหา</h3>
<ul>
<li>อย่าให้ปั๊มแห้ง (ทำงานโดยไม่มีน้ำ)</li>
<li>ตรวจสอบรอยรั่วอย่างสม่ำเสมอ</li>
<li>ใช้ตัวกรองเพื่อป้องกันสิ่งสกปรก</li>
</ul>
<h3 id="เมื่อต้องเปลี่ยนปั๊ม">เมื่อต้องเปลี่ยนปั๊ม</h3>
<ul>
<li>เลือกปั๊มที่มีคุณภาพ</li>
<li>พิจารณาขนาดและกำลังที่เหมาะสม</li>
<li>ติดตั้งโดยช่างผู้เชี่ยวชาญ</li>
</ul>
<h2 id="สรุป">สรุป</h2>
<p>การบำรุงรักษาปั๊มน้ำอย่างสม่ำเสมอจะช่วยยืดอายุการใช้งาน ลดปัญหาการเสีย และประหยัดค่าใช้จ่ายในระยะยาว ควรตรวจสอบและบำรุงรักษาตามตารางที่กำหนด และหากพบปัญหาที่ไม่สามารถแก้ไขได้เอง ควรติดต่อช่างผู้เชี่ยวชาญ</p>
<hr>
<p><strong>ต้องการซื้อปั๊มน้ำหรืออุปกรณ์เสริม?</strong>
ติดต่อเราได้ที่:</p>
<ul>
<li>โทร: 090-555-1415</li>
<li>LINE: jppselection</li>
</ul>
<p><a href="/%E0%B8%9B%E0%B8%B1%E0%B9%8A%E0%B8%A1%E0%B8%99%E0%B9%89%E0%B8%B3-pump">ดูสินค้าปั๊มน้ำทั้งหมด</a></p> </div> <!-- Back to Blog --> <div class="mt-12 pt-8 border-t border-secondary-200"> <a href="/blog/" class="inline-flex items-center text-primary-600 font-medium hover:text-primary-700 transition-colors"> <svg class="w-5 h-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path> </svg>
กลับสู่หน้าบทความ
</a> </div> </article> </main> </body></html>

View File

@@ -1,14 +0,0 @@
import { c as createComponent, d as addAttribute, h as renderHead, i as renderSlot, a as renderTemplate, b as createAstro } from './astro/server_D-JZF3a4.mjs';
import 'piccolore';
import 'clsx';
/* empty css */
const $$Astro = createAstro();
const $$BaseLayout = createComponent(($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
Astro2.self = $$BaseLayout;
const { title, description, image } = Astro2.props;
return renderTemplate`<html lang="th"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator"${addAttribute(Astro2.generator, "content")}><meta name="description"${addAttribute(description || "\u0E1A\u0E23\u0E34\u0E29\u0E31\u0E17 \u0E14\u0E35\u0E25 \u0E1E\u0E25\u0E31\u0E2A \u0E40\u0E17\u0E04 \u0E08\u0E33\u0E01\u0E31\u0E14 - \u0E1C\u0E39\u0E49\u0E40\u0E0A\u0E35\u0E48\u0E22\u0E27\u0E0A\u0E32\u0E0D\u0E14\u0E49\u0E32\u0E19\u0E23\u0E30\u0E1A\u0E1A\u0E19\u0E49\u0E33 \u0E17\u0E48\u0E2D PPR \u0E15\u0E23\u0E32\u0E0A\u0E49\u0E32\u0E07 \u0E17\u0E48\u0E2D\u0E1E\u0E35\u0E1E\u0E35\u0E2D\u0E32\u0E23\u0E4C \u0E17\u0E48\u0E2D HDPE", "content")}><!-- Favicon --><link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="alternate icon" href="/favicon.ico" sizes="any"><link rel="apple-touch-icon" href="/favicon.svg"><!-- Google Fonts: Kanit for Thai --><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;500;600;700&display=swap" rel="stylesheet"><!-- SEO --><meta property="og:title"${addAttribute(title, "content")}><meta property="og:description"${addAttribute(description || "Deal Plus Tech - \u0E1C\u0E39\u0E49\u0E40\u0E0A\u0E35\u0E48\u0E22\u0E27\u0E0A\u0E32\u0E0D\u0E14\u0E49\u0E32\u0E19\u0E23\u0E30\u0E1A\u0E1A\u0E19\u0E49\u0E33", "content")}><meta property="og:image"${addAttribute(image || "/og-image.jpg", "content")}><meta property="og:type" content="website"><meta name="twitter:card" content="summary_large_image"><title>${title} | ดีล พลัส เทค</title>${renderHead()}</head> <body class="flex flex-col min-h-screen"> ${renderSlot($$result, $$slots["default"])} </body></html>`;
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/layouts/BaseLayout.astro", void 0);
export { $$BaseLayout as $ };

File diff suppressed because it is too large Load Diff

View File

@@ -1,469 +0,0 @@
import { escape } from 'html-escaper';
import { Traverse } from 'neotraverse/modern';
import pLimit from 'p-limit';
import { z } from 'zod';
import { removeBase, isRemotePath, prependForwardSlash } from '@astrojs/internal-helpers/path';
import { A as AstroError, R as RenderUndefinedEntryError, c as createComponent, u as unescapeHTML, a as renderTemplate, U as UnknownContentCollectionError, e as renderUniqueStylesheet, f as renderScriptElement, g as createHeadAndContent, r as renderComponent } from './astro/server_D-JZF3a4.mjs';
import 'piccolore';
import * as devalue from 'devalue';
const CONTENT_IMAGE_FLAG = "astroContentImageFlag";
const IMAGE_IMPORT_PREFIX = "__ASTRO_IMAGE_";
const VALID_INPUT_FORMATS = [
"jpeg",
"jpg",
"png",
"tiff",
"webp",
"gif",
"svg",
"avif"
];
const VALID_SUPPORTED_FORMATS = [
"jpeg",
"jpg",
"png",
"tiff",
"webp",
"gif",
"svg",
"avif"
];
const DEFAULT_OUTPUT_FORMAT = "webp";
const DEFAULT_HASH_PROPS = [
"src",
"width",
"height",
"format",
"quality",
"fit",
"position",
"background"
];
function imageSrcToImportId(imageSrc, filePath) {
imageSrc = removeBase(imageSrc, IMAGE_IMPORT_PREFIX);
if (isRemotePath(imageSrc)) {
return;
}
const ext = imageSrc.split(".").at(-1)?.toLowerCase();
if (!ext || !VALID_INPUT_FORMATS.includes(ext)) {
return;
}
const params = new URLSearchParams(CONTENT_IMAGE_FLAG);
if (filePath) {
params.set("importer", filePath);
}
return `${imageSrc}?${params.toString()}`;
}
class ImmutableDataStore {
_collections = /* @__PURE__ */ new Map();
constructor() {
this._collections = /* @__PURE__ */ new Map();
}
get(collectionName, key) {
return this._collections.get(collectionName)?.get(String(key));
}
entries(collectionName) {
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
return [...collection.entries()];
}
values(collectionName) {
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
return [...collection.values()];
}
keys(collectionName) {
const collection = this._collections.get(collectionName) ?? /* @__PURE__ */ new Map();
return [...collection.keys()];
}
has(collectionName, key) {
const collection = this._collections.get(collectionName);
if (collection) {
return collection.has(String(key));
}
return false;
}
hasCollection(collectionName) {
return this._collections.has(collectionName);
}
collections() {
return this._collections;
}
/**
* Attempts to load a DataStore from the virtual module.
* This only works in Vite.
*/
static async fromModule() {
try {
const data = await import('./_astro_data-layer-content_NX5tLOlp.mjs');
if (data.default instanceof Map) {
return ImmutableDataStore.fromMap(data.default);
}
const map = devalue.unflatten(data.default);
return ImmutableDataStore.fromMap(map);
} catch {
}
return new ImmutableDataStore();
}
static async fromMap(data) {
const store = new ImmutableDataStore();
store._collections = data;
return store;
}
}
function dataStoreSingleton() {
let instance = void 0;
return {
get: async () => {
if (!instance) {
instance = ImmutableDataStore.fromModule();
}
return instance;
},
set: (store) => {
instance = store;
}
};
}
const globalDataStore = dataStoreSingleton();
const __vite_import_meta_env__ = {"ASSETS_PREFIX": undefined, "BASE_URL": "/", "DEV": false, "MODE": "production", "PROD": true, "SITE": undefined, "SSR": true};
function createCollectionToGlobResultMap({
globResult,
contentDir
}) {
const collectionToGlobResultMap = {};
for (const key in globResult) {
const keyRelativeToContentDir = key.replace(new RegExp(`^${contentDir}`), "");
const segments = keyRelativeToContentDir.split("/");
if (segments.length <= 1) continue;
const collection = segments[0];
collectionToGlobResultMap[collection] ??= {};
collectionToGlobResultMap[collection][key] = globResult[key];
}
return collectionToGlobResultMap;
}
z.object({
tags: z.array(z.string()).optional(),
lastModified: z.date().optional()
});
function createGetCollection({
contentCollectionToEntryMap,
dataCollectionToEntryMap,
getRenderEntryImport,
cacheEntriesByCollection,
liveCollections
}) {
return async function getCollection(collection, filter) {
if (collection in liveCollections) {
throw new AstroError({
...UnknownContentCollectionError,
message: `Collection "${collection}" is a live collection. Use getLiveCollection() instead of getCollection().`
});
}
const hasFilter = typeof filter === "function";
const store = await globalDataStore.get();
let type;
if (collection in contentCollectionToEntryMap) {
type = "content";
} else if (collection in dataCollectionToEntryMap) {
type = "data";
} else if (store.hasCollection(collection)) {
const { default: imageAssetMap } = await import('./content-assets_DleWbedO.mjs');
const result = [];
for (const rawEntry of store.values(collection)) {
const data = updateImageReferencesInData(rawEntry.data, rawEntry.filePath, imageAssetMap);
let entry = {
...rawEntry,
data,
collection
};
if (entry.legacyId) {
entry = emulateLegacyEntry(entry);
}
if (hasFilter && !filter(entry)) {
continue;
}
result.push(entry);
}
return result;
} else {
console.warn(
`The collection ${JSON.stringify(
collection
)} does not exist or is empty. Please check your content config file for errors.`
);
return [];
}
const lazyImports = Object.values(
type === "content" ? contentCollectionToEntryMap[collection] : dataCollectionToEntryMap[collection]
);
let entries = [];
if (!Object.assign(__vite_import_meta_env__, { _: process.env._ })?.DEV && cacheEntriesByCollection.has(collection)) {
entries = cacheEntriesByCollection.get(collection);
} else {
const limit = pLimit(10);
entries = await Promise.all(
lazyImports.map(
(lazyImport) => limit(async () => {
const entry = await lazyImport();
return type === "content" ? {
id: entry.id,
slug: entry.slug,
body: entry.body,
collection: entry.collection,
data: entry.data,
async render() {
return render({
collection: entry.collection,
id: entry.id,
renderEntryImport: await getRenderEntryImport(collection, entry.slug)
});
}
} : {
id: entry.id,
collection: entry.collection,
data: entry.data
};
})
)
);
cacheEntriesByCollection.set(collection, entries);
}
if (hasFilter) {
return entries.filter(filter);
} else {
return entries.slice();
}
};
}
function emulateLegacyEntry({ legacyId, ...entry }) {
const legacyEntry = {
...entry,
id: legacyId,
slug: entry.id
};
return {
...legacyEntry,
// Define separately so the render function isn't included in the object passed to `renderEntry()`
render: () => renderEntry(legacyEntry)
};
}
const CONTENT_LAYER_IMAGE_REGEX = /__ASTRO_IMAGE_="([^"]+)"/g;
async function updateImageReferencesInBody(html, fileName) {
const { default: imageAssetMap } = await import('./content-assets_DleWbedO.mjs');
const imageObjects = /* @__PURE__ */ new Map();
const { getImage } = await import('./_astro_assets_CWrzoUoo.mjs').then(n => n._);
for (const [_full, imagePath] of html.matchAll(CONTENT_LAYER_IMAGE_REGEX)) {
try {
const decodedImagePath = JSON.parse(imagePath.replaceAll("&#x22;", '"'));
let image;
if (URL.canParse(decodedImagePath.src)) {
image = await getImage(decodedImagePath);
} else {
const id = imageSrcToImportId(decodedImagePath.src, fileName);
const imported = imageAssetMap.get(id);
if (!id || imageObjects.has(id) || !imported) {
continue;
}
image = await getImage({ ...decodedImagePath, src: imported });
}
imageObjects.set(imagePath, image);
} catch {
throw new Error(`Failed to parse image reference: ${imagePath}`);
}
}
return html.replaceAll(CONTENT_LAYER_IMAGE_REGEX, (full, imagePath) => {
const image = imageObjects.get(imagePath);
if (!image) {
return full;
}
const { index, ...attributes } = image.attributes;
return Object.entries({
...attributes,
src: image.src,
srcset: image.srcSet.attribute,
// This attribute is used by the toolbar audit
...Object.assign(__vite_import_meta_env__, { _: process.env._ }).DEV ? { "data-image-component": "true" } : {}
}).map(([key, value]) => value ? `${key}="${escape(value)}"` : "").join(" ");
});
}
function updateImageReferencesInData(data, fileName, imageAssetMap) {
return new Traverse(data).map(function(ctx, val) {
if (typeof val === "string" && val.startsWith(IMAGE_IMPORT_PREFIX)) {
const src = val.replace(IMAGE_IMPORT_PREFIX, "");
const id = imageSrcToImportId(src, fileName);
if (!id) {
ctx.update(src);
return;
}
const imported = imageAssetMap?.get(id);
if (imported) {
ctx.update(imported);
} else {
ctx.update(src);
}
}
});
}
async function renderEntry(entry) {
if (!entry) {
throw new AstroError(RenderUndefinedEntryError);
}
if ("render" in entry && !("legacyId" in entry)) {
return entry.render();
}
if (entry.deferredRender) {
try {
const { default: contentModules } = await import('./content-modules_Dz-S_Wwv.mjs');
const renderEntryImport = contentModules.get(entry.filePath);
return render({
collection: "",
id: entry.id,
renderEntryImport
});
} catch (e) {
console.error(e);
}
}
const html = entry?.rendered?.metadata?.imagePaths?.length && entry.filePath ? await updateImageReferencesInBody(entry.rendered.html, entry.filePath) : entry?.rendered?.html;
const Content = createComponent(() => renderTemplate`${unescapeHTML(html)}`);
return {
Content,
headings: entry?.rendered?.metadata?.headings ?? [],
remarkPluginFrontmatter: entry?.rendered?.metadata?.frontmatter ?? {}
};
}
async function render({
collection,
id,
renderEntryImport
}) {
const UnexpectedRenderError = new AstroError({
...UnknownContentCollectionError,
message: `Unexpected error while rendering ${String(collection)}${String(id)}.`
});
if (typeof renderEntryImport !== "function") throw UnexpectedRenderError;
const baseMod = await renderEntryImport();
if (baseMod == null || typeof baseMod !== "object") throw UnexpectedRenderError;
const { default: defaultMod } = baseMod;
if (isPropagatedAssetsModule(defaultMod)) {
const { collectedStyles, collectedLinks, collectedScripts, getMod } = defaultMod;
if (typeof getMod !== "function") throw UnexpectedRenderError;
const propagationMod = await getMod();
if (propagationMod == null || typeof propagationMod !== "object") throw UnexpectedRenderError;
const Content = createComponent({
factory(result, baseProps, slots) {
let styles = "", links = "", scripts = "";
if (Array.isArray(collectedStyles)) {
styles = collectedStyles.map((style) => {
return renderUniqueStylesheet(result, {
type: "inline",
content: style
});
}).join("");
}
if (Array.isArray(collectedLinks)) {
links = collectedLinks.map((link) => {
return renderUniqueStylesheet(result, {
type: "external",
src: isRemotePath(link) ? link : prependForwardSlash(link)
});
}).join("");
}
if (Array.isArray(collectedScripts)) {
scripts = collectedScripts.map((script) => renderScriptElement(script)).join("");
}
let props = baseProps;
if (id.endsWith("mdx")) {
props = {
components: propagationMod.components ?? {},
...baseProps
};
}
return createHeadAndContent(
unescapeHTML(styles + links + scripts),
renderTemplate`${renderComponent(
result,
"Content",
propagationMod.Content,
props,
slots
)}`
);
},
propagation: "self"
});
return {
Content,
headings: propagationMod.getHeadings?.() ?? [],
remarkPluginFrontmatter: propagationMod.frontmatter ?? {}
};
} else if (baseMod.Content && typeof baseMod.Content === "function") {
return {
Content: baseMod.Content,
headings: baseMod.getHeadings?.() ?? [],
remarkPluginFrontmatter: baseMod.frontmatter ?? {}
};
} else {
throw UnexpectedRenderError;
}
}
function isPropagatedAssetsModule(module) {
return typeof module === "object" && module != null && "__astroPropagation" in module;
}
// astro-head-inject
const liveCollections = {};
const contentDir = '/src/content/';
const contentEntryGlob = "";
const contentCollectionToEntryMap = createCollectionToGlobResultMap({
globResult: contentEntryGlob,
contentDir,
});
const dataEntryGlob = "";
const dataCollectionToEntryMap = createCollectionToGlobResultMap({
globResult: dataEntryGlob,
contentDir,
});
createCollectionToGlobResultMap({
globResult: { ...contentEntryGlob, ...dataEntryGlob },
contentDir,
});
let lookupMap = {};
lookupMap = {};
new Set(Object.keys(lookupMap));
function createGlobLookup(glob) {
return async (collection, lookupId) => {
const filePath = lookupMap[collection]?.entries[lookupId];
if (!filePath) return undefined;
return glob[collection][filePath];
};
}
const renderEntryGlob = "";
const collectionToRenderEntryMap = createCollectionToGlobResultMap({
globResult: renderEntryGlob,
contentDir,
});
const cacheEntriesByCollection = new Map();
const getCollection = createGetCollection({
contentCollectionToEntryMap,
dataCollectionToEntryMap,
getRenderEntryImport: createGlobLookup(collectionToRenderEntryMap),
cacheEntriesByCollection,
liveCollections,
});
export { DEFAULT_OUTPUT_FORMAT as D, VALID_SUPPORTED_FORMATS as V, DEFAULT_HASH_PROPS as a, getCollection as g, renderEntry as r };

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +0,0 @@
import 'piccolore';
import './astro/server_D-JZF3a4.mjs';
import 'clsx';

View File

@@ -1,3 +0,0 @@
const contentAssets = new Map();
export { contentAssets as default };

View File

@@ -1,3 +0,0 @@
const contentModules = new Map();
export { contentModules as default };

View File

@@ -1,101 +0,0 @@
import { A as AstroError, z as MissingSharp } from './astro/server_D-JZF3a4.mjs';
import { b as baseService, p as parseQuality } from './_astro_assets_CWrzoUoo.mjs';
let sharp;
const qualityTable = {
low: 25,
mid: 50,
high: 80,
max: 100
};
async function loadSharp() {
let sharpImport;
try {
sharpImport = (await import('sharp')).default;
} catch {
throw new AstroError(MissingSharp);
}
sharpImport.cache(false);
return sharpImport;
}
const fitMap = {
fill: "fill",
contain: "inside",
cover: "cover",
none: "outside",
"scale-down": "inside",
outside: "outside",
inside: "inside"
};
const sharpService = {
validateOptions: baseService.validateOptions,
getURL: baseService.getURL,
parseURL: baseService.parseURL,
getHTMLAttributes: baseService.getHTMLAttributes,
getSrcSet: baseService.getSrcSet,
async transform(inputBuffer, transformOptions, config) {
if (!sharp) sharp = await loadSharp();
const transform = transformOptions;
const kernel = config.service.config.kernel;
if (transform.format === "svg") return { data: inputBuffer, format: "svg" };
const result = sharp(inputBuffer, {
failOnError: false,
pages: -1,
limitInputPixels: config.service.config.limitInputPixels
});
result.rotate();
const { format } = await result.metadata();
const withoutEnlargement = Boolean(transform.fit);
if (transform.width && transform.height && transform.fit) {
const fit = fitMap[transform.fit] ?? "inside";
result.resize({
width: Math.round(transform.width),
height: Math.round(transform.height),
kernel,
fit,
position: transform.position,
withoutEnlargement
});
} else if (transform.height && !transform.width) {
result.resize({
height: Math.round(transform.height),
kernel,
withoutEnlargement
});
} else if (transform.width) {
result.resize({
width: Math.round(transform.width),
kernel,
withoutEnlargement
});
}
if (transform.background) {
result.flatten({ background: transform.background });
}
if (transform.format) {
let quality = void 0;
if (transform.quality) {
const parsedQuality = parseQuality(transform.quality);
if (typeof parsedQuality === "number") {
quality = parsedQuality;
} else {
quality = transform.quality in qualityTable ? qualityTable[transform.quality] : void 0;
}
}
if (transform.format === "webp" && format === "gif") {
result.webp({ quality: typeof quality === "number" ? quality : void 0, loop: 0 });
} else {
result.toFormat(transform.format, { quality });
}
}
const { data, info } = await result.toBuffer({ resolveWithObject: true });
const needsCopy = "buffer" in data && data.buffer instanceof SharedArrayBuffer;
return {
data: needsCopy ? new Uint8Array(data) : data,
format: info.format
};
}
};
var sharp_default = sharpService;
export { sharp_default as default };

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

View File

@@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

Before

Width:  |  Height:  |  Size: 749 B

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +0,0 @@
const server = {};
export { server };

View File

@@ -1,46 +0,0 @@
import { c as createComponent, m as maybeRenderHead, d as addAttribute, a as renderTemplate, b as createAstro, r as renderComponent } from '../chunks/astro/server_D-JZF3a4.mjs';
import 'piccolore';
import { g as getCollection } from '../chunks/_astro_content_C5hvN5fw.mjs';
import 'clsx';
import { $ as $$BaseLayout } from '../chunks/BaseLayout_CQrO8n43.mjs';
export { renderers } from '../renderers.mjs';
const $$Astro = createAstro();
const $$BlogCard = createComponent(($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
Astro2.self = $$BlogCard;
const { post } = Astro2.props;
const { title, excerpt, date, author, category, categories, image, featuredImage } = post.data;
const postCategory = category || (Array.isArray(categories) ? categories[0] : "\u0E17\u0E31\u0E48\u0E27\u0E44\u0E1B");
const postImage = image || featuredImage || "/images/2021/03/ppr-pipe_000C.jpg";
return renderTemplate`${maybeRenderHead()}<a${addAttribute(`/blog/${post.slug}`, "href")} class="card group"> <div class="relative aspect-video bg-secondary-100 overflow-hidden"> <img${addAttribute(postImage, "src")}${addAttribute(title, "alt")} class="object-cover w-full h-48 group-hover:scale-105 transition-transform duration-300" loading="lazy"> <div class="absolute top-4 left-4"> <span class="industrial-badge">${postCategory}</span> </div> </div> <div class="p-6"> <time class="text-sm text-secondary-500"> ${new Date(date).toLocaleDateString("th-TH", { year: "numeric", month: "long", day: "numeric" })} </time> <h3 class="mt-2 text-xl font-bold text-secondary-900 group-hover:text-primary-600 transition-colors line-clamp-2"> ${title} </h3> ${excerpt && renderTemplate`<p class="mt-3 text-secondary-600 text-sm line-clamp-3"> ${excerpt} </p>`} <div class="mt-4 flex items-center text-primary-600 font-medium"> <span>อ่านต่อ</span> <svg class="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M9 5l7 7-7 7"></path> </svg> </div> </div> </a>`;
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/components/BlogCard.astro", void 0);
const metadata = {
title: "\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21\u0E04\u0E27\u0E32\u0E21\u0E23\u0E39\u0E49",
description: "\u0E1A\u0E17\u0E04\u0E27\u0E32\u0E21\u0E04\u0E27\u0E32\u0E21\u0E23\u0E39\u0E49\u0E40\u0E01\u0E35\u0E48\u0E22\u0E27\u0E01\u0E31\u0E1A\u0E27\u0E31\u0E2A\u0E14\u0E38\u0E17\u0E48\u0E2D \u0E2D\u0E38\u0E1B\u0E01\u0E23\u0E13\u0E4C\u0E23\u0E30\u0E1A\u0E1A\u0E17\u0E48\u0E2D \u0E41\u0E25\u0E30\u0E40\u0E17\u0E04\u0E19\u0E34\u0E04\u0E01\u0E32\u0E23\u0E15\u0E34\u0E14\u0E15\u0E31\u0E49\u0E07"
};
const prerender = true;
const $$Index = createComponent(async ($$result, $$props, $$slots) => {
const posts = await getCollection("blog");
return renderTemplate`${renderComponent($$result, "BaseLayout", $$BaseLayout, { "title": metadata.title, "description": metadata.description }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<main class="pt-32 pb-16"> <div class="container mx-auto px-4"> <!-- Hero --> <div class="text-center mb-12"> <h1 class="text-4xl md:text-5xl font-bold text-secondary-900 mb-4">
บทความ<span class="text-primary-600">ความร</span> </h1> <p class="text-xl text-secondary-600 max-w-2xl mx-auto">
บทความความรเกยวกบวสด ปกรณระบบท และเทคนคการตดต
</p> </div> <!-- Blog Grid --> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> ${posts.map((post) => renderTemplate`${renderComponent($$result2, "BlogCard", $$BlogCard, { "post": post })}`)} </div> </div> </main> ` })}`;
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/blog/index.astro", void 0);
const $$file = "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/blog/index.astro";
const $$url = "/blog";
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: $$Index,
file: $$file,
metadata,
prerender,
url: $$url
}, Symbol.toStringTag, { value: 'Module' }));
const page = () => _page;
export { page };

View File

@@ -1,45 +0,0 @@
import { c as createComponent, r as renderComponent, a as renderTemplate, b as createAstro, m as maybeRenderHead, d as addAttribute } from '../../chunks/astro/server_D-JZF3a4.mjs';
import 'piccolore';
import { r as renderEntry, g as getCollection } from '../../chunks/_astro_content_C5hvN5fw.mjs';
import { $ as $$BaseLayout } from '../../chunks/BaseLayout_CQrO8n43.mjs';
export { renderers } from '../../renderers.mjs';
const $$Astro = createAstro();
async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.map((post) => ({
params: { slug: post.id },
props: { post }
}));
}
const $$slug = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
Astro2.self = $$slug;
const { post } = Astro2.props;
const { Content } = await renderEntry(post);
const { title, date, author, category, categories, image, featuredImage } = post.data;
const postCategory = category || (Array.isArray(categories) ? categories[0] : "\u0E17\u0E31\u0E48\u0E27\u0E44\u0E1B");
const postImage = image || featuredImage || "/images/2021/03/ppr-pipe_000C.jpg";
return renderTemplate`${renderComponent($$result, "BaseLayout", $$BaseLayout, { "title": title, "description": post.data.excerpt }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<main class="pt-32 pb-16"> <article class="container mx-auto px-4 max-w-4xl"> <!-- Header --> <header class="mb-8"> <div class="flex items-center gap-4 mb-4"> <span class="industrial-badge">${postCategory}</span> <time class="text-secondary-500"> ${new Date(date).toLocaleDateString("th-TH", {
year: "numeric",
month: "long",
day: "numeric"
})} </time> <span class="text-secondary-500"></span> <span class="text-secondary-500">${author}</span> </div> <h1 class="text-4xl md:text-5xl font-bold text-secondary-900 mb-4"> ${title} </h1> </header> <!-- Featured Image --> <div class="relative aspect-video bg-secondary-100 rounded-xl overflow-hidden mb-8"> <img${addAttribute(postImage, "src")}${addAttribute(title, "alt")} class="object-cover w-full h-full" loading="lazy"> </div> <!-- Content --> <div class="prose prose-lg max-w-none prose-headings:font-bold prose-a:text-primary-600 hover:prose-a:text-primary-700 prose-img:rounded-xl"> ${renderComponent($$result2, "Content", Content, {})} </div> <!-- Back to Blog --> <div class="mt-12 pt-8 border-t border-secondary-200"> <a href="/blog/" class="inline-flex items-center text-primary-600 font-medium hover:text-primary-700 transition-colors"> <svg class="w-5 h-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M10 19l-7-7m0 0l7-7m-7 7h18"></path> </svg>
กลบสหนาบทความ
</a> </div> </article> </main> ` })}`;
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/blog/[slug].astro", void 0);
const $$file = "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/blog/[slug].astro";
const $$url = "/blog/[slug]";
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: $$slug,
file: $$file,
getStaticPaths,
url: $$url
}, Symbol.toStringTag, { value: 'Module' }));
const page = () => _page;
export { page };

View File

@@ -1,65 +0,0 @@
import { c as createComponent, m as maybeRenderHead, d as addAttribute, a as renderTemplate, r as renderComponent } from '../chunks/astro/server_D-JZF3a4.mjs';
import 'piccolore';
import { $ as $$BaseLayout } from '../chunks/BaseLayout_CQrO8n43.mjs';
import 'clsx';
import { s as siteConfig, p as productCategories } from '../chunks/site-config_BJHCdssj.mjs';
export { renderers } from '../renderers.mjs';
const $$FloatingContact = createComponent(($$result, $$props, $$slots) => {
return renderTemplate`${maybeRenderHead()}<div class="fixed bottom-6 right-6 z-40 flex flex-col gap-3"> <!-- LINE --> <a${addAttribute(`https://line.me/ti/p/${siteConfig.lineId}`, "href")} target="_blank" rel="noopener noreferrer" class="w-14 h-14 bg-[#00B900] rounded-full flex items-center justify-center shadow-lg hover:scale-110 transition-transform" aria-label="ติดต่อผ่าน LINE"> <svg class="w-7 h-7 text-white" viewBox="0 0 24 24" fill="currentColor"> <path d="M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63H17.61v1.125h1.755c.349 0 .63.283.63.63 0 .344-.281.629-.63.629h-2.386c-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63h2.386c.346 0 .627.285.627.63 0 .349-.281.63-.63.63H17.61v1.125h1.755zm-3.855 3.016c0 .27-.174.51-.432.596-.064.021-.133.031-.199.031-.211 0-.391-.09-.51-.25l-2.443-3.317v2.94c0 .344-.279.629-.631.629-.346 0-.626-.285-.626-.629V8.108c0-.27.173-.51.43-.595.06-.023.136-.033.194-.033.195 0 .375.104.495.254l2.462 3.33V8.108c0-.345.282-.63.63-.63.345 0 .63.285.63.63v4.771zm-5.741 0c0 .344-.282.629-.631.629-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.63.63-.63.346 0 .628.285.628.63v4.771zm-2.466.629H4.917c-.345 0-.63-.285-.63-.629V8.108c0-.345.285-.63.63-.63.348 0 .63.285.63.63v4.141h1.756c.348 0 .629.283.629.63 0 .344-.282.629-.629.629M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608.391.082.923.258 1.058.59.12.301.079.766.038 1.08l-.164 1.02c-.045.301-.24 1.186 1.049.645 1.291-.539 6.916-4.078 9.436-6.975C23.176 14.393 24 12.458 24 10.314"></path> </svg> </a> <!-- Phone --> <a${addAttribute(`tel:${siteConfig.phone}`, "href")} class="w-14 h-14 bg-primary-600 rounded-full flex items-center justify-center shadow-lg hover:scale-110 transition-transform" aria-label="โทรหาเรา"> <svg class="w-7 h-7 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"></path> </svg> </a> </div>`;
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/components/FloatingContact.astro", void 0);
const $$Index = createComponent(($$result, $$props, $$slots) => {
const featuredProducts = productCategories.filter(
(p) => ["ppr-elephant", "hdpe", "poloplast", "syler", "xylent"].includes(p.id)
).slice(0, 6);
return renderTemplate`${renderComponent($$result, "BaseLayout", $$BaseLayout, { "title": "\u0E2B\u0E19\u0E49\u0E32\u0E41\u0E23\u0E01", "description": "\u0E1A\u0E23\u0E34\u0E29\u0E31\u0E17 \u0E14\u0E35\u0E25 \u0E1E\u0E25\u0E31\u0E2A \u0E40\u0E17\u0E04 \u0E08\u0E33\u0E01\u0E31\u0E14 - \u0E1C\u0E39\u0E49\u0E40\u0E0A\u0E35\u0E48\u0E22\u0E27\u0E0A\u0E32\u0E0D\u0E14\u0E49\u0E32\u0E19\u0E23\u0E30\u0E1A\u0E1A\u0E17\u0E48\u0E2D\u0E41\u0E25\u0E30 HVAC" }, { "default": ($$result2) => renderTemplate` ${maybeRenderHead()}<main> <!-- Hero Section --> <section class="relative h-[70vh] min-h-[500px] bg-secondary-900"> <div class="absolute inset-0 bg-gradient-to-r from-secondary-900 via-secondary-900/90 to-secondary-900/60 z-10"></div> <img src="/images/2021/03/ppr-pipe_000C.jpg" alt="" class="absolute inset-0 w-full h-full object-cover opacity-50" loading="eager"> <div class="relative z-20 container mx-auto px-4 h-full flex items-center"> <div class="max-w-2xl"> <span class="inline-block px-4 py-2 bg-primary-600 text-white font-semibold mb-4 rounded">
เชยวชาญดานระบบทอและ HVAC
</span> <h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
สด ปกรณ HVAC
<span class="text-primary-400 block">และฉนวนหมท</span> </h1> <p class="text-lg md:text-xl text-secondary-200 mb-8">
จำหนายและตดตงท PPR, HDPE, กรลแอร, เทอรโมเบรค และอปกรณระบบทอครบวงจร พรอมบรการใหคำปรกษาจากทมมออาช
</p> <div class="flex flex-wrap gap-4"> <a href="/products/" class="btn-primary">
นคาทงหมด
</a> <a href="/contact-us/" class="btn-outline border-white text-white hover:bg-white hover:text-secondary-900">
ขอใบเสนอราคา
</a> </div> </div> </div> </section> <!-- Features Section --> <section class="py-16 bg-secondary-800"> <div class="container mx-auto px-4"> <div class="grid grid-cols-1 md:grid-cols-3 gap-8"> <div class="text-center p-6"> <div class="w-16 h-16 bg-primary-600 rounded-lg flex items-center justify-center mx-auto mb-4"> <svg class="w-8 h-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"></path> </svg> </div> <h3 class="text-xl font-bold text-white mb-2"></h3> <p class="text-secondary-300">
นคาทกชนผานมาตรฐานคณภาพ พรอมรบประก
</p> </div> <div class="text-center p-6"> <div class="w-16 h-16 bg-primary-600 rounded-lg flex items-center justify-center mx-auto mb-4"> <svg class="w-8 h-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M13 10V3L4 14h7v7l9-11h-7z"></path> </svg> </div> <h3 class="text-xl font-bold text-white mb-2"></h3> <p class="text-secondary-300">
ดสงสนคาทวประเทศ รวดเรวและปลอดภ
</p> </div> <div class="text-center p-6"> <div class="w-16 h-16 bg-primary-600 rounded-lg flex items-center justify-center mx-auto mb-4"> <svg class="w-8 h-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"></path> </svg> </div> <h3 class="text-xl font-bold text-white mb-2"></h3> <p class="text-secondary-300">
มงานพรอมใหคำปรกษาและดแลอยางตอเนอง
</p> </div> </div> </div> </section> <!-- Featured Products --> <section class="py-16 bg-secondary-50"> <div class="container mx-auto px-4"> <div class="text-center mb-12"> <h2 class="text-3xl md:text-4xl font-bold text-secondary-900 mb-4">
นค<span class="text-primary-600">เด</span> </h2> <p class="text-secondary-600 text-lg"></p> </div> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> ${featuredProducts.map((product) => renderTemplate`<a${addAttribute(product.href, "href")} class="card group"> <div class="relative aspect-video bg-secondary-100 overflow-hidden"> <img${addAttribute(product.image, "src")}${addAttribute(product.name, "alt")} class="object-cover w-full h-48 group-hover:scale-105 transition-transform duration-300" loading="lazy"> </div> <div class="p-6"> <h3 class="text-lg font-bold text-secondary-900 group-hover:text-primary-600 transition-colors"> ${product.name} </h3> <p class="mt-2 text-sm text-secondary-600 line-clamp-2"> ${product.shortDescription || product.description} </p> <div class="mt-4 flex items-center text-primary-600 font-medium"> <span></span> <svg class="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M9 5l7 7-7 7"></path> </svg> </div> </div> </a>`)} </div> <div class="text-center mt-12"> <a href="/products/" class="btn-primary">
นคาทงหมด
</a> </div> </div> </section> <!-- About Preview --> <section class="py-16 bg-white"> <div class="container mx-auto px-4"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center"> <div> <h2 class="text-3xl md:text-4xl font-bold text-secondary-900 mb-6">
เกยวก<span class="text-primary-600">เรา</span> </h2> <p class="text-lg text-secondary-600 mb-6">
บร พล เทค จำก เราเปนผเชยวชาญดานระบบน ใหคำแนะนำและจำหนายท PPR ตราชาง อพอาร PPR HDPE Thai PPR ณภาพส ราคาถ
</p> <p class="text-secondary-700 mb-8">
วยประสบการณยาวนาน เราพรอมใหบรการสนคาคณภาพและคำแนะนำจากผเชยวชาญ เพอใหงานระบบของคณมประสทธภาพสงส
</p> <a href="/about-us/" class="btn-secondary">
านเพมเต
</a> </div> <div class="relative aspect-video bg-secondary-100 rounded-xl overflow-hidden"> <img src="/images/2021/03/ppr-pipe_000C.jpg" alt="" class="object-cover w-full h-full" loading="lazy"> </div> </div> </div> </section> <!-- CTA Section --> <section class="py-16 bg-primary-600"> <div class="container mx-auto px-4 text-center"> <h2 class="text-3xl md:text-4xl font-bold text-white mb-4">
สนใจสนคาหรอตองการคำปรกษา?
</h2> <p class="text-xl text-primary-100 mb-8 max-w-2xl mx-auto">
มงานของเราพรอมใหคำแนะนำและชวยคณเลอกสนคาทเหมาะสมท
</p> <div class="flex flex-wrap justify-center gap-4"> <a href="tel:090-555-1415" class="btn-secondary bg-white text-primary-600 hover:bg-primary-50">
โทร: 090-555-1415
</a> <a href="https://line.me/ti/p/@dealplustech" target="_blank" rel="noopener" class="btn-outline border-white text-white hover:bg-white hover:text-primary-600">
เพิ่มเพื่อน LINE
</a> </div> </div> </section> </main> ${renderComponent($$result2, "FloatingContact", $$FloatingContact, {})} ` })}`;
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/index.astro", void 0);
const $$file = "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/index.astro";
const $$url = "";
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: $$Index,
file: $$file,
url: $$url
}, Symbol.toStringTag, { value: 'Module' }));
const page = () => _page;
export { page };

View File

@@ -1,34 +0,0 @@
import { c as createComponent, m as maybeRenderHead, d as addAttribute, a as renderTemplate, b as createAstro, r as renderComponent } from '../chunks/astro/server_D-JZF3a4.mjs';
import 'piccolore';
import { g as getCollection } from '../chunks/_astro_content_C5hvN5fw.mjs';
import 'clsx';
import { $ as $$BaseLayout } from '../chunks/BaseLayout_CQrO8n43.mjs';
export { renderers } from '../renderers.mjs';
const $$Astro = createAstro();
const $$ProductCard = createComponent(($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
Astro2.self = $$ProductCard;
const { product } = Astro2.props;
const { name, shortDescription, image } = product.data;
return renderTemplate`${maybeRenderHead()}<a${addAttribute(`/products/${product.data.slug}`, "href")} class="card group"> <div class="aspect-w-16 aspect-h-9 overflow-hidden bg-secondary-100"> <img${addAttribute(image || "/placeholder.jpg", "src")}${addAttribute(name, "alt")} class="object-cover w-full h-48 group-hover:scale-105 transition-transform duration-300" loading="lazy"> </div> <div class="p-6"> <h3 class="text-lg font-bold text-secondary-900 group-hover:text-primary-600 transition-colors"> ${name} </h3> ${shortDescription && renderTemplate`<p class="mt-2 text-sm text-secondary-600 line-clamp-2"> ${shortDescription} </p>`} <div class="mt-4 flex items-center text-primary-600 font-medium"> <span>ดูรายละเอียด</span> <svg class="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M9 5l7 7-7 7"></path> </svg> </div> </div> </a>`;
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/components/ProductCard.astro", void 0);
const $$Index = createComponent(async ($$result, $$props, $$slots) => {
const products = await getCollection("products");
return renderTemplate`${renderComponent($$result, "BaseLayout", $$BaseLayout, { "title": "\u0E2A\u0E34\u0E19\u0E04\u0E49\u0E32\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14", "description": "\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E2A\u0E34\u0E19\u0E04\u0E49\u0E32\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14\u0E08\u0E32\u0E01 Deal Plus Tech" }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<main class="py-12"> <div class="container mx-auto px-4 max-w-6xl"> <h1 class="section-title mb-8">นคาทงหมด</h1> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> ${products.map((product) => renderTemplate`${renderComponent($$result2, "ProductCard", $$ProductCard, { "product": product })}`)} </div> </div> </main> ` })}`;
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/products/index.astro", void 0);
const $$file = "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/products/index.astro";
const $$url = "/products";
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: $$Index,
file: $$file,
url: $$url
}, Symbol.toStringTag, { value: 'Module' }));
const page = () => _page;
export { page };

View File

@@ -1,48 +0,0 @@
import { c as createComponent, r as renderComponent, a as renderTemplate, b as createAstro, m as maybeRenderHead, d as addAttribute } from '../../chunks/astro/server_D-JZF3a4.mjs';
import 'piccolore';
import { r as renderEntry, g as getCollection } from '../../chunks/_astro_content_C5hvN5fw.mjs';
import { $ as $$BaseLayout } from '../../chunks/BaseLayout_CQrO8n43.mjs';
import { p as productCategories } from '../../chunks/site-config_BJHCdssj.mjs';
/* empty css */
export { renderers } from '../../renderers.mjs';
const $$Astro = createAstro();
async function getStaticPaths() {
const products = await getCollection("products");
return products.map((product) => ({
params: { slug: product.data.slug },
props: { product }
}));
}
const $$slug = createComponent(async ($$result, $$props, $$slots) => {
const Astro2 = $$result.createAstro($$Astro, $$props, $$slots);
Astro2.self = $$slug;
const { product } = Astro2.props;
const { Content } = await renderEntry(product);
const productData = productCategories.find((p) => p.id === product.data.id);
const productTables = productData?.productTables || [];
return renderTemplate`${renderComponent($$result, "BaseLayout", $$BaseLayout, { "title": product.data.name, "description": product.data.shortDescription || product.data.description, "data-astro-cid-o422f4lv": true }, { "default": async ($$result2) => renderTemplate` ${maybeRenderHead()}<main class="py-12" data-astro-cid-o422f4lv> <article class="container mx-auto px-4 max-w-7xl" data-astro-cid-o422f4lv> <!-- Product Header --> <header class="mb-12" data-astro-cid-o422f4lv> <h1 class="text-4xl md:text-5xl lg:text-6xl xl:text-7xl font-bold text-secondary-900 mb-6" data-astro-cid-o422f4lv> ${product.data.name} </h1> <p class="text-lg md:text-xl lg:text-2xl xl:text-3xl text-secondary-600 max-w-4xl" data-astro-cid-o422f4lv> ${product.data.description} </p> </header> <!-- Content from Markdown --> <div class="prose prose-lg md:prose-xl lg:prose-2xl max-w-none mb-12" data-astro-cid-o422f4lv> ${renderComponent($$result2, "Content", Content, { "data-astro-cid-o422f4lv": true })} </div> <!-- Product Tables Section --> ${productTables.length > 0 && renderTemplate`<section class="mb-12" data-astro-cid-o422f4lv> <h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-secondary-900 mb-8 flex items-center gap-3" data-astro-cid-o422f4lv> <svg class="w-8 h-8 md:w-10 md:h-10 lg:w-12 lg:h-12 text-primary-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" data-astro-cid-o422f4lv> <path stroke-linecap="round" stroke-linejoin="round"${addAttribute(2, "stroke-width")} d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" data-astro-cid-o422f4lv></path> </svg>
ตารางขอมลผลตภณฑ
</h2> <div class="space-y-10" data-astro-cid-o422f4lv> ${productTables.map((table2, tableIndex) => renderTemplate`<div class="bg-white rounded-2xl border-2 border-secondary-200 shadow-lg" data-astro-cid-o422f4lv> <h3 class="text-lg md:text-xl lg:text-2xl font-semibold text-secondary-800 p-4 md:p-6 bg-secondary-50 border-b-2 border-secondary-200" data-astro-cid-o422f4lv> ${table2.tableName} </h3> <div class="w-full overflow-x-auto -mx-2 md:mx-0" data-astro-cid-o422f4lv> <div class="px-2 md:px-0" data-astro-cid-o422f4lv> <table class="w-full min-w-[600px] border-collapse" data-astro-cid-o422f4lv> <thead data-astro-cid-o422f4lv> <tr class="bg-primary-100" data-astro-cid-o422f4lv> ${table2.headers.map((header, headerIndex) => renderTemplate`<th class="px-3 py-2 md:px-4 md:py-3 text-left text-xs md:text-sm lg:text-base font-bold text-primary-800 border-b-2 border-primary-300 whitespace-nowrap" data-astro-cid-o422f4lv> ${header} </th>`)} </tr> </thead> <tbody data-astro-cid-o422f4lv> ${table2.rows.map((row, rowIndex) => renderTemplate`<tr${addAttribute(rowIndex % 2 === 0 ? "bg-white" : "bg-secondary-50", "class")} data-astro-cid-o422f4lv> ${row.map((cell, cellIndex) => renderTemplate`<td class="px-3 py-2 md:px-4 md:py-3 text-xs md:text-sm lg:text-base text-secondary-700 border-b border-secondary-100 break-words max-w-[200px] md:max-w-none" data-astro-cid-o422f4lv> ${cell} </td>`)} </tr>`)} </tbody> </table> </div> </div> </div>`)} </div> </section>`})}
<tbody data-astro-cid-o422f4lv> ${table.rows.map((row, rowIndex) => renderTemplate`<tr${addAttribute(rowIndex % 2 === 0 ? "bg-white" : "bg-secondary-50", "class")} data-astro-cid-o422f4lv> ${row.map((cell, cellIndex) => renderTemplate`<td class="px-5 py-4 md:px-6 md:py-5 text-base md:text-lg lg:text-xl text-secondary-700 border-b border-secondary-100" data-astro-cid-o422f4lv> ${cell} </td>`)} </tr>`)} </tbody>
))}
)}
<!-- Specifications --> ${product.data.specifications && product.data.specifications.length > 0 && renderTemplate`<section class="mb-12" data-astro-cid-o422f4lv> <h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-secondary-900 mb-8" data-astro-cid-o422f4lv>ข้อมูลจำเพาะ</h2> <div class="bg-white rounded-2xl border-2 border-secondary-200 p-6 md:p-8 shadow-lg" data-astro-cid-o422f4lv> <dl class="grid grid-cols-1 md:grid-cols-2 gap-6" data-astro-cid-o422f4lv> ${product.data.specifications.map((spec, index) => renderTemplate`<div class="flex flex-col md:flex-row md:justify-between border-b border-secondary-100 pb-4" data-astro-cid-o422f4lv> <dt class="text-base md:text-lg lg:text-xl font-semibold text-secondary-700 mb-2 md:mb-0 md:mr-4" data-astro-cid-o422f4lv>${spec.label}</dt> <dd class="text-base md:text-lg lg:text-xl text-secondary-900" data-astro-cid-o422f4lv> ${spec.value} ${spec.unit && renderTemplate`<span class="text-secondary-500 ml-2" data-astro-cid-o422f4lv>${spec.unit}</span>`} </dd> </div>`)} </dl> </div> </section>`} <!-- Features --> ${product.data.features && product.data.features.length > 0 && renderTemplate`<section class="mb-12" data-astro-cid-o422f4lv> <h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-secondary-900 mb-8" data-astro-cid-o422f4lv>คุณสมบัติเด่น</h2> <ul class="grid grid-cols-1 md:grid-cols-2 gap-6" data-astro-cid-o422f4lv> ${product.data.features.map((feature, index) => renderTemplate`<li class="flex items-start gap-4 bg-white p-6 rounded-xl border border-secondary-200 shadow" data-astro-cid-o422f4lv> <span class="text-2xl md:text-3xl text-primary-600 flex-shrink-0" data-astro-cid-o422f4lv>✓</span> <span class="text-base md:text-lg lg:text-xl text-secondary-700" data-astro-cid-o422f4lv>${feature}</span> </li>`)} </ul> </section>`} <!-- FAQ --> ${product.data.faq && product.data.faq.length > 0 && renderTemplate`<section class="mb-12" data-astro-cid-o422f4lv> <h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-secondary-900 mb-8" data-astro-cid-o422f4lv>คำถามที่พบบ่อย</h2> <div class="space-y-6" data-astro-cid-o422f4lv> ${product.data.faq.map((item, index) => renderTemplate`<div class="bg-white rounded-2xl p-6 md:p-8 border-2 border-secondary-200 shadow" data-astro-cid-o422f4lv> <h3 class="text-xl md:text-2xl lg:text-3xl font-bold text-secondary-900 mb-4" data-astro-cid-o422f4lv>${item.question}</h3> <p class="text-base md:text-lg lg:text-xl text-secondary-700 leading-relaxed" data-astro-cid-o422f4lv>${item.answer}</p> </div>`)} </div> </section>`} </article> </main> ` })} `;
}, "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/products/[slug].astro", void 0);
const $$file = "/Users/kunthawatgreethong/Gitea/dealplustech/dealplustech-astro/src/pages/products/[slug].astro";
const $$url = "/products/[slug]";
const _page = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
default: $$slug,
file: $$file,
getStaticPaths,
url: $$url
}, Symbol.toStringTag, { value: 'Module' }));
const page = () => _page;
export { page };

View File

@@ -1,3 +0,0 @@
const renderers = [];
export { renderers };

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env node
import process from 'node:process';
import isDocker from './index.js';
process.exitCode = isDocker() ? 0 : 2;

View File

@@ -1,13 +0,0 @@
/**
Check if the process is running inside a Docker container.
@example
```
import isDocker from 'is-docker';
if (isDocker()) {
console.log('Running inside a Docker container');
}
```
*/
export default function isDocker(): boolean;

View File

@@ -1,29 +0,0 @@
import fs from 'node:fs';
let isDockerCached;
function hasDockerEnv() {
try {
fs.statSync('/.dockerenv');
return true;
} catch {
return false;
}
}
function hasDockerCGroup() {
try {
return fs.readFileSync('/proc/self/cgroup', 'utf8').includes('docker');
} catch {
return false;
}
}
export default function isDocker() {
// TODO: Use `??=` when targeting Node.js 16.
if (isDockerCached === undefined) {
isDockerCached = hasDockerEnv() || hasDockerCGroup();
}
return isDockerCached;
}

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,44 +0,0 @@
{
"name": "is-docker",
"version": "3.0.0",
"description": "Check if the process is running inside a Docker container",
"license": "MIT",
"repository": "sindresorhus/is-docker",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"bin": "./cli.js",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts",
"cli.js"
],
"keywords": [
"detect",
"docker",
"dockerized",
"container",
"inside",
"is",
"env",
"environment",
"process"
],
"devDependencies": {
"ava": "^3.15.0",
"sinon": "^11.1.2",
"tsd": "^0.17.0",
"xo": "^0.44.0"
}
}

View File

@@ -1,27 +0,0 @@
# is-docker
> Check if the process is running inside a Docker container
## Install
```
$ npm install is-docker
```
## Usage
```js
import isDocker from 'is-docker';
if (isDocker()) {
console.log('Running inside a Docker container');
}
```
## CLI
```
$ is-docker
```
Exits with code 0 if inside a Docker container and 2 if not.

View File

@@ -1,23 +0,0 @@
import fs from 'node:fs';
import isDocker from 'is-docker';
let cachedResult;
// Podman detection
const hasContainerEnv = () => {
try {
fs.statSync('/run/.containerenv');
return true;
} catch {
return false;
}
};
export default function isInsideContainer() {
// TODO: Use `??=` when targeting Node.js 16.
if (cachedResult === undefined) {
cachedResult = hasContainerEnv() || isDocker();
}
return cachedResult;
}

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,35 +0,0 @@
/**
Check if a value is a plain object.
An object is plain if it's created by either `{}`, `new Object()`, or `Object.create(null)`.
@example
```
import isPlainObject from 'is-plain-obj';
import {runInNewContext} from 'node:vm';
isPlainObject({foo: 'bar'});
//=> true
isPlainObject(new Object());
//=> true
isPlainObject(Object.create(null));
//=> true
// This works across realms
isPlainObject(runInNewContext('({})'));
//=> true
isPlainObject([1, 2, 3]);
//=> false
class Unicorn {}
isPlainObject(new Unicorn());
//=> false
isPlainObject(Math);
//=> false
```
*/
export default function isPlainObject<Value>(value: unknown): value is Record<PropertyKey, Value>;

View File

@@ -1,8 +0,0 @@
export default function isPlainObject(value) {
if (typeof value !== 'object' || value === null) {
return false;
}
const prototype = Object.getPrototypeOf(value);
return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value);
}

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,41 +0,0 @@
{
"name": "is-plain-obj",
"version": "4.1.0",
"description": "Check if a value is a plain object",
"license": "MIT",
"repository": "sindresorhus/is-plain-obj",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"engines": {
"node": ">=12"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"object",
"is",
"check",
"test",
"type",
"plain",
"vanilla",
"pure",
"simple"
],
"devDependencies": {
"ava": "^3.15.0",
"tsd": "^0.14.0",
"xo": "^0.38.2"
}
}

View File

@@ -1,58 +0,0 @@
# is-plain-obj
> Check if a value is a plain object
An object is plain if it's created by either `{}`, `new Object()`, or `Object.create(null)`.
## Install
```
$ npm install is-plain-obj
```
## Usage
```js
import isPlainObject from 'is-plain-obj';
import {runInNewContext} from 'node:vm';
isPlainObject({foo: 'bar'});
//=> true
isPlainObject(new Object());
//=> true
isPlainObject(Object.create(null));
//=> true
// This works across realms
isPlainObject(runInNewContext('({})'));
//=> true
isPlainObject([1, 2, 3]);
//=> false
class Unicorn {}
isPlainObject(new Unicorn());
//=> false
isPlainObject(Math);
//=> false
```
## Related
- [is-obj](https://github.com/sindresorhus/is-obj) - Check if a value is an object
- [is](https://github.com/sindresorhus/is) - Type check values
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-is-plain-obj?utm_source=npm-is-plain-obj&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Madeline Gurriarán
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,25 +0,0 @@
# tinyglobby
[![npm version](https://img.shields.io/npm/v/tinyglobby.svg?maxAge=3600)](https://npmjs.com/package/tinyglobby)
[![monthly downloads](https://img.shields.io/npm/dm/tinyglobby.svg?maxAge=3600)](https://npmjs.com/package/tinyglobby)
A fast and minimal alternative to globby and fast-glob, meant to behave the same way.
Both globby and fast-glob present some behavior no other globbing lib has,
which makes it hard to manually replace with something smaller and better.
This library uses only two subdependencies, compared to `globby`'s [23](https://npmgraph.js.org/?q=globby@14.1.0)
and `fast-glob`'s [17](https://npmgraph.js.org/?q=fast-glob@3.3.3).
## Usage
```js
import { glob, globSync } from 'tinyglobby';
await glob(['files/*.ts', '!**/*.d.ts'], { cwd: 'src' });
globSync('src/**/*.ts', { ignore: '**/*.d.ts' });
```
## Documentation
Visit https://superchupu.dev/tinyglobby to read the full documentation.

View File

@@ -1,350 +0,0 @@
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let fs = require("fs");
fs = __toESM(fs);
let path = require("path");
path = __toESM(path);
let url = require("url");
url = __toESM(url);
let fdir = require("fdir");
fdir = __toESM(fdir);
let picomatch = require("picomatch");
picomatch = __toESM(picomatch);
//#region src/utils.ts
const isReadonlyArray = Array.isArray;
const isWin = process.platform === "win32";
const ONLY_PARENT_DIRECTORIES = /^(\/?\.\.)+$/;
function getPartialMatcher(patterns, options = {}) {
const patternsCount = patterns.length;
const patternsParts = Array(patternsCount);
const matchers = Array(patternsCount);
const globstarEnabled = !options.noglobstar;
for (let i = 0; i < patternsCount; i++) {
const parts = splitPattern(patterns[i]);
patternsParts[i] = parts;
const partsCount = parts.length;
const partMatchers = Array(partsCount);
for (let j = 0; j < partsCount; j++) partMatchers[j] = (0, picomatch.default)(parts[j], options);
matchers[i] = partMatchers;
}
return (input) => {
const inputParts = input.split("/");
if (inputParts[0] === ".." && ONLY_PARENT_DIRECTORIES.test(input)) return true;
for (let i = 0; i < patterns.length; i++) {
const patternParts = patternsParts[i];
const matcher = matchers[i];
const inputPatternCount = inputParts.length;
const minParts = Math.min(inputPatternCount, patternParts.length);
let j = 0;
while (j < minParts) {
const part = patternParts[j];
if (part.includes("/")) return true;
const match = matcher[j](inputParts[j]);
if (!match) break;
if (globstarEnabled && part === "**") return true;
j++;
}
if (j === inputPatternCount) return true;
}
return false;
};
}
/* node:coverage ignore next 2 */
const WIN32_ROOT_DIR = /^[A-Z]:\/$/i;
const isRoot = isWin ? (p) => WIN32_ROOT_DIR.test(p) : (p) => p === "/";
function buildFormat(cwd, root, absolute) {
if (cwd === root || root.startsWith(`${cwd}/`)) {
if (absolute) {
const start = isRoot(cwd) ? cwd.length : cwd.length + 1;
return (p, isDir) => p.slice(start, isDir ? -1 : void 0) || ".";
}
const prefix = root.slice(cwd.length + 1);
if (prefix) return (p, isDir) => {
if (p === ".") return prefix;
const result = `${prefix}/${p}`;
return isDir ? result.slice(0, -1) : result;
};
return (p, isDir) => isDir && p !== "." ? p.slice(0, -1) : p;
}
if (absolute) return (p) => path.posix.relative(cwd, p) || ".";
return (p) => path.posix.relative(cwd, `${root}/${p}`) || ".";
}
function buildRelative(cwd, root) {
if (root.startsWith(`${cwd}/`)) {
const prefix = root.slice(cwd.length + 1);
return (p) => `${prefix}/${p}`;
}
return (p) => {
const result = path.posix.relative(cwd, `${root}/${p}`);
if (p.endsWith("/") && result !== "") return `${result}/`;
return result || ".";
};
}
const splitPatternOptions = { parts: true };
function splitPattern(path$2) {
var _result$parts;
const result = picomatch.default.scan(path$2, splitPatternOptions);
return ((_result$parts = result.parts) === null || _result$parts === void 0 ? void 0 : _result$parts.length) ? result.parts : [path$2];
}
const ESCAPED_WIN32_BACKSLASHES = /\\(?![()[\]{}!+@])/g;
function convertPosixPathToPattern(path$2) {
return escapePosixPath(path$2);
}
function convertWin32PathToPattern(path$2) {
return escapeWin32Path(path$2).replace(ESCAPED_WIN32_BACKSLASHES, "/");
}
/**
* Converts a path to a pattern depending on the platform.
* Identical to {@link escapePath} on POSIX systems.
* @see {@link https://superchupu.dev/tinyglobby/documentation#convertPathToPattern}
*/
/* node:coverage ignore next 3 */
const convertPathToPattern = isWin ? convertWin32PathToPattern : convertPosixPathToPattern;
const POSIX_UNESCAPED_GLOB_SYMBOLS = /(?<!\\)([()[\]{}*?|]|^!|[!+@](?=\()|\\(?![()[\]{}!*+?@|]))/g;
const WIN32_UNESCAPED_GLOB_SYMBOLS = /(?<!\\)([()[\]{}]|^!|[!+@](?=\())/g;
const escapePosixPath = (path$2) => path$2.replace(POSIX_UNESCAPED_GLOB_SYMBOLS, "\\$&");
const escapeWin32Path = (path$2) => path$2.replace(WIN32_UNESCAPED_GLOB_SYMBOLS, "\\$&");
/**
* Escapes a path's special characters depending on the platform.
* @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath}
*/
/* node:coverage ignore next */
const escapePath = isWin ? escapeWin32Path : escapePosixPath;
/**
* Checks if a pattern has dynamic parts.
*
* Has a few minor differences with [`fast-glob`](https://github.com/mrmlnc/fast-glob) for better accuracy:
*
* - Doesn't necessarily return `false` on patterns that include `\`.
* - Returns `true` if the pattern includes parentheses, regardless of them representing one single pattern or not.
* - Returns `true` for unfinished glob extensions i.e. `(h`, `+(h`.
* - Returns `true` for unfinished brace expansions as long as they include `,` or `..`.
*
* @see {@link https://superchupu.dev/tinyglobby/documentation#isDynamicPattern}
*/
function isDynamicPattern(pattern, options) {
if ((options === null || options === void 0 ? void 0 : options.caseSensitiveMatch) === false) return true;
const scan = picomatch.default.scan(pattern);
return scan.isGlob || scan.negated;
}
function log(...tasks) {
console.log(`[tinyglobby ${(/* @__PURE__ */ new Date()).toLocaleTimeString("es")}]`, ...tasks);
}
//#endregion
//#region src/index.ts
const PARENT_DIRECTORY = /^(\/?\.\.)+/;
const ESCAPING_BACKSLASHES = /\\(?=[()[\]{}!*+?@|])/g;
const BACKSLASHES = /\\/g;
function normalizePattern(pattern, expandDirectories, cwd, props, isIgnore) {
let result = pattern;
if (pattern.endsWith("/")) result = pattern.slice(0, -1);
if (!result.endsWith("*") && expandDirectories) result += "/**";
const escapedCwd = escapePath(cwd);
if (path.default.isAbsolute(result.replace(ESCAPING_BACKSLASHES, ""))) result = path.posix.relative(escapedCwd, result);
else result = path.posix.normalize(result);
const parentDirectoryMatch = PARENT_DIRECTORY.exec(result);
const parts = splitPattern(result);
if (parentDirectoryMatch === null || parentDirectoryMatch === void 0 ? void 0 : parentDirectoryMatch[0]) {
const n = (parentDirectoryMatch[0].length + 1) / 3;
let i = 0;
const cwdParts = escapedCwd.split("/");
while (i < n && parts[i + n] === cwdParts[cwdParts.length + i - n]) {
result = result.slice(0, (n - i - 1) * 3) + result.slice((n - i) * 3 + parts[i + n].length + 1) || ".";
i++;
}
const potentialRoot = path.posix.join(cwd, parentDirectoryMatch[0].slice(i * 3));
if (!potentialRoot.startsWith(".") && props.root.length > potentialRoot.length) {
props.root = potentialRoot;
props.depthOffset = -n + i;
}
}
if (!isIgnore && props.depthOffset >= 0) {
var _props$commonPath;
(_props$commonPath = props.commonPath) !== null && _props$commonPath !== void 0 || (props.commonPath = parts);
const newCommonPath = [];
const length = Math.min(props.commonPath.length, parts.length);
for (let i = 0; i < length; i++) {
const part = parts[i];
if (part === "**" && !parts[i + 1]) {
newCommonPath.pop();
break;
}
if (part !== props.commonPath[i] || isDynamicPattern(part) || i === parts.length - 1) break;
newCommonPath.push(part);
}
props.depthOffset = newCommonPath.length;
props.commonPath = newCommonPath;
props.root = newCommonPath.length > 0 ? path.posix.join(cwd, ...newCommonPath) : cwd;
}
return result;
}
function processPatterns({ patterns = ["**/*"], ignore = [], expandDirectories = true }, cwd, props) {
if (typeof patterns === "string") patterns = [patterns];
if (typeof ignore === "string") ignore = [ignore];
const matchPatterns = [];
const ignorePatterns = [];
for (const pattern of ignore) {
if (!pattern) continue;
if (pattern[0] !== "!" || pattern[1] === "(") ignorePatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, true));
}
for (const pattern of patterns) {
if (!pattern) continue;
if (pattern[0] !== "!" || pattern[1] === "(") matchPatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, false));
else if (pattern[1] !== "!" || pattern[2] === "(") ignorePatterns.push(normalizePattern(pattern.slice(1), expandDirectories, cwd, props, true));
}
return {
match: matchPatterns,
ignore: ignorePatterns
};
}
function formatPaths(paths, relative) {
for (let i = paths.length - 1; i >= 0; i--) {
const path$2 = paths[i];
paths[i] = relative(path$2);
}
return paths;
}
function normalizeCwd(cwd) {
if (!cwd) return process.cwd().replace(BACKSLASHES, "/");
if (cwd instanceof URL) return (0, url.fileURLToPath)(cwd).replace(BACKSLASHES, "/");
return path.default.resolve(cwd).replace(BACKSLASHES, "/");
}
function getCrawler(patterns, inputOptions = {}) {
const options = process.env.TINYGLOBBY_DEBUG ? {
...inputOptions,
debug: true
} : inputOptions;
const cwd = normalizeCwd(options.cwd);
if (options.debug) log("globbing with:", {
patterns,
options,
cwd
});
if (Array.isArray(patterns) && patterns.length === 0) return [{
sync: () => [],
withPromise: async () => []
}, false];
const props = {
root: cwd,
commonPath: null,
depthOffset: 0
};
const processed = processPatterns({
...options,
patterns
}, cwd, props);
if (options.debug) log("internal processing patterns:", processed);
const matchOptions = {
dot: options.dot,
nobrace: options.braceExpansion === false,
nocase: options.caseSensitiveMatch === false,
noextglob: options.extglob === false,
noglobstar: options.globstar === false,
posix: true
};
const matcher = (0, picomatch.default)(processed.match, {
...matchOptions,
ignore: processed.ignore
});
const ignore = (0, picomatch.default)(processed.ignore, matchOptions);
const partialMatcher = getPartialMatcher(processed.match, matchOptions);
const format = buildFormat(cwd, props.root, options.absolute);
const formatExclude = options.absolute ? format : buildFormat(cwd, props.root, true);
const fdirOptions = {
filters: [options.debug ? (p, isDirectory) => {
const path$2 = format(p, isDirectory);
const matches = matcher(path$2);
if (matches) log(`matched ${path$2}`);
return matches;
} : (p, isDirectory) => matcher(format(p, isDirectory))],
exclude: options.debug ? (_, p) => {
const relativePath = formatExclude(p, true);
const skipped = relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath);
if (skipped) log(`skipped ${p}`);
else log(`crawling ${p}`);
return skipped;
} : (_, p) => {
const relativePath = formatExclude(p, true);
return relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath);
},
fs: options.fs ? {
readdir: options.fs.readdir || fs.default.readdir,
readdirSync: options.fs.readdirSync || fs.default.readdirSync,
realpath: options.fs.realpath || fs.default.realpath,
realpathSync: options.fs.realpathSync || fs.default.realpathSync,
stat: options.fs.stat || fs.default.stat,
statSync: options.fs.statSync || fs.default.statSync
} : void 0,
pathSeparator: "/",
relativePaths: true,
resolveSymlinks: true,
signal: options.signal
};
if (options.deep !== void 0) fdirOptions.maxDepth = Math.round(options.deep - props.depthOffset);
if (options.absolute) {
fdirOptions.relativePaths = false;
fdirOptions.resolvePaths = true;
fdirOptions.includeBasePath = true;
}
if (options.followSymbolicLinks === false) {
fdirOptions.resolveSymlinks = false;
fdirOptions.excludeSymlinks = true;
}
if (options.onlyDirectories) {
fdirOptions.excludeFiles = true;
fdirOptions.includeDirs = true;
} else if (options.onlyFiles === false) fdirOptions.includeDirs = true;
props.root = props.root.replace(BACKSLASHES, "");
const root = props.root;
if (options.debug) log("internal properties:", props);
const relative = cwd !== root && !options.absolute && buildRelative(cwd, props.root);
return [new fdir.fdir(fdirOptions).crawl(root), relative];
}
async function glob(patternsOrOptions, options) {
if (patternsOrOptions && (options === null || options === void 0 ? void 0 : options.patterns)) throw new Error("Cannot pass patterns as both an argument and an option");
const isModern = isReadonlyArray(patternsOrOptions) || typeof patternsOrOptions === "string";
const opts = isModern ? options : patternsOrOptions;
const patterns = isModern ? patternsOrOptions : patternsOrOptions.patterns;
const [crawler, relative] = getCrawler(patterns, opts);
if (!relative) return crawler.withPromise();
return formatPaths(await crawler.withPromise(), relative);
}
function globSync(patternsOrOptions, options) {
if (patternsOrOptions && (options === null || options === void 0 ? void 0 : options.patterns)) throw new Error("Cannot pass patterns as both an argument and an option");
const isModern = isReadonlyArray(patternsOrOptions) || typeof patternsOrOptions === "string";
const opts = isModern ? options : patternsOrOptions;
const patterns = isModern ? patternsOrOptions : patternsOrOptions.patterns;
const [crawler, relative] = getCrawler(patterns, opts);
if (!relative) return crawler.sync();
return formatPaths(crawler.sync(), relative);
}
//#endregion
exports.convertPathToPattern = convertPathToPattern;
exports.escapePath = escapePath;
exports.glob = glob;
exports.globSync = globSync;
exports.isDynamicPattern = isDynamicPattern;

View File

@@ -1,147 +0,0 @@
import { FSLike } from "fdir";
//#region src/utils.d.ts
/**
* Converts a path to a pattern depending on the platform.
* Identical to {@link escapePath} on POSIX systems.
* @see {@link https://superchupu.dev/tinyglobby/documentation#convertPathToPattern}
*/
declare const convertPathToPattern: (path: string) => string;
/**
* Escapes a path's special characters depending on the platform.
* @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath}
*/
declare const escapePath: (path: string) => string;
/**
* Checks if a pattern has dynamic parts.
*
* Has a few minor differences with [`fast-glob`](https://github.com/mrmlnc/fast-glob) for better accuracy:
*
* - Doesn't necessarily return `false` on patterns that include `\`.
* - Returns `true` if the pattern includes parentheses, regardless of them representing one single pattern or not.
* - Returns `true` for unfinished glob extensions i.e. `(h`, `+(h`.
* - Returns `true` for unfinished brace expansions as long as they include `,` or `..`.
*
* @see {@link https://superchupu.dev/tinyglobby/documentation#isDynamicPattern}
*/
declare function isDynamicPattern(pattern: string, options?: {
caseSensitiveMatch: boolean;
}): boolean;
//#endregion
//#region src/index.d.ts
interface GlobOptions {
/**
* Whether to return absolute paths. Disable to have relative paths.
* @default false
*/
absolute?: boolean;
/**
* Enables support for brace expansion syntax, like `{a,b}` or `{1..9}`.
* @default true
*/
braceExpansion?: boolean;
/**
* Whether to match in case-sensitive mode.
* @default true
*/
caseSensitiveMatch?: boolean;
/**
* The working directory in which to search. Results will be returned relative to this directory, unless
* {@link absolute} is set.
*
* It is important to avoid globbing outside this directory when possible, even with absolute paths enabled,
* as doing so can harm performance due to having to recalculate relative paths.
* @default process.cwd()
*/
cwd?: string | URL;
/**
* Logs useful debug information. Meant for development purposes. Logs can change at any time.
* @default false
*/
debug?: boolean;
/**
* Maximum directory depth to crawl.
* @default Infinity
*/
deep?: number;
/**
* Whether to return entries that start with a dot, like `.gitignore` or `.prettierrc`.
* @default false
*/
dot?: boolean;
/**
* Whether to automatically expand directory patterns.
*
* Important to disable if migrating from [`fast-glob`](https://github.com/mrmlnc/fast-glob).
* @default true
*/
expandDirectories?: boolean;
/**
* Enables support for extglobs, like `+(pattern)`.
* @default true
*/
extglob?: boolean;
/**
* Whether to traverse and include symbolic links. Can slightly affect performance.
* @default true
*/
followSymbolicLinks?: boolean;
/**
* An object that overrides `node:fs` functions.
* @default import('node:fs')
*/
fs?: FileSystemAdapter;
/**
* Enables support for matching nested directories with globstars (`**`).
* If `false`, `**` behaves exactly like `*`.
* @default true
*/
globstar?: boolean;
/**
* Glob patterns to exclude from the results.
* @default []
*/
ignore?: string | readonly string[];
/**
* Enable to only return directories.
* If `true`, disables {@link onlyFiles}.
* @default false
*/
onlyDirectories?: boolean;
/**
* Enable to only return files.
* @default true
*/
onlyFiles?: boolean;
/**
* @deprecated Provide patterns as the first argument instead.
*/
patterns?: string | readonly string[];
/**
* An `AbortSignal` to abort crawling the file system.
* @default undefined
*/
signal?: AbortSignal;
}
type FileSystemAdapter = Partial<FSLike>;
/**
* Asynchronously match files following a glob pattern.
* @see {@link https://superchupu.dev/tinyglobby/documentation#glob}
*/
declare function glob(patterns: string | readonly string[], options?: Omit<GlobOptions, "patterns">): Promise<string[]>;
/**
* @deprecated Provide patterns as the first argument instead.
*/
declare function glob(options: GlobOptions): Promise<string[]>;
/**
* Synchronously match files following a glob pattern.
* @see {@link https://superchupu.dev/tinyglobby/documentation#globSync}
*/
declare function globSync(patterns: string | readonly string[], options?: Omit<GlobOptions, "patterns">): string[];
/**
* @deprecated Provide patterns as the first argument instead.
*/
declare function globSync(options: GlobOptions): string[];
//#endregion
export { FileSystemAdapter, GlobOptions, convertPathToPattern, escapePath, glob, globSync, isDynamicPattern };

View File

@@ -1,147 +0,0 @@
import { FSLike } from "fdir";
//#region src/utils.d.ts
/**
* Converts a path to a pattern depending on the platform.
* Identical to {@link escapePath} on POSIX systems.
* @see {@link https://superchupu.dev/tinyglobby/documentation#convertPathToPattern}
*/
declare const convertPathToPattern: (path: string) => string;
/**
* Escapes a path's special characters depending on the platform.
* @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath}
*/
declare const escapePath: (path: string) => string;
/**
* Checks if a pattern has dynamic parts.
*
* Has a few minor differences with [`fast-glob`](https://github.com/mrmlnc/fast-glob) for better accuracy:
*
* - Doesn't necessarily return `false` on patterns that include `\`.
* - Returns `true` if the pattern includes parentheses, regardless of them representing one single pattern or not.
* - Returns `true` for unfinished glob extensions i.e. `(h`, `+(h`.
* - Returns `true` for unfinished brace expansions as long as they include `,` or `..`.
*
* @see {@link https://superchupu.dev/tinyglobby/documentation#isDynamicPattern}
*/
declare function isDynamicPattern(pattern: string, options?: {
caseSensitiveMatch: boolean;
}): boolean;
//#endregion
//#region src/index.d.ts
interface GlobOptions {
/**
* Whether to return absolute paths. Disable to have relative paths.
* @default false
*/
absolute?: boolean;
/**
* Enables support for brace expansion syntax, like `{a,b}` or `{1..9}`.
* @default true
*/
braceExpansion?: boolean;
/**
* Whether to match in case-sensitive mode.
* @default true
*/
caseSensitiveMatch?: boolean;
/**
* The working directory in which to search. Results will be returned relative to this directory, unless
* {@link absolute} is set.
*
* It is important to avoid globbing outside this directory when possible, even with absolute paths enabled,
* as doing so can harm performance due to having to recalculate relative paths.
* @default process.cwd()
*/
cwd?: string | URL;
/**
* Logs useful debug information. Meant for development purposes. Logs can change at any time.
* @default false
*/
debug?: boolean;
/**
* Maximum directory depth to crawl.
* @default Infinity
*/
deep?: number;
/**
* Whether to return entries that start with a dot, like `.gitignore` or `.prettierrc`.
* @default false
*/
dot?: boolean;
/**
* Whether to automatically expand directory patterns.
*
* Important to disable if migrating from [`fast-glob`](https://github.com/mrmlnc/fast-glob).
* @default true
*/
expandDirectories?: boolean;
/**
* Enables support for extglobs, like `+(pattern)`.
* @default true
*/
extglob?: boolean;
/**
* Whether to traverse and include symbolic links. Can slightly affect performance.
* @default true
*/
followSymbolicLinks?: boolean;
/**
* An object that overrides `node:fs` functions.
* @default import('node:fs')
*/
fs?: FileSystemAdapter;
/**
* Enables support for matching nested directories with globstars (`**`).
* If `false`, `**` behaves exactly like `*`.
* @default true
*/
globstar?: boolean;
/**
* Glob patterns to exclude from the results.
* @default []
*/
ignore?: string | readonly string[];
/**
* Enable to only return directories.
* If `true`, disables {@link onlyFiles}.
* @default false
*/
onlyDirectories?: boolean;
/**
* Enable to only return files.
* @default true
*/
onlyFiles?: boolean;
/**
* @deprecated Provide patterns as the first argument instead.
*/
patterns?: string | readonly string[];
/**
* An `AbortSignal` to abort crawling the file system.
* @default undefined
*/
signal?: AbortSignal;
}
type FileSystemAdapter = Partial<FSLike>;
/**
* Asynchronously match files following a glob pattern.
* @see {@link https://superchupu.dev/tinyglobby/documentation#glob}
*/
declare function glob(patterns: string | readonly string[], options?: Omit<GlobOptions, "patterns">): Promise<string[]>;
/**
* @deprecated Provide patterns as the first argument instead.
*/
declare function glob(options: GlobOptions): Promise<string[]>;
/**
* Synchronously match files following a glob pattern.
* @see {@link https://superchupu.dev/tinyglobby/documentation#globSync}
*/
declare function globSync(patterns: string | readonly string[], options?: Omit<GlobOptions, "patterns">): string[];
/**
* @deprecated Provide patterns as the first argument instead.
*/
declare function globSync(options: GlobOptions): string[];
//#endregion
export { FileSystemAdapter, GlobOptions, convertPathToPattern, escapePath, glob, globSync, isDynamicPattern };

View File

@@ -1,318 +0,0 @@
import nativeFs from "fs";
import path, { posix } from "path";
import { fileURLToPath } from "url";
import { fdir } from "fdir";
import picomatch from "picomatch";
//#region src/utils.ts
const isReadonlyArray = Array.isArray;
const isWin = process.platform === "win32";
const ONLY_PARENT_DIRECTORIES = /^(\/?\.\.)+$/;
function getPartialMatcher(patterns, options = {}) {
const patternsCount = patterns.length;
const patternsParts = Array(patternsCount);
const matchers = Array(patternsCount);
const globstarEnabled = !options.noglobstar;
for (let i = 0; i < patternsCount; i++) {
const parts = splitPattern(patterns[i]);
patternsParts[i] = parts;
const partsCount = parts.length;
const partMatchers = Array(partsCount);
for (let j = 0; j < partsCount; j++) partMatchers[j] = picomatch(parts[j], options);
matchers[i] = partMatchers;
}
return (input) => {
const inputParts = input.split("/");
if (inputParts[0] === ".." && ONLY_PARENT_DIRECTORIES.test(input)) return true;
for (let i = 0; i < patterns.length; i++) {
const patternParts = patternsParts[i];
const matcher = matchers[i];
const inputPatternCount = inputParts.length;
const minParts = Math.min(inputPatternCount, patternParts.length);
let j = 0;
while (j < minParts) {
const part = patternParts[j];
if (part.includes("/")) return true;
const match = matcher[j](inputParts[j]);
if (!match) break;
if (globstarEnabled && part === "**") return true;
j++;
}
if (j === inputPatternCount) return true;
}
return false;
};
}
/* node:coverage ignore next 2 */
const WIN32_ROOT_DIR = /^[A-Z]:\/$/i;
const isRoot = isWin ? (p) => WIN32_ROOT_DIR.test(p) : (p) => p === "/";
function buildFormat(cwd, root, absolute) {
if (cwd === root || root.startsWith(`${cwd}/`)) {
if (absolute) {
const start = isRoot(cwd) ? cwd.length : cwd.length + 1;
return (p, isDir) => p.slice(start, isDir ? -1 : void 0) || ".";
}
const prefix = root.slice(cwd.length + 1);
if (prefix) return (p, isDir) => {
if (p === ".") return prefix;
const result = `${prefix}/${p}`;
return isDir ? result.slice(0, -1) : result;
};
return (p, isDir) => isDir && p !== "." ? p.slice(0, -1) : p;
}
if (absolute) return (p) => posix.relative(cwd, p) || ".";
return (p) => posix.relative(cwd, `${root}/${p}`) || ".";
}
function buildRelative(cwd, root) {
if (root.startsWith(`${cwd}/`)) {
const prefix = root.slice(cwd.length + 1);
return (p) => `${prefix}/${p}`;
}
return (p) => {
const result = posix.relative(cwd, `${root}/${p}`);
if (p.endsWith("/") && result !== "") return `${result}/`;
return result || ".";
};
}
const splitPatternOptions = { parts: true };
function splitPattern(path$1) {
var _result$parts;
const result = picomatch.scan(path$1, splitPatternOptions);
return ((_result$parts = result.parts) === null || _result$parts === void 0 ? void 0 : _result$parts.length) ? result.parts : [path$1];
}
const ESCAPED_WIN32_BACKSLASHES = /\\(?![()[\]{}!+@])/g;
function convertPosixPathToPattern(path$1) {
return escapePosixPath(path$1);
}
function convertWin32PathToPattern(path$1) {
return escapeWin32Path(path$1).replace(ESCAPED_WIN32_BACKSLASHES, "/");
}
/**
* Converts a path to a pattern depending on the platform.
* Identical to {@link escapePath} on POSIX systems.
* @see {@link https://superchupu.dev/tinyglobby/documentation#convertPathToPattern}
*/
/* node:coverage ignore next 3 */
const convertPathToPattern = isWin ? convertWin32PathToPattern : convertPosixPathToPattern;
const POSIX_UNESCAPED_GLOB_SYMBOLS = /(?<!\\)([()[\]{}*?|]|^!|[!+@](?=\()|\\(?![()[\]{}!*+?@|]))/g;
const WIN32_UNESCAPED_GLOB_SYMBOLS = /(?<!\\)([()[\]{}]|^!|[!+@](?=\())/g;
const escapePosixPath = (path$1) => path$1.replace(POSIX_UNESCAPED_GLOB_SYMBOLS, "\\$&");
const escapeWin32Path = (path$1) => path$1.replace(WIN32_UNESCAPED_GLOB_SYMBOLS, "\\$&");
/**
* Escapes a path's special characters depending on the platform.
* @see {@link https://superchupu.dev/tinyglobby/documentation#escapePath}
*/
/* node:coverage ignore next */
const escapePath = isWin ? escapeWin32Path : escapePosixPath;
/**
* Checks if a pattern has dynamic parts.
*
* Has a few minor differences with [`fast-glob`](https://github.com/mrmlnc/fast-glob) for better accuracy:
*
* - Doesn't necessarily return `false` on patterns that include `\`.
* - Returns `true` if the pattern includes parentheses, regardless of them representing one single pattern or not.
* - Returns `true` for unfinished glob extensions i.e. `(h`, `+(h`.
* - Returns `true` for unfinished brace expansions as long as they include `,` or `..`.
*
* @see {@link https://superchupu.dev/tinyglobby/documentation#isDynamicPattern}
*/
function isDynamicPattern(pattern, options) {
if ((options === null || options === void 0 ? void 0 : options.caseSensitiveMatch) === false) return true;
const scan = picomatch.scan(pattern);
return scan.isGlob || scan.negated;
}
function log(...tasks) {
console.log(`[tinyglobby ${(/* @__PURE__ */ new Date()).toLocaleTimeString("es")}]`, ...tasks);
}
//#endregion
//#region src/index.ts
const PARENT_DIRECTORY = /^(\/?\.\.)+/;
const ESCAPING_BACKSLASHES = /\\(?=[()[\]{}!*+?@|])/g;
const BACKSLASHES = /\\/g;
function normalizePattern(pattern, expandDirectories, cwd, props, isIgnore) {
let result = pattern;
if (pattern.endsWith("/")) result = pattern.slice(0, -1);
if (!result.endsWith("*") && expandDirectories) result += "/**";
const escapedCwd = escapePath(cwd);
if (path.isAbsolute(result.replace(ESCAPING_BACKSLASHES, ""))) result = posix.relative(escapedCwd, result);
else result = posix.normalize(result);
const parentDirectoryMatch = PARENT_DIRECTORY.exec(result);
const parts = splitPattern(result);
if (parentDirectoryMatch === null || parentDirectoryMatch === void 0 ? void 0 : parentDirectoryMatch[0]) {
const n = (parentDirectoryMatch[0].length + 1) / 3;
let i = 0;
const cwdParts = escapedCwd.split("/");
while (i < n && parts[i + n] === cwdParts[cwdParts.length + i - n]) {
result = result.slice(0, (n - i - 1) * 3) + result.slice((n - i) * 3 + parts[i + n].length + 1) || ".";
i++;
}
const potentialRoot = posix.join(cwd, parentDirectoryMatch[0].slice(i * 3));
if (!potentialRoot.startsWith(".") && props.root.length > potentialRoot.length) {
props.root = potentialRoot;
props.depthOffset = -n + i;
}
}
if (!isIgnore && props.depthOffset >= 0) {
var _props$commonPath;
(_props$commonPath = props.commonPath) !== null && _props$commonPath !== void 0 || (props.commonPath = parts);
const newCommonPath = [];
const length = Math.min(props.commonPath.length, parts.length);
for (let i = 0; i < length; i++) {
const part = parts[i];
if (part === "**" && !parts[i + 1]) {
newCommonPath.pop();
break;
}
if (part !== props.commonPath[i] || isDynamicPattern(part) || i === parts.length - 1) break;
newCommonPath.push(part);
}
props.depthOffset = newCommonPath.length;
props.commonPath = newCommonPath;
props.root = newCommonPath.length > 0 ? posix.join(cwd, ...newCommonPath) : cwd;
}
return result;
}
function processPatterns({ patterns = ["**/*"], ignore = [], expandDirectories = true }, cwd, props) {
if (typeof patterns === "string") patterns = [patterns];
if (typeof ignore === "string") ignore = [ignore];
const matchPatterns = [];
const ignorePatterns = [];
for (const pattern of ignore) {
if (!pattern) continue;
if (pattern[0] !== "!" || pattern[1] === "(") ignorePatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, true));
}
for (const pattern of patterns) {
if (!pattern) continue;
if (pattern[0] !== "!" || pattern[1] === "(") matchPatterns.push(normalizePattern(pattern, expandDirectories, cwd, props, false));
else if (pattern[1] !== "!" || pattern[2] === "(") ignorePatterns.push(normalizePattern(pattern.slice(1), expandDirectories, cwd, props, true));
}
return {
match: matchPatterns,
ignore: ignorePatterns
};
}
function formatPaths(paths, relative) {
for (let i = paths.length - 1; i >= 0; i--) {
const path$1 = paths[i];
paths[i] = relative(path$1);
}
return paths;
}
function normalizeCwd(cwd) {
if (!cwd) return process.cwd().replace(BACKSLASHES, "/");
if (cwd instanceof URL) return fileURLToPath(cwd).replace(BACKSLASHES, "/");
return path.resolve(cwd).replace(BACKSLASHES, "/");
}
function getCrawler(patterns, inputOptions = {}) {
const options = process.env.TINYGLOBBY_DEBUG ? {
...inputOptions,
debug: true
} : inputOptions;
const cwd = normalizeCwd(options.cwd);
if (options.debug) log("globbing with:", {
patterns,
options,
cwd
});
if (Array.isArray(patterns) && patterns.length === 0) return [{
sync: () => [],
withPromise: async () => []
}, false];
const props = {
root: cwd,
commonPath: null,
depthOffset: 0
};
const processed = processPatterns({
...options,
patterns
}, cwd, props);
if (options.debug) log("internal processing patterns:", processed);
const matchOptions = {
dot: options.dot,
nobrace: options.braceExpansion === false,
nocase: options.caseSensitiveMatch === false,
noextglob: options.extglob === false,
noglobstar: options.globstar === false,
posix: true
};
const matcher = picomatch(processed.match, {
...matchOptions,
ignore: processed.ignore
});
const ignore = picomatch(processed.ignore, matchOptions);
const partialMatcher = getPartialMatcher(processed.match, matchOptions);
const format = buildFormat(cwd, props.root, options.absolute);
const formatExclude = options.absolute ? format : buildFormat(cwd, props.root, true);
const fdirOptions = {
filters: [options.debug ? (p, isDirectory) => {
const path$1 = format(p, isDirectory);
const matches = matcher(path$1);
if (matches) log(`matched ${path$1}`);
return matches;
} : (p, isDirectory) => matcher(format(p, isDirectory))],
exclude: options.debug ? (_, p) => {
const relativePath = formatExclude(p, true);
const skipped = relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath);
if (skipped) log(`skipped ${p}`);
else log(`crawling ${p}`);
return skipped;
} : (_, p) => {
const relativePath = formatExclude(p, true);
return relativePath !== "." && !partialMatcher(relativePath) || ignore(relativePath);
},
fs: options.fs ? {
readdir: options.fs.readdir || nativeFs.readdir,
readdirSync: options.fs.readdirSync || nativeFs.readdirSync,
realpath: options.fs.realpath || nativeFs.realpath,
realpathSync: options.fs.realpathSync || nativeFs.realpathSync,
stat: options.fs.stat || nativeFs.stat,
statSync: options.fs.statSync || nativeFs.statSync
} : void 0,
pathSeparator: "/",
relativePaths: true,
resolveSymlinks: true,
signal: options.signal
};
if (options.deep !== void 0) fdirOptions.maxDepth = Math.round(options.deep - props.depthOffset);
if (options.absolute) {
fdirOptions.relativePaths = false;
fdirOptions.resolvePaths = true;
fdirOptions.includeBasePath = true;
}
if (options.followSymbolicLinks === false) {
fdirOptions.resolveSymlinks = false;
fdirOptions.excludeSymlinks = true;
}
if (options.onlyDirectories) {
fdirOptions.excludeFiles = true;
fdirOptions.includeDirs = true;
} else if (options.onlyFiles === false) fdirOptions.includeDirs = true;
props.root = props.root.replace(BACKSLASHES, "");
const root = props.root;
if (options.debug) log("internal properties:", props);
const relative = cwd !== root && !options.absolute && buildRelative(cwd, props.root);
return [new fdir(fdirOptions).crawl(root), relative];
}
async function glob(patternsOrOptions, options) {
if (patternsOrOptions && (options === null || options === void 0 ? void 0 : options.patterns)) throw new Error("Cannot pass patterns as both an argument and an option");
const isModern = isReadonlyArray(patternsOrOptions) || typeof patternsOrOptions === "string";
const opts = isModern ? options : patternsOrOptions;
const patterns = isModern ? patternsOrOptions : patternsOrOptions.patterns;
const [crawler, relative] = getCrawler(patterns, opts);
if (!relative) return crawler.withPromise();
return formatPaths(await crawler.withPromise(), relative);
}
function globSync(patternsOrOptions, options) {
if (patternsOrOptions && (options === null || options === void 0 ? void 0 : options.patterns)) throw new Error("Cannot pass patterns as both an argument and an option");
const isModern = isReadonlyArray(patternsOrOptions) || typeof patternsOrOptions === "string";
const opts = isModern ? options : patternsOrOptions;
const patterns = isModern ? patternsOrOptions : patternsOrOptions.patterns;
const [crawler, relative] = getCrawler(patterns, opts);
if (!relative) return crawler.sync();
return formatPaths(crawler.sync(), relative);
}
//#endregion
export { convertPathToPattern, escapePath, glob, globSync, isDynamicPattern };

View File

@@ -1,73 +0,0 @@
{
"name": "tinyglobby",
"version": "0.2.15",
"description": "A fast and minimal alternative to globby and fast-glob",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.cts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./package.json": "./package.json"
},
"sideEffects": false,
"files": [
"dist"
],
"author": "Superchupu",
"license": "MIT",
"keywords": [
"glob",
"patterns",
"fast",
"implementation"
],
"repository": {
"type": "git",
"url": "git+https://github.com/SuperchupuDev/tinyglobby.git"
},
"bugs": {
"url": "https://github.com/SuperchupuDev/tinyglobby/issues"
},
"homepage": "https://superchupu.dev/tinyglobby",
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
},
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"devDependencies": {
"@biomejs/biome": "^2.2.3",
"@types/node": "^24.3.1",
"@types/picomatch": "^4.0.2",
"fast-glob": "^3.3.3",
"fs-fixture": "^2.8.1",
"glob": "^11.0.3",
"tinybench": "^5.0.1",
"tsdown": "^0.14.2",
"typescript": "^5.9.2"
},
"engines": {
"node": ">=12.0.0"
},
"publishConfig": {
"provenance": true
},
"scripts": {
"bench": "node benchmark/bench.ts",
"bench:setup": "node benchmark/setup.ts",
"build": "tsdown",
"check": "biome check",
"check:fix": "biome check --write --unsafe",
"format": "biome format --write",
"lint": "biome lint",
"test": "node --test \"test/**/*.ts\"",
"test:coverage": "node --test --experimental-test-coverage \"test/**/*.ts\"",
"test:only": "node --test --test-only \"test/**/*.ts\"",
"typecheck": "tsc --noEmit"
}
}

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2026 Bjorn and Dominik
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,11 +0,0 @@
# vitefu
Utilities for building frameworks with Vite.
## Usage
See [src/index.d.ts](./src/index.d.ts).
## License
MIT

View File

@@ -1,51 +0,0 @@
{
"name": "vitefu",
"description": "Utilities for building frameworks with Vite",
"version": "1.1.2",
"license": "MIT",
"type": "module",
"types": "./src/index.d.ts",
"exports": {
".": {
"import": "./src/index.js",
"require": "./src/index.cjs"
}
},
"files": [
"src"
],
"repository": {
"type": "git",
"url": "git+https://github.com/svitejs/vitefu.git"
},
"bugs": {
"url": "https://github.com/svitejs/vitefu/issues"
},
"keywords": [
"vite",
"framework",
"utilities"
],
"scripts": {
"test": "uvu tests \".*\\.test\\.js\""
},
"peerDependencies": {
"vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0"
},
"peerDependenciesMeta": {
"vite": {
"optional": true
}
},
"devDependencies": {
"@types/node": "^14.18.63",
"@types/pnpapi": "^0.0.5",
"uvu": "^0.5.6",
"vite": "^3.2.11"
},
"workspaces": [
"tests/deps/*",
"tests/projects/*",
"tests/projects/workspace/packages/*"
]
}

View File

@@ -1,18 +0,0 @@
// CJS -> ESM proxy file
// Reference: https://github.com/vitejs/vite/blob/9f268dad2e82c0f1276b1098c0a28f1cf245aa50/packages/vite/index.cjs
module.exports = require('./sync.cjs')
// redirect async functions to ESM
const asyncFunctions = [
'crawlFrameworkPkgs',
'findDepPkgJsonPath',
'findClosestPkgJsonPath',
'pkgNeedsOptimization'
]
for (const fn of asyncFunctions) {
module.exports[fn] = function () {
return import('./index.js').then((mod) => mod[fn].apply(this, arguments))
}
}

View File

@@ -1,59 +0,0 @@
// CJS types like `index.d.ts` but dumbed down and doesn't import from `vite`. Thanks TypeScript.
export interface CrawlFrameworkPkgsOptions {
root: string
isBuild: boolean
workspaceRoot?: string
viteUserConfig?: any
isFrameworkPkgByJson?: (pkgJson: Record<string, any>) => boolean
isFrameworkPkgByName?: (pkgName: string) => boolean | undefined
isSemiFrameworkPkgByJson?: (pkgJson: Record<string, any>) => boolean
isSemiFrameworkPkgByName?: (pkgName: string) => boolean | undefined
}
export interface CrawlFrameworkPkgsResult {
optimizeDeps: {
include: string[]
exclude: string[]
}
ssr: {
noExternal: string[]
external: string[]
}
}
export declare function crawlFrameworkPkgs(
options: CrawlFrameworkPkgsOptions
): Promise<CrawlFrameworkPkgsResult>
export declare function findDepPkgJsonPath(
dep: string,
parent: string
): Promise<string | undefined>
export declare function findClosestPkgJsonPath(
dir: string,
predicate?: (pkgJsonPath: string) => boolean | Promise<boolean>
): Promise<string | undefined>
export declare function pkgNeedsOptimization(
pkgJson: Record<string, any>,
pkgJsonPath: string
): Promise<boolean>
export declare function isDepExcluded(
dep: string,
optimizeDepsExclude: any
): boolean
export declare function isDepIncluded(
dep: string,
optimizeDepsInclude: any
): boolean
export declare function isDepNoExternaled(
dep: string,
ssrNoExternal: any
): boolean
export declare function isDepExternaled(dep: string, ssrExternal: any): boolean

View File

@@ -1,187 +0,0 @@
import type { DepOptimizationOptions, SSROptions, UserConfig } from 'vite'
export interface CrawlFrameworkPkgsOptions {
/**
* Path to the root of the project that contains the `package.json`
*/
root: string
/**
* Whether we're currently in a Vite build
*/
isBuild: boolean
/**
* Path to workspace root of the project
*
* setting this enables crawling devDependencies of private packages inside the workspace
* you can use `import {searchForWorkspaceRoot} from 'vite'` to find it.
*/
workspaceRoot?: string
/**
* Optional. If a Vite user config is passed, the output Vite config will respect the
* set `optimizeDeps` and `ssr` options so it doesn't override it
*/
viteUserConfig?: UserConfig
/**
* Whether this is a framework package by checking it's `package.json`.
* A framework package is one that exports special files that can't be processed
* by esbuild natively. For example, exporting `.framework` files.
*
* @example
* ```ts
* return pkgJson.keywords?.includes('my-framework')
* ```
*/
isFrameworkPkgByJson?: (pkgJson: Record<string, any>) => boolean
/**
* Whether this is a framework package by checking it's name. This is
* usually used as a fast path. Return `true` or `false` if you know 100%
* if it's a framework package or not. Return `undefined` to fallback to
* `isFrameworkPkgByJson`.
*
* @example
* ```ts
* return SPECIAL_PACKAGES.includes(pkgName) || undefined
* ```
*/
isFrameworkPkgByName?: (pkgName: string) => boolean | undefined
/**
* Whether this is a semi-framework package by checking it's `package.json`.
* A semi-framework package is one that **doesn't** export special files but
* consumes other APIs of the framework. For example, it only does
* `import { debounce } from 'my-framework/utils'`.
*
* @example
* ```ts
* return Object.keys(pkgJson.dependencies || {}).includes('my-framework')
* ```
*/
isSemiFrameworkPkgByJson?: (pkgJson: Record<string, any>) => boolean
/**
* Whether this is a semi-framework package by checking it's name. This is
* usually used as a fast path. Return `true` or `false` if you know 100%
* if it's a semi-framework package or not. Return `undefined` to fallback to
* `isSemiFrameworkPkgByJson`.
*
* @example
* ```ts
* return SPECIAL_SEMI_PACKAGES.includes(pkgName) || undefined
* ```
*/
isSemiFrameworkPkgByName?: (pkgName: string) => boolean | undefined
}
export interface CrawlFrameworkPkgsResult {
optimizeDeps: {
include: string[]
exclude: string[]
}
ssr: {
noExternal: string[]
external: string[]
}
}
/**
* Crawls for framework packages starting from `<root>/package.json` to build
* out a partial Vite config. See the source code for details of how this is built.
*/
export declare function crawlFrameworkPkgs(
options: CrawlFrameworkPkgsOptions
): Promise<CrawlFrameworkPkgsResult>
/**
* Find the `package.json` of a dep, starting from the parent, e.g. `process.cwd()`.
* A simplified implementation of https://nodejs.org/api/esm.html#resolver-algorithm-specification
* (PACKAGE_RESOLVE) for `package.json` specifically.
*/
export declare function findDepPkgJsonPath(
dep: string,
parent: string,
): Promise<string | undefined>
/**
* Find the closest `package.json` path by walking `dir` upwards.
*
* Pass a function to `predicate` to check if the current `package.json` is the
* one you're looking for. For example, finding `package.json` that has the
* `name` field only. Throwing inside the `predicate` is safe and acts the same
* as returning false.
*/
export declare function findClosestPkgJsonPath(
dir: string,
predicate?: (pkgJsonPath: string) => boolean | Promise<boolean>
): Promise<string | undefined>
/**
* Check if a package needs to be optimized by Vite, aka if it's CJS-only
*/
export declare function pkgNeedsOptimization(
pkgJson: Record<string, any>,
pkgJsonPath: string
): Promise<boolean>
/**
* Check if a dependency is part of an existing `optimizeDeps.exclude` config
* @param dep Dependency to be included
* @param optimizeDepsExclude Existing `optimizeDeps.exclude` config
* @example
* ```ts
* optimizeDeps: {
* include: includesToAdd.filter((dep) => !isDepExcluded(dep, existingExclude))
* }
* ```
*/
export declare function isDepExcluded(
dep: string,
optimizeDepsExclude: NonNullable<DepOptimizationOptions['exclude']>
): boolean
/**
* Check if a dependency is part of an existing `optimizeDeps.include` config
* @param dep Dependency to be excluded
* @param optimizeDepsInclude Existing `optimizeDeps.include` config
* @example
* ```ts
* optimizeDeps: {
* exclude: excludesToAdd.filter((dep) => !isDepIncluded(dep, existingInclude))
* }
* ```
*/
export declare function isDepIncluded(
dep: string,
optimizeDepsInclude: NonNullable<DepOptimizationOptions['include']>
): boolean
/**
* Check if a dependency is part of an existing `ssr.noExternal` config
* @param dep Dependency to be excluded
* @param ssrNoExternal Existing `ssr.noExternal` config
* @example
* ```ts
* ssr: {
* external: externalsToAdd.filter((dep) => !isDepNoExternal(dep, existingNoExternal))
* }
* ```
*/
export declare function isDepNoExternaled(
dep: string,
ssrNoExternal: NonNullable<SSROptions['noExternal']>
): boolean
/**
* Check if a dependency is part of an existing `ssr.external` config
* @param dep Dependency to be noExternaled
* @param ssrExternal Existing `ssr.external` config
* @example
* ```ts
* ssr: {
* noExternal: noExternalsToAdd.filter((dep) => !isDepExternal(dep, existingExternal))
* }
* ```
*/
export declare function isDepExternaled(
dep: string,
ssrExternal: NonNullable<SSROptions['external']>
): boolean

View File

@@ -1,315 +0,0 @@
import fs from 'node:fs/promises'
import fsSync from 'node:fs'
import { createRequire } from 'node:module'
import path from 'node:path'
import {
isDepIncluded,
isDepExcluded,
isDepNoExternaled,
isDepExternaled
} from './sync.cjs'
/** @type {import('pnpapi')} */
let pnp
/** @type {Array<{ name: string; reference: string; }>} */
let pnpWorkspaceLocators;
if (process.versions.pnp) {
try {
pnp = createRequire(import.meta.url)('pnpapi')
// returns a set of physical locators https://yarnpkg.com/advanced/pnpapi#getdependencytreeroots
// @ts-expect-error unfortunately doesn't exist in the `@types` package
pnpWorkspaceLocators = pnp.getDependencyTreeRoots()
} catch {}
}
export { isDepIncluded, isDepExcluded, isDepNoExternaled, isDepExternaled }
/** @type {import('./index.d.ts').crawlFrameworkPkgs} */
export async function crawlFrameworkPkgs(options) {
const pkgJsonPath = await findClosestPkgJsonPath(options.root)
if (!pkgJsonPath) {
// don't throw as package.json is not required
return {
optimizeDeps: { include: [], exclude: [] },
ssr: { noExternal: [], external: [] }
}
}
const pkgJson = await readJson(pkgJsonPath).catch((e) => {
throw new Error(`Unable to read ${pkgJsonPath}`, { cause: e })
})
/** @type {string[]} */
let optimizeDepsInclude = []
/** @type {string[]} */
let optimizeDepsExclude = []
/** @type {string[]} */
let ssrNoExternal = []
/** @type {string[]} */
let ssrExternal = []
await crawl(pkgJsonPath, pkgJson)
// respect vite user config
if (options.viteUserConfig) {
// remove includes that are explicitly excluded in optimizeDeps
const _optimizeDepsExclude = options.viteUserConfig?.optimizeDeps?.exclude
if (_optimizeDepsExclude) {
optimizeDepsInclude = optimizeDepsInclude.filter(
(dep) => !isDepExcluded(dep, _optimizeDepsExclude)
)
}
// remove excludes that are explicitly included in optimizeDeps
const _optimizeDepsInclude = options.viteUserConfig?.optimizeDeps?.include
if (_optimizeDepsInclude) {
optimizeDepsExclude = optimizeDepsExclude.filter(
(dep) => !isDepIncluded(dep, _optimizeDepsInclude)
)
}
// remove noExternals that are explicitly externalized
const _ssrExternal = options.viteUserConfig?.ssr?.external
if (_ssrExternal) {
ssrNoExternal = ssrNoExternal.filter(
(dep) => !isDepExternaled(dep, _ssrExternal)
)
}
// remove externals that are explicitly noExternal
const _ssrNoExternal = options.viteUserConfig?.ssr?.noExternal
if (_ssrNoExternal) {
ssrExternal = ssrExternal.filter(
(dep) => !isDepNoExternaled(dep, _ssrNoExternal)
)
}
}
return {
optimizeDeps: {
include: optimizeDepsInclude,
exclude: optimizeDepsExclude
},
ssr: {
noExternal: ssrNoExternal,
external: ssrExternal
}
}
/**
* crawl the package.json dependencies for framework packages. rules:
* 1. a framework package should be `optimizeDeps.exclude` and `ssr.noExternal`.
* 2. the deps of the framework package should be `optimizeDeps.include` and `ssr.external`
* unless the dep is also a framework package, in which case do no1 & no2 recursively.
* 3. any non-framework packages that aren't imported by a framework package can be skipped entirely.
* 4. a semi-framework package is like a framework package, except it isn't `optimizeDeps.exclude`,
* but only applies `ssr.noExternal`.
* @param {string} pkgJsonPath
* @param {Record<string, any>} pkgJson
* @param {string[]} [parentDepNames]
*/
async function crawl(pkgJsonPath, pkgJson, parentDepNames = []) {
const isRoot = parentDepNames.length === 0
const crawlDevDependencies = isRoot || isPrivateWorkspacePackage(pkgJsonPath,pkgJson,options.workspaceRoot)
/** @type {string[]} */
let deps = [
...Object.keys(pkgJson.dependencies || {}),
...((crawlDevDependencies) ? Object.keys(pkgJson.devDependencies || {}) : [])
]
deps = deps.filter((dep) => {
// skip circular deps
if (parentDepNames.includes(dep)) {
return false
}
const isFrameworkPkg = options.isFrameworkPkgByName?.(dep)
const isSemiFrameworkPkg = options.isSemiFrameworkPkgByName?.(dep)
if (isFrameworkPkg) {
// framework packages should be excluded from optimization as esbuild can't handle them.
// otherwise it'll cause https://github.com/vitejs/vite/issues/3910
optimizeDepsExclude.push(dep)
// framework packages should be noExternal so that they go through vite's transformation
// pipeline, since nodejs can't support them.
ssrNoExternal.push(dep)
} else if (isSemiFrameworkPkg) {
// semi-framework packages should do the same except for optimization exclude as they
// aren't needed to work (they don't contain raw framework components)
ssrNoExternal.push(dep)
}
// only those that are explictly false can skip crawling since we don't need to do anything
// special for them
if (isFrameworkPkg === false || isSemiFrameworkPkg === false) {
return false
}
// if `true`, we need to crawl the nested deps to deep include and ssr externalize them in dev.
// if `undefined`, it's the same as "i don't know". we need to crawl and find the package.json
// to find out.
else {
return true
}
})
const promises = deps.map(async (dep) => {
const depPkgJsonPath = await _findDepPkgJsonPath(dep, pkgJsonPath, !!options.workspaceRoot)
if (!depPkgJsonPath) return
const depPkgJson = await readJson(depPkgJsonPath).catch(() => {})
if (!depPkgJson) return
// fast path if this dep is already a framework dep based on the filter condition above
const cachedIsFrameworkPkg = ssrNoExternal.includes(dep)
if (cachedIsFrameworkPkg) {
return crawl(depPkgJsonPath, depPkgJson, parentDepNames.concat(dep))
}
// check if this dep is a framework dep, if so, track and crawl it
const isFrameworkPkg = options.isFrameworkPkgByJson?.(depPkgJson)
const isSemiFrameworkPkg = options.isSemiFrameworkPkgByJson?.(depPkgJson)
if (isFrameworkPkg || isSemiFrameworkPkg) {
// see explanation in filter condition above
if (isFrameworkPkg) {
optimizeDepsExclude.push(dep)
ssrNoExternal.push(dep)
} else if (isSemiFrameworkPkg) {
ssrNoExternal.push(dep)
}
return crawl(depPkgJsonPath, depPkgJson, parentDepNames.concat(dep))
}
// if we're crawling in a non-root state, the parent is 100% a framework package
// because of the above if block. in this case, if it's dep of a non-framework
// package, handle special cases for them.
if (!isRoot) {
// deep include it if it's a CJS package, so it becomes ESM and vite is happy.
if (await pkgNeedsOptimization(depPkgJson, depPkgJsonPath)) {
optimizeDepsInclude.push(parentDepNames.concat(dep).join(' > '))
}
// also externalize it in dev so it doesn't trip vite's SSR transformation.
// we do in dev only as build cannot access deep external packages in strict
// dependency installations, such as pnpm.
if (!options.isBuild && !ssrExternal.includes(dep)) {
ssrExternal.push(dep)
}
}
})
await Promise.all(promises)
}
}
/** @type {import('./index.d.ts').findDepPkgJsonPath} */
export async function findDepPkgJsonPath(dep, parent) {
return _findDepPkgJsonPath(dep, parent, false);
}
/**
* internal implementation to avoid exposing the usePnpWorkspaceLocators flag
*
* @param {string} dep
* @param {string} parent
* @param {boolean} usePnpWorkspaceLocators
* @returns {Promise<undefined|string>}
* @private
*/
async function _findDepPkgJsonPath(dep, parent, usePnpWorkspaceLocators) {
if (pnp) {
if(usePnpWorkspaceLocators) {
try {
// if we're in a workspace and the dep is a workspace dep,
// then we'll try to resolve to it's real location
const locator = pnpWorkspaceLocators.find((root) => root.name === dep)
if (locator) {
const pkgPath = pnp.getPackageInformation(locator).packageLocation
return path.resolve(pkgPath, 'package.json')
}
} catch {}
}
try {
const depRoot = pnp.resolveToUnqualified(dep, parent)
if (!depRoot) return undefined
return path.join(depRoot, 'package.json')
} catch {
return undefined
}
}
let root = parent
while (root) {
const pkg = path.join(root, 'node_modules', dep, 'package.json')
try {
await fs.access(pkg)
// use 'node:fs' version to match 'vite:resolve' and avoid realpath.native quirk
// https://github.com/sveltejs/vite-plugin-svelte/issues/525#issuecomment-1355551264
return fsSync.realpathSync(pkg)
} catch {}
const nextRoot = path.dirname(root)
if (nextRoot === root) break
root = nextRoot
}
return undefined
}
/** @type {import('./index.d.ts').findClosestPkgJsonPath} */
export async function findClosestPkgJsonPath(dir, predicate = undefined) {
if (dir.endsWith('package.json')) {
dir = path.dirname(dir)
}
while (dir) {
const pkg = path.join(dir, 'package.json')
try {
const stat = await fs.stat(pkg)
if (stat.isFile() && (!predicate || (await predicate(pkg)))) {
return pkg
}
} catch {}
const nextDir = path.dirname(dir)
if (nextDir === dir) break
dir = nextDir
}
return undefined
}
/** @type {import('./index.d.ts').pkgNeedsOptimization} */
export async function pkgNeedsOptimization(pkgJson, pkgJsonPath) {
// only optimize if is cjs, using the below as heuristic
// see https://github.com/sveltejs/vite-plugin-svelte/issues/162
if (pkgJson.module || pkgJson.exports) return false
// if have main, ensure entry is js so vite can prebundle it
// see https://github.com/sveltejs/vite-plugin-svelte/issues/233
if (pkgJson.main) {
const entryExt = path.extname(pkgJson.main)
return !entryExt || entryExt === '.js' || entryExt === '.cjs'
}
// check if has implicit index.js entrypoint to prebundle
// see https://github.com/sveltejs/vite-plugin-svelte/issues/281
// see https://github.com/solidjs/vite-plugin-solid/issues/70#issuecomment-1306488154
try {
await fs.access(path.join(path.dirname(pkgJsonPath), 'index.js'))
return true
} catch {
return false
}
}
/**
* @param {string} findDepPkgJsonPath
* @returns {Promise<Record<string, any>>}
*/
async function readJson(findDepPkgJsonPath) {
return JSON.parse(await fs.readFile(findDepPkgJsonPath, 'utf8'))
}
/**
*
* @param {string} pkgJsonPath
* @param {Record<string,any>} pkgJson
* @param {string} [workspaceRoot]
* @returns {boolean}
*/
function isPrivateWorkspacePackage(pkgJsonPath,pkgJson,workspaceRoot = undefined) {
return !!(
workspaceRoot &&
pkgJson.private &&
!pkgJsonPath.match(/[/\\]node_modules[/\\]/) &&
!path.relative(workspaceRoot,pkgJsonPath).startsWith('..')
)
}

View File

@@ -1,66 +0,0 @@
// contains synchronous API only so it can be exported as CJS and ESM
/** @type {import('./index.d.ts').isDepIncluded} */
function isDepIncluded(dep, optimizeDepsInclude) {
return optimizeDepsInclude.some((id) => parseIncludeStr(id) === dep)
}
/** @type {import('./index.d.ts').isDepExcluded} */
function isDepExcluded(dep, optimizeDepsExclude) {
dep = parseIncludeStr(dep)
return optimizeDepsExclude.some(
(id) => id === dep || dep.startsWith(`${id}/`)
)
}
/** @type {import('./index.d.ts').isDepNoExternaled} */
function isDepNoExternaled(dep, ssrNoExternal) {
if (ssrNoExternal === true) {
return true
} else {
return isMatch(dep, ssrNoExternal)
}
}
/** @type {import('./index.d.ts').isDepExternaled} */
function isDepExternaled(dep, ssrExternal) {
// If `ssrExternal` is `true`, it just means that all linked
// dependencies should also be externalized by default. It doesn't
// mean that a dependency is being explicitly externalized. So we
// return `false` in this case.
// @ts-expect-error can be true in Vite 6
if (ssrExternal === true) {
return false
} else {
return ssrExternal.includes(dep)
}
}
/**
* @param {string} raw could be "foo" or "foo > bar" etc
*/
function parseIncludeStr(raw) {
const lastArrow = raw.lastIndexOf('>')
return lastArrow === -1 ? raw : raw.slice(lastArrow + 1).trim()
}
/**
* @param {string} target
* @param {string | RegExp | (string | RegExp)[]} pattern
*/
function isMatch(target, pattern) {
if (Array.isArray(pattern)) {
return pattern.some((p) => isMatch(target, p))
} else if (typeof pattern === 'string') {
return target === pattern
} else if (pattern instanceof RegExp) {
return pattern.test(target)
}
}
module.exports = {
isDepIncluded,
isDepExcluded,
isDepNoExternaled,
isDepExternaled
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
{
"name": "dealplustech-astro",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@tailwindcss/vite": "^4.2.1",
"astro": "^5.17.1",
"tailwindcss": "^4.2.1"
}
}

View File

@@ -1,5 +0,0 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}

5
next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,88 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
// Only use standalone output in production (causes issues in dev mode)
...(process.env.NODE_ENV === 'production' && { output: 'standalone' }),
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'www.dealplustech.co.th',
},
{
protocol: 'https',
hostname: 'dealplustech.co.th',
},
{
protocol: 'https',
hostname: 'www.dealplustech.com',
},
{
protocol: 'https',
hostname: 'dealplustech.com',
},
],
dangerouslyAllowSVG: true,
contentDispositionType: 'attachment',
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
experimental: {
optimizePackageImports: ['clsx', 'tailwind-merge'],
},
reactStrictMode: true,
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin',
},
],
},
{
source: '/images/(.*)',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
{
source: '/favicon-(.*)',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
{
source: '/og-image.jpg',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
];
},
};
export default nextConfig;

7941
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +0,0 @@
{
"name": "dealplustech",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"axios": "^1.13.5",
"cheerio": "^1.2.0",
"clsx": "^2.1.1",
"gray-matter": "^4.0.3",
"next": "14.2.3",
"playwright": "^1.58.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"remark": "^15.0.1",
"remark-gfm": "^4.0.1",
"remark-html": "^16.0.1",
"tailwind-merge": "^2.3.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@types/node": "^20.12.12",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-config-next": "14.2.3",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"typescript": "^5.4.5"
},
"description": "Complete redesign of dealplustech.co.th website built with Next.js 14, TypeScript, and Tailwind CSS.",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://git.moreminimore.com/kunthawat/dealplustech.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

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